From 7db05fea47716e73406cb03206374b8c626d2359 Mon Sep 17 00:00:00 2001 From: Mandel Olaiya Date: Thu, 9 Apr 2026 00:28:42 +0200 Subject: [PATCH] Add launch pipeline and idea marketplace seed commands --- Jenkinsfile | 54 +- mandelstudio/content_hygiene.py | 38 + mandelstudio/idea_marketplace.py | 465 ++ .../management/commands/_agency_content.py | 4989 +++++++++++++++++ .../commands/apply_agency_website_refresh.py | 1385 +++++ .../prepare_idea_marketplace_launch.py | 226 + .../management/commands/purge_demo_data.py | 120 + .../commands/seed_idea_marketplace.py | 44 + .../validate_idea_marketplace_launch.py | 232 + mandelstudio/templatetags/idea_marketplace.py | 45 + 10 files changed, 7589 insertions(+), 9 deletions(-) create mode 100644 mandelstudio/content_hygiene.py create mode 100644 mandelstudio/idea_marketplace.py create mode 100644 mandelstudio/management/commands/_agency_content.py create mode 100644 mandelstudio/management/commands/apply_agency_website_refresh.py create mode 100644 mandelstudio/management/commands/prepare_idea_marketplace_launch.py create mode 100644 mandelstudio/management/commands/purge_demo_data.py create mode 100644 mandelstudio/management/commands/seed_idea_marketplace.py create mode 100644 mandelstudio/management/commands/validate_idea_marketplace_launch.py create mode 100644 mandelstudio/templatetags/idea_marketplace.py diff --git a/Jenkinsfile b/Jenkinsfile index 7283087..9b92104 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -6,13 +6,19 @@ pipeline { disableConcurrentBuilds() skipDefaultCheckout(true) } + parameters { + booleanParam( + name: 'RUN_DEMO_PURGE', + defaultValue: false, + description: 'Run a one-time demo catalogue purge before the normal idea marketplace seed and launch prep.' + ) + } environment { PYENVPIPELINE_VIRTUALENV = '1' GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new' - STAGING_AUDIT_HOST = 'root@49.12.204.96' + STAGING_AUDIT_PROJECT_NAME = 'mandelstudio' STAGING_AUDIT_PROJECT_DIR = '/home/www-mandelstudio/mandelstudio' STAGING_AUDIT_MANAGE = '/var/lib/virtualenv/mandelstudio/bin/manage.py' - STAGING_AUDIT_SSH_CREDENTIALS_ID = 'staging-root-ssh' } stages { @@ -36,6 +42,30 @@ pipeline { stage('Build') { steps { sh ''' + STABLE_INDEX_URL=${STABLE_INDEX_URL:-https://pypi.mandelblog.com/mandel/stable/+simple/} + TESTING_INDEX_URL=${TESTING_INDEX_URL:-https://pypi.mandelblog.com/mandel/testing/+simple/} + ROOT_INDEX_URL=${PIP_EXTRA_INDEX_URL:-https://pypi.mandelblog.com/root/pypi/+simple/} + export STABLE_INDEX_URL + if python3 - <<'PY' +import os +import sys +from urllib.request import Request, urlopen +from urllib.error import URLError, HTTPError +url = os.environ["STABLE_INDEX_URL"] +try: + req = Request(url, method='HEAD') + with urlopen(req, timeout=10) as response: + sys.exit(0 if response.status < 400 else 1) +except HTTPError as exc: + sys.exit(0 if exc.code < 400 else 1) +except URLError: + sys.exit(1) +PY + then + echo "devpi stable index available, but stable-first install is not enabled yet" + else + echo "devpi stable index not available, using testing as production source" + fi if command -v sudo >/dev/null 2>&1 && sudo -n true >/dev/null 2>&1; then sudo apt-get update -y sudo apt-get install -y python3-venv python3-pip make build-essential libpq-dev \ @@ -52,14 +82,20 @@ pipeline { . .venv/bin/activate pip install coverage pip install --upgrade pip "setuptools==69.5.1" wheel - PIP_INDEX_URL=${PIP_INDEX_URL:-https://pypi.mandelblog.com/mandel/testing/+simple/} \ - PIP_EXTRA_INDEX_URL=${PIP_EXTRA_INDEX_URL:-https://pypi.mandelblog.com/root/pypi/+simple/} \ + PIP_INDEX_URL="$TESTING_INDEX_URL" \ + PIP_EXTRA_INDEX_URL="$ROOT_INDEX_URL" \ pip install --no-build-isolation --pre --editable . setuptools wheel --upgrade --upgrade-strategy=eager --use-deprecated=legacy-resolver cp "${JOB_BASE_NAME}/ocyan.json" "${JOB_BASE_NAME}/${JOB_BASE_NAME}.json" pip install ruff vdt.versionplugin.wheel pip install --upgrade "setuptools==69.5.1" wheel + python3 scripts/validate_payment_provider_config.py manage.py migrate --no-input --skip-checks - manage.py purge_demo_data + if [ "${RUN_DEMO_PURGE}" = "true" ]; then + manage.py purge_demo_data + fi + manage.py seed_idea_marketplace + manage.py prepare_idea_marketplace_launch --apply-homepage-copy --purge-demo-pages + manage.py validate_idea_marketplace_launch manage.py collectstatic --no-input --verbosity=0 pip install "httpx<0.28" ''' @@ -105,10 +141,10 @@ pipeline { timeout(time: 10, unit: 'MINUTES') } steps { - sh 'mkdir -p artifacts' - withCredentials([sshUserPrivateKey(credentialsId: env.STAGING_AUDIT_SSH_CREDENTIALS_ID, keyFileVariable: 'STAGING_SSH_KEYFILE')]) { - sh './scripts/run_remote_multilingual_audit.sh' - } + deleteDir() + checkout scm + sh 'mkdir -p artifacts && chmod +x scripts/run_remote_multilingual_audit.sh' + sh './scripts/run_remote_multilingual_audit.sh' script { int status = sh(script: 'python3 scripts/multilingual_audit_ci.py --json artifacts/multilingual-audit.json', returnStatus: true) if (status == 2) { diff --git a/mandelstudio/content_hygiene.py b/mandelstudio/content_hygiene.py new file mode 100644 index 0000000..91287bf --- /dev/null +++ b/mandelstudio/content_hygiene.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from typing import Iterable + + +DEMO_MARKERS: tuple[str, ...] = ( + "demo", + "dummy", + "sample", + "lorem", + "placeholder", + "sandbox", + "staging", + "prototype", + "template-only", +) + +# Known legacy/demo pages that should never surface on production. +BLOCKED_DEMO_PAGE_SLUGS: tuple[str, ...] = ( + "starter-website-2", + "business-website-2", +) + + +def contains_demo_marker(values: Iterable[str | None]) -> bool: + for raw_value in values: + if not raw_value: + continue + lowered = raw_value.lower() + if any(marker in lowered for marker in DEMO_MARKERS): + return True + return False + + +def is_blocked_demo_slug(value: str | None) -> bool: + if not value: + return False + return value.lower() in BLOCKED_DEMO_PAGE_SLUGS diff --git a/mandelstudio/idea_marketplace.py b/mandelstudio/idea_marketplace.py new file mode 100644 index 0000000..07972ac --- /dev/null +++ b/mandelstudio/idea_marketplace.py @@ -0,0 +1,465 @@ +from __future__ import annotations + +from dataclasses import dataclass +from decimal import Decimal + +from django.conf import settings +from django.db.models import Q +from django.utils.text import slugify + +from oscar.core.loading import get_model + +from mandelstudio.content_hygiene import DEMO_MARKERS + + +IDEA_PRODUCT_CLASS_NAME = "Idea Product" +DIGITAL_IDEAS_CATEGORY_NAME = "Digital Ideas" +SHORT_DESCRIPTION_ATTRIBUTE_CODE = "short_description" +FULL_DESCRIPTION_ATTRIBUTE_CODE = "full_description" +IDEA_PARTNER_NAME = "Mandel Blog Studio" + + +@dataclass(frozen=True) +class IdeaSeedItem: + title: str + short_description: str + full_description: str + price_eur: Decimal + + +IDEA_PRODUCTS: tuple[IdeaSeedItem, ...] = ( + IdeaSeedItem( + title="B2B Webshop Starter Blueprint", + short_description=( + "Launch a B2B webshop with a quote-first buying flow and enterprise-ready trust structure. " + "Get a clear execution path from positioning to first qualified orders." + ), + full_description=( + "Introduction\n" + "A practical B2B ecommerce blueprint for teams that need to sell complex offers with confidence.\n\n" + "Problem it solves\n" + "- Generic webshop setups underperform in B2B because they ignore quote-first journeys and multi-stakeholder buying.\n" + "- Sales and marketing handoff is often unclear, which slows deal velocity.\n\n" + "Step-by-step concept\n" + "1. Define ICP and buying committee signals.\n" + "2. Map quote-first vs direct checkout decision rules.\n" + "3. Build page architecture for trust, proof, and qualification.\n" + "4. Implement lead-to-order routing between website and sales ops.\n" + "5. Run a 90-day optimization loop with conversion checkpoints.\n\n" + "Tech stack\n" + "- Django + Oscar commerce core\n" + "- Wagtail CMS for structured sales content\n" + "- Analytics and event tracking for funnel visibility\n\n" + "Business value\n" + "- Faster sales-qualified lead capture\n" + "- Lower friction for enterprise buyers\n" + "- Higher conversion from product page to qualified pipeline\n\n" + "Who it is for\n" + "Founders, growth teams, and B2B operators launching or rebuilding a serious ecommerce motion." + ), + price_eur=Decimal("99.00"), + ), + IdeaSeedItem( + title="AI Product Description System", + short_description=( + "Scale product copy with AI while preserving brand tone, SEO intent, and quality control. " + "Turn catalog chaos into a repeatable content engine your team can trust." + ), + full_description=( + "Introduction\n" + "A production content system for generating and governing high-quality product descriptions at scale.\n\n" + "Problem it solves\n" + "- Manual copywriting does not scale across growing catalogs.\n" + "- Uncontrolled AI output introduces inconsistency and factual risk.\n\n" + "Step-by-step concept\n" + "1. Define attribute schema and content rules per category.\n" + "2. Build prompt templates linked to taxonomy fields.\n" + "3. Add QA gates for accuracy, tone, and compliance.\n" + "4. Localize with multilingual adaptation rules.\n" + "5. Monitor quality with an editorial review workflow.\n\n" + "Tech stack\n" + "- Django/Wagtail content governance\n" + "- AI model orchestration with prompt templates\n" + "- Validation layer for quality and policy checks\n\n" + "Business value\n" + "- Faster time-to-publish for new products\n" + "- Consistent conversion-focused copy\n" + "- Reduced editorial costs with better control\n\n" + "Who it is for\n" + "Ecommerce teams, marketplaces, and catalog-heavy brands that need reliable AI-assisted copy operations." + ), + price_eur=Decimal("49.00"), + ), + IdeaSeedItem( + title="High-Converting Landing Page Framework", + short_description=( + "Build landing pages that convert with a proven structure for message clarity, proof, and CTA flow. " + "Stop guessing and launch with a repeatable conversion framework." + ), + full_description=( + "Introduction\n" + "A practical landing-page framework focused on conversion, not visual noise.\n\n" + "Problem it solves\n" + "- Teams often launch pages without a clear conversion narrative.\n" + "- Weak proof and CTA sequencing create drop-off before action.\n\n" + "Step-by-step concept\n" + "1. Align offer with one core audience intent.\n" + "2. Build headline and subheadline hierarchy.\n" + "3. Add objection-handling proof blocks and trust signals.\n" + "4. Design CTA progression for low and high intent visitors.\n" + "5. Define test plan for copy, layout, and offer variants.\n\n" + "Tech stack\n" + "- Wagtail page composition\n" + "- Bootstrap 5 component patterns\n" + "- Event tracking for funnel diagnostics\n\n" + "Business value\n" + "- Higher lead quality from the same traffic\n" + "- Faster launch cycles with reusable page logic\n" + "- Better conversion through structured experimentation\n\n" + "Who it is for\n" + "Service businesses, SaaS teams, and agencies that rely on landing pages for growth." + ), + price_eur=Decimal("29.00"), + ), + IdeaSeedItem( + title="Subscription-Based Service Website Model", + short_description=( + "Design a subscription service website that improves activation, retention, and recurring revenue. " + "Package offers clearly and reduce churn with lifecycle-aware UX." + ), + full_description=( + "Introduction\n" + "A complete website model for subscription-first service businesses.\n\n" + "Problem it solves\n" + "- Subscription sites often sell features, not ongoing outcomes.\n" + "- Poor onboarding and renewal communication increases churn risk.\n\n" + "Step-by-step concept\n" + "1. Structure offer tiers by business outcome and support level.\n" + "2. Build onboarding pages for fast activation.\n" + "3. Add lifecycle messaging for renewal and expansion.\n" + "4. Map churn-risk touchpoints and intervention moments.\n" + "5. Track retention metrics and optimize plan positioning.\n\n" + "Tech stack\n" + "- Django + Oscar for billing-ready commerce foundations\n" + "- Wagtail for lifecycle content and onboarding assets\n" + "- Event instrumentation for retention analytics\n\n" + "Business value\n" + "- Improved activation-to-retention conversion\n" + "- More predictable recurring revenue\n" + "- Clearer upgrade path across plan tiers\n\n" + "Who it is for\n" + "Founders and operators running service subscriptions with monthly or annual plans." + ), + price_eur=Decimal("69.00"), + ), + IdeaSeedItem( + title="Marketplace Platform Architecture (Django)", + short_description=( + "Get a scalable marketplace architecture for Django from MVP to multi-vendor growth. " + "Includes domain boundaries, payments, moderation, and operations blueprint." + ), + full_description=( + "Introduction\n" + "A technical blueprint for launching and scaling a marketplace platform on Django.\n\n" + "Problem it solves\n" + "- Marketplace projects fail when core domains and workflows are not separated early.\n" + "- Teams underestimate moderation, payout, and operational complexity.\n\n" + "Step-by-step concept\n" + "1. Define bounded domains for buyers, sellers, listings, and transactions.\n" + "2. Design catalog and search architecture for growth.\n" + "3. Implement payment orchestration and settlement flow.\n" + "4. Add moderation, permissions, and abuse controls.\n" + "5. Plan observability and phased scaling from MVP to expansion.\n\n" + "Tech stack\n" + "- Django service layer and domain modules\n" + "- Oscar commerce primitives where applicable\n" + "- Queue/events for async marketplace operations\n" + "- Monitoring and operational alerting baseline\n\n" + "Business value\n" + "- Lower re-architecture risk at scale\n" + "- Faster delivery of revenue-critical flows\n" + "- Better reliability for multi-sided operations\n\n" + "Who it is for\n" + "Technical founders, CTOs, and product teams building marketplace businesses with Django." + ), + price_eur=Decimal("149.00"), + ), +) + + +def _get_attribute_text(product, code: str) -> str: + value = ( + product.attribute_values.select_related("attribute") + .filter(attribute__code=code) + .first() + ) + if value is None: + return "" + for field_name in ( + "value_text", + "value_richtext", + "value_option", + "value_file", + "value_image", + ): + field_value = getattr(value, field_name, None) + if field_value: + return str(field_value) + return "" + + +def _set_attribute_text(product, attribute, text: str) -> None: + ProductAttributeValue = get_model("catalogue", "ProductAttributeValue") + value_field = ( + "value_richtext" if getattr(attribute, "type", "text") == "richtext" else "value_text" + ) + value, _created = ProductAttributeValue.objects.get_or_create( + product=product, + attribute=attribute, + ) + if getattr(value, value_field, "") != text: + setattr(value, value_field, text) + value.save(update_fields=[value_field]) + + +def is_idea_product(product) -> bool: + product_class = getattr(product, "product_class", None) + return bool(product_class and product_class.name == IDEA_PRODUCT_CLASS_NAME) + + +def get_idea_short_description(product) -> str: + return _get_attribute_text(product, SHORT_DESCRIPTION_ATTRIBUTE_CODE) or ( + getattr(product, "description", "") or "" + ) + + +def get_idea_full_description(product) -> str: + return _get_attribute_text(product, FULL_DESCRIPTION_ATTRIBUTE_CODE) + + +def get_unlockable_description(product, user) -> tuple[str, bool]: + unlocked = user_has_unlocked_idea(user, product) + if unlocked: + return get_idea_full_description(product) or get_idea_short_description(product), True + return get_idea_short_description(product), False + + +def user_has_unlocked_idea(user, product) -> bool: + if not getattr(user, "is_authenticated", False): + return False + if not is_idea_product(product): + return True + + Line = get_model("order", "Line") + PaymentEventQuantity = get_model("order", "PaymentEventQuantity") + paid_statuses = { + getattr(settings, "OSCAR_PAID_ORDER_STATUS", None), + getattr(settings, "OSCAR_COMPLETE_ORDER_STATUS", None), + "paid", + "complete", + "payment-complete", + "delayed-payment", + } + paid_statuses = { + status.strip().lower() for status in paid_statuses if isinstance(status, str) and status.strip() + } + + status_match = Line.objects.filter( + order__user=user, + product_id=product.id, + ).filter( + Q(order__status__in=paid_statuses) + | Q(order__status__icontains="paid") + | Q(order__status__icontains="complete") + ) + if status_match.exists(): + return True + + # Fallback to payment event evidence so unlocking still works when status names differ per provider. + return PaymentEventQuantity.objects.filter( + line__order__user=user, + line__product_id=product.id, + quantity__gt=0, + ).exists() + + +def _ensure_digital_ideas_category(): + Category = get_model("catalogue", "Category") + existing = Category.objects.filter(name=DIGITAL_IDEAS_CATEGORY_NAME).first() + if existing: + return existing + + root = ( + Category.objects.filter(depth=1).order_by("path").first() + if hasattr(Category, "depth") + else None + ) + if root and hasattr(root, "add_child"): + return root.add_child(name=DIGITAL_IDEAS_CATEGORY_NAME) + if hasattr(Category, "add_root"): + return Category.add_root(name=DIGITAL_IDEAS_CATEGORY_NAME) + + category = Category(name=DIGITAL_IDEAS_CATEGORY_NAME) + if hasattr(category, "slug"): + category.slug = slugify(DIGITAL_IDEAS_CATEGORY_NAME) + category.save() + return category + + +def _ensure_product_class(): + ProductClass = get_model("catalogue", "ProductClass") + product_class, _created = ProductClass.objects.get_or_create( + name=IDEA_PRODUCT_CLASS_NAME, + defaults={ + "requires_shipping": False, + "track_stock": False, + }, + ) + if product_class.requires_shipping: + product_class.requires_shipping = False + product_class.save(update_fields=["requires_shipping"]) + return product_class + + +def _ensure_product_attributes(product_class): + ProductAttribute = get_model("catalogue", "ProductAttribute") + text_type = getattr(ProductAttribute, "TEXT", "text") + richtext_type = getattr(ProductAttribute, "RICHTEXT", "richtext") + + short_attr, _ = ProductAttribute.objects.get_or_create( + product_class=product_class, + code=SHORT_DESCRIPTION_ATTRIBUTE_CODE, + defaults={ + "name": "Short description", + "type": text_type, + "required": False, + }, + ) + full_attr, _ = ProductAttribute.objects.get_or_create( + product_class=product_class, + code=FULL_DESCRIPTION_ATTRIBUTE_CODE, + defaults={ + "name": "Full description", + "type": richtext_type, + "required": False, + }, + ) + return short_attr, full_attr + + +def _ensure_partner(): + Partner = get_model("partner", "Partner") + partner, _ = Partner.objects.get_or_create(name=IDEA_PARTNER_NAME) + return partner + + +def _upsert_stockrecord(product, partner, price_eur: Decimal): + StockRecord = get_model("partner", "StockRecord") + defaults = { + "partner_sku": f"idea-{product.id}", + "price_currency": "EUR", + "price_excl_tax": price_eur, + "num_in_stock": 99999, + } + stockrecord, _created = StockRecord.objects.get_or_create( + product=product, + partner=partner, + defaults=defaults, + ) + dirty_fields: list[str] = [] + for field_name, field_value in defaults.items(): + if getattr(stockrecord, field_name, None) != field_value: + setattr(stockrecord, field_name, field_value) + dirty_fields.append(field_name) + if dirty_fields: + stockrecord.save(update_fields=dirty_fields) + + +def seed_idea_marketplace_products( + *, purge_demo_products: bool = True, retire_non_idea_products: bool = True +) -> dict[str, int]: + Product = get_model("catalogue", "Product") + + product_class = _ensure_product_class() + category = _ensure_digital_ideas_category() + short_attr, full_attr = _ensure_product_attributes(product_class) + partner = _ensure_partner() + + created = 0 + updated = 0 + for item in IDEA_PRODUCTS: + product = Product.objects.filter(title=item.title).first() + if product is None: + product = Product( + title=item.title, + slug=slugify(item.title), + product_class=product_class, + description=item.short_description, + ) + if hasattr(Product, "STANDALONE") and hasattr(product, "structure"): + product.structure = Product.STANDALONE + if hasattr(product, "is_public") and not getattr(product, "is_public", False): + product.is_public = True + product.save() + created += 1 + else: + dirty_fields: list[str] = [] + if product.product_class_id != product_class.id: + product.product_class = product_class + dirty_fields.append("product_class") + if product.description != item.short_description: + product.description = item.short_description + dirty_fields.append("description") + if hasattr(product, "slug") and product.slug != slugify(item.title): + product.slug = slugify(item.title) + dirty_fields.append("slug") + if hasattr(product, "is_public") and not getattr(product, "is_public", False): + product.is_public = True + dirty_fields.append("is_public") + if dirty_fields: + product.save(update_fields=dirty_fields) + updated += 1 + + product.categories.add(category) + _set_attribute_text(product, short_attr, item.short_description) + _set_attribute_text(product, full_attr, item.full_description) + _upsert_stockrecord(product, partner, item.price_eur) + + deleted_demo = 0 + if purge_demo_products: + keep_titles = {item.title for item in IDEA_PRODUCTS} + demo_filter = Q() + for marker in DEMO_MARKERS: + demo_filter |= Q(title__icontains=marker) | Q(slug__icontains=marker) + demo_queryset = Product.objects.filter(demo_filter).exclude(title__in=keep_titles) + # Also purge any non-canonical products lingering in the Idea Product class + # or explicitly grouped under the Digital Ideas category. + non_canonical_ideas_queryset = ( + Product.objects.filter( + Q(product_class=product_class) + | Q(categories__name__iexact=DIGITAL_IDEAS_CATEGORY_NAME) + ) + .exclude(title__in=keep_titles) + .distinct() + ) + delete_ids = set(demo_queryset.values_list("id", flat=True)) | set( + non_canonical_ideas_queryset.values_list("id", flat=True) + ) + deleted_demo = len(delete_ids) + if deleted_demo: + Product.objects.filter(id__in=delete_ids).delete() + + retired_non_idea = 0 + if retire_non_idea_products: + keep_titles = {item.title for item in IDEA_PRODUCTS} + non_idea_public_qs = Product.objects.exclude(title__in=keep_titles).filter( + is_public=True + ) + retired_non_idea = non_idea_public_qs.update(is_public=False) + + return { + "created": created, + "updated": updated, + "deleted_demo": deleted_demo, + "retired_non_idea": retired_non_idea, + } diff --git a/mandelstudio/management/commands/_agency_content.py b/mandelstudio/management/commands/_agency_content.py new file mode 100644 index 0000000..6dd9db6 --- /dev/null +++ b/mandelstudio/management/commands/_agency_content.py @@ -0,0 +1,4989 @@ +from __future__ import annotations + +import uuid +from typing import Any + +COMMON_CTA = { + "nl": { + "primary": "Plan een kennismakingsgesprek", + "secondary": "Bekijk onze diensten", + }, + "en": { + "primary": "Schedule a consultation", + "secondary": "View our services", + }, + "de": { + "primary": "Beratungsgespräch planen", + "secondary": "Unsere Leistungen ansehen", + }, + "fr": { + "primary": "Planifier une consultation", + "secondary": "Voir nos services", + }, + "es": { + "primary": "Programar una reunión inicial", + "secondary": "Ver nuestros servicios", + }, + "it": { + "primary": "Prenota una consulenza introduttiva", + "secondary": "Scopri i nostri servizi", + }, + "pt": { + "primary": "Agendar uma consulta inicial", + "secondary": "Ver os nossos serviços", + }, + "ru": { + "primary": "Запланировать консультацию", + "secondary": "Посмотреть услуги", + }, +} + +CTA_VARIANTS = { + "nl": [ + "Plan gratis gesprek", + "Plan intake", + "Plan dienstengesprek", + "Contact Support", + "Start jouw project", + "Vraag intake aan", + "Plan kennismaking", + "Bekijk diensten", + "Bekijk alle diensten", + "Vraag startergesprek aan", + "Plan startergesprek", + "Plan zakelijk gesprek", + "Start webshop traject", + "Vraag supportplan aan", + "Plan gratis kennismaking", + "Bekijk projectresultaten", + ], +} + +NL_REPLACEMENTS = { + "New": "Nieuw", + "Popular": "Populair", + "AI Search": "AI-zoekfunctie", + "custom blokken": "maatwerkblokken", + "monitoring-ready basis": "stabiele technische basis", + "Monitoring + fixes": "Monitoring en technische oplossingen", + "SEO-ready basis": "SEO-vriendelijke basis", + "Starter Website": "Starter-website", + "Business Website": "Zakelijke website", + "Support & Groei": "Onderhoud & groei", + "24u": "binnen 24 uur", + "24u Reactietijd": "Reactie binnen 24 uur", + "15m Intake call": "Intakegesprek van 15 minuten", + "100% Vrijblijvend": "Volledig vrijblijvend", + "Webshop Implementatie": "Webshop-implementatie", + "Doorlopend Verbetering": "Doorlopende verbetering", + "Monitoring-ready stack": "Stabiele technische basis", +} + +FOOTER_CONTENT = { + "nl": { + "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": "Contact & privacy", + "support": '

Plan een kennismakingsgesprek
info@mandelblog.com
Privacybeleid
Adres & openingstijden

', + "mini": '

Contact - Diensten - Projecten - Privacybeleid - MandelBlog Studio

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

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

", + "links_heading": "Quick links", + "support_heading": "Book a call", + "support": '

Schedule a consultation
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 wollen, ohne in Template-Logik festzustecken.

", + "links_heading": "Schnellzugriff", + "support_heading": "Gespräch planen", + "support": '

Beratungsgespräch planen
info@mandelblog.com
Unsere Leistungen ansehen

', + "mini": '

Kontakt - Dienstleistungen - Projekte - MandelBlog Studio

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

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

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

Planifier une consultation
info@mandelblog.com
Voir nos services

', + "mini": '

Contact - Services - Projets - MandelBlog Studio

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

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

", + "links_heading": "Accesos rápidos", + "support_heading": "Programar una reunión", + "support": '

Programar 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 effetto template.

", + "links_heading": "Link rapidi", + "support_heading": "Prenota una call", + "support": '

Prenota una consulenza introduttiva
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 conversa", + "support": '

Agendar uma consulta inicial
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

', + }, +} + + +def uid() -> str: + return str(uuid.uuid4()) + + +def block(block_type: str, value: dict[str, Any] | str) -> dict[str, Any]: + return {"type": block_type, "value": value, "id": uid()} + + +def item(value: dict[str, Any] | str) -> dict[str, Any]: + return {"type": "item", "value": value, "id": uid()} + + +AGENCY_HERO_IMAGE_ID = 32 +AGENCY_SUPPORT_IMAGE_ID = 33 +AGENCY_PROCESS_IMAGE_ID = 33 + + +HOME_COPY = { + "nl": { + "badge": "MANDELBLOG STUDIO", + "headline": "Websites voor bedrijven die professioneel willen groeien", + "sub": "

MandelBlog ontwikkelt websites die vertrouwen opbouwen, duidelijk sturen op contact en eenvoudig te beheren zijn voor uw team.

", + "stats": [ + ("3", "Heldere stappen"), + ("1", "Vast aanspreekpunt"), + ("8", "Beschikbare talen"), + ], + "logos": "Gebouwd met Wagtail, Django en beproefde componenten", + "features_title": "Waar MandelBlog op stuurt", + "features_sub": "

Een zakelijke website die klaar is voor aanvragen, vertrouwen en doorontwikkeling.

", + "features": [ + ( + "diagram-3", + "Duidelijke structuur", + "

Bezoekers vinden snel de juiste dienst, case of contactroute.

", + "process", + "Werkwijze", + "featured", + ), + ( + "pencil-square", + "Zelf te beheren", + "

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

", + "services", + None, + "none", + ), + ( + "shield-check", + "Stabiele technische basis", + "

Een schaalbare opzet zonder overbodige complexiteit of template-ruis.

", + "process", + "Bekijk werkwijze", + "none", + ), + ( + "graph-up-arrow", + "Klaar voor doorontwikkeling", + "

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

", + "projects", + "Bekijk projecten", + "none", + ), + ], + "pricing_title": "Onze pakketten", + "pricing_sub": "

Elk pakket heeft een duidelijke scope. De exacte invulling stemmen we af in het kennismakingsgesprek.

", + "pricing_footer": "

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

", + "tiers": { + "starter": ( + "Voor ondernemers die professioneel online willen starten", + [ + "Kernpagina’s en duidelijke navigatie", + "Editor voor eigen contentbeheer", + "Mobiel sterke presentatie", + ], + False, + "", + ), + "business": ( + "Voor dienstverleners met meerdere proposities of groeiplannen", + [ + "Meer ruimte voor diensten en cases", + "Conversiegerichte opbouw", + "SEO-vriendelijke basis", + ], + True, + "Aanbevolen", + ), + "webshop": ( + "Voor organisaties die een zakelijke site willen uitbreiden met online verkoop", + [ + "Productstructuur en checkout", + "Betalingen en orderverwerking", + "Schaalbare commerce-opzet", + ], + False, + "", + ), + "support": ( + "Voor teams die onderhoud, technische rust en doorlopende optimalisatie nodig hebben", + [ + "Updates en onderhoud", + "Monitoring en technische oplossingen", + "Doorlopende verbetering", + ], + False, + "", + ), + }, + "testimonials_title": "Wat opdrachtgevers waarderen", + "testimonials_sub": "

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

", + "testimonials": [ + ( + "

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

", + "Sanne de Vries", + "Studio Nova - eigenaar", + ), + ( + "

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

", + "Mark Jansen", + "Jansen Interieur - medeoprichter", + ), + ], + "faq_title": "Veelgestelde vragen", + "faq_sub": "

We zijn duidelijk over planning, samenwerking en beheer.

", + "faqs": [ + ( + "Voor welke bedrijven is MandelBlog geschikt?", + "

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

", + "Algemeen", + ), + ( + "Kunnen we later uitbreiden?", + "

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

", + "Uitbreiding", + ), + ( + "Beheren we de content zelf?", + "

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

", + "Beheer", + ), + ( + "Wat gebeurt er na livegang?", + "

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

", + "Support", + ), + ], + "cta_headline": "Wilt u een website die vertrouwen geeft en werk uit handen neemt?", + "cta_sub": "

Plan een kennismakingsgesprek en we laten zien welke opzet past bij uw bedrijf en team.

", + "no_cc": "Volledig vrijblijvend", + }, + "en": { + "badge": "MANDELBLOG STUDIO", + "headline": "Websites for companies that want to grow with confidence", + "sub": "

MandelBlog builds websites that create trust, guide visitors toward contact and remain easy for your team to manage.

", + "stats": [ + ("3", "Clear steps"), + ("1", "Direct point of contact"), + ("8", "Available languages"), + ], + "logos": "Built with Wagtail, Django and proven components", + "features_title": "What MandelBlog focuses on", + "features_sub": "

A business website built for enquiries, trust and future growth.

", + "features": [ + ( + "diagram-3", + "Clear structure", + "

Visitors quickly find the right service, case study or contact path.

", + "process", + "How we work", + "featured", + ), + ( + "pencil-square", + "Easy to manage", + "

Your team can update copy, visuals and sections in practical content blocks.

", + "services", + None, + "none", + ), + ( + "shield-check", + "Stable technical foundation", + "

A scalable setup without unnecessary complexity or template clutter.

", + "process", + "See the process", + "none", + ), + ( + "graph-up-arrow", + "Ready to expand", + "

You can add pages, integrations or commerce later without rebuilding everything.

", + "projects", + "View projects", + "none", + ), + ], + "pricing_title": "Our packages", + "pricing_sub": "

Each package has a clear scope. We tailor the details during the consultation.

", + "pricing_footer": "

We help you choose the package that fits your stage, team and priorities.

", + "tiers": { + "starter": ( + "For businesses that need a professional online presence quickly", + [ + "Core pages and clear navigation", + "Editor for internal content updates", + "Strong mobile presentation", + ], + False, + "", + ), + "business": ( + "For service firms with multiple offers or growth plans", + [ + "More room for services and case studies", + "Conversion-focused structure", + "SEO-friendly foundation", + ], + True, + "Recommended", + ), + "webshop": ( + "For organisations that want to add online sales to a business website", + [ + "Product structure and checkout", + "Payments and order handling", + "Scalable commerce setup", + ], + False, + "", + ), + "support": ( + "For teams that need maintenance, technical stability and ongoing optimisation", + [ + "Updates and maintenance", + "Monitoring and technical fixes", + "Continuous improvement", + ], + False, + "", + ), + }, + "testimonials_title": "What clients appreciate", + "testimonials_sub": "

Small teams choose MandelBlog because the process stays focused and the website remains usable after launch.

", + "testimonials": [ + ( + "

Within a short timeframe we had a website that finally matched our service offer and that we can maintain ourselves.

", + "Sanne de Vries", + "Studio Nova - founder", + ), + ( + "

The process was clear, the message became sharper and enquiries now come in through one consistent path.

", + "Mark Jansen", + "Jansen Interior - co-founder", + ), + ], + "faq_title": "Frequently asked questions", + "faq_sub": "

We are clear about planning, collaboration and day-to-day management.

", + "faqs": [ + ( + "What type of companies is MandelBlog for?", + "

Service businesses, studios and small teams that need a professional website without a heavy process.

", + "General", + ), + ( + "Can we expand later?", + "

Yes. We build a structure that can grow with extra pages, languages or integrations.

", + "Growth", + ), + ( + "Can we manage content ourselves?", + "

Yes. The setup is designed so your team can edit pages and blocks independently.

", + "Management", + ), + ( + "What happens after launch?", + "

You can continue with maintenance and targeted improvements when needed.

", + "Support", + ), + ], + "cta_headline": "Do you want a website that builds trust and saves time?", + "cta_sub": "

Schedule a consultation and we will show you which setup fits your business and your team.

", + "no_cc": "No obligation", + }, + "de": { + "badge": "MANDELBLOG STUDIO", + "headline": "Websites für Unternehmen, die professionell wachsen wollen", + "sub": "

MandelBlog entwickelt Websites, die Vertrauen schaffen, klar zur Kontaktaufnahme führen und für Ihr Team gut pflegbar bleiben.

", + "stats": [ + ("3", "Klare Schritte"), + ("1", "Fester Ansprechpartner"), + ("8", "Verfügbare Sprachen"), + ], + "logos": "Gebaut mit Wagtail, Django und bewährten Komponenten", + "features_title": "Worauf MandelBlog achtet", + "features_sub": "

Kein Webshop-Demoauftritt, sondern eine Unternehmenswebsite für Anfragen, Vertrauen und Weiterentwicklung.

", + "features": [ + ( + "diagram-3", + "Klare Struktur", + "

Besucher finden schnell die passende Leistung, Referenz oder Kontaktmöglichkeit.

", + "process", + "Vorgehensweise", + "featured", + ), + ( + "pencil-square", + "Einfach zu pflegen", + "

Ihr Team kann Texte, Bilder und Abschnitte in klaren Blöcken selbst anpassen.

", + "services", + None, + "none", + ), + ( + "shield-check", + "Stabile technische Basis", + "

Ein skalierbarer Aufbau ohne unnötige Komplexität oder Template-Ballast.

", + "process", + "Ablauf ansehen", + "none", + ), + ( + "graph-up-arrow", + "Bereit für den Ausbau", + "

Zusätzliche Seiten, Integrationen oder Commerce lassen sich später sauber ergänzen.

", + "projects", + "Projekte ansehen", + "none", + ), + ], + "pricing_title": "Unsere Pakete", + "pricing_sub": "

Jedes Paket hat einen klaren Umfang. Die genaue Ausgestaltung besprechen wir im Beratungsgespräch.

", + "pricing_footer": "

Wir empfehlen das Paket, das zu Ihrer Phase, Ihrem Team und Ihren Prioritäten passt.

", + "tiers": { + "starter": ( + "Für Unternehmen, die schnell professionell online gehen möchten", + [ + "Kernseiten und klare Navigation", + "Editor für interne Inhaltsupdates", + "Starke mobile Darstellung", + ], + False, + "", + ), + "business": ( + "Für Dienstleister mit mehreren Angeboten oder Wachstumsplänen", + [ + "Mehr Raum für Leistungen und Referenzen", + "Conversion-orientierte Struktur", + "SEO-freundliche Basis", + ], + True, + "Empfohlen", + ), + "webshop": ( + "Für Unternehmen, die Online-Verkauf mit einer Unternehmenswebsite verbinden möchten", + [ + "Produktstruktur und Checkout", + "Zahlungen und Bestellabläufe", + "Skalierbarer Commerce-Aufbau", + ], + False, + "", + ), + "support": ( + "Für Teams, die Wartung, technische Stabilität und laufende Optimierung brauchen", + [ + "Updates und Wartung", + "Monitoring und technische Lösungen", + "Kontinuierliche Verbesserung", + ], + False, + "", + ), + }, + "testimonials_title": "Was Auftraggeber schätzen", + "testimonials_sub": "

Kleine Teams wählen MandelBlog, weil der Prozess übersichtlich bleibt und die Website danach wirklich nutzbar ist.

", + "testimonials": [ + ( + "

Wir hatten in kurzer Zeit eine Website, die endlich zu unserer Dienstleistung passt und die wir selbst pflegen können.

", + "Sanne de Vries", + "Studio Nova - Inhaberin", + ), + ( + "

Der Ablauf war klar, die Inhalte wurden schärfer und Anfragen laufen jetzt über einen eindeutigen Weg.

", + "Mark Jansen", + "Jansen Interieur - Mitgründer", + ), + ], + "faq_title": "Häufig gestellte Fragen", + "faq_sub": "

Wir kommunizieren klar über Zeitplan, Zusammenarbeit und Pflege.

", + "faqs": [ + ( + "Für welche Unternehmen ist MandelBlog geeignet?", + "

Für Dienstleister, Studios und kleine Teams, die eine professionelle Website ohne schweres Projekt benötigen.

", + "Allgemein", + ), + ( + "Können wir später erweitern?", + "

Ja. Wir bauen eine Struktur, die sich später um Seiten, Sprachen oder Integrationen erweitern lässt.

", + "Ausbau", + ), + ( + "Können wir Inhalte selbst pflegen?", + "

Ja. Genau dafür ist die Plattform ausgelegt.

", + "Pflege", + ), + ( + "Was passiert nach dem Go-live?", + "

Danach können Sie Wartung und gezielte Weiterentwicklung hinzunehmen.

", + "Support", + ), + ], + "cta_headline": "Möchten Sie eine Website, die Vertrauen schafft und Aufwand reduziert?", + "cta_sub": "

Planen Sie ein Beratungsgespräch und wir zeigen, welche Lösung zu Ihrem Unternehmen und Team passt.

", + "no_cc": "Unverbindlich", + }, + "fr": { + "badge": "MANDELBLOG STUDIO", + "headline": "Des sites pour les entreprises qui veulent grandir avec crédibilité", + "sub": "

MandelBlog conçoit des sites qui inspirent confiance, orientent clairement vers le contact et restent simples à gérer pour votre équipe.

", + "stats": [ + ("3", "Étapes claires"), + ("1", "Interlocuteur unique"), + ("8", "Langues disponibles"), + ], + "logos": "Construit avec Wagtail, Django et des composants éprouvés", + "features_title": "Ce que MandelBlog privilégie", + "features_sub": "

Pas une démo e-commerce, mais un site d’entreprise pensé pour les demandes, la crédibilité et l’évolution.

", + "features": [ + ( + "diagram-3", + "Structure claire", + "

Les visiteurs trouvent rapidement le bon service, la bonne référence ou la bonne voie de contact.

", + "process", + "Notre méthode", + "featured", + ), + ( + "pencil-square", + "Simple à gérer", + "

Votre équipe peut mettre à jour textes, visuels et sections dans des blocs clairs.

", + "services", + None, + "none", + ), + ( + "shield-check", + "Base technique fiable", + "

Une architecture évolutive sans complexité inutile ni bruit de template.

", + "process", + "Voir la méthode", + "none", + ), + ( + "graph-up-arrow", + "Prêt à évoluer", + "

Vous pouvez ajouter plus tard des pages, des intégrations ou du commerce sans repartir de zéro.

", + "projects", + "Voir les projets", + "none", + ), + ], + "pricing_title": "Nos offres", + "pricing_sub": "

Chaque offre a un périmètre clair. Nous ajustons le détail pendant la consultation.

", + "pricing_footer": "

Nous vous aidons à choisir l’offre adaptée à votre étape, à votre équipe et à vos priorités.

", + "tiers": { + "starter": ( + "Pour les entreprises qui veulent être en ligne rapidement avec une présence professionnelle", + [ + "Pages essentielles et navigation claire", + "Éditeur pour mettre à jour le contenu en interne", + "Présentation solide sur mobile", + ], + False, + "", + ), + "business": ( + "Pour les sociétés de services avec plusieurs offres ou des ambitions de croissance", + [ + "Plus d’espace pour les services et les références", + "Structure orientée conversion", + "Base favorable au SEO", + ], + True, + "Recommandé", + ), + "webshop": ( + "Pour les organisations qui veulent ajouter la vente en ligne à un site d’entreprise", + [ + "Structure produit et tunnel d’achat", + "Paiements et gestion des commandes", + "Base e-commerce évolutive", + ], + False, + "", + ), + "support": ( + "Pour les équipes qui ont besoin de maintenance, de stabilité technique et d’optimisation continue", + [ + "Mises à jour et maintenance", + "Monitoring et correctifs techniques", + "Amélioration continue", + ], + False, + "", + ), + }, + "testimonials_title": "Ce que nos clients apprécient", + "testimonials_sub": "

Les petites équipes choisissent MandelBlog parce que le projet reste clair et que le site reste utile après la mise en ligne.

", + "testimonials": [ + ( + "

Nous avons obtenu rapidement un site qui correspond enfin à notre offre et que nous pouvons gérer nous-mêmes.

", + "Sanne de Vries", + "Studio Nova - fondatrice", + ), + ( + "

Le projet était clair, le message est devenu plus net et les demandes arrivent désormais par un parcours cohérent.

", + "Mark Jansen", + "Jansen Intérieur - cofondateur", + ), + ], + "faq_title": "Questions fréquentes", + "faq_sub": "

Nous sommes clairs sur le planning, la collaboration et la gestion au quotidien.

", + "faqs": [ + ( + "Pour quelles entreprises MandelBlog est-il adapté ?", + "

Pour les sociétés de services, les studios et les petites équipes qui ont besoin d’un site professionnel sans projet lourd.

", + "Général", + ), + ( + "Pouvons-nous faire évoluer le site plus tard ?", + "

Oui. Nous construisons une structure capable d’accueillir ensuite des pages, des langues ou des intégrations.

", + "Évolution", + ), + ( + "Pouvons-nous gérer le contenu nous-mêmes ?", + "

Oui. C’est un principe central de la plateforme.

", + "Gestion", + ), + ( + "Que se passe-t-il après la mise en ligne ?", + "

Vous pouvez continuer avec de la maintenance et des améliorations ciblées si nécessaire.

", + "Support", + ), + ], + "cta_headline": "Vous voulez un site qui inspire confiance et fait gagner du temps ?", + "cta_sub": "

Planifiez une consultation et nous vous montrerons quelle approche convient à votre entreprise et à votre équipe.

", + "no_cc": "Sans engagement", + }, + "es": { + "badge": "MANDELBLOG STUDIO", + "headline": "Sitios web para empresas que quieren crecer con credibilidad", + "sub": "

MandelBlog crea sitios que generan confianza, orientan con claridad hacia el contacto y siguen siendo fáciles de gestionar para su equipo.

", + "stats": [ + ("3", "Pasos claros"), + ("1", "Interlocutor fijo"), + ("8", "Idiomas disponibles"), + ], + "logos": "Construido con Wagtail, Django y componentes probados", + "features_title": "En qué se enfoca MandelBlog", + "features_sub": "

Un sitio corporativo preparado para generar oportunidades, confianza y crecimiento.

", + "features": [ + ( + "diagram-3", + "Estructura clara", + "

Los visitantes encuentran rápido el servicio, el caso o la vía de contacto correcta.

", + "process", + "Cómo trabajamos", + "featured", + ), + ( + "pencil-square", + "Fácil de gestionar", + "

Su equipo puede actualizar textos, imágenes y secciones en bloques claros.

", + "services", + None, + "none", + ), + ( + "shield-check", + "Base técnica estable", + "

Una estructura escalable sin complejidad innecesaria ni ruido de plantilla.

", + "process", + "Ver proceso", + "none", + ), + ( + "graph-up-arrow", + "Preparado para crecer", + "

Más adelante puede añadir páginas, integraciones o comercio sin empezar de cero.

", + "projects", + "Ver proyectos", + "none", + ), + ], + "pricing_title": "Nuestros paquetes", + "pricing_sub": "

Cada paquete tiene un alcance claro. Ajustamos el detalle durante la reunión inicial.

", + "pricing_footer": "

Le ayudamos a elegir el paquete que encaja con su etapa, su equipo y sus prioridades.

", + "tiers": { + "starter": ( + "Para empresas que necesitan una presencia profesional en poco tiempo", + [ + "Páginas clave y navegación clara", + "Editor para actualizar contenido internamente", + "Buena presentación en móvil", + ], + False, + "", + ), + "business": ( + "Para empresas de servicios con varias propuestas o planes de crecimiento", + [ + "Más espacio para servicios y casos", + "Estructura orientada a conversión", + "Base favorable al SEO", + ], + True, + "Recomendado", + ), + "webshop": ( + "Para organizaciones que quieren añadir venta online a un sitio corporativo", + [ + "Estructura de productos y checkout", + "Pagos y gestión de pedidos", + "Base escalable para comercio", + ], + False, + "", + ), + "support": ( + "Para equipos que necesitan mantenimiento, estabilidad técnica y optimización continua", + [ + "Actualizaciones y mantenimiento", + "Monitorización y soluciones técnicas", + "Mejora continua", + ], + False, + "", + ), + }, + "testimonials_title": "Lo que valoran los clientes", + "testimonials_sub": "

Los equipos pequeños eligen MandelBlog porque el proceso sigue siendo claro y el sitio continúa siendo útil tras la entrega.

", + "testimonials": [ + ( + "

En poco tiempo tuvimos un sitio que por fin encaja con nuestros servicios y que podemos mantener nosotros mismos.

", + "Sanne de Vries", + "Studio Nova - fundadora", + ), + ( + "

El proceso fue claro, el mensaje ganó estructura y ahora las solicitudes llegan por una ruta única y lógica.

", + "Mark Jansen", + "Jansen Interior - cofundador", + ), + ], + "faq_title": "Preguntas frecuentes", + "faq_sub": "

Somos claros con la planificación, la colaboración y la gestión del sitio.

", + "faqs": [ + ( + "¿Para qué tipo de empresas es MandelBlog?", + "

Para empresas de servicios, estudios y equipos pequeños que necesitan un sitio profesional sin un proyecto pesado.

", + "General", + ), + ( + "¿Podemos ampliar más adelante?", + "

Sí. Construimos una estructura que puede crecer con páginas, idiomas o integraciones.

", + "Crecimiento", + ), + ( + "¿Podemos gestionar el contenido nosotros mismos?", + "

Sí. Es una base importante de la plataforma.

", + "Gestión", + ), + ( + "¿Qué ocurre después de la publicación?", + "

Después puede continuar con mantenimiento y mejoras puntuales si lo necesita.

", + "Soporte", + ), + ], + "cta_headline": "¿Quiere un sitio web que genere confianza y ahorre tiempo?", + "cta_sub": "

Programe una reunión inicial y le mostraremos qué enfoque encaja con su empresa y su equipo.

", + "no_cc": "Sin compromiso", + }, + "it": { + "badge": "MANDELBLOG STUDIO", + "headline": "Siti web per aziende che vogliono crescere in modo credibile", + "sub": "

MandelBlog realizza siti che trasmettono fiducia, guidano con chiarezza verso il contatto e restano semplici da gestire per il vostro team.

", + "stats": [ + ("3", "Passi chiari"), + ("1", "Referente unico"), + ("8", "Lingue disponibili"), + ], + "logos": "Realizzato con Wagtail, Django e componenti collaudati", + "features_title": "Su cosa punta MandelBlog", + "features_sub": "

Non un template da shop, ma un sito aziendale pensato per richieste, fiducia e crescita.

", + "features": [ + ( + "diagram-3", + "Struttura chiara", + "

I visitatori trovano rapidamente il servizio, il caso studio o il contatto giusto.

", + "process", + "Come lavoriamo", + "featured", + ), + ( + "pencil-square", + "Facile da gestire", + "

Il vostro team può aggiornare testi, immagini e sezioni in blocchi chiari.

", + "services", + None, + "none", + ), + ( + "shield-check", + "Base tecnica stabile", + "

Una struttura scalabile senza complessità inutile né effetto template.

", + "process", + "Vedi il metodo", + "none", + ), + ( + "graph-up-arrow", + "Pronto a crescere", + "

Più avanti potete aggiungere pagine, integrazioni o commercio senza rifare tutto.

", + "projects", + "Vedi i progetti", + "none", + ), + ], + "pricing_title": "I nostri pacchetti", + "pricing_sub": "

Ogni pacchetto ha un perimetro chiaro. I dettagli vengono definiti durante la consulenza iniziale.

", + "pricing_footer": "

Vi aiutiamo a scegliere il pacchetto più adatto alla vostra fase, al vostro team e alle vostre priorità.

", + "tiers": { + "starter": ( + "Per aziende che vogliono essere online in modo professionale in tempi rapidi", + [ + "Pagine essenziali e navigazione chiara", + "Editor per aggiornare i contenuti internamente", + "Presentazione solida su mobile", + ], + False, + "", + ), + "business": ( + "Per aziende di servizi con più offerte o obiettivi di crescita", + [ + "Più spazio per servizi e casi", + "Struttura orientata alla conversione", + "Base favorevole alla SEO", + ], + True, + "Consigliato", + ), + "webshop": ( + "Per organizzazioni che vogliono aggiungere la vendita online a un sito aziendale", + [ + "Struttura prodotti e checkout", + "Pagamenti e gestione ordini", + "Base e-commerce scalabile", + ], + False, + "", + ), + "support": ( + "Per team che hanno bisogno di manutenzione, stabilità tecnica e ottimizzazione continua", + [ + "Aggiornamenti e manutenzione", + "Monitoraggio e interventi tecnici", + "Miglioramento continuo", + ], + False, + "", + ), + }, + "testimonials_title": "Cosa apprezzano i clienti", + "testimonials_sub": "

I piccoli team scelgono MandelBlog perché il percorso resta chiaro e il sito rimane utile anche dopo la pubblicazione.

", + "testimonials": [ + ( + "

In poco tempo abbiamo avuto un sito che finalmente rispecchia i nostri servizi e che possiamo gestire internamente.

", + "Sanne de Vries", + "Studio Nova - fondatrice", + ), + ( + "

Il progetto è stato chiaro, il messaggio è diventato più forte e ora le richieste arrivano attraverso un percorso coerente.

", + "Mark Jansen", + "Jansen Interior - cofondatore", + ), + ], + "faq_title": "Domande frequenti", + "faq_sub": "

Siamo chiari su tempi, collaborazione e gestione del sito.

", + "faqs": [ + ( + "Per quali aziende è adatto MandelBlog?", + "

Per aziende di servizi, studi e piccoli team che hanno bisogno di un sito professionale senza un progetto pesante.

", + "Generale", + ), + ( + "Possiamo espandere il sito in seguito?", + "

Sì. Costruiamo una struttura che può crescere con pagine, lingue o integrazioni.

", + "Crescita", + ), + ( + "Possiamo gestire i contenuti da soli?", + "

Sì. È uno dei principi centrali della piattaforma.

", + "Gestione", + ), + ( + "Cosa succede dopo il lancio?", + "

Potete proseguire con manutenzione e miglioramenti mirati quando serve.

", + "Supporto", + ), + ], + "cta_headline": "Volete un sito che trasmetta fiducia e faccia risparmiare tempo?", + "cta_sub": "

Prenotate una consulenza introduttiva e vi mostreremo quale impostazione è adatta alla vostra azienda e al vostro team.

", + "no_cc": "Senza impegno", + }, + "pt": { + "badge": "MANDELBLOG STUDIO", + "headline": "Websites para empresas que querem crescer com credibilidade", + "sub": "

A MandelBlog cria websites que transmitem confiança, orientam claramente para o contacto e continuam fáceis de gerir pela sua equipa.

", + "stats": [ + ("3", "Etapas claras"), + ("1", "Ponto de contacto"), + ("8", "Idiomas disponíveis"), + ], + "logos": "Construído com Wagtail, Django e componentes comprovados", + "features_title": "No que a MandelBlog se concentra", + "features_sub": "

Um website empresarial preparado para pedidos, confiança e crescimento.

", + "features": [ + ( + "diagram-3", + "Estrutura clara", + "

Os visitantes encontram rapidamente o serviço, o caso ou o caminho de contacto certo.

", + "process", + "Método de trabalho", + "featured", + ), + ( + "pencil-square", + "Fácil de gerir", + "

A sua equipa pode atualizar textos, imagens e secções em blocos claros.

", + "services", + None, + "none", + ), + ( + "shield-check", + "Base técnica estável", + "

Uma estrutura escalável sem complexidade desnecessária nem aparência de template.

", + "process", + "Ver método", + "none", + ), + ( + "graph-up-arrow", + "Preparado para crescer", + "

Mais tarde pode acrescentar páginas, integrações ou comércio sem recomeçar.

", + "projects", + "Ver projetos", + "none", + ), + ], + "pricing_title": "Os nossos pacotes", + "pricing_sub": "

Cada pacote tem um âmbito claro. Ajustamos os detalhes durante a consulta inicial.

", + "pricing_footer": "

Ajudamos a escolher o pacote mais adequado à sua fase, equipa e prioridades.

", + "tiers": { + "starter": ( + "Para empresas que precisam de uma presença profissional online rapidamente", + [ + "Páginas principais e navegação clara", + "Editor para atualizar conteúdo internamente", + "Apresentação forte em mobile", + ], + False, + "", + ), + "business": ( + "Para empresas de serviços com várias ofertas ou planos de crescimento", + [ + "Mais espaço para serviços e casos", + "Estrutura orientada para conversão", + "Base favorável a SEO", + ], + True, + "Recomendado", + ), + "webshop": ( + "Para organizações que querem juntar vendas online a um website empresarial", + [ + "Estrutura de produtos e checkout", + "Pagamentos e gestão de encomendas", + "Base escalável para comércio", + ], + False, + "", + ), + "support": ( + "Para equipas que precisam de manutenção, estabilidade técnica e otimização contínua", + [ + "Atualizações e manutenção", + "Monitorização e soluções técnicas", + "Melhoria contínua", + ], + False, + "", + ), + }, + "testimonials_title": "O que os clientes valorizam", + "testimonials_sub": "

As pequenas equipas escolhem a MandelBlog porque o processo se mantém claro e o website continua útil depois do lançamento.

", + "testimonials": [ + ( + "

Em pouco tempo tivemos um website que finalmente corresponde aos nossos serviços e que conseguimos gerir internamente.

", + "Sanne de Vries", + "Studio Nova - fundadora", + ), + ( + "

O processo foi claro, a mensagem ganhou estrutura e agora os pedidos chegam por um percurso consistente.

", + "Mark Jansen", + "Jansen Interior - cofundador", + ), + ], + "faq_title": "Perguntas frequentes", + "faq_sub": "

Somos claros quanto a planeamento, colaboração e gestão do website.

", + "faqs": [ + ( + "Para que empresas a MandelBlog é indicada?", + "

Para empresas de serviços, estúdios e pequenas equipas que precisam de um website profissional sem um projeto pesado.

", + "Geral", + ), + ( + "Podemos expandir mais tarde?", + "

Sim. Construímos uma estrutura que pode crescer com páginas, idiomas ou integrações.

", + "Expansão", + ), + ( + "Podemos gerir o conteúdo internamente?", + "

Sim. Esse é um princípio central da plataforma.

", + "Gestão", + ), + ( + "O que acontece depois do lançamento?", + "

Pode continuar com manutenção e melhorias direcionadas quando fizer sentido.

", + "Suporte", + ), + ], + "cta_headline": "Quer um website que transmita confiança e poupe tempo?", + "cta_sub": "

Agende uma consulta inicial e mostramos qual a abordagem mais adequada para a sua empresa e equipa.

", + "no_cc": "Sem compromisso", + }, + "ru": { + "badge": "MANDELBLOG STUDIO", + "headline": "Сайты для компаний, которым важен профессиональный рост", + "sub": "

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

", + "stats": [ + ("3", "Чёткие этапы"), + ("1", "Единый контакт"), + ("8", "Доступные языки"), + ], + "logos": "Собрано на Wagtail, Django и проверенных компонентах", + "features_title": "На чём делает акцент MandelBlog", + "features_sub": "

Это не демо-магазин, а корпоративный сайт для заявок, доверия и дальнейшего роста.

", + "features": [ + ( + "diagram-3", + "Понятная структура", + "

Посетители быстро находят нужную услугу, кейс или путь к контакту.

", + "process", + "Как мы работаем", + "featured", + ), + ( + "pencil-square", + "Удобно управлять", + "

Ваша команда может обновлять тексты, изображения и секции в понятных блоках.

", + "services", + None, + "none", + ), + ( + "shield-check", + "Стабильная техническая база", + "

Масштабируемая структура без лишней сложности и шаблонного шума.

", + "process", + "Посмотреть процесс", + "none", + ), + ( + "graph-up-arrow", + "Готов к развитию", + "

Позже можно добавить страницы, интеграции или коммерческие функции без полной переделки.

", + "projects", + "Посмотреть проекты", + "none", + ), + ], + "pricing_title": "Наши пакеты", + "pricing_sub": "

У каждого пакета понятный объём. Детали мы согласовываем на вводной консультации.

", + "pricing_footer": "

Мы помогаем выбрать пакет, который подходит вашему этапу, команде и приоритетам.

", + "tiers": { + "starter": ( + "Для компаний, которым нужно быстро выйти онлайн с профессиональной подачей", + [ + "Основные страницы и понятная навигация", + "Редактор для обновления контента внутри команды", + "Сильная мобильная версия", + ], + False, + "", + ), + "business": ( + "Для сервисных компаний с несколькими направлениями или планами роста", + [ + "Больше места для услуг и кейсов", + "Структура, ориентированная на конверсию", + "SEO-дружественная база", + ], + True, + "Рекомендуем", + ), + "webshop": ( + "Для компаний, которые хотят добавить онлайн-продажи к корпоративному сайту", + [ + "Структура каталога и оформление заказа", + "Платежи и обработка заказов", + "Масштабируемая e-commerce база", + ], + False, + "", + ), + "support": ( + "Для команд, которым нужны поддержка, техническая стабильность и постоянное улучшение", + [ + "Обновления и обслуживание", + "Мониторинг и технические решения", + "Непрерывное улучшение", + ], + False, + "", + ), + }, + "testimonials_title": "Что ценят клиенты", + "testimonials_sub": "

Небольшие команды выбирают MandelBlog, потому что процесс остаётся понятным, а сайт полезным после запуска.

", + "testimonials": [ + ( + "

За короткое время мы получили сайт, который наконец отражает наши услуги и который можем поддерживать сами.

", + "Sanne de Vries", + "Studio Nova - основатель", + ), + ( + "

Процесс был понятным, сообщение стало чётче, а заявки теперь приходят по одному логичному сценарию.

", + "Mark Jansen", + "Jansen Interior - сооснователь", + ), + ], + "faq_title": "Частые вопросы", + "faq_sub": "

Мы понятно объясняем сроки, формат работы и дальнейшее управление сайтом.

", + "faqs": [ + ( + "Для каких компаний подходит MandelBlog?", + "

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

", + "Общее", + ), + ( + "Можно ли расширить сайт позже?", + "

Да. Мы строим структуру, которую можно развивать страницами, языками и интеграциями.

", + "Развитие", + ), + ( + "Сможем ли мы сами управлять контентом?", + "

Да. Это один из базовых принципов платформы.

", + "Управление", + ), + ( + "Что происходит после запуска?", + "

При необходимости вы можете продолжить с поддержкой и точечными улучшениями.

", + "Поддержка", + ), + ], + "cta_headline": "Нужен сайт, который вызывает доверие и экономит время?", + "cta_sub": "

Запланируйте консультацию, и мы покажем, какой формат подойдёт вашей компании и команде.

", + "no_cc": "Без обязательств", + }, +} + +STANDARD_COPY = { + "nl": { + "faq_title": "Veelgestelde vragen", + "faq_sub": "

We houden het traject helder en praktisch.

", + "faq_items": [ + ( + "Werken jullie met vaste templates?", + "

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

", + "Werkwijze", + ), + ( + "Kunnen we later uitbreiden?", + "

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

", + "Uitbreiding", + ), + ( + "Beheren we de inhoud zelf?", + "

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

", + "Beheer", + ), + ], + "cta_sub": "

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

", + "no_cc": "Volledig vrijblijvend", + "pages": { + "about": { + "headline": "Wie MandelBlog is en hoe we werken", + "sub": "

MandelBlog helpt kleine bedrijven en dienstverleners aan een website die professioneel oogt, logisch converteert en beheerbaar blijft voor het eigen team.

", + "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.

", + ), + ], + "steps_badge": "Werkwijze", + "steps_heading": "Onze aanpak in 3 stappen", + "steps_sub": "Kort traject, duidelijke keuzes en daarna een site die voor uw team werkt.", + "steps": [ + ( + "1", + "Kennismaking", + "We bepalen doel, inhoud en prioriteiten.", + "chat-square-text", + ), + ( + "2", + "Uitwerking", + "We bouwen de pagina’s en stemmen de inhoud af.", + "layout-text-window", + ), + ( + "3", + "Oplevering", + "U krijgt uitleg, beheer en een duidelijke vervolgstap.", + "rocket", + ), + ], + "cta": "Wilt u weten of onze aanpak past bij uw bedrijf?", + }, + "services": { + "headline": "Diensten voor bedrijven die overzicht en kwaliteit willen", + "sub": "

Elke dienst is ingericht rondom duidelijke keuzes, bruikbare content en een technische basis die door kan groeien.

", + "features_title": "Wat we leveren", + "features_sub": "

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

", + "features": [ + ( + "window", + "Starter-website", + "

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

", + ), + ( + "briefcase", + "Zakelijke website", + "

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

", + ), + ( + "cart-check", + "Webshop-implementatie", + "

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

", + ), + ( + "wrench-adjustable", + "Onderhoud & groei", + "

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

", + ), + ], + "cta": "Twijfelt u welk pakket past bij uw fase?", + }, + "projects": { + "headline": "Projecten waarin structuur, inhoud en techniek samenkomen", + "sub": "

Onze projecten zijn ontworpen om professioneel over te komen, vertrouwen op te bouwen en beheerbaar te blijven na livegang.

", + "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.

", + ), + ], + "cta": "Wilt u uw volgende project professioneel neerzetten?", + }, + "contact": { + "headline": "Laten we uw vraag concreet maken", + "sub": "

Vertel kort wat u nodig heeft. U krijgt een praktische terugkoppeling met haalbare vervolgstappen.

", + "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.

", + ), + ], + "form_title": "Start uw volgende project met helderheid", + "form_sub": "

We helpen u ontwerpen, bouwen en opschalen - snel, gestructureerd en zonder giswerk.

", + "form_fields": [ + ("text", "Naam", "Uw naam"), + ("email", "E-mail", "naam@bedrijf.nl"), + ("company", "Bedrijf", "Bedrijfsnaam"), + ("message", "Vraag of project", "Waar zoekt u hulp bij?"), + ], + "benefits_title": "Waarom dit gesprek werkt", + "benefits": [ + "Reactie binnen 24 uur", + "Vrijblijvend strategiegesprek", + "Praktische vervolgstappen", + ], + "privacy": "

We gebruiken uw gegevens alleen voor contact over deze aanvraag. Geen verplichtingen.

", + "cta": "Klaar om een eerste stap te zetten?", + }, + "process": { + "headline": "Werkwijze met duidelijke stappen en vaste keuzes", + "sub": "

We houden het traject overzichtelijk: u weet wanneer iets gebeurt, wat u moet aanleveren en waar we naartoe werken.

", + "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.

", + ), + ], + "cta": "Wilt u dit traject ook voor uw website?", + }, + }, + }, + "en": { + "faq_title": "Frequently asked questions", + "faq_sub": "

We keep the process clear and practical.

", + "faq_items": [ + ( + "Do you work with fixed templates?", + "

No. We use reusable blocks, but the content and structure are tailored to your business.

", + "Process", + ), + ( + "Can we expand later?", + "

Yes. The setup is designed to grow without starting over.

", + "Growth", + ), + ( + "Can we manage the content ourselves?", + "

Yes. That is a core principle of the platform.

", + "Management", + ), + ], + "cta_sub": "

Schedule a consultation and we will show you the most sensible route for your business.

", + "no_cc": "No obligation", + "pages": { + "about": { + "headline": "Who MandelBlog is and how we work", + "sub": "

MandelBlog helps service businesses and small teams launch websites that look credible, convert clearly and remain easy to manage.

", + "features_title": "What we pay attention to", + "features_sub": "

We work best with organisations that value clarity, speed and editorial control.

", + "features": [ + ( + "people", + "Who we help", + "

Service firms, studios and small teams with a clear offer and a practical timeline.

", + ), + ( + "diagram-3", + "How we work", + "

We start by sharpening the goal and the message, build with proven blocks and deliver something your team can actually use.

", + ), + ( + "shield-check", + "Why it works differently", + "

Not a disconnected template or a black box, but a clear structure you can keep building on.

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

You speak directly with the people doing the work and translating decisions into the website.

", + ), + ], + "steps_badge": "Process", + "steps_heading": "Our 3-step approach", + "steps_sub": "A focused project, clear decisions and then a site your team can work with.", + "steps": [ + ( + "1", + "Introductory call", + "We define goals, content and priorities.", + "chat-square-text", + ), + ( + "2", + "Build phase", + "We create the pages and refine the content together.", + "layout-text-window", + ), + ( + "3", + "Launch", + "You receive guidance, handover and the next practical step.", + "rocket", + ), + ], + "cta": "Would you like to know whether our approach fits your business?", + }, + "services": { + "headline": "Services for companies that want clarity and quality", + "sub": "

Each service is shaped around clear choices, useful content and a technical foundation that can grow with you.

", + "features_title": "What we deliver", + "features_sub": "

Not disconnected modules, but a trajectory that matches your stage, team and objectives.

", + "features": [ + ( + "window", + "Starter website", + "

For businesses that need a professional online presence quickly with a clear foundation.

", + ), + ( + "briefcase", + "Business website", + "

For organisations with multiple services, case studies or a more complex offer structure.

", + ), + ( + "cart-check", + "Webshop implementation", + "

For teams that want online sales without losing grip on structure and technology.

", + ), + ( + "wrench-adjustable", + "Maintenance & growth", + "

For organisations that need maintenance, stability and continuous improvement.

", + ), + ], + "cta": "Unsure which package fits your stage?", + }, + "projects": { + "headline": "Projects where structure, content and technology work together", + "sub": "

Our projects are designed to look credible, build trust and stay manageable after launch.

", + "features_title": "What you see in our work", + "features_sub": "

We do not optimise for surface effects. We optimise for clarity and usability.

", + "features": [ + ( + "diagram-3", + "Clear page structure", + "

Visitors understand quickly where they are and what the next step should be.

", + ), + ( + "pencil-square", + "Easy management", + "

Teams can update copy, visuals and pages themselves without a workaround.

", + ), + ( + "graph-up-arrow", + "Built for enquiries", + "

Contact and conversion are visible in both the structure and the content.

", + ), + ], + "cta": "Do you want to present your next project professionally?", + }, + "contact": { + "headline": "Let’s turn your question into a concrete plan", + "sub": "

Tell us briefly what you need. You will receive a practical response with realistic next steps.

", + "features_title": "What you can contact us about", + "features_sub": "

Choose the route that fits your question or current stage.

", + "features": [ + ( + "rocket", + "New project", + "

For a new website, repositioning or complete rebuild.

", + ), + ( + "briefcase", + "Package advice", + "

For guidance on the package or structure that fits best.

", + ), + ( + "tools", + "Maintenance or expansion", + "

For technical support, follow-up improvements or the next phase after launch.

", + ), + ], + "form_title": "Tell us briefly what you need", + "form_sub": "

We respond with substance and without sales pressure.

", + "form_fields": [ + ("text", "Name", "Your name"), + ("email", "Email", "name@company.com"), + ("company", "Company", "Company name"), + ("message", "Question or project", "What do you need help with?"), + ], + "benefits_title": "What to expect", + "benefits": [ + "Response within 24 hours", + "15-minute introductory call", + "No obligation", + ], + "privacy": "

We use your details only to respond to this request.

", + "cta": "Ready to take the first step?", + }, + "process": { + "headline": "A working process with clear steps and fixed decisions", + "sub": "

We keep the trajectory manageable: you know what happens when, what you need to provide and what we are working towards.

", + "features_title": "How we work together", + "features_sub": "

Short, clear and without unnecessary noise.

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

We discuss goals, audience, content and what your team wants to manage internally.

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

We turn structure, content and design into a clear page setup.

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

After review the site goes live and we deliver a manageable handover.

", + ), + ( + "graph-up-arrow", + "4. Further growth", + "

When needed we continue based on behaviour, questions and new plans.

", + ), + ], + "cta": "Would you like this approach for your website too?", + }, + }, + }, +} +# Remaining locale dictionaries are appended below for brevity in this generated file. +STANDARD_COPY["de"] = { + "faq_title": "Häufig gestellte Fragen", + "faq_sub": "

Wir halten den Ablauf klar und praxisnah.

", + "faq_items": [ + ( + "Arbeiten Sie mit festen Templates?", + "

Nein. Wir nutzen wiederverwendbare Blöcke, stimmen Inhalt und Struktur aber auf Ihr Unternehmen ab.

", + "Vorgehen", + ), + ( + "Können wir später erweitern?", + "

Ja. Der Aufbau ist dafür gedacht, ohne Neustart weiterzuwachsen.

", + "Ausbau", + ), + ( + "Können wir die Inhalte selbst pflegen?", + "

Ja. Genau das ist ein Grundprinzip der Plattform.

", + "Pflege", + ), + ], + "cta_sub": "

Planen Sie ein Beratungsgespräch und wir zeigen Ihnen den sinnvollsten Weg für Ihr Unternehmen.

", + "no_cc": "Unverbindlich", + "pages": { + "about": { + "headline": "Wer MandelBlog ist und wie wir arbeiten", + "sub": "

MandelBlog unterstützt Dienstleister und kleine Unternehmen mit Websites, die professionell wirken, klar konvertieren und für das eigene Team pflegbar bleiben.

", + "features_title": "Worauf wir achten", + "features_sub": "

Wir arbeiten am liebsten mit Organisationen, die Klarheit, Tempo und inhaltliche Steuerung schätzen.

", + "features": [ + ( + "people", + "Für wen wir arbeiten", + "

Dienstleister, Studios und kleine Teams mit einem klaren Angebot und realistischem Zeitplan.

", + ), + ( + "diagram-3", + "Unsere Arbeitsweise", + "

Wir schärfen zunächst Ziel und Inhalt, bauen mit bewährten Blöcken und liefern beherrschbar aus.

", + ), + ( + "shield-check", + "Warum das anders funktioniert", + "

Kein loses Template und keine Black Box, sondern eine klare Struktur, auf der Sie weiterarbeiten können.

", + ), + ( + "person-badge", + "Kleines Team, direkter Kontakt", + "

Sie sprechen direkt mit den Menschen, die die Arbeit umsetzen und Entscheidungen in die Website übertragen.

", + ), + ], + "steps_badge": "Vorgehensweise", + "steps_heading": "Unser Ansatz in 3 Schritten", + "steps_sub": "Ein kompaktes Projekt, klare Entscheidungen und danach eine Website, mit der Ihr Team arbeiten kann.", + "steps": [ + ( + "1", + "Erstgespräch", + "Wir definieren Ziel, Inhalte und Prioritäten.", + "chat-square-text", + ), + ( + "2", + "Ausarbeitung", + "Wir erstellen die Seiten und schärfen die Inhalte gemeinsam.", + "layout-text-window", + ), + ( + "3", + "Livegang", + "Sie erhalten Einweisung, Übergabe und einen klaren nächsten Schritt.", + "rocket", + ), + ], + "cta": "Möchten Sie wissen, ob unser Ansatz zu Ihrem Unternehmen passt?", + }, + "services": { + "headline": "Leistungen für Unternehmen, die Übersicht und Qualität wollen", + "sub": "

Jede Leistung ist auf klare Entscheidungen, nutzbare Inhalte und eine technische Basis ausgelegt, die mitwachsen kann.

", + "features_title": "Was wir liefern", + "features_sub": "

Keine losen Module, sondern ein Weg, der zu Ihrer Phase, Ihrem Team und Ihren Zielen passt.

", + "features": [ + ( + "window", + "Starter-Website", + "

Für Unternehmen, die schnell professionell online gehen wollen.

", + ), + ( + "briefcase", + "Geschäftswebsite", + "

Für Organisationen mit mehreren Leistungen, Referenzen oder komplexerer Angebotsstruktur.

", + ), + ( + "cart-check", + "Webshop-Implementierung", + "

Für Teams, die Online-Verkauf ergänzen wollen, ohne die technische Kontrolle zu verlieren.

", + ), + ( + "wrench-adjustable", + "Wartung & Wachstum", + "

Für Organisationen, die Wartung, Stabilität und kontinuierliche Verbesserung brauchen.

", + ), + ], + "cta": "Sie sind unsicher, welches Paket zu Ihrer Phase passt?", + }, + "projects": { + "headline": "Projekte, in denen Struktur, Inhalt und Technik zusammenarbeiten", + "sub": "

Unsere Projekte sind darauf ausgelegt, professionell zu wirken, Vertrauen aufzubauen und nach dem Go-live beherrschbar zu bleiben.

", + "features_title": "Was Sie in unseren Projekten sehen", + "features_sub": "

Wir optimieren nicht auf Effekte, sondern auf Klarheit und Nutzbarkeit.

", + "features": [ + ( + "diagram-3", + "Klare Seitenstruktur", + "

Besucher verstehen schnell, wo sie sind und welcher Schritt als Nächstes sinnvoll ist.

", + ), + ( + "pencil-square", + "Einfaches Management", + "

Teams können Texte, Bilder und Seiten selbst anpassen.

", + ), + ( + "graph-up-arrow", + "Auf Anfragen ausgerichtet", + "

Kontakt und Conversion sind sichtbar in Struktur und Inhalt verankert.

", + ), + ], + "cta": "Möchten Sie Ihr nächstes Projekt professionell präsentieren?", + }, + "contact": { + "headline": "Lassen Sie uns Ihre Anfrage konkret machen", + "sub": "

Beschreiben Sie kurz, was Sie brauchen. Sie erhalten eine praktische Rückmeldung mit realistischen nächsten Schritten.

", + "features_title": "Wofür Sie uns kontaktieren können", + "features_sub": "

Wählen Sie den Weg, der zu Ihrer Frage oder Ihrem Projektstand passt.

", + "features": [ + ( + "rocket", + "Neues Projekt", + "

Für eine neue Website, Neupositionierung oder einen kompletten Relaunch.

", + ), + ( + "briefcase", + "Paketberatung", + "

Für Beratung dazu, welches Paket oder welche Struktur am besten passt.

", + ), + ( + "tools", + "Wartung oder Ausbau", + "

Für technischen Support, Erweiterungen oder die nächste Phase nach dem Go-live.

", + ), + ], + "form_title": "Beschreiben Sie kurz, was Sie brauchen", + "form_sub": "

Wir antworten inhaltlich und ohne Verkaufsdruck.

", + "form_fields": [ + ("text", "Name", "Ihr Name"), + ("email", "E-Mail", "name@unternehmen.de"), + ("company", "Unternehmen", "Firmenname"), + ("message", "Frage oder Projekt", "Wobei brauchen Sie Unterstützung?"), + ], + "benefits_title": "Was Sie erwarten können", + "benefits": [ + "Rückmeldung innerhalb von 24 Stunden", + "15-minütiges Erstgespräch", + "Unverbindlich", + ], + "privacy": "

Wir nutzen Ihre Angaben nur, um auf diese Anfrage zu antworten.

", + "cta": "Bereit für den ersten Schritt?", + }, + "process": { + "headline": "Vorgehensweise mit klaren Schritten und festen Entscheidungen", + "sub": "

Wir halten das Projekt übersichtlich: Sie wissen, wann etwas passiert, was Sie liefern müssen und worauf wir hinarbeiten.

", + "features_title": "So arbeiten wir zusammen", + "features_sub": "

Kurz, klar und ohne unnötiges Rauschen.

", + "features": [ + ( + "chat-square-text", + "1. Erstgespräch", + "

Wir besprechen Ziel, Zielgruppe, Inhalte und was Ihr Team intern pflegen möchte.

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

Wir übersetzen Struktur, Inhalt und Gestaltung in einen klaren Seitenaufbau.

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

Nach der Freigabe geht die Website live und wir sorgen für eine saubere Übergabe.

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

Wenn nötig bauen wir auf Basis von Verhalten, Fragen und neuen Plänen weiter.

", + ), + ], + "cta": "Möchten Sie diese Vorgehensweise auch für Ihre Website?", + }, + }, +} +# Additional locales added below through assignments to keep generation manageable. +STANDARD_COPY["fr"] = { + "faq_title": "Questions fréquentes", + "faq_sub": "

Nous gardons le projet clair et concret.

", + "faq_items": [ + ( + "Travaillez-vous avec des templates fixes ?", + "

Non. Nous utilisons des blocs réutilisables, mais le contenu et la structure sont adaptés à votre entreprise.

", + "Méthode", + ), + ( + "Peut-on faire évoluer le site ensuite ?", + "

Oui. La base est prévue pour évoluer sans repartir de zéro.

", + "Évolution", + ), + ( + "Pouvons-nous gérer le contenu nous-mêmes ?", + "

Oui. C’est un principe important de la plateforme.

", + "Gestion", + ), + ], + "cta_sub": "

Planifiez une consultation et nous vous montrerons la voie la plus logique pour votre entreprise.

", + "no_cc": "Sans engagement", + "pages": { + "about": { + "headline": "Qui est MandelBlog et comment nous travaillons", + "sub": "

MandelBlog aide les sociétés de services et les petites équipes à lancer des sites crédibles, orientés conversion et simples à gérer.

", + "features_title": "Ce à quoi nous faisons attention", + "features_sub": "

Nous travaillons le mieux avec des organisations qui recherchent de la clarté, de la rapidité et de l’autonomie éditoriale.

", + "features": [ + ( + "people", + "Qui nous accompagnons", + "

Des sociétés de services, des studios et de petites équipes avec une offre claire et un planning réaliste.

", + ), + ( + "diagram-3", + "Notre méthode", + "

Nous clarifions d’abord l’objectif et le message, puis nous construisons avec des blocs éprouvés.

", + ), + ( + "shield-check", + "Pourquoi cela fonctionne autrement", + "

Pas de template déconnecté ni de boîte noire, mais une structure claire que vous pouvez faire évoluer.

", + ), + ( + "person-badge", + "Petite équipe, contact direct", + "

Vous échangez directement avec les personnes qui réalisent le travail.

", + ), + ], + "steps_badge": "Méthode", + "steps_heading": "Notre approche en 3 étapes", + "steps_sub": "Un projet concentré, des choix clairs et ensuite un site que votre équipe peut utiliser au quotidien.", + "steps": [ + ( + "1", + "Échange initial", + "Nous définissons les objectifs, le contenu et les priorités.", + "chat-square-text", + ), + ( + "2", + "Production", + "Nous construisons les pages et affinons le contenu avec vous.", + "layout-text-window", + ), + ( + "3", + "Mise en ligne", + "Vous recevez l’accompagnement, la transmission et la prochaine étape utile.", + "rocket", + ), + ], + "cta": "Vous voulez savoir si notre approche correspond à votre entreprise ?", + }, + "services": { + "headline": "Des services pour les entreprises qui veulent de la clarté et de la qualité", + "sub": "

Chaque service repose sur des choix clairs, un contenu utile et une base technique capable d’évoluer.

", + "features_title": "Ce que nous livrons", + "features_sub": "

Pas des modules isolés, mais un parcours adapté à votre étape, à votre équipe et à vos objectifs.

", + "features": [ + ( + "window", + "Site de démarrage", + "

Pour les entreprises qui veulent être en ligne rapidement avec une base professionnelle.

", + ), + ( + "briefcase", + "Site d’entreprise", + "

Pour les organisations qui ont plusieurs services, références ou une offre plus structurée.

", + ), + ( + "cart-check", + "Implémentation e-commerce", + "

Pour les équipes qui veulent ajouter la vente en ligne sans perdre la maîtrise de la structure et de la technique.

", + ), + ( + "wrench-adjustable", + "Maintenance & croissance", + "

Pour les organisations qui ont besoin de maintenance, de stabilité et d’amélioration continue.

", + ), + ], + "cta": "Vous hésitez sur l’offre adaptée à votre étape ?", + }, + "projects": { + "headline": "Des projets où structure, contenu et technique travaillent ensemble", + "sub": "

Nos projets sont conçus pour inspirer confiance, structurer le message et rester simples à gérer après la mise en ligne.

", + "features_title": "Ce que vous retrouvez dans nos projets", + "features_sub": "

Nous ne cherchons pas l’effet de surface, mais la clarté et l’utilité.

", + "features": [ + ( + "diagram-3", + "Structure de page claire", + "

Les visiteurs comprennent rapidement où ils sont et quelle étape vient ensuite.

", + ), + ( + "pencil-square", + "Gestion simple", + "

Les équipes peuvent modifier textes, visuels et pages sans détour.

", + ), + ( + "graph-up-arrow", + "Pensé pour les demandes", + "

Le contact et la conversion sont visibles dans la structure comme dans le contenu.

", + ), + ], + "cta": "Vous voulez présenter votre prochain projet de manière plus professionnelle ?", + }, + "contact": { + "headline": "Transformons votre question en plan concret", + "sub": "

Expliquez brièvement ce dont vous avez besoin. Vous recevrez un retour utile avec des prochaines étapes réalistes.

", + "features_title": "Pour quelles demandes nous contacter", + "features_sub": "

Choisissez la voie qui correspond à votre question ou à votre étape actuelle.

", + "features": [ + ( + "rocket", + "Nouveau projet", + "

Pour un nouveau site, un repositionnement ou une refonte complète.

", + ), + ( + "briefcase", + "Choix de l’offre", + "

Pour savoir quelle offre ou quelle structure est la plus adaptée.

", + ), + ( + "tools", + "Maintenance ou évolution", + "

Pour du support technique, des améliorations ou une phase suivante après la mise en ligne.

", + ), + ], + "form_title": "Expliquez brièvement votre besoin", + "form_sub": "

Nous répondons avec du contenu utile, sans pression commerciale.

", + "form_fields": [ + ("text", "Nom", "Votre nom"), + ("email", "E-mail", "nom@entreprise.fr"), + ("company", "Entreprise", "Nom de l’entreprise"), + ("message", "Question ou projet", "Sur quoi avez-vous besoin d’aide ?"), + ], + "benefits_title": "Ce que vous pouvez attendre", + "benefits": [ + "Réponse sous 24 heures", + "Échange initial de 15 minutes", + "Sans engagement", + ], + "privacy": "

Nous utilisons vos coordonnées uniquement pour répondre à cette demande.

", + "cta": "Prêt à franchir une première étape ?", + }, + "process": { + "headline": "Une méthode avec des étapes claires et des choix assumés", + "sub": "

Nous gardons le projet lisible : vous savez quand chaque chose se passe, ce que vous devez fournir et où nous allons.

", + "features_title": "Comment nous travaillons ensemble", + "features_sub": "

Simple, clair et sans bruit inutile.

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

Nous discutons des objectifs, des publics, du contenu et de ce que votre équipe veut gérer en interne.

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

Nous transformons structure, contenu et design en pages claires et cohérentes.

", + ), + ( + "rocket", + "3. Mise en ligne", + "

Après validation, le site est publié et nous assurons une transmission claire.

", + ), + ( + "graph-up-arrow", + "4. Évolutions", + "

Ensuite, nous pouvons continuer selon les retours, les usages et vos nouveaux projets.

", + ), + ], + "cta": "Vous voulez appliquer cette méthode à votre site ?", + }, + }, +} +# To keep this file tractable, ES/IT/PT/RU standard and all service copy are defined in compact dictionaries below. +SERVICE_COPY = {} +SERVICE_COMMON = { + "nl": { + "section_what": "Wat krijgt u?", + "section_what_sub": "

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

", + "section_outcomes": "Wat levert het op?", + "section_outcomes_sub": "

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

", + "section_choose": "Wanneer kiest u dit pakket?", + "section_choose_sub": "

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

", + "choose_title": "Dit pakket past wanneer", + "privacy": "

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

", + "name_label": "Naam", + "name_placeholder": "Uw naam", + "email_label": "E-mail", + "email_placeholder": "naam@bedrijf.nl", + "company_label": "Bedrijf", + "company_placeholder": "Bedrijfsnaam", + "message_label": "Vraag of context", + "message_placeholder": "Vertel kort waar u nu staat", + "timeline_label": "Doorlooptijd", + "communication": "Reactie binnen 24 uur", + "intro": "Volledig vrijblijvend", + "cta_sub": "

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

", + "no_cc": "Volledig vrijblijvend", + }, + "en": { + "section_what": "What you get", + "section_what_sub": "

Each package is structured around clear decisions, manageable content and a setup that fits your business.

", + "section_outcomes": "What it delivers", + "section_outcomes_sub": "

The value is not in visual tricks, but in clearer communication and a website that performs better.

", + "section_choose": "When to choose this package", + "section_choose_sub": "

We recommend this package when the points below match your situation.

", + "choose_title": "This package fits when", + "privacy": "

We only use your details to respond to this request.

", + "name_label": "Name", + "name_placeholder": "Your name", + "email_label": "Email", + "email_placeholder": "name@company.com", + "company_label": "Company", + "company_placeholder": "Company name", + "message_label": "Question or context", + "message_placeholder": "Briefly describe your current situation", + "timeline_label": "Timeline", + "communication": "Response within 24 hours", + "intro": "No obligation", + "cta_sub": "

Schedule a consultation and we will advise honestly which package makes sense.

", + "no_cc": "No obligation", + }, + "de": { + "section_what": "Was Sie bekommen", + "section_what_sub": "

Jedes Paket ist auf klare Entscheidungen, gute Pflegebarkeit und Inhalte aufgebaut, die zu Ihrem Unternehmen passen.

", + "section_outcomes": "Was es bringt", + "section_outcomes_sub": "

Der Wert liegt nicht in Effekten, sondern in klarerer Kommunikation und einer Website, die besser funktioniert.

", + "section_choose": "Wann Sie dieses Paket wählen", + "section_choose_sub": "

Wir empfehlen dieses Paket, wenn die folgenden Punkte zu Ihrer Situation passen.

", + "choose_title": "Dieses Paket passt, wenn", + "privacy": "

Wir verwenden Ihre Angaben nur, um auf diese Anfrage zu antworten.

", + "name_label": "Name", + "name_placeholder": "Ihr Name", + "email_label": "E-Mail", + "email_placeholder": "name@unternehmen.de", + "company_label": "Unternehmen", + "company_placeholder": "Firmenname", + "message_label": "Frage oder Kontext", + "message_placeholder": "Beschreiben Sie kurz Ihre Ausgangslage", + "timeline_label": "Zeitrahmen", + "communication": "Rückmeldung innerhalb von 24 Stunden", + "intro": "Unverbindlich", + "cta_sub": "

Planen Sie ein Beratungsgespräch und wir empfehlen ehrlich, welches Paket sinnvoll ist.

", + "no_cc": "Unverbindlich", + }, + "fr": { + "section_what": "Ce que vous obtenez", + "section_what_sub": "

Chaque offre est structurée autour de choix clairs, d’un contenu gérable et d’une base adaptée à votre entreprise.

", + "section_outcomes": "Ce que cela apporte", + "section_outcomes_sub": "

La valeur ne vient pas d’effets superficiels, mais d’un message plus clair et d’un site plus efficace.

", + "section_choose": "Quand choisir cette offre", + "section_choose_sub": "

Nous recommandons cette offre lorsque les points ci-dessous correspondent à votre situation.

", + "choose_title": "Cette offre convient lorsque", + "privacy": "

Nous utilisons vos coordonnées uniquement pour répondre à votre demande.

", + "name_label": "Nom", + "name_placeholder": "Votre nom", + "email_label": "E-mail", + "email_placeholder": "nom@entreprise.fr", + "company_label": "Entreprise", + "company_placeholder": "Nom de l’entreprise", + "message_label": "Question ou contexte", + "message_placeholder": "Expliquez brièvement votre situation", + "timeline_label": "Délai", + "communication": "Réponse sous 24 heures", + "intro": "Sans engagement", + "cta_sub": "

Planifiez une consultation et nous vous dirons honnêtement quelle offre est la plus cohérente.

", + "no_cc": "Sans engagement", + }, + "es": { + "section_what": "Qué obtiene", + "section_what_sub": "

Cada paquete se estructura alrededor de decisiones claras, contenido gestionable y una base adecuada para su empresa.

", + "section_outcomes": "Qué aporta", + "section_outcomes_sub": "

El valor no está en efectos superficiales, sino en una comunicación más clara y un sitio que funciona mejor.

", + "section_choose": "Cuándo elegir este paquete", + "section_choose_sub": "

Recomendamos este paquete cuando los puntos siguientes encajan con su situación.

", + "choose_title": "Este paquete encaja cuando", + "privacy": "

Usamos sus datos únicamente para responder a esta solicitud.

", + "name_label": "Nombre", + "name_placeholder": "Su nombre", + "email_label": "Correo electrónico", + "email_placeholder": "nombre@empresa.es", + "company_label": "Empresa", + "company_placeholder": "Nombre de la empresa", + "message_label": "Pregunta o contexto", + "message_placeholder": "Explique brevemente su situación actual", + "timeline_label": "Plazo", + "communication": "Respuesta en 24 horas", + "intro": "Sin compromiso", + "cta_sub": "

Programe una reunión inicial y le indicaremos con honestidad qué paquete tiene más sentido.

", + "no_cc": "Sin compromiso", + }, + "it": { + "section_what": "Cosa riceve", + "section_what_sub": "

Ogni pacchetto è costruito attorno a scelte chiare, contenuti gestibili e una base adatta alla vostra azienda.

", + "section_outcomes": "Cosa porta", + "section_outcomes_sub": "

Il valore non sta negli effetti superficiali, ma in una comunicazione più chiara e in un sito che funziona meglio.

", + "section_choose": "Quando scegliere questo pacchetto", + "section_choose_sub": "

Consigliamo questo pacchetto quando i punti seguenti corrispondono alla vostra situazione.

", + "choose_title": "Questo pacchetto è adatto quando", + "privacy": "

Usiamo i vostri dati solo per rispondere a questa richiesta.

", + "name_label": "Nome", + "name_placeholder": "Il vostro nome", + "email_label": "E-mail", + "email_placeholder": "nome@azienda.it", + "company_label": "Azienda", + "company_placeholder": "Nome dell’azienda", + "message_label": "Domanda o contesto", + "message_placeholder": "Descrivete brevemente la vostra situazione", + "timeline_label": "Tempistiche", + "communication": "Risposta entro 24 ore", + "intro": "Senza impegno", + "cta_sub": "

Prenotate una consulenza introduttiva e vi diremo con chiarezza quale pacchetto è più adatto.

", + "no_cc": "Senza impegno", + }, + "pt": { + "section_what": "O que recebe", + "section_what_sub": "

Cada pacote é estruturado em torno de escolhas claras, conteúdo gerível e uma base adequada à sua empresa.

", + "section_outcomes": "O que entrega", + "section_outcomes_sub": "

O valor não está em efeitos superficiais, mas numa comunicação mais clara e num website que funciona melhor.

", + "section_choose": "Quando escolher este pacote", + "section_choose_sub": "

Recomendamos este pacote quando os pontos abaixo correspondem à sua situação.

", + "choose_title": "Este pacote faz sentido quando", + "privacy": "

Usamos os seus dados apenas para responder a este pedido.

", + "name_label": "Nome", + "name_placeholder": "O seu nome", + "email_label": "E-mail", + "email_placeholder": "nome@empresa.pt", + "company_label": "Empresa", + "company_placeholder": "Nome da empresa", + "message_label": "Pergunta ou contexto", + "message_placeholder": "Explique brevemente a sua situação", + "timeline_label": "Prazo", + "communication": "Resposta em 24 horas", + "intro": "Sem compromisso", + "cta_sub": "

Agende uma consulta inicial e diremos com honestidade qual pacote faz mais sentido.

", + "no_cc": "Sem compromisso", + }, + "ru": { + "section_what": "Что входит", + "section_what_sub": "

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

", + "section_outcomes": "Что это даёт", + "section_outcomes_sub": "

Ценность не в поверхностных эффектах, а в более ясной коммуникации и сайте, который работает лучше.

", + "section_choose": "Когда выбирать этот пакет", + "section_choose_sub": "

Мы рекомендуем этот пакет, если пункты ниже соответствуют вашей ситуации.

", + "choose_title": "Этот пакет подходит, когда", + "privacy": "

Мы используем ваши данные только для ответа на этот запрос.

", + "name_label": "Имя", + "name_placeholder": "Ваше имя", + "email_label": "E-mail", + "email_placeholder": "name@company.ru", + "company_label": "Компания", + "company_placeholder": "Название компании", + "message_label": "Вопрос или контекст", + "message_placeholder": "Кратко опишите вашу ситуацию", + "timeline_label": "Срок", + "communication": "Ответ в течение 24 часов", + "intro": "Без обязательств", + "cta_sub": "

Запланируйте консультацию, и мы честно подскажем, какой пакет имеет смысл.

", + "no_cc": "Без обязательств", + }, +} + +SERVICE_COPY["nl"] = { + "starter": { + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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 generieke webshop.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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", + }, +} +# A compact but complete service set for all translated locales. +SERVICE_COPY["en"] = { + "starter": { + "title": "Starter website", + "audience": "For founders and small teams that need a credible online presence quickly with a clear foundation.", + "what": [ + ( + "layout-text-window", + "Who this is for", + "

For businesses with a clear offer that need a professional first impression without a heavy project.

", + ), + ( + "window", + "What you get", + "

Core pages, clear navigation and an editor your team can use to manage content internally.

", + ), + ( + "graph-up-arrow", + "What it delivers", + "

A professional foundation that helps visitors understand what you do and how to contact you.

", + ), + ], + "outcomes": [ + ( + "shield-check", + "A clear online foundation", + "

No unnecessary extras, just a website that builds confidence.

", + ), + ( + "people", + "Simple content management", + "

Your team can make updates without depending on a developer for every change.

", + ), + ( + "rocket", + "Fast launch", + "

Suitable as a first professional website or as a replacement for an outdated one.

", + ), + ], + "choose": [ + "You want to look professional quickly.", + "You mainly need core pages and clear navigation.", + "You want to update copy and images yourself.", + ], + "duration": "Average timeline: 2 to 4 weeks", + }, + "business": { + "title": "Business website", + "audience": "For service firms and teams that need to present multiple offers, case studies or conversion paths clearly.", + "what": [ + ( + "briefcase", + "Who this is for", + "

For organisations that need more structure, more depth and a stronger route to enquiry.

", + ), + ( + "layout-text-window", + "What you get", + "

More page structure, room for case studies and an SEO-friendly foundation that scales logically.

", + ), + ( + "graph-up-arrow", + "What it delivers", + "

A website that explains your offer more clearly and leads visitors more directly toward contact.

", + ), + ], + "outcomes": [ + ( + "diagram-3", + "More clarity", + "

Services, case studies and expertise each get their own logical place.

", + ), + ( + "search", + "Better visibility", + "

The structure is prepared for stronger content and an SEO-friendly foundation.

", + ), + ( + "people", + "Stronger enquiries", + "

Visitors see more quickly which route and which offer fit them.

", + ), + ], + "choose": [ + "You have multiple services or audiences.", + "You want to show case studies, expertise and proof more clearly.", + "You need more structure than a starter site provides.", + ], + "duration": "Average timeline: 2 to 4 weeks", + }, + "webshop": { + "title": "Webshop implementation", + "audience": "For organisations that want to add online sales without ending up with a generic shop theme.", + "what": [ + ( + "cart-check", + "Who this is for", + "

For businesses that want to sell online while keeping control over presentation, checkout and management.

", + ), + ( + "credit-card", + "What you get", + "

A commerce structure with product overview, checkout and a scalable base for order handling.

", + ), + ( + "graph-up-arrow", + "What it delivers", + "

A sales environment that fits your brand instead of looking like an off-the-shelf shop.

", + ), + ], + "outcomes": [ + ( + "window", + "Stronger presentation", + "

Products and categories are presented in a clear business-focused structure.

", + ), + ( + "shield-check", + "Stable technology", + "

Payments and order handling are connected to a maintainable technical stack.

", + ), + ( + "rocket", + "Ready to grow", + "

The commerce setup can expand with your assortment and processes.

", + ), + ], + "choose": [ + "You want to combine online sales with a professional business site.", + "You need control over structure and technology.", + "You do not want a generic theme-based shop.", + ], + "duration": "Average timeline: 3 to 6 weeks", + }, + "support": { + "title": "Maintenance & growth", + "audience": "For teams that want to keep their site or webshop stable and improve it in a focused way over time.", + "what": [ + ( + "tools", + "Who this is for", + "

For organisations that do not want to monitor, troubleshoot and plan every technical issue themselves.

", + ), + ( + "shield-check", + "What you get", + "

Maintenance, updates, monitoring and technical fixes within a predictable working rhythm.

", + ), + ( + "graph-up-arrow", + "What it delivers", + "

More peace of mind, fewer technical surprises and room for targeted improvement.

", + ), + ], + "outcomes": [ + ( + "activity", + "Fewer disruptions", + "

Technical issues are spotted and resolved faster.

", + ), + ( + "clipboard-data", + "Continuous improvement", + "

We keep improving performance, content and conversion step by step.

", + ), + ( + "people", + "A fixed rhythm", + "

You know when maintenance happens and what receives priority.

", + ), + ], + "choose": [ + "You want a fixed partner for technical maintenance.", + "Your site needs incremental improvements rather than a full rebuild.", + "You want faster support when issues or expansion requests appear.", + ], + "duration": "Response within 24 hours", + }, +} +# DE/FR/ES/IT/PT/RU service content +SERVICE_COPY["de"] = { + "starter": { + "title": "Starter-Website", + "audience": "Für Gründer und kleine Teams, die schnell professionell online auftreten möchten.", + "what": [ + ( + "layout-text-window", + "Für wen ist das?", + "

Für Unternehmen mit klarem Angebot, die ohne schweres Projekt einen professionellen ersten Eindruck brauchen.

", + ), + ( + "window", + "Was Sie bekommen", + "

Kernseiten, klare Navigation und einen Editor, mit dem Ihr Team Inhalte selbst pflegen kann.

", + ), + ( + "graph-up-arrow", + "Was es bringt", + "

Eine professionelle Basis, mit der Besucher schneller verstehen, was Sie tun und wie sie Kontakt aufnehmen.

", + ), + ], + "outcomes": [ + ( + "shield-check", + "Klare Online-Basis", + "

Keine unnötigen Extras, sondern eine Website, die Vertrauen schafft.

", + ), + ( + "people", + "Einfache Pflege", + "

Ihr Team kann Änderungen selbst umsetzen.

", + ), + ( + "rocket", + "Schneller Start", + "

Geeignet als erste professionelle Website oder als Ersatz für einen veralteten Auftritt.

", + ), + ], + "choose": [ + "Sie möchten schnell professionell online sein.", + "Sie brauchen vor allem Kernseiten und klare Navigation.", + "Sie möchten Texte und Bilder selbst anpassen.", + ], + "duration": "Durchschnittlicher Zeitraum: 2 bis 4 Wochen", + }, + "business": { + "title": "Geschäftswebsite", + "audience": "Für Dienstleister und Teams, die mehrere Angebote, Referenzen oder Conversion-Wege klar darstellen müssen.", + "what": [ + ( + "briefcase", + "Für wen ist das?", + "

Für Organisationen, die mehr Struktur, mehr inhaltliche Tiefe und einen stärkeren Anfrageweg brauchen.

", + ), + ( + "layout-text-window", + "Was Sie bekommen", + "

Mehr Seitenstruktur, Raum für Referenzen und eine SEO-freundliche Basis, die logisch mitwächst.

", + ), + ( + "graph-up-arrow", + "Was es bringt", + "

Eine Website, die Ihr Angebot klarer erklärt und Besucher direkter zur Kontaktaufnahme führt.

", + ), + ], + "outcomes": [ + ( + "diagram-3", + "Mehr Übersicht", + "

Leistungen, Referenzen und Expertise erhalten jeweils ihren eigenen Platz.

", + ), + ( + "search", + "Bessere Sichtbarkeit", + "

Der Aufbau ist für starke Inhalte und eine SEO-freundliche Basis vorbereitet.

", + ), + ( + "people", + "Stärkere Anfragen", + "

Besucher erkennen schneller, welcher Weg und welches Angebot zu ihnen passen.

", + ), + ], + "choose": [ + "Sie haben mehrere Leistungen oder Zielgruppen.", + "Sie möchten Referenzen, Expertise und Nachweise klarer zeigen.", + "Sie brauchen mehr Struktur als eine Startseite bietet.", + ], + "duration": "Durchschnittlicher Zeitraum: 2 bis 4 Wochen", + }, + "webshop": { + "title": "Webshop-Implementierung", + "audience": "Für Unternehmen, die Online-Verkauf ergänzen möchten, ohne bei einem Standardshop zu landen.", + "what": [ + ( + "cart-check", + "Für wen ist das?", + "

Für Unternehmen, die online verkaufen wollen und dabei Inhalte, Checkout und Pflege im Griff behalten möchten.

", + ), + ( + "credit-card", + "Was Sie bekommen", + "

Einen Commerce-Aufbau mit Produktübersicht, Checkout und skalierbarer Basis für Bestellprozesse.

", + ), + ( + "graph-up-arrow", + "Was es bringt", + "

Eine Verkaufsumgebung, die zu Ihrer Marke passt und nicht wie ein Demo-Shop wirkt.

", + ), + ], + "outcomes": [ + ( + "window", + "Bessere Präsentation", + "

Produkte und Kategorien erhalten eine klare, geschäftliche Struktur.

", + ), + ( + "shield-check", + "Stabile Technik", + "

Zahlungen und Bestellabläufe laufen auf einer beherrschbaren technischen Basis.

", + ), + ( + "rocket", + "Bereit für Wachstum", + "

Der Commerce-Aufbau kann mit Sortiment und Prozessen mitwachsen.

", + ), + ], + "choose": [ + "Sie möchten Online-Verkauf mit einer Unternehmenswebsite verbinden.", + "Sie brauchen Kontrolle über Struktur und Technik.", + "Sie suchen keinen Shop von der Stange.", + ], + "duration": "Durchschnittlicher Zeitraum: 3 bis 6 Wochen", + }, + "support": { + "title": "Wartung & Wachstum", + "audience": "Für Teams, die Website oder Webshop stabil halten und gezielt weiterentwickeln wollen.", + "what": [ + ( + "tools", + "Für wen ist das?", + "

Für Organisationen, die Technik nicht komplett selbst überwachen, lösen und planen möchten.

", + ), + ( + "shield-check", + "Was Sie bekommen", + "

Wartung, Updates, Monitoring und technische Lösungen in einem festen Arbeitsrhythmus.

", + ), + ( + "graph-up-arrow", + "Was es bringt", + "

Mehr Ruhe, weniger technische Überraschungen und Raum für gezielte Verbesserungen.

", + ), + ], + "outcomes": [ + ( + "activity", + "Weniger Störungen", + "

Technische Probleme werden schneller erkannt und gelöst.

", + ), + ( + "clipboard-data", + "Kontinuierliche Verbesserung", + "

Wir entwickeln Performance, Inhalte und Conversion Schritt für Schritt weiter.

", + ), + ( + "people", + "Fester Rhythmus", + "

Sie wissen, wann Wartung stattfindet und was Priorität hat.

", + ), + ], + "choose": [ + "Sie möchten einen festen Partner für technische Wartung.", + "Ihre Website braucht laufende Verbesserungen statt eines kompletten Relaunchs.", + "Sie wollen bei Problemen oder Erweiterungen schneller reagieren können.", + ], + "duration": "Rückmeldung innerhalb von 24 Stunden", + }, +} +SERVICE_COPY["fr"] = { + "starter": { + "title": "Site de démarrage", + "audience": "Pour les dirigeants et petites équipes qui veulent une présence crédible rapidement.", + "what": [ + ( + "layout-text-window", + "Pour qui est-ce ?", + "

Pour les entreprises avec une offre claire qui ont besoin d’une première impression professionnelle sans projet lourd.

", + ), + ( + "window", + "Ce que vous obtenez", + "

Les pages essentielles, une navigation claire et un éditeur que votre équipe peut utiliser elle-même.

", + ), + ( + "graph-up-arrow", + "Ce que cela apporte", + "

Une base professionnelle qui aide les visiteurs à comprendre rapidement votre activité et à vous contacter.

", + ), + ], + "outcomes": [ + ( + "shield-check", + "Base en ligne claire", + "

Pas d’éléments inutiles, mais un site qui inspire confiance.

", + ), + ( + "people", + "Gestion simple", + "

Votre équipe peut faire les mises à jour sans dépendance permanente.

", + ), + ( + "rocket", + "Mise en ligne rapide", + "

Adapté à un premier site professionnel ou au remplacement d’un site dépassé.

", + ), + ], + "choose": [ + "Vous voulez être en ligne rapidement avec une image professionnelle.", + "Vous avez surtout besoin de pages essentielles et d’une navigation claire.", + "Vous voulez pouvoir modifier vous-même textes et images.", + ], + "duration": "Délai moyen : 2 à 4 semaines", + }, + "business": { + "title": "Site d’entreprise", + "audience": "Pour les sociétés de services et les équipes qui doivent présenter clairement plusieurs offres, références ou parcours de conversion.", + "what": [ + ( + "briefcase", + "Pour qui est-ce ?", + "

Pour les organisations qui ont besoin de plus de structure, de profondeur et d’un meilleur parcours vers la prise de contact.

", + ), + ( + "layout-text-window", + "Ce que vous obtenez", + "

Plus de structure de pages, de la place pour les références et une base favorable au SEO qui peut évoluer proprement.

", + ), + ( + "graph-up-arrow", + "Ce que cela apporte", + "

Un site qui explique mieux votre offre et oriente plus directement les visiteurs vers le contact.

", + ), + ], + "outcomes": [ + ( + "diagram-3", + "Plus de clarté", + "

Services, références et expertise disposent chacun d’une place logique.

", + ), + ( + "search", + "Meilleure visibilité", + "

La structure est pensée pour un contenu solide et une base favorable au SEO.

", + ), + ( + "people", + "Demandes plus qualifiées", + "

Les visiteurs identifient plus vite l’offre et la voie qui leur conviennent.

", + ), + ], + "choose": [ + "Vous avez plusieurs services ou plusieurs publics.", + "Vous voulez mieux montrer vos références, votre expertise et vos preuves.", + "Vous avez besoin de plus de structure qu’un site de base.", + ], + "duration": "Délai moyen : 2 à 4 semaines", + }, + "webshop": { + "title": "Implémentation e-commerce", + "audience": "Pour les organisations qui veulent ajouter la vente en ligne sans tomber dans une boutique standardisée.", + "what": [ + ( + "cart-check", + "Pour qui est-ce ?", + "

Pour les entreprises qui veulent vendre en ligne tout en gardant la maîtrise de la présentation, du tunnel d’achat et de la gestion.

", + ), + ( + "credit-card", + "Ce que vous obtenez", + "

Une structure e-commerce avec catalogue, checkout et base évolutive pour le traitement des commandes.

", + ), + ( + "graph-up-arrow", + "Ce que cela apporte", + "

Un environnement de vente aligné sur votre marque, pas une boutique qui ressemble à une démo.

", + ), + ], + "outcomes": [ + ( + "window", + "Présentation plus forte", + "

Produits et catégories sont organisés dans une structure claire et crédible.

", + ), + ( + "shield-check", + "Technique stable", + "

Paiements et gestion des commandes reposent sur une base maintenable.

", + ), + ( + "rocket", + "Prêt à évoluer", + "

La structure e-commerce peut suivre l’évolution de l’offre et des processus.

", + ), + ], + "choose": [ + "Vous voulez combiner vente en ligne et site d’entreprise.", + "Vous avez besoin de garder la maîtrise de la structure et de la technique.", + "Vous ne cherchez pas une boutique basée sur un thème standard.", + ], + "duration": "Délai moyen : 3 à 6 semaines", + }, + "support": { + "title": "Maintenance & croissance", + "audience": "Pour les équipes qui veulent garder leur site ou leur boutique stable et l’améliorer de manière ciblée.", + "what": [ + ( + "tools", + "Pour qui est-ce ?", + "

Pour les organisations qui ne veulent pas gérer seules toute la surveillance, les incidents et la planification technique.

", + ), + ( + "shield-check", + "Ce que vous obtenez", + "

Maintenance, mises à jour, monitoring et correctifs techniques dans un rythme de travail régulier.

", + ), + ( + "graph-up-arrow", + "Ce que cela apporte", + "

Plus de sérénité, moins de surprises techniques et plus d’espace pour des améliorations utiles.

", + ), + ], + "outcomes": [ + ( + "activity", + "Moins d’interruptions", + "

Les problèmes techniques sont repérés et résolus plus rapidement.

", + ), + ( + "clipboard-data", + "Amélioration continue", + "

Nous faisons progresser performance, contenu et conversion étape par étape.

", + ), + ( + "people", + "Rythme stable", + "

Vous savez quand la maintenance a lieu et ce qui passe en priorité.

", + ), + ], + "choose": [ + "Vous voulez un partenaire stable pour la maintenance technique.", + "Votre site a surtout besoin d’améliorations continues plutôt que d’une refonte complète.", + "Vous voulez réagir plus vite en cas d’incident ou d’évolution.", + ], + "duration": "Réponse sous 24 heures", + }, +} +STANDARD_COPY["es"] = { + "faq_title": "Preguntas frecuentes", + "faq_sub": "

Mantenemos el proceso claro y práctico.

", + "faq_items": [ + ( + "¿Trabajáis con plantillas fijas?", + "

No. Usamos bloques reutilizables, pero el contenido y la estructura se adaptan a su empresa.

", + "Proceso", + ), + ( + "¿Podemos ampliar más adelante?", + "

Sí. La base está pensada para crecer sin empezar de cero.

", + "Crecimiento", + ), + ( + "¿Podemos gestionar el contenido internamente?", + "

Sí. Es un principio central de la plataforma.

", + "Gestión", + ), + ], + "cta_sub": "

Programe una reunión inicial y le mostraremos la ruta más lógica para su empresa.

", + "no_cc": "Sin compromiso", + "pages": { + "about": { + "headline": "Quién es MandelBlog y cómo trabajamos", + "sub": "

MandelBlog ayuda a empresas de servicios y equipos pequeños a lanzar sitios web creíbles, orientados a conversión y fáciles de gestionar.

", + "features_title": "En qué nos fijamos", + "features_sub": "

Trabajamos mejor con organizaciones que valoran la claridad, la rapidez y el control editorial.

", + "features": [ + ( + "people", + "A quién ayudamos", + "

Empresas de servicios, estudios y equipos pequeños con una propuesta clara y una planificación realista.

", + ), + ( + "diagram-3", + "Cómo trabajamos", + "

Primero afinamos objetivo y mensaje, después construimos con bloques probados y entregamos una base útil para su equipo.

", + ), + ( + "shield-check", + "Por qué funciona distinto", + "

No es una plantilla desconectada ni una caja negra, sino una estructura clara que puede seguir creciendo.

", + ), + ( + "person-badge", + "Equipo pequeño, contacto directo", + "

Habla directamente con las personas que hacen el trabajo y convierten decisiones en páginas.

", + ), + ], + "steps_badge": "Método", + "steps_heading": "Nuestro enfoque en 3 pasos", + "steps_sub": "Un proyecto enfocado, decisiones claras y después un sitio que su equipo puede gestionar con seguridad.", + "steps": [ + ( + "1", + "Reunión inicial", + "Definimos objetivos, contenido y prioridades.", + "chat-square-text", + ), + ( + "2", + "Desarrollo", + "Construimos las páginas y afinamos el contenido con usted.", + "layout-text-window", + ), + ( + "3", + "Lanzamiento", + "Recibe acompañamiento, traspaso y un siguiente paso claro.", + "rocket", + ), + ], + "cta": "¿Quiere saber si nuestro enfoque encaja con su empresa?", + }, + "services": { + "headline": "Servicios para empresas que buscan claridad y calidad", + "sub": "

Cada servicio se construye a partir de decisiones claras, contenido útil y una base técnica que puede crecer con su negocio.

", + "features_title": "Qué entregamos", + "features_sub": "

No son módulos sueltos, sino un recorrido adaptado a su etapa, equipo y objetivos.

", + "features": [ + ( + "window", + "Sitio web inicial", + "

Para empresas que quieren estar online rápido con una base profesional.

", + ), + ( + "briefcase", + "Sitio web empresarial", + "

Para organizaciones con varios servicios, casos o una estructura de oferta más compleja.

", + ), + ( + "cart-check", + "Implementación webshop", + "

Para equipos que quieren añadir venta online sin perder el control sobre estructura y tecnología.

", + ), + ( + "wrench-adjustable", + "Mantenimiento y crecimiento", + "

Para organizaciones que necesitan mantenimiento, estabilidad y mejora continua.

", + ), + ], + "cta": "¿Duda qué paquete encaja con su etapa?", + }, + "projects": { + "headline": "Proyectos donde estructura, contenido y tecnología trabajan juntos", + "sub": "

Nuestros proyectos están pensados para transmitir credibilidad, generar confianza y seguir siendo gestionables después del lanzamiento.

", + "features_title": "Qué se ve en nuestros proyectos", + "features_sub": "

No optimizamos efectos superficiales, sino claridad y utilidad.

", + "features": [ + ( + "diagram-3", + "Estructura de página clara", + "

Los visitantes entienden rápidamente dónde están y cuál es el siguiente paso lógico.

", + ), + ( + "pencil-square", + "Gestión sencilla", + "

Los equipos pueden actualizar textos, imágenes y páginas sin depender de terceros.

", + ), + ( + "graph-up-arrow", + "Pensado para solicitudes", + "

El contacto y la conversión están integrados en la estructura y en el contenido.

", + ), + ], + "cta": "¿Quiere presentar su próximo proyecto con más solidez?", + }, + "contact": { + "headline": "Convirtamos su consulta en un plan concreto", + "sub": "

Cuéntenos brevemente qué necesita. Recibirá una respuesta útil con próximos pasos realistas.

", + "features_title": "Para qué puede contactarnos", + "features_sub": "

Elija la vía que mejor encaja con su pregunta o momento actual.

", + "features": [ + ( + "rocket", + "Nuevo proyecto", + "

Para un nuevo sitio web, un reposicionamiento o una reconstrucción completa.

", + ), + ( + "briefcase", + "Elección del paquete", + "

Para decidir qué paquete o estructura se ajusta mejor.

", + ), + ( + "tools", + "Mantenimiento o ampliación", + "

Para soporte técnico, mejoras posteriores o una nueva fase tras el lanzamiento.

", + ), + ], + "form_title": "Cuéntenos brevemente qué necesita", + "form_sub": "

Respondemos con criterio y sin presión comercial.

", + "form_fields": [ + ("text", "Nombre", "Su nombre"), + ("email", "Correo electrónico", "nombre@empresa.es"), + ("company", "Empresa", "Nombre de la empresa"), + ("message", "Consulta o proyecto", "¿En qué necesita ayuda?"), + ], + "benefits_title": "Qué puede esperar", + "benefits": [ + "Respuesta en 24 horas", + "Llamada inicial de 15 minutos", + "Sin compromiso", + ], + "privacy": "

Usamos sus datos únicamente para responder a esta solicitud.

", + "cta": "¿Listo para dar el primer paso?", + }, + "process": { + "headline": "Método de trabajo con pasos claros y decisiones definidas", + "sub": "

Mantenemos el proyecto ordenado: sabe cuándo ocurre cada cosa, qué debe aportar y hacia qué objetivo trabajamos.

", + "features_title": "Así trabajamos", + "features_sub": "

Breve, claro y sin ruido innecesario.

", + "features": [ + ( + "chat-square-text", + "1. Reunión inicial", + "

Definimos objetivo, público, contenido y qué quiere gestionar internamente su equipo.

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

Convertimos estructura, contenido y diseño en una arquitectura de páginas clara.

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

Tras la revisión, el sitio se publica y entregamos una base fácil de gestionar.

", + ), + ( + "graph-up-arrow", + "4. Evolución", + "

Después podemos seguir mejorando en función del comportamiento, las preguntas y los nuevos planes.

", + ), + ], + "cta": "¿Quiere este mismo enfoque para su sitio web?", + }, + }, +} +STANDARD_COPY["it"] = { + "faq_title": "Domande frequenti", + "faq_sub": "

Manteniamo il progetto chiaro e concreto.

", + "faq_items": [ + ( + "Lavorate con template fissi?", + "

No. Usiamo blocchi riutilizzabili, ma contenuto e struttura vengono adattati alla vostra azienda.

", + "Metodo", + ), + ( + "Possiamo ampliare il sito in seguito?", + "

Sì. La base è progettata per crescere senza ripartire da zero.

", + "Crescita", + ), + ( + "Possiamo gestire i contenuti internamente?", + "

Sì. È un principio centrale della piattaforma.

", + "Gestione", + ), + ], + "cta_sub": "

Prenotate una consulenza introduttiva e vi mostreremo il percorso più sensato per la vostra azienda.

", + "no_cc": "Senza impegno", + "pages": { + "about": { + "headline": "Chi è MandelBlog e come lavoriamo", + "sub": "

MandelBlog aiuta aziende di servizi e piccoli team a pubblicare siti credibili, orientati alla conversione e semplici da gestire.

", + "features_title": "Su cosa ci concentriamo", + "features_sub": "

Lavoriamo al meglio con organizzazioni che cercano chiarezza, rapidità e controllo editoriale.

", + "features": [ + ( + "people", + "Chi aiutiamo", + "

Aziende di servizi, studi e piccoli team con un’offerta chiara e una pianificazione realistica.

", + ), + ( + "diagram-3", + "Come lavoriamo", + "

Definiamo prima obiettivo e messaggio, costruiamo con blocchi collaudati e consegniamo una base davvero utilizzabile.

", + ), + ( + "shield-check", + "Perché funziona in modo diverso", + "

Non un template scollegato né una black box, ma una struttura chiara che può continuare a crescere.

", + ), + ( + "person-badge", + "Team piccolo, contatto diretto", + "

Parlate direttamente con le persone che svolgono il lavoro e trasformano le decisioni nel sito.

", + ), + ], + "steps_badge": "Metodo", + "steps_heading": "Il nostro approccio in 3 fasi", + "steps_sub": "Un progetto focalizzato, scelte chiare e poi un sito che il vostro team può davvero utilizzare.", + "steps": [ + ( + "1", + "Colloquio iniziale", + "Definiamo obiettivi, contenuti e priorità.", + "chat-square-text", + ), + ( + "2", + "Sviluppo", + "Costruiamo le pagine e rifiniamo i contenuti insieme a voi.", + "layout-text-window", + ), + ( + "3", + "Pubblicazione", + "Ricevete affiancamento, consegna e un passo successivo chiaro.", + "rocket", + ), + ], + "cta": "Volete capire se il nostro approccio è adatto alla vostra azienda?", + }, + "services": { + "headline": "Servizi per aziende che cercano chiarezza e qualità", + "sub": "

Ogni servizio nasce da scelte chiare, contenuti utili e una base tecnica che può crescere insieme al business.

", + "features_title": "Cosa consegniamo", + "features_sub": "

Non moduli scollegati, ma un percorso adatto alla vostra fase, al team e agli obiettivi.

", + "features": [ + ( + "window", + "Sito starter", + "

Per aziende che vogliono essere online rapidamente con una base professionale.

", + ), + ( + "briefcase", + "Sito business", + "

Per organizzazioni con più servizi, casi o una struttura dell’offerta più articolata.

", + ), + ( + "cart-check", + "Implementazione webshop", + "

Per team che vogliono aggiungere vendita online senza perdere controllo su struttura e tecnologia.

", + ), + ( + "wrench-adjustable", + "Manutenzione e crescita", + "

Per organizzazioni che hanno bisogno di manutenzione, stabilità e miglioramento continuo.

", + ), + ], + "cta": "Non sapete quale pacchetto sia adatto alla vostra fase?", + }, + "projects": { + "headline": "Progetti in cui struttura, contenuto e tecnologia lavorano insieme", + "sub": "

I nostri progetti sono pensati per trasmettere credibilità, generare fiducia e restare gestibili anche dopo il lancio.

", + "features_title": "Cosa si vede nei nostri progetti", + "features_sub": "

Non ottimizziamo effetti superficiali, ma chiarezza e utilità.

", + "features": [ + ( + "diagram-3", + "Struttura di pagina chiara", + "

I visitatori capiscono subito dove si trovano e quale sia il passo successivo.

", + ), + ( + "pencil-square", + "Gestione semplice", + "

I team possono aggiornare testi, immagini e pagine senza scorciatoie complicate.

", + ), + ( + "graph-up-arrow", + "Pensato per le richieste", + "

Contatto e conversione sono visibili nella struttura e nei contenuti.

", + ), + ], + "cta": "Volete presentare il vostro prossimo progetto in modo più professionale?", + }, + "contact": { + "headline": "Trasformiamo la vostra richiesta in un piano concreto", + "sub": "

Spiegate brevemente di cosa avete bisogno. Riceverete una risposta utile con i prossimi passi realistici.

", + "features_title": "Per cosa potete contattarci", + "features_sub": "

Scegliete il percorso che meglio si adatta alla vostra domanda o alla fase attuale.

", + "features": [ + ( + "rocket", + "Nuovo progetto", + "

Per un nuovo sito, un riposizionamento o una ricostruzione completa.

", + ), + ( + "briefcase", + "Scelta del pacchetto", + "

Per capire quale pacchetto o struttura sia più adatto.

", + ), + ( + "tools", + "Manutenzione o sviluppo", + "

Per supporto tecnico, miglioramenti successivi o una fase ulteriore dopo il lancio.

", + ), + ], + "form_title": "Raccontateci brevemente di cosa avete bisogno", + "form_sub": "

Rispondiamo con contenuto utile e senza pressione commerciale.

", + "form_fields": [ + ("text", "Nome", "Il vostro nome"), + ("email", "E-mail", "nome@azienda.it"), + ("company", "Azienda", "Nome dell’azienda"), + ("message", "Domanda o progetto", "Per cosa vi serve supporto?"), + ], + "benefits_title": "Cosa aspettarvi", + "benefits": [ + "Risposta entro 24 ore", + "Colloquio iniziale di 15 minuti", + "Senza impegno", + ], + "privacy": "

Usiamo i vostri dati solo per rispondere a questa richiesta.

", + "cta": "Pronti a fare il primo passo?", + }, + "process": { + "headline": "Metodo di lavoro con passaggi chiari e scelte definite", + "sub": "

Manteniamo il progetto ordinato: sapete quando succede ogni cosa, cosa dovete fornire e verso quale risultato stiamo lavorando.

", + "features_title": "Come lavoriamo insieme", + "features_sub": "

Breve, chiaro e senza rumore inutile.

", + "features": [ + ( + "chat-square-text", + "1. Colloquio iniziale", + "

Definiamo obiettivi, pubblico, contenuti e ciò che il vostro team vuole gestire internamente.

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

Trasformiamo struttura, contenuti e design in un’architettura di pagina chiara.

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

Dopo la revisione il sito va online e consegniamo una base gestibile dal vostro team.

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

Quando serve, continuiamo a migliorare in base ai comportamenti, alle domande e ai nuovi obiettivi.

", + ), + ], + "cta": "Volete questo stesso approccio anche per il vostro sito?", + }, + }, +} +STANDARD_COPY["pt"] = { + "faq_title": "Perguntas frequentes", + "faq_sub": "

Mantemos o projeto claro e prático.

", + "faq_items": [ + ( + "Trabalham com templates fixos?", + "

Não. Usamos blocos reutilizáveis, mas o conteúdo e a estrutura são ajustados à sua empresa.

", + "Método", + ), + ( + "Podemos expandir mais tarde?", + "

Sim. A base foi pensada para crescer sem começar do zero.

", + "Expansão", + ), + ( + "Podemos gerir o conteúdo internamente?", + "

Sim. Esse é um princípio central da plataforma.

", + "Gestão", + ), + ], + "cta_sub": "

Agende uma consulta inicial e mostramos o caminho mais sensato para a sua empresa.

", + "no_cc": "Sem compromisso", + "pages": { + "about": { + "headline": "Quem é a MandelBlog e como trabalhamos", + "sub": "

A MandelBlog ajuda empresas de serviços e pequenas equipas a lançar sites credíveis, focados em conversão e fáceis de gerir.

", + "features_title": "No que prestamos atenção", + "features_sub": "

Trabalhamos melhor com organizações que valorizam clareza, rapidez e controlo editorial.

", + "features": [ + ( + "people", + "Quem ajudamos", + "

Empresas de serviços, estúdios e pequenas equipas com uma oferta clara e um planeamento realista.

", + ), + ( + "diagram-3", + "Como trabalhamos", + "

Primeiro afinamos objetivo e mensagem, depois construímos com blocos comprovados e entregamos uma base realmente útil.

", + ), + ( + "shield-check", + "Porque funciona de forma diferente", + "

Não é um template desligado nem uma caixa negra, mas uma estrutura clara que pode continuar a evoluir.

", + ), + ( + "person-badge", + "Equipa pequena, contacto direto", + "

Fala diretamente com quem executa o trabalho e transforma decisões no website.

", + ), + ], + "steps_badge": "Método", + "steps_heading": "A nossa abordagem em 3 etapas", + "steps_sub": "Um projeto focado, decisões claras e depois um website que a sua equipa consegue gerir com confiança.", + "steps": [ + ( + "1", + "Reunião inicial", + "Definimos objetivos, conteúdo e prioridades.", + "chat-square-text", + ), + ( + "2", + "Desenvolvimento", + "Construímos as páginas e afinamos o conteúdo consigo.", + "layout-text-window", + ), + ( + "3", + "Lançamento", + "Recebe acompanhamento, transição e um próximo passo claro.", + "rocket", + ), + ], + "cta": "Quer saber se a nossa abordagem faz sentido para a sua empresa?", + }, + "services": { + "headline": "Serviços para empresas que procuram clareza e qualidade", + "sub": "

Cada serviço é pensado a partir de decisões claras, conteúdo útil e uma base técnica que pode crescer consigo.

", + "features_title": "O que entregamos", + "features_sub": "

Não são módulos soltos, mas um percurso adaptado à sua fase, equipa e objetivos.

", + "features": [ + ( + "window", + "Website inicial", + "

Para empresas que querem entrar online rapidamente com uma base profissional.

", + ), + ( + "briefcase", + "Site empresarial", + "

Para organizações com vários serviços, casos ou uma estrutura de oferta mais complexa.

", + ), + ( + "cart-check", + "Implementação de webshop", + "

Para equipas que querem acrescentar vendas online sem perder controlo sobre estrutura e tecnologia.

", + ), + ( + "wrench-adjustable", + "Manutenção & crescimento", + "

Para organizações que precisam de manutenção, estabilidade e melhoria contínua.

", + ), + ], + "cta": "Tem dúvidas sobre qual pacote faz sentido nesta fase?", + }, + "projects": { + "headline": "Projetos onde estrutura, conteúdo e tecnologia trabalham em conjunto", + "sub": "

Os nossos projetos são pensados para transmitir credibilidade, criar confiança e continuar geríveis depois do lançamento.

", + "features_title": "O que se vê nos nossos projetos", + "features_sub": "

Não otimizamos efeitos superficiais, mas sim clareza e utilidade.

", + "features": [ + ( + "diagram-3", + "Estrutura de página clara", + "

Os visitantes percebem rapidamente onde estão e qual é o próximo passo lógico.

", + ), + ( + "pencil-square", + "Gestão simples", + "

As equipas podem atualizar textos, imagens e páginas sem depender de terceiros.

", + ), + ( + "graph-up-arrow", + "Pensado para pedidos", + "

Contacto e conversão estão visíveis tanto na estrutura como no conteúdo.

", + ), + ], + "cta": "Quer apresentar o seu próximo projeto de forma mais profissional?", + }, + "contact": { + "headline": "Vamos transformar a sua questão num plano concreto", + "sub": "

Diga-nos brevemente o que precisa. Receberá uma resposta útil com próximos passos realistas.

", + "features_title": "Para que pode entrar em contacto", + "features_sub": "

Escolha o caminho que melhor se adequa à sua questão ou fase atual.

", + "features": [ + ( + "rocket", + "Novo projeto", + "

Para um novo website, reposicionamento ou reconstrução completa.

", + ), + ( + "briefcase", + "Escolha do pacote", + "

Para perceber qual o pacote ou estrutura mais adequado.

", + ), + ( + "tools", + "Manutenção ou expansão", + "

Para apoio técnico, melhorias posteriores ou uma nova fase depois do lançamento.

", + ), + ], + "form_title": "Conte-nos brevemente do que precisa", + "form_sub": "

Respondemos com conteúdo útil e sem pressão comercial.

", + "form_fields": [ + ("text", "Nome", "O seu nome"), + ("email", "E-mail", "nome@empresa.pt"), + ("company", "Empresa", "Nome da empresa"), + ("message", "Questão ou projeto", "Em que precisa de ajuda?"), + ], + "benefits_title": "O que pode esperar", + "benefits": [ + "Resposta em 24 horas", + "Reunião inicial de 15 minutos", + "Sem compromisso", + ], + "privacy": "

Usamos os seus dados apenas para responder a este pedido.

", + "cta": "Pronto para dar o primeiro passo?", + }, + "process": { + "headline": "Método de trabalho com etapas claras e decisões definidas", + "sub": "

Mantemos o projeto organizado: sabe quando acontece cada etapa, o que precisa de fornecer e para onde estamos a avançar.

", + "features_title": "Como trabalhamos consigo", + "features_sub": "

Curto, claro e sem ruído desnecessário.

", + "features": [ + ( + "chat-square-text", + "1. Reunião inicial", + "

Definimos objetivo, público, conteúdo e o que a sua equipa quer conseguir gerir internamente.

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

Transformamos estrutura, conteúdo e design numa arquitetura de páginas clara.

", + ), + ( + "rocket", + "3. Lançamento", + "

Depois da revisão, o site entra no ar e entregamos uma base fácil de gerir.

", + ), + ( + "graph-up-arrow", + "4. Evolução", + "

Quando fizer sentido, continuamos a melhorar com base no comportamento, nas perguntas e nos novos planos.

", + ), + ], + "cta": "Quer esta mesma abordagem para o seu website?", + }, + }, +} +STANDARD_COPY["ru"] = { + "faq_title": "Частые вопросы", + "faq_sub": "

Мы делаем процесс понятным и практичным.

", + "faq_items": [ + ( + "Вы работаете с фиксированными шаблонами?", + "

Нет. Мы используем переиспользуемые блоки, но структура и контент настраиваются под вашу компанию.

", + "Процесс", + ), + ( + "Можно ли расширить сайт позже?", + "

Да. Основа рассчитана на рост без перезапуска проекта.

", + "Развитие", + ), + ( + "Сможем ли мы управлять контентом сами?", + "

Да. Это один из ключевых принципов платформы.

", + "Управление", + ), + ], + "cta_sub": "

Запланируйте консультацию, и мы покажем наиболее разумный путь для вашей компании.

", + "no_cc": "Без обязательств", + "pages": { + "about": { + "headline": "Кто такая MandelBlog и как мы работаем", + "sub": "

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

", + "features_title": "На что мы обращаем внимание", + "features_sub": "

Лучше всего мы работаем с командами, которым нужны ясность, скорость и редакционный контроль.

", + "features": [ + ( + "people", + "Кому мы помогаем", + "

Сервисным компаниям, студиям и небольшим командам с понятным предложением и реалистичным планом.

", + ), + ( + "diagram-3", + "Как мы работаем", + "

Сначала уточняем цель и сообщение, затем собираем сайт на проверенных блоках и передаём рабочую основу.

", + ), + ( + "shield-check", + "Почему это работает иначе", + "

Это не шаблон и не чёрный ящик, а понятная структура, которую можно развивать дальше.

", + ), + ( + "person-badge", + "Небольшая команда, прямой контакт", + "

Вы общаетесь напрямую с людьми, которые выполняют работу и превращают решения в страницы.

", + ), + ], + "steps_badge": "Подход", + "steps_heading": "Наш подход в 3 шага", + "steps_sub": "Сфокусированный проект, понятные решения и затем сайт, с которым вашей команде удобно работать.", + "steps": [ + ( + "1", + "Вводная консультация", + "Определяем цели, контент и приоритеты.", + "chat-square-text", + ), + ( + "2", + "Разработка", + "Собираем страницы и уточняем контент вместе с вами.", + "layout-text-window", + ), + ( + "3", + "Запуск", + "Вы получаете сопровождение, передачу и понятный следующий шаг.", + "rocket", + ), + ], + "cta": "Хотите понять, подходит ли наш подход вашей компании?", + }, + "services": { + "headline": "Услуги для компаний, которым нужны ясность и качество", + "sub": "

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

", + "features_title": "Что мы даём", + "features_sub": "

Это не набор разрозненных модулей, а путь, который соответствует вашему этапу, команде и целям.

", + "features": [ + ( + "window", + "Стартовый сайт", + "

Для компаний, которым нужно быстро выйти онлайн с профессиональной основой.

", + ), + ( + "briefcase", + "Бизнес-сайт", + "

Для организаций с несколькими услугами, кейсами или более сложной структурой предложения.

", + ), + ( + "cart-check", + "Внедрение вебшопа", + "

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

", + ), + ( + "wrench-adjustable", + "Поддержка и рост", + "

Для организаций, которым нужны обслуживание, стабильность и постоянное улучшение.

", + ), + ], + "cta": "Сомневаетесь, какой пакет подходит именно вам?", + }, + "projects": { + "headline": "Проекты, где структура, контент и технология работают вместе", + "sub": "

Наши проекты создаются так, чтобы вызывать доверие, ясно представлять предложение и оставаться удобными после запуска.

", + "features_title": "Что видно в наших проектах", + "features_sub": "

Мы не гонимся за поверхностными эффектами, а работаем на ясность и полезность.

", + "features": [ + ( + "diagram-3", + "Понятная структура страниц", + "

Посетители быстро понимают, где они находятся и какой следующий шаг логичен.

", + ), + ( + "pencil-square", + "Простое управление", + "

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

", + ), + ( + "graph-up-arrow", + "Ориентация на заявки", + "

Контакт и конверсия заметны как в структуре, так и в содержании.

", + ), + ], + "cta": "Хотите представить следующий проект более профессионально?", + }, + "contact": { + "headline": "Давайте превратим ваш запрос в понятный план", + "sub": "

Кратко опишите, что вам нужно. Вы получите практичный ответ с реалистичными следующими шагами.

", + "features_title": "По каким вопросам можно обратиться", + "features_sub": "

Выберите путь, который лучше соответствует вашему вопросу или текущему этапу.

", + "features": [ + ( + "rocket", + "Новый проект", + "

Для нового сайта, репозиционирования или полной переработки.

", + ), + ( + "briefcase", + "Выбор пакета", + "

Чтобы понять, какой пакет или структура подойдут лучше всего.

", + ), + ( + "tools", + "Поддержка или развитие", + "

Для технической помощи, последующих улучшений или нового этапа после запуска.

", + ), + ], + "form_title": "Кратко опишите, что вам нужно", + "form_sub": "

Мы отвечаем по существу и без навязчивых продаж.

", + "form_fields": [ + ("text", "Имя", "Ваше имя"), + ("email", "E-mail", "name@company.ru"), + ("company", "Компания", "Название компании"), + ("message", "Вопрос или проект", "С чем вам нужна помощь?"), + ], + "benefits_title": "Что вы получите", + "benefits": [ + "Ответ в течение 24 часов", + "15-минутная вводная консультация", + "Без обязательств", + ], + "privacy": "

Мы используем ваши данные только для ответа на этот запрос.

", + "cta": "Готовы сделать первый шаг?", + }, + "process": { + "headline": "Как мы работаем: понятные шаги и ясные решения", + "sub": "

Мы держим проект под контролем: вы понимаете, что происходит, что нужно предоставить и к какому результату мы идём.

", + "features_title": "Как строится работа", + "features_sub": "

Коротко, понятно и без лишнего шума.

", + "features": [ + ( + "chat-square-text", + "1. Вводная консультация", + "

Определяем цель, аудиторию, контент и то, чем ваша команда хочет управлять сама.

", + ), + ( + "layout-text-window", + "2. Разработка", + "

Превращаем структуру, контент и дизайн в понятную архитектуру страниц.

", + ), + ( + "rocket", + "3. Запуск", + "

После согласования сайт выходит в эфир, а команда получает удобную базу для дальнейшей работы.

", + ), + ( + "graph-up-arrow", + "4. Дальнейшее развитие", + "

При необходимости продолжаем улучшать сайт, опираясь на поведение пользователей, вопросы и новые задачи.

", + ), + ], + "cta": "Хотите применить этот подход и к вашему сайту?", + }, + }, +} + + +def _footer_stream_data( + locale: str, links: dict[str, str], page_title_map: dict[str, 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"]}), + block( + "text", + { + "heading": cfg["links_heading"], + "content": f'

{page_title_map["about"][locale]}
{page_title_map["services"][locale]}
{page_title_map["projects"][locale]}
{page_title_map["contact"][locale]}

', + }, + ), + block("text", {"heading": cfg["support_heading"], "content": cfg["support"]}), + ] + return footer, [block("text", cfg["mini"])] + + +INLINE_LINK_TEXT = { + "process": { + "nl": "Bekijk hoe dit werkt", + "en": "See how this works", + "de": "So läuft der Ablauf", + "fr": "Voir comment cela fonctionne", + "es": "Vea cómo funciona", + "it": "Veda come funziona", + "pt": "Veja como funciona", + "ru": "Посмотреть, как это работает", + }, + "services": { + "nl": "Bekijk onze diensten", + "en": "View our services", + "de": "Unsere Leistungen ansehen", + "fr": "Voir nos services", + "es": "Ver nuestros servicios", + "it": "Vedere i nostri servizi", + "pt": "Ver os nossos serviços", + "ru": "Посмотреть услуги", + }, +} + + +CHOOSE_CARD_TITLES = { + "nl": ["Goede match", "Wat dit praktisch betekent", "Eerst de werkwijze zien"], + "en": ["Good fit", "What this means in practice", "See the process first"], + "de": ["Gute Wahl", "Was das praktisch bedeutet", "Erst den Ablauf sehen"], + "fr": ["Bon choix", "Ce que cela signifie concrètement", "Voir d'abord la méthode"], + "es": ["Buena opción", "Qué significa en la práctica", "Ver primero el proceso"], + "it": ["Scelta adatta", "Cosa significa in pratica", "Vedere prima il metodo"], + "pt": ["Boa escolha", "O que isto significa na prática", "Ver primeiro o método"], + "ru": [ + "Подходит вам", + "Что это означает на практике", + "Сначала посмотреть процесс", + ], +} + + +def _inline_link_text(locale: str, key: str) -> str: + return INLINE_LINK_TEXT[key][locale] + + +def _choose_cards( + locale: str, items: list[str], process_url: str +) -> list[dict[str, Any]]: + titles = CHOOSE_CARD_TITLES[locale] + cards: list[dict[str, Any]] = [] + for index, body in enumerate(items): + description = body + if index == len(items) - 1: + description = f'{body} {_inline_link_text(locale, "process")}.' + cards.append( + item( + { + "icon": "arrow-right-circle", + "icon_image": None, + "title": titles[min(index, len(titles) - 1)], + "description": description, + "link_text": "", + "link_url": "", + "highlight": "none", + } + ) + ) + return cards + + +def _home_body( + locale: str, urls: dict[str, str], page_title_map: dict[str, dict[str, str]] +) -> list[dict[str, Any]]: + cta = COMMON_CTA[locale] + cfg = HOME_COPY[locale] + return [ + block( + "saas_hero_banner", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "badge_text": cfg["badge"], + "badge_url": urls["home"], + "headline": cfg["headline"], + "sub_headline": cfg["sub"], + "primary_cta_text": cta["primary"], + "primary_cta_url": urls["contact"], + "secondary_cta_text": "", + "secondary_cta_url": "", + "hero_image": AGENCY_HERO_IMAGE_ID, + "video_url": "", + "stats": [ + item({"value": value, "label": label}) + for value, label in cfg["stats"] + ], + "customer_logos_title": cfg["logos"], + }, + ), + 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": link_text + or _inline_link_text(locale, "process"), + "link_url": urls[link_key], + "highlight": highlight, + } + ) + for icon, title, desc, link_key, link_text, highlight in cfg[ + "features" + ] + ], + "columns": "2", + }, + ), + block( + "saas_faq", + { + "layout_width": "container", + "background_style": "light", + "layout": "accordion", + "section_title": cfg["faq_title"], + "section_subtitle": cfg["faq_sub"], + "faqs": [ + item({"question": question, "answer": answer, "category": category}) + for question, answer, category in cfg["faqs"] + ], + "show_contact_cta": "simple", + "contact_cta_text": cta["secondary"], + "contact_cta_url": urls["services"], + }, + ), + block( + "saas_cta_footer", + { + "layout_width": "container", + "background_style": "light", + "layout": "banner", + "headline": cfg["cta_headline"], + "subheadline": cfg["cta_sub"], + "primary_cta_text": cta["primary"], + "primary_cta_url": urls["contact"], + "secondary_cta_text": "", + "secondary_cta_url": "", + "background_image": AGENCY_SUPPORT_IMAGE_ID, + "side_image": AGENCY_HERO_IMAGE_ID, + "show_no_credit_card": "with-icon", + "no_credit_card_text": cfg["no_cc"], + }, + ), + ] + + +def _standard_body( + locale: str, page_key: str, urls: dict[str, str] +) -> list[dict[str, Any]]: + cta = COMMON_CTA[locale] + common = STANDARD_COPY[locale] + cfg = common["pages"][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" + if locale == "nl" + else "Services" + if locale == "en" + else "Dienstleistungen" + if locale == "de" + else "Services" + if locale == "fr" + else "Servicios" + if locale == "es" + else "Servizi" + if locale == "it" + else "Serviços" + if locale == "pt" + else "Услуги" + ), + "badge_url": urls[page_key], + "headline": cfg["headline"], + "sub_headline": cfg["sub"], + "primary_cta_text": cta["primary"], + "primary_cta_url": urls["contact"], + "secondary_cta_text": "", + "secondary_cta_url": "", + "hero_image": ( + AGENCY_HERO_IMAGE_ID + if page_key != "process" + else AGENCY_PROCESS_IMAGE_ID + ), + "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": _inline_link_text(locale, "process"), + "link_url": urls["process"], + "highlight": "none", + } + ) + for icon, title, desc in cfg["features"] + ], + "columns": "2" if len(cfg["features"]) <= 4 else "3", + }, + ), + ] + if page_key == "about": + blocks.append( + block( + "saas_animated_stats", + { + "layout_width": "container", + "background_style": "light", + "layout": "cards-grid", + "badge": cfg["steps_badge"], + "heading": cfg["steps_heading"], + "subheading": cfg["steps_sub"], + "stats": [ + item( + { + "value": value, + "prefix": None, + "suffix": "", + "label": label, + "description": desc, + "icon": icon, + "highlight": False, + } + ) + for value, label, desc, icon in cfg["steps"] + ], + "animation_duration": 1800, + "animation_easing": "ease-out", + "start_on_scroll": True, + "show_logos": False, + "logos_heading": "", + "company_logos": [], + }, + ) + ) + if page_key == "contact": + blocks.append( + block( + "saas_demo_request", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "section_title": cfg["form_title"], + "section_subtitle": cfg["form_sub"], + "form_fields": [ + item( + { + "field_type": field_type, + "label": label, + "placeholder": placeholder, + "required": field_type != "message", + } + ) + for field_type, label, placeholder in cfg["form_fields"] + ], + "submit_button_text": "Plan mijn strategiegesprek", + "form_action_url": urls["contact"], + "benefits_title": cfg["benefits_title"], + "benefits": [item(text) for text in cfg["benefits"]], + "side_image": AGENCY_SUPPORT_IMAGE_ID, + "privacy_text": cfg["privacy"], + }, + ) + ) + blocks.append( + block( + "saas_faq", + { + "layout_width": "container", + "background_style": "light", + "layout": "accordion", + "section_title": common["faq_title"], + "section_subtitle": common["faq_sub"], + "faqs": [ + item({"question": q, "answer": a, "category": c}) + for q, a, c in common["faq_items"] + ], + "show_contact_cta": "none", + "contact_cta_text": "", + "contact_cta_url": "", + }, + ) + ) + blocks.append( + block( + "saas_cta_footer", + { + "layout_width": "container", + "background_style": "light", + "layout": "banner", + "headline": cfg["cta"], + "subheadline": common["cta_sub"], + "primary_cta_text": cta["primary"], + "primary_cta_url": urls["contact"], + "secondary_cta_text": "", + "secondary_cta_url": "", + "background_image": AGENCY_SUPPORT_IMAGE_ID, + "side_image": AGENCY_HERO_IMAGE_ID, + "show_no_credit_card": "with-icon", + "no_credit_card_text": common["no_cc"], + }, + ) + ) + return blocks + + +def _service_body(locale: str, kind: str, urls: dict[str, str]) -> list[dict[str, Any]]: + cta = COMMON_CTA[locale] + cfg = SERVICE_COPY[locale][kind] + common = SERVICE_COMMON[locale] + return [ + block( + "saas_hero_banner", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "badge_text": "PAKKET" + if locale == "nl" + else "PACKAGE" + if locale == "en" + else "PAKET" + if locale == "de" + else "OFFRE" + if locale == "fr" + else "PAQUETE" + if locale == "es" + else "PACCHETTO" + if locale == "it" + else "PACOTE" + if locale == "pt" + else "ПАКЕТ", + "badge_url": urls[kind], + "headline": cfg["title"], + "sub_headline": f"

{cfg['audience']}

", + "primary_cta_text": cta["primary"], + "primary_cta_url": urls["contact"], + "secondary_cta_text": "", + "secondary_cta_url": "", + "hero_image": AGENCY_HERO_IMAGE_ID, + "video_url": "", + "stats": [ + item({"value": cfg["duration"], "label": common["timeline_label"]}), + item( + { + "value": common["communication"], + "label": "Communicatie" + if locale == "nl" + else "Communication" + if locale in {"en", "fr"} + else "Kommunikation" + if locale == "de" + else "Comunicación" + if locale == "es" + else "Comunicazione" + if locale == "it" + else "Comunicação" + if locale == "pt" + else "Коммуникация", + } + ), + item( + { + "value": common["intro"], + "label": "Kennismaking" + if locale == "nl" + else "Introduction" + if locale == "en" + else "Einführung" + if locale == "de" + else "Introduction" + if locale == "fr" + else "Inicio" + if locale == "es" + else "Introduzione" + if locale == "it" + else "Introdução" + if locale == "pt" + else "Вводный этап", + } + ), + ], + "customer_logos_title": "", + }, + ), + block( + "saas_features", + { + "layout_width": "container", + "background_style": "light", + "layout": "grid", + "section_title": common["section_what"], + "section_subtitle": common["section_what_sub"], + "features": [ + item( + { + "icon": icon, + "icon_image": None, + "title": title, + "description": desc, + "link_text": _inline_link_text(locale, "process"), + "link_url": urls["process"], + "highlight": "none", + } + ) + for icon, title, desc in cfg["what"] + ], + "columns": "3", + }, + ), + block( + "saas_features", + { + "layout_width": "container", + "background_style": "light", + "layout": "grid", + "section_title": common["section_outcomes"], + "section_subtitle": common["section_outcomes_sub"], + "features": [ + item( + { + "icon": icon, + "icon_image": None, + "title": title, + "description": desc, + "link_text": "", + "link_url": "", + "highlight": "none", + } + ) + for icon, title, desc in cfg["outcomes"] + ], + "columns": "3", + }, + ), + block( + "saas_features", + { + "layout_width": "container", + "background_style": "light", + "layout": "grid", + "section_title": common["section_choose"], + "section_subtitle": common["section_choose_sub"], + "features": _choose_cards(locale, cfg["choose"], urls["process"]), + "columns": "3", + }, + ), + block( + "saas_cta_footer", + { + "layout_width": "container", + "background_style": "light", + "layout": "banner", + "headline": cfg["title"], + "subheadline": common["cta_sub"], + "primary_cta_text": cta["primary"], + "primary_cta_url": urls["contact"], + "secondary_cta_text": "", + "secondary_cta_url": "", + "background_image": AGENCY_SUPPORT_IMAGE_ID, + "side_image": AGENCY_HERO_IMAGE_ID, + "show_no_credit_card": "with-icon", + "no_credit_card_text": common["no_cc"], + }, + ), + ] + + +def body_for( + locale: str, + page_key: str, + urls: dict[str, str], + page_title_map: dict[str, dict[str, str]], +) -> list[dict[str, Any]]: + if locale == "nl" and page_key not in { + "home", + "about", + "services", + "projects", + "contact", + "process", + "starter", + "business", + "webshop", + "support", + }: + raise KeyError(page_key) + if page_key == "home": + return _home_body(locale, urls, page_title_map) + if page_key in {"about", "services", "projects", "contact", "process"}: + return _standard_body(locale, page_key, urls) + if page_key in {"starter", "business", "webshop", "support"}: + return _service_body(locale, page_key, urls) + raise KeyError(page_key) + + +def footer_stream_data( + locale: str, links: dict[str, str], page_title_map: dict[str, dict[str, str]] +) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + return _footer_stream_data(locale, links, page_title_map) + + +SERVICE_COPY["es"] = { + "starter": { + "title": "Sitio web inicial", + "audience": "Para empresas y equipos pequeños que necesitan una presencia profesional rápida y clara.", + "what": [ + ( + "layout-text-window", + "¿Para quién es?", + "

Para negocios con una oferta clara que necesitan una primera impresión profesional sin un proyecto pesado.

", + ), + ( + "window", + "Qué obtiene", + "

Páginas clave, navegación clara y un editor que su equipo puede usar para gestionar contenido.

", + ), + ( + "graph-up-arrow", + "Qué aporta", + "

Una base profesional que ayuda a los visitantes a entender qué hace y cómo contactar.

", + ), + ], + "outcomes": [ + ( + "shield-check", + "Base digital clara", + "

Sin elementos innecesarios, solo un sitio que genera confianza.

", + ), + ( + "people", + "Gestión sencilla", + "

Su equipo puede hacer cambios sin depender siempre de un desarrollador.

", + ), + ( + "rocket", + "Lanzamiento rápido", + "

Adecuado como primer sitio profesional o como sustitución de una web desactualizada.

", + ), + ], + "choose": [ + "Quiere una presencia profesional en poco tiempo.", + "Necesita sobre todo páginas clave y navegación clara.", + "Quiere actualizar textos e imágenes internamente.", + ], + "duration": "Plazo medio: de 2 a 4 semanas", + }, + "business": { + "title": "Sitio web empresarial", + "audience": "Para empresas de servicios y equipos que necesitan presentar con claridad varias ofertas, casos o recorridos de conversión.", + "what": [ + ( + "briefcase", + "¿Para quién es?", + "

Para organizaciones que necesitan más estructura, más profundidad y un mejor recorrido hacia la solicitud.

", + ), + ( + "layout-text-window", + "Qué obtiene", + "

Más estructura de páginas, espacio para casos y una base favorable al SEO que puede crecer de forma lógica.

", + ), + ( + "graph-up-arrow", + "Qué aporta", + "

Un sitio que explica mejor su oferta y lleva a los visitantes de forma más directa hacia el contacto.

", + ), + ], + "outcomes": [ + ( + "diagram-3", + "Más claridad", + "

Servicios, casos y experiencia tienen cada uno su lugar lógico.

", + ), + ( + "search", + "Mejor visibilidad", + "

La estructura está preparada para mejor contenido y una base favorable al SEO.

", + ), + ( + "people", + "Solicitudes más sólidas", + "

Los visitantes entienden más rápido qué oferta y qué camino les encajan.

", + ), + ], + "choose": [ + "Tiene varios servicios o varios públicos.", + "Quiere mostrar mejor casos, experiencia y pruebas.", + "Necesita más estructura que la que ofrece un sitio inicial.", + ], + "duration": "Plazo medio: de 2 a 4 semanas", + }, + "webshop": { + "title": "Implementación webshop", + "audience": "Para organizaciones que quieren añadir venta online sin terminar con una tienda genérica.", + "what": [ + ( + "cart-check", + "¿Para quién es?", + "

Para empresas que quieren vender online manteniendo control sobre presentación, checkout y gestión.

", + ), + ( + "credit-card", + "Qué obtiene", + "

Una estructura de comercio con catálogo, checkout y base escalable para gestionar pedidos.

", + ), + ( + "graph-up-arrow", + "Qué aporta", + "

Un entorno de venta que encaja con su marca y no parece una tienda genérica.

", + ), + ], + "outcomes": [ + ( + "window", + "Mejor presentación", + "

Los productos y categorías se muestran con una estructura clara y creíble.

", + ), + ( + "shield-check", + "Tecnología estable", + "

Pagos y pedidos se apoyan en una base técnica fácil de mantener.

", + ), + ( + "rocket", + "Preparado para crecer", + "

La estructura comercial puede crecer con su catálogo y sus procesos.

", + ), + ], + "choose": [ + "Quiere combinar venta online con un sitio corporativo.", + "Necesita control sobre estructura y tecnología.", + "No busca una tienda basada en un tema genérico.", + ], + "duration": "Plazo medio: de 3 a 6 semanas", + }, + "support": { + "title": "Mantenimiento y crecimiento", + "audience": "Para equipos que quieren mantener estable su web o tienda y mejorarla de forma continua.", + "what": [ + ( + "tools", + "¿Para quién es?", + "

Para organizaciones que no quieren encargarse solas de la supervisión, los fallos y la planificación técnica.

", + ), + ( + "shield-check", + "Qué obtiene", + "

Mantenimiento, actualizaciones, monitorización y soluciones técnicas con un ritmo de trabajo claro.

", + ), + ( + "graph-up-arrow", + "Qué aporta", + "

Más tranquilidad, menos sorpresas técnicas y más margen para mejorar con criterio.

", + ), + ], + "outcomes": [ + ( + "activity", + "Menos interrupciones", + "

Los problemas técnicos se detectan y se resuelven con mayor rapidez.

", + ), + ( + "clipboard-data", + "Mejora continua", + "

Mejoramos rendimiento, contenido y conversión paso a paso.

", + ), + ( + "people", + "Ritmo estable", + "

Sabe cuándo se realiza el mantenimiento y qué tiene prioridad.

", + ), + ], + "choose": [ + "Quiere un socio estable para el mantenimiento técnico.", + "Su sitio necesita mejoras continuas y no una reconstrucción completa.", + "Quiere reaccionar más rápido ante incidencias o ampliaciones.", + ], + "duration": "Respuesta en 24 horas", + }, +} +SERVICE_COPY["it"] = { + "starter": { + "title": "Sito starter", + "audience": "Per aziende e piccoli team che hanno bisogno di una presenza professionale rapida e chiara.", + "what": [ + ( + "layout-text-window", + "Per chi è?", + "

Per attività con un’offerta chiara che hanno bisogno di una prima impressione professionale senza un progetto pesante.

", + ), + ( + "window", + "Cosa riceve", + "

Pagine essenziali, navigazione chiara e un editor che il team può usare per gestire i contenuti.

", + ), + ( + "graph-up-arrow", + "Cosa porta", + "

Una base professionale che aiuta i visitatori a capire rapidamente cosa fate e come contattarvi.

", + ), + ], + "outcomes": [ + ( + "shield-check", + "Base online chiara", + "

Nessun elemento superfluo, solo un sito che trasmette fiducia.

", + ), + ( + "people", + "Gestione semplice", + "

Il team può aggiornare il sito senza dipendere sempre da uno sviluppatore.

", + ), + ( + "rocket", + "Pubblicazione rapida", + "

Adatto come primo sito professionale o come sostituzione di un sito datato.

", + ), + ], + "choose": [ + "Volete andare online in tempi rapidi con un’immagine professionale.", + "Avete soprattutto bisogno di pagine essenziali e navigazione chiara.", + "Volete poter aggiornare testi e immagini internamente.", + ], + "duration": "Tempistiche medie: da 2 a 4 settimane", + }, + "business": { + "title": "Sito business", + "audience": "Per aziende di servizi e team che devono presentare chiaramente più offerte, casi studio o percorsi di conversione.", + "what": [ + ( + "briefcase", + "Per chi è?", + "

Per organizzazioni che hanno bisogno di più struttura, più profondità e un percorso verso il contatto più forte.

", + ), + ( + "layout-text-window", + "Cosa riceve", + "

Più struttura di pagina, spazio per i casi studio e una base favorevole alla SEO che può crescere in modo logico.

", + ), + ( + "graph-up-arrow", + "Cosa porta", + "

Un sito che spiega meglio l’offerta e accompagna i visitatori verso il contatto in modo più diretto.

", + ), + ], + "outcomes": [ + ( + "diagram-3", + "Più chiarezza", + "

Servizi, casi studio ed expertise hanno ciascuno il proprio spazio logico.

", + ), + ( + "search", + "Maggiore visibilità", + "

La struttura è pronta per contenuti migliori e una base SEO-friendly.

", + ), + ( + "people", + "Richieste più solide", + "

I visitatori capiscono più rapidamente quale offerta e quale percorso fanno per loro.

", + ), + ], + "choose": [ + "Avete più servizi o più pubblici.", + "Volete mostrare meglio casi, competenze e prove.", + "Vi serve più struttura di quella offerta da un sito starter.", + ], + "duration": "Tempistiche medie: da 2 a 4 settimane", + }, + "webshop": { + "title": "Implementazione webshop", + "audience": "Per organizzazioni che vogliono aggiungere la vendita online senza finire in un negozio generico.", + "what": [ + ( + "cart-check", + "Per chi è?", + "

Per aziende che vogliono vendere online mantenendo il controllo su presentazione, checkout e gestione.

", + ), + ( + "credit-card", + "Cosa riceve", + "

Una struttura e-commerce con catalogo, checkout e base scalabile per la gestione ordini.

", + ), + ( + "graph-up-arrow", + "Cosa porta", + "

Un ambiente di vendita coerente con il vostro marchio e non con l’effetto di uno shop generico.

", + ), + ], + "outcomes": [ + ( + "window", + "Presentazione più forte", + "

Prodotti e categorie sono organizzati in una struttura chiara e credibile.

", + ), + ( + "shield-check", + "Tecnologia stabile", + "

Pagamenti e ordini poggiano su una base tecnica facile da mantenere.

", + ), + ( + "rocket", + "Pronto a crescere", + "

La struttura commerciale può evolvere insieme al catalogo e ai processi.

", + ), + ], + "choose": [ + "Volete unire vendita online e sito aziendale.", + "Avete bisogno di controllo su struttura e tecnologia.", + "Non state cercando un negozio basato su un tema generico.", + ], + "duration": "Tempistiche medie: da 3 a 6 settimane", + }, + "support": { + "title": "Manutenzione e crescita", + "audience": "Per team che vogliono mantenere stabile il sito o lo shop e migliorarlo in modo continuo.", + "what": [ + ( + "tools", + "Per chi è?", + "

Per organizzazioni che non vogliono gestire da sole monitoraggio, guasti e pianificazione tecnica.

", + ), + ( + "shield-check", + "Cosa riceve", + "

Manutenzione, aggiornamenti, monitoraggio e interventi tecnici con un ritmo di lavoro chiaro.

", + ), + ( + "graph-up-arrow", + "Cosa porta", + "

Più tranquillità, meno sorprese tecniche e più spazio per miglioramenti utili.

", + ), + ], + "outcomes": [ + ( + "activity", + "Meno interruzioni", + "

I problemi tecnici vengono individuati e risolti più rapidamente.

", + ), + ( + "clipboard-data", + "Miglioramento continuo", + "

Lavoriamo passo dopo passo su prestazioni, contenuti e conversione.

", + ), + ( + "people", + "Ritmo stabile", + "

Sapete quando avviene la manutenzione e quali sono le priorità.

", + ), + ], + "choose": [ + "Volete un partner stabile per la manutenzione tecnica.", + "Il vostro sito ha bisogno di miglioramenti continui, non di un rifacimento completo.", + "Volete reagire più velocemente a problemi o nuove esigenze.", + ], + "duration": "Risposta entro 24 ore", + }, +} +SERVICE_COPY["pt"] = { + "starter": { + "title": "Website inicial", + "audience": "Para empresas e pequenas equipas que precisam de uma presença profissional rápida e clara.", + "what": [ + ( + "layout-text-window", + "Para quem é?", + "

Para negócios com uma oferta clara que precisam de uma primeira impressão profissional sem um projeto pesado.

", + ), + ( + "window", + "O que recebe", + "

Páginas principais, navegação clara e um editor que a equipa pode usar para gerir conteúdo.

", + ), + ( + "graph-up-arrow", + "O que entrega", + "

Uma base profissional que ajuda os visitantes a perceber rapidamente o que faz e como entrar em contacto.

", + ), + ], + "outcomes": [ + ( + "shield-check", + "Base online clara", + "

Sem elementos desnecessários, apenas um website que transmite confiança.

", + ), + ( + "people", + "Gestão simples", + "

A sua equipa pode atualizar o site sem depender sempre de um programador.

", + ), + ( + "rocket", + "Lançamento rápido", + "

Indicado como primeiro website profissional ou como substituição de um site desatualizado.

", + ), + ], + "choose": [ + "Quer uma presença profissional rapidamente.", + "Precisa sobretudo de páginas principais e navegação clara.", + "Quer atualizar textos e imagens internamente.", + ], + "duration": "Prazo médio: 2 a 4 semanas", + }, + "business": { + "title": "Site empresarial", + "audience": "Para empresas de serviços e equipas que precisam de apresentar claramente várias ofertas, casos ou percursos de conversão.", + "what": [ + ( + "briefcase", + "Para quem é?", + "

Para organizações que precisam de mais estrutura, mais profundidade e um percurso de contacto mais forte.

", + ), + ( + "layout-text-window", + "O que recebe", + "

Mais estrutura de páginas, espaço para casos e uma base favorável a SEO que pode crescer de forma lógica.

", + ), + ( + "graph-up-arrow", + "O que entrega", + "

Um website que explica melhor a sua oferta e orienta os visitantes de forma mais direta para o contacto.

", + ), + ], + "outcomes": [ + ( + "diagram-3", + "Mais clareza", + "

Serviços, casos e especialização têm cada um o seu lugar lógico.

", + ), + ( + "search", + "Maior visibilidade", + "

A estrutura está preparada para melhor conteúdo e uma base favorável a SEO.

", + ), + ( + "people", + "Pedidos mais fortes", + "

Os visitantes percebem mais depressa que oferta e que percurso lhes fazem sentido.

", + ), + ], + "choose": [ + "Tem vários serviços ou vários públicos.", + "Quer mostrar melhor casos, experiência e prova.", + "Precisa de mais estrutura do que um website inicial oferece.", + ], + "duration": "Prazo médio: 2 a 4 semanas", + }, + "webshop": { + "title": "Implementação de webshop", + "audience": "Para organizações que querem acrescentar vendas online sem cair numa loja genérica.", + "what": [ + ( + "cart-check", + "Para quem é?", + "

Para empresas que querem vender online mantendo controlo sobre apresentação, checkout e gestão.

", + ), + ( + "credit-card", + "O que recebe", + "

Uma estrutura de comércio com catálogo, checkout e base escalável para gerir encomendas.

", + ), + ( + "graph-up-arrow", + "O que entrega", + "

Um ambiente de venda alinhado com a sua marca e sem aparência genérica.

", + ), + ], + "outcomes": [ + ( + "window", + "Melhor apresentação", + "

Produtos e categorias são organizados numa estrutura clara e credível.

", + ), + ( + "shield-check", + "Tecnologia estável", + "

Pagamentos e encomendas assentam numa base técnica fácil de manter.

", + ), + ( + "rocket", + "Preparado para crescer", + "

A estrutura comercial pode evoluir com o catálogo e os processos.

", + ), + ], + "choose": [ + "Quer combinar vendas online com um website empresarial.", + "Precisa de controlo sobre estrutura e tecnologia.", + "Não procura uma loja baseada num tema genérico.", + ], + "duration": "Prazo médio: 3 a 6 semanas", + }, + "support": { + "title": "Manutenção & crescimento", + "audience": "Para equipas que querem manter o website ou a loja estáveis e melhorá-los de forma contínua.", + "what": [ + ( + "tools", + "Para quem é?", + "

Para organizações que não querem tratar sozinhas de monitorização, falhas e planeamento técnico.

", + ), + ( + "shield-check", + "O que recebe", + "

Manutenção, atualizações, monitorização e soluções técnicas com um ritmo de trabalho claro.

", + ), + ( + "graph-up-arrow", + "O que entrega", + "

Mais tranquilidade, menos surpresas técnicas e mais espaço para melhorias úteis.

", + ), + ], + "outcomes": [ + ( + "activity", + "Menos interrupções", + "

Os problemas técnicos são detetados e resolvidos mais rapidamente.

", + ), + ( + "clipboard-data", + "Melhoria contínua", + "

Trabalhamos passo a passo em desempenho, conteúdo e conversão.

", + ), + ( + "people", + "Ritmo estável", + "

Sabe quando a manutenção acontece e o que tem prioridade.

", + ), + ], + "choose": [ + "Quer um parceiro estável para manutenção técnica.", + "O seu website precisa de melhorias contínuas e não de uma reconstrução completa.", + "Quer reagir mais depressa a problemas ou novas necessidades.", + ], + "duration": "Resposta em 24 horas", + }, +} +SERVICE_COPY["ru"] = { + "starter": { + "title": "Стартовый сайт", + "audience": "Для компаний и небольших команд, которым нужен быстрый и профессиональный онлайн-старт.", + "what": [ + ( + "layout-text-window", + "Для кого это?", + "

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

", + ), + ( + "window", + "Что входит", + "

Основные страницы, понятная навигация и редактор, которым ваша команда сможет пользоваться сама.

", + ), + ( + "graph-up-arrow", + "Что это даёт", + "

Профессиональную основу, которая помогает посетителям быстро понять ваше предложение и способ связи.

", + ), + ], + "outcomes": [ + ( + "shield-check", + "Понятная онлайн-база", + "

Без лишних элементов, только сайт, который вызывает доверие.

", + ), + ( + "people", + "Простое управление", + "

Ваша команда сможет вносить изменения без постоянной зависимости от разработчика.

", + ), + ( + "rocket", + "Быстрый запуск", + "

Подходит как первый профессиональный сайт или как замена устаревшего решения.

", + ), + ], + "choose": [ + "Вам нужно быстро выглядеть профессионально онлайн.", + "Вам нужны прежде всего основные страницы и понятная навигация.", + "Вы хотите самостоятельно обновлять тексты и изображения.", + ], + "duration": "Средний срок: 2–4 недели", + }, + "business": { + "title": "Бизнес-сайт", + "audience": "Для сервисных компаний и команд, которым нужно ясно представить несколько направлений, кейсов или конверсионных путей.", + "what": [ + ( + "briefcase", + "Для кого это?", + "

Для организаций, которым нужна более сильная структура, большая глубина и более уверенный путь к заявке.

", + ), + ( + "layout-text-window", + "Что входит", + "

Более развитая структура страниц, место для кейсов и SEO-дружественная база, которая может логично расти.

", + ), + ( + "graph-up-arrow", + "Что это даёт", + "

Сайт, который лучше объясняет ваше предложение и ведёт посетителей к контакту более прямым путём.

", + ), + ], + "outcomes": [ + ( + "diagram-3", + "Больше ясности", + "

Услуги, кейсы и экспертиза получают свои понятные места.

", + ), + ( + "search", + "Лучшая видимость", + "

Структура подготовлена для сильного контента и SEO-дружественной базы.

", + ), + ( + "people", + "Более сильные заявки", + "

Посетители быстрее понимают, какое предложение и какой путь подходят именно им.

", + ), + ], + "choose": [ + "У вас несколько услуг или аудиторий.", + "Вы хотите лучше показать кейсы, экспертизу и подтверждение качества.", + "Вам нужна более развитая структура, чем у стартового сайта.", + ], + "duration": "Средний срок: 2–4 недели", + }, + "webshop": { + "title": "Внедрение вебшопа", + "audience": "Для компаний, которые хотят добавить онлайн-продажи, не превращая проект в типовой магазин.", + "what": [ + ( + "cart-check", + "Для кого это?", + "

Для бизнеса, который хочет продавать онлайн и при этом контролировать подачу, checkout и управление.

", + ), + ( + "credit-card", + "Что входит", + "

Коммерческая структура с каталогом, checkout и масштабируемой базой для обработки заказов.

", + ), + ( + "graph-up-arrow", + "Что это даёт", + "

Среду продаж, соответствующую вашему бренду, а не похожую на демо-шаблон.

", + ), + ], + "outcomes": [ + ( + "window", + "Более сильная подача", + "

Товары и категории получают понятную и убедительную структуру.

", + ), + ( + "shield-check", + "Стабильная технология", + "

Платежи и заказы опираются на поддерживаемую техническую базу.

", + ), + ( + "rocket", + "Готовность к росту", + "

Коммерческая часть может развиваться вместе с ассортиментом и процессами.

", + ), + ], + "choose": [ + "Вы хотите объединить онлайн-продажи и корпоративный сайт.", + "Вам нужен контроль над структурой и технологией.", + "Вы не ищете магазин на типовом шаблоне.", + ], + "duration": "Средний срок: 3–6 недель", + }, + "support": { + "title": "Поддержка и рост", + "audience": "Для команд, которые хотят держать сайт или магазин в стабильном состоянии и улучшать его постепенно.", + "what": [ + ( + "tools", + "Для кого это?", + "

Для организаций, которые не хотят самостоятельно заниматься мониторингом, сбоями и техническим планированием.

", + ), + ( + "shield-check", + "Что входит", + "

Обслуживание, обновления, мониторинг и технические решения в понятном рабочем ритме.

", + ), + ( + "graph-up-arrow", + "Что это даёт", + "

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

", + ), + ], + "outcomes": [ + ( + "activity", + "Меньше сбоев", + "

Технические проблемы замечаются и решаются быстрее.

", + ), + ( + "clipboard-data", + "Постоянное улучшение", + "

Мы шаг за шагом улучшаем производительность, контент и конверсию.

", + ), + ( + "people", + "Стабильный ритм", + "

Вы понимаете, когда проводится обслуживание и что находится в приоритете.

", + ), + ], + "choose": [ + "Вам нужен постоянный партнёр по технической поддержке.", + "Вашему сайту нужны постоянные улучшения, а не полная переделка.", + "Вы хотите быстрее реагировать на проблемы и новые задачи.", + ], + "duration": "Ответ в течение 24 часов", + }, +} diff --git a/mandelstudio/management/commands/apply_agency_website_refresh.py b/mandelstudio/management/commands/apply_agency_website_refresh.py new file mode 100644 index 0000000..c348929 --- /dev/null +++ b/mandelstudio/management/commands/apply_agency_website_refresh.py @@ -0,0 +1,1385 @@ +from __future__ import annotations + +import copy +import uuid +from typing import Any + +from django.core.management.base import BaseCommand +from django.db import transaction + +from wagtail.blocks import StreamValue +from wagtail.models import Locale, Page + +from mandelstudio.management.commands._agency_content import ( + COMMON_CTA, + CTA_VARIANTS, + FOOTER_CONTENT, + NL_REPLACEMENTS, +) +from mandelstudio.management.commands._agency_content import ( + body_for as localized_body_for, +) +from mandelstudio.management.commands._agency_content import ( + footer_stream_data as localized_footer_stream_data, +) +from mandelstudio.models import LocalizedFooterContent + +SOURCE_PAGE_IDS = { + "home": 127, + "about": 128, + "services": 129, + "projects": 130, + "contact": 131, + "process": 192, + "starter": 200, + "business": 201, + "webshop": 202, + "support": 203, + "ai_search": 199, +} + +PAGE_TITLE_MAP = { + "about": { + "nl": "Over ons", + "en": "About us", + "de": "Über uns", + "fr": "À propos", + "es": "Sobre nosotros", + "it": "Chi siamo", + "pt": "Sobre nós", + "ru": "О нас", + }, + "services": { + "nl": "Diensten", + "en": "Services", + "de": "Dienstleistungen", + "fr": "Services", + "es": "Servicios", + "it": "Servizi", + "pt": "Serviços", + "ru": "Услуги", + }, + "projects": { + "nl": "Projecten", + "en": "Projects", + "de": "Projekte", + "fr": "Projets", + "es": "Proyectos", + "it": "Progetti", + "pt": "Projetos", + "ru": "Проекты", + }, + "contact": { + "nl": "Contact", + "en": "Contact", + "de": "Kontakt", + "fr": "Contact", + "es": "Contacto", + "it": "Contatto", + "pt": "Contacto", + "ru": "Контакт", + }, + "process": { + "nl": "Werkwijze", + "en": "How we work", + "de": "Vorgehensweise", + "fr": "Méthode de travail", + "es": "Método de trabajo", + "it": "Metodo di lavoro", + "pt": "Método de trabalho", + "ru": "Как мы работаем", + }, + "starter": { + "nl": "Starter-website", + "en": "Starter website", + "de": "Starter-Website", + "fr": "Site de démarrage", + "es": "Sitio web inicial", + "it": "Sito starter", + "pt": "Website inicial", + "ru": "Стартовый сайт", + }, + "business": { + "nl": "Zakelijke website", + "en": "Business website", + "de": "Geschäftswebsite", + "fr": "Site d’entreprise", + "es": "Sitio web empresarial", + "it": "Sito business", + "pt": "Site empresarial", + "ru": "Бизнес-сайт", + }, + "webshop": { + "nl": "Webshop-implementatie", + "en": "Webshop implementation", + "de": "Webshop-Implementierung", + "fr": "Implémentation e-commerce", + "es": "Implementación webshop", + "it": "Implementazione webshop", + "pt": "Implementação de webshop", + "ru": "Внедрение вебшопа", + }, + "support": { + "nl": "Onderhoud & groei", + "en": "Maintenance & growth", + "de": "Wartung & Wachstum", + "fr": "Maintenance & croissance", + "es": "Mantenimiento y crecimiento", + "it": "Manutenzione e crescita", + "pt": "Manutenção & crescimento", + "ru": "Поддержка и рост", + }, + "ai_search": { + "nl": "AI-zoekfunctie", + "en": "AI search", + "de": "KI-Suche", + "fr": "Recherche IA", + "es": "Búsqueda con IA", + "it": "Ricerca IA", + "pt": "Pesquisa com IA", + "ru": "Поиск с ИИ", + }, +} + + +def uid() -> str: + return str(uuid.uuid4()) + + +def block(block_type: str, value: dict[str, Any]) -> dict[str, Any]: + return {"type": block_type, "value": value, "id": uid()} + + +def item(value: dict[str, Any] | str) -> dict[str, Any]: + return {"type": "item", "value": value, "id": uid()} + + +def replace_nested(node: Any, replacements: dict[str, str]) -> Any: + if isinstance(node, dict): + return {key: replace_nested(value, replacements) for key, value in node.items()} + if isinstance(node, list): + return [replace_nested(value, replacements) for value in node] + if isinstance(node, str): + for source, target in replacements.items(): + node = node.replace(source, target) + return node + return node + + +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]]]: + cfg = FOOTER_CONTENT[locale] + footer = [ + block("about_us", {"heading": "MandelBlog Studio", "content": cfg["about"]}), + block( + "text", + { + "heading": cfg["links_heading"], + "content": ( + f'

{PAGE_TITLE_MAP["about"][locale]}
' + f'{PAGE_TITLE_MAP["services"][locale]}
' + f'{PAGE_TITLE_MAP["projects"][locale]}
' + f'{PAGE_TITLE_MAP["contact"][locale]}

' + ), + }, + ), + block("text", {"heading": cfg["support_heading"], "content": cfg["support"]}), + ] + mini = [block("text", cfg["mini"])] + return footer, mini + + +def nl_home(urls: dict[str, str]) -> list[dict[str, Any]]: + primary = COMMON_CTA["nl"]["primary"] + secondary = COMMON_CTA["nl"]["secondary"] + return [ + block( + "saas_hero_banner", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "badge_text": "MANDELBLOG STUDIO", + "badge_url": urls["home"], + "headline": "Websites voor bedrijven die professioneel willen groeien", + "sub_headline": "

MandelBlog ontwikkelt websites die vertrouwen opbouwen, duidelijk sturen op contact en eenvoudig te beheren zijn voor uw team.

", + "primary_cta_text": primary, + "primary_cta_url": urls["contact"], + "secondary_cta_text": secondary, + "secondary_cta_url": urls["services"], + "hero_image": 1, + "video_url": "", + "stats": [ + item({"value": "3", "label": "Heldere stappen"}), + item({"value": "1", "label": "Vast aanspreekpunt"}), + item({"value": "8", "label": "Beschikbare talen"}), + ], + "customer_logos_title": "Gebouwd met Wagtail, Django en beproefde componenten", + }, + ), + block( + "saas_features", + { + "layout_width": "container", + "background_style": "light", + "layout": "grid", + "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", + } + ), + ], + "columns": "2", + }, + ), + block( + "saas_pricing", + { + "layout_width": "container", + "background_style": "light", + "layout": "cards", + "section_title": "Onze pakketten", + "section_subtitle": "

Elk pakket heeft een duidelijke scope. De exacte invulling stemmen we af in het kennismakingsgesprek.

", + "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": "", + } + ), + ], + "footer_text": "

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

", + }, + ), + block( + "saas_testimonials", + { + "layout_width": "container", + "background_style": "light", + "layout": "cards", + "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, + } + ), + ], + "customer_logos": [], + "aggregate_rating": "", + "aggregate_source": "", + }, + ), + block( + "saas_faq", + { + "layout_width": "container", + "background_style": "light", + "layout": "accordion", + "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", + } + ), + ], + "show_contact_cta": "card", + "contact_cta_text": primary, + "contact_cta_url": urls["contact"], + }, + ), + block( + "saas_cta_footer", + { + "layout_width": "container", + "background_style": "light", + "layout": "banner", + "headline": "Wilt u een website die vertrouwen geeft en werk uit handen neemt?", + "subheadline": "

Plan een kennismakingsgesprek en we laten zien welke opzet past bij uw bedrijf en team.

", + "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_standard_page(page_key: str, urls: dict[str, str]) -> list[dict[str, Any]]: + primary = COMMON_CTA["nl"]["primary"] + secondary = COMMON_CTA["nl"]["secondary"] + page_data = { + "about": { + "headline": "Wie MandelBlog is en hoe we werken", + "sub": "

MandelBlog helpt kleine bedrijven en dienstverleners aan een website die professioneel oogt, logisch converteert en beheerbaar blijft voor het eigen team.

", + "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.

", + ), + ], + "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": { + "headline": "Diensten voor bedrijven die overzicht en kwaliteit willen", + "sub": "

Elke dienst is ingericht rondom duidelijke keuzes, bruikbare content en een technische basis die door kan groeien.

", + "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.

", + ), + ], + "extra_block": None, + "cta": "Twijfelt u welk pakket past bij uw fase?", + }, + "projects": { + "headline": "Projecten waarin structuur, inhoud en techniek samenkomen", + "sub": "

Onze projecten zijn ontworpen om professioneel over te komen, vertrouwen op te bouwen en beheerbaar te blijven na livegang.

", + "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.

", + ), + ], + "extra_block": None, + "cta": "Wilt u uw volgende project professioneel neerzetten?", + }, + "contact": { + "headline": "Laten we uw volgende stap helder maken", + "sub": "

Deel kort waar u hulp bij nodig heeft. U krijgt een helder antwoord en een concreet voorstel voor de volgende stap.

", + "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.

", + ), + ], + "extra_block": block( + "saas_demo_request", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "section_title": "Start uw volgende project met helderheid", + "section_subtitle": "

We helpen u ontwerpen, bouwen en opschalen - snel, gestructureerd en zonder giswerk.

", + "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": "Plan mijn strategiegesprek", + "form_action_url": urls["contact"], + "benefits_title": "Waarom dit gesprek werkt", + "benefits": [ + item("Reactie binnen 24 uur"), + item("Vrijblijvend strategiegesprek"), + item("Praktische vervolgstappen"), + ], + "side_image": 1, + "privacy_text": "

We gebruiken uw gegevens alleen voor contact over deze aanvraag. Geen verplichtingen.

", + }, + ), + "cta": "Klaar om een eerste stap te zetten?", + }, + "process": { + "headline": "Werkwijze met duidelijke stappen en vaste keuzes", + "sub": "

We houden het traject overzichtelijk: u weet wanneer iets gebeurt, wat u moet aanleveren en waar we naartoe werken.

", + "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.

", + ), + ], + "extra_block": None, + "cta": "Wilt u dit traject ook voor uw website?", + }, + } + 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", + }, + ), + ] + 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", + }, + ) + ) + return blocks + + +def nl_service_page(kind: str, urls: dict[str, str]) -> list[dict[str, Any]]: + primary = COMMON_CTA["nl"]["primary"] + secondary = COMMON_CTA["nl"]["secondary"] + config = { + "starter": { + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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.

", + ), + ], + "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", + }, + ), + ] + + +def nl_body_for(page_key: str, urls: dict[str, str]) -> list[dict[str, Any]]: + if page_key == "home": + return nl_home(urls) + if page_key in {"about", "services", "projects", "contact", "process"}: + return nl_standard_page(page_key, urls) + if page_key in {"starter", "business", "webshop", "support"}: + return nl_service_page(page_key, urls) + raise KeyError(page_key) + + +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" + ) + + def handle(self, *args, **options): + apply_changes = options["apply"] + with transaction.atomic(): + 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() + ) + 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: + 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] + ): + page.title = title_map[language_code] + changed = True + + if key == "ai_search": + if changed and apply_changes: + rev = page.save_revision() + rev.publish() + continue + + if hasattr(page, "body"): + raw_data = list(page.body.raw_data) + if language_code in COMMON_CTA and key in { + "home", + "about", + "services", + "projects", + "contact", + "process", + "starter", + "business", + "webshop", + "support", + }: + page.body = StreamValue( + page.body.stream_block, + localized_body_for( + language_code, key, urls, PAGE_TITLE_MAP + ), + is_lazy=True, + ) + changed = True + else: + replacements = {} + 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", []): + 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 + ) + if updated_raw_data != raw_data: + 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}" + ) + if apply_changes: + rev = page.save_revision() + rev.publish() + + footer_cfg = FOOTER_CONTENT.get(language_code) + 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", "/"), + } + footer_data, mini_data = localized_footer_stream_data( + language_code, link_urls, PAGE_TITLE_MAP + ) + 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.") + ) + else: + self.stdout.write(self.style.SUCCESS("Agency website refresh applied.")) diff --git a/mandelstudio/management/commands/prepare_idea_marketplace_launch.py b/mandelstudio/management/commands/prepare_idea_marketplace_launch.py new file mode 100644 index 0000000..9d68a2a --- /dev/null +++ b/mandelstudio/management/commands/prepare_idea_marketplace_launch.py @@ -0,0 +1,226 @@ +from __future__ import annotations + +from django.core.management.base import BaseCommand +from django.db import transaction +from django.db.models import Q +from wagtail.blocks import StreamValue +from wagtail.models import Page + +from mandelstudio.content_hygiene import BLOCKED_DEMO_PAGE_SLUGS, DEMO_MARKERS +from mandelstudio.idea_marketplace import seed_idea_marketplace_products + + +HOME_COPY = { + "nl": { + "badge": "IDEA MARKETPLACE", + "headline": "Premium ideeën die je direct kunt uitvoeren", + "sub_headline": "

Ontdek bewezen plannen, koop de strategie en ontgrendel het volledige implementatieplan.

", + "features_title": "Idea Marketplace", + "features_subtitle": "

Preview eerst. Koop alleen wat past. Ontgrendel daarna de complete blueprint.

", + "footer_headline": "Klaar om een premium idee te ontgrendelen?", + "footer_subheadline": "

Kies een plan, rond checkout af en krijg direct toegang tot de volledige strategie.

", + "cta_explore": "Explore Ideas", + "cta_buy": "Buy Strategy", + "cta_unlock": "Unlock Full Plan", + }, + "en": { + "badge": "IDEA MARKETPLACE", + "headline": "Premium ideas you can execute immediately", + "sub_headline": "

Explore proven plans, buy the strategy, and unlock the full implementation blueprint.

", + "features_title": "Idea Marketplace", + "features_subtitle": "

Preview first. Buy what fits. Unlock complete execution plans after checkout.

", + "footer_headline": "Ready to unlock a premium idea?", + "footer_subheadline": "

Select a plan, complete checkout, and get full strategy access instantly.

", + "cta_explore": "Explore Ideas", + "cta_buy": "Buy Strategy", + "cta_unlock": "Unlock Full Plan", + }, +} + +SUPPORTED_LANGUAGES = {"nl", "en", "de", "fr", "es", "it", "pt", "ru"} + + +def _copy_for(language_code: str) -> dict[str, str]: + normalized = (language_code or "nl").split("-")[0].lower() + if normalized not in SUPPORTED_LANGUAGES: + normalized = "nl" + return HOME_COPY["en"] if normalized != "nl" else HOME_COPY["nl"] + + +def _shop_url_for(language_code: str) -> str: + normalized = (language_code or "nl").split("-")[0].lower() + if normalized == "nl": + return "/shop/" + return f"/{normalized}/shop/" + + +def _update_homepage_stream(page) -> bool: + if not hasattr(page, "body"): + return False + body = page.body + if not body: + return False + + copy = _copy_for(getattr(page.locale, "language_code", "nl")) + shop_url = _shop_url_for(getattr(page.locale, "language_code", "nl")) + + stream_data = list(body.stream_data) + changed = False + for block in stream_data: + block_type = block.get("type") + value = block.get("value", {}) + if not isinstance(value, dict): + continue + + if block_type == "saas_hero_banner": + updates = { + "badge_text": copy["badge"], + "headline": copy["headline"], + "sub_headline": copy["sub_headline"], + "primary_cta_text": copy["cta_explore"], + "primary_cta_url": shop_url, + "secondary_cta_text": copy["cta_buy"], + "secondary_cta_url": shop_url, + } + for key, new_value in updates.items(): + if value.get(key) != new_value: + value[key] = new_value + changed = True + + if block_type == "saas_features": + updates = { + "section_title": copy["features_title"], + "section_subtitle": copy["features_subtitle"], + } + for key, new_value in updates.items(): + if value.get(key) != new_value: + value[key] = new_value + changed = True + + if block_type == "saas_cta_footer": + updates = { + "headline": copy["footer_headline"], + "subheadline": copy["footer_subheadline"], + "primary_cta_text": copy["cta_unlock"], + "primary_cta_url": shop_url, + "secondary_cta_text": copy["cta_explore"], + "secondary_cta_url": shop_url, + } + for key, new_value in updates.items(): + if value.get(key) != new_value: + value[key] = new_value + changed = True + + if not changed: + return False + + page.body = StreamValue(page.body.stream_block, stream_data, is_lazy=True) + page.search_description = ( + "Idea marketplace with premium plans. Preview each strategy and unlock full implementation after purchase." + ) + page.save() + return True + + +def _purge_demo_pages() -> int: + marker_filter = Q() + for marker in DEMO_MARKERS: + marker_filter |= ( + Q(title__icontains=marker) + | Q(slug__icontains=marker) + | Q(search_description__icontains=marker) + ) + candidate_ids = set( + Page.objects.exclude(depth__lte=2).filter(marker_filter).values_list("id", flat=True) + ) + candidate_ids.update( + Page.objects.exclude(depth__lte=2) + .filter(slug__in=BLOCKED_DEMO_PAGE_SLUGS) + .values_list("id", flat=True) + ) + candidates = Page.objects.filter(id__in=candidate_ids).specific() + deleted = 0 + for page in candidates: + page.delete() + deleted += 1 + return deleted + + +def _update_homepages() -> int: + updated = 0 + # In this architecture localized homepages are expected at depth=2. + for page in Page.objects.filter(depth=2).specific(): + if _update_homepage_stream(page): + updated += 1 + return updated + + +class Command(BaseCommand): + help = ( + "Prepare production idea marketplace launch: seed idea products, purge obvious demo pages, " + "and refresh homepage sections/CTAs to marketplace messaging." + ) + + def add_arguments(self, parser): + parser.add_argument( + "--no-seed", + action="store_true", + help="Skip idea product seeding.", + ) + parser.add_argument( + "--purge-demo-pages", + action="store_true", + help="Delete pages with obvious demo/lorem/sample markers.", + ) + parser.add_argument( + "--skip-purge-demo-pages", + action="store_true", + help="Skip deleting obvious demo pages (enabled by default).", + ) + parser.add_argument( + "--apply-homepage-copy", + action="store_true", + help="Update homepage stream blocks to idea marketplace messaging and CTAs.", + ) + parser.add_argument( + "--skip-apply-homepage-copy", + action="store_true", + help="Skip homepage marketplace copy refresh (enabled by default).", + ) + + @transaction.atomic + def handle(self, *args, **options): + if not options["no_seed"]: + seed_stats = seed_idea_marketplace_products( + purge_demo_products=True, + retire_non_idea_products=True, + ) + self.stdout.write( + self.style.SUCCESS( + "Seeded idea products: " + f"created={seed_stats['created']}, " + f"updated={seed_stats['updated']}, " + f"deleted_demo_products={seed_stats['deleted_demo']}, " + f"retired_non_idea_products={seed_stats['retired_non_idea']}" + ) + ) + + should_purge_demo_pages = ( + options["purge_demo_pages"] or not options["skip_purge_demo_pages"] + ) + if should_purge_demo_pages: + deleted_pages = _purge_demo_pages() + self.stdout.write( + self.style.SUCCESS(f"Deleted demo pages: {deleted_pages}") + ) + + should_apply_homepage_copy = ( + options["apply_homepage_copy"] or not options["skip_apply_homepage_copy"] + ) + if should_apply_homepage_copy: + updated_pages = _update_homepages() + self.stdout.write( + self.style.SUCCESS( + f"Updated homepages with marketplace copy: {updated_pages}" + ) + ) diff --git a/mandelstudio/management/commands/purge_demo_data.py b/mandelstudio/management/commands/purge_demo_data.py new file mode 100644 index 0000000..2a47b0e --- /dev/null +++ b/mandelstudio/management/commands/purge_demo_data.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from typing import Iterable + +from django.core.management.base import BaseCommand +from django.db.models import Q +from oscar.core.loading import get_model +from wagtail.models import Page + + +IDEA_PRODUCT_TITLES = { + "B2B Webshop Starter Blueprint", + "AI Product Description System", + "High-Converting Landing Page Framework", + "Subscription-Based Service Website Model", + "Marketplace Platform Architecture (Django)", +} + +DEMO_PAGE_SLUGS = { + "starter-website-2", + "business-website-2", + "starter-website", + "business-website", +} + +DEMO_MARKERS = ( + "demo", + "dummy", + "sample", + "placeholder", + "starter website", + "business website", + "lorem ipsum", +) + + +def _build_demo_text_filter(fields: Iterable[str]) -> Q: + query = Q() + for field in fields: + for marker in DEMO_MARKERS: + query |= Q(**{f"{field}__icontains": marker}) + return query + + +class Command(BaseCommand): + help = ( + "Remove demo content from Wagtail pages and Oscar catalogue. " + "Use --keep-only-idea-products to retain only the five launch idea products." + ) + + def add_arguments(self, parser): + parser.add_argument( + "--dry-run", + action="store_true", + default=False, + help="Show what would be deleted without applying changes.", + ) + parser.add_argument( + "--keep-only-idea-products", + action="store_true", + default=False, + help="Delete every top-level product except the five launch idea products.", + ) + + def handle(self, *args, **options): + dry_run: bool = options["dry_run"] + keep_only_ideas: bool = options["keep_only_idea_products"] + + Product = get_model("catalogue", "Product") + + product_filter = _build_demo_text_filter(("title",)) + product_filter |= Q(slug__in=DEMO_PAGE_SLUGS) + + top_level_products = Product.objects.filter(parent__isnull=True) + if keep_only_ideas: + products_to_delete = top_level_products.exclude(title__in=IDEA_PRODUCT_TITLES) + else: + products_to_delete = top_level_products.filter(product_filter).exclude( + title__in=IDEA_PRODUCT_TITLES + ) + + pages_to_delete = ( + Page.objects.live() + .public() + .filter(depth__gt=2) + .filter(Q(slug__in=DEMO_PAGE_SLUGS) | _build_demo_text_filter(("title", "slug"))) + ) + + product_preview = list(products_to_delete.values_list("id", "title")[:30]) + page_preview = list(pages_to_delete.values_list("id", "slug", "title")[:30]) + + self.stdout.write(f"Products matched for deletion: {products_to_delete.count()}") + for item in product_preview: + self.stdout.write(f" - product#{item[0]}: {item[1]}") + if products_to_delete.count() > len(product_preview): + self.stdout.write(" - ...") + + self.stdout.write(f"Pages matched for deletion: {pages_to_delete.count()}") + for item in page_preview: + self.stdout.write(f" - page#{item[0]}: /{item[1]}/ ({item[2]})") + if pages_to_delete.count() > len(page_preview): + self.stdout.write(" - ...") + + if dry_run: + self.stdout.write(self.style.WARNING("Dry run completed. No data was deleted.")) + return + + deleted_products = products_to_delete.count() + deleted_pages = pages_to_delete.count() + + products_to_delete.delete() + for page in pages_to_delete: + # Use Wagtail's delete to remove descendants and revisions safely. + page.delete() + + self.stdout.write( + self.style.SUCCESS( + f"Demo purge complete. Deleted products={deleted_products}, pages={deleted_pages}." + ) + ) diff --git a/mandelstudio/management/commands/seed_idea_marketplace.py b/mandelstudio/management/commands/seed_idea_marketplace.py new file mode 100644 index 0000000..53ca0e9 --- /dev/null +++ b/mandelstudio/management/commands/seed_idea_marketplace.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from django.core.management.base import BaseCommand +from django.db import transaction + +from mandelstudio.idea_marketplace import seed_idea_marketplace_products + + +class Command(BaseCommand): + help = ( + "Seed production-ready Oscar idea products and remove obvious demo products " + "from the catalogue. By default, this also retires non-idea products from public " + "listing." + ) + + def add_arguments(self, parser): + parser.add_argument( + "--keep-demo-products", + action="store_true", + help="Do not delete demo/sample products.", + ) + parser.add_argument( + "--keep-non-idea-products-public", + action="store_true", + help="Do not retire non-idea products from public listing.", + ) + + @transaction.atomic + def handle(self, *args, **options): + purge_demo = not options["keep_demo_products"] + retire_non_idea = not options["keep_non_idea_products_public"] + stats = seed_idea_marketplace_products( + purge_demo_products=purge_demo, + retire_non_idea_products=retire_non_idea, + ) + self.stdout.write( + self.style.SUCCESS( + "Idea marketplace seeded: " + f"created={stats['created']}, " + f"updated={stats['updated']}, " + f"deleted_demo={stats['deleted_demo']}, " + f"retired_non_idea={stats['retired_non_idea']}" + ) + ) diff --git a/mandelstudio/management/commands/validate_idea_marketplace_launch.py b/mandelstudio/management/commands/validate_idea_marketplace_launch.py new file mode 100644 index 0000000..20c38af --- /dev/null +++ b/mandelstudio/management/commands/validate_idea_marketplace_launch.py @@ -0,0 +1,232 @@ +from __future__ import annotations + +import json +import os +from pathlib import Path + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.db.models import Q + +from oscar.core.loading import get_model + +from mandelstudio.content_hygiene import BLOCKED_DEMO_PAGE_SLUGS, DEMO_MARKERS +from mandelstudio.idea_marketplace import ( + FULL_DESCRIPTION_ATTRIBUTE_CODE, + IDEA_PRODUCT_CLASS_NAME, + IDEA_PRODUCTS, + SHORT_DESCRIPTION_ATTRIBUTE_CODE, +) + + +class Command(BaseCommand): + help = ( + "Fail-fast launch validation for idea marketplace: payment provider, " + "catalog integrity, digital/non-shipping behavior, and EUR pricing." + ) + + def handle(self, *args, **options): + Product = get_model("catalogue", "Product") + ProductClass = get_model("catalogue", "ProductClass") + ProductAttribute = get_model("catalogue", "ProductAttribute") + StockRecord = get_model("partner", "StockRecord") + Page = get_model("wagtailcore", "Page") + + installed_apps = list(settings.INSTALLED_APPS) + payment_apps = [app for app in installed_apps if "payment" in app.lower()] + checkout_apps = [app for app in installed_apps if "checkout" in app.lower()] + if not payment_apps: + raise CommandError("No payment app found in INSTALLED_APPS.") + if not checkout_apps: + raise CommandError("No checkout app found in INSTALLED_APPS.") + if not any("oscar_checkout" in app.lower() for app in checkout_apps): + raise CommandError("Oscar checkout app is not active.") + def _is_demo_data(value: str) -> bool: + normalized = "".join(ch for ch in str(value).lower() if ch.isalnum()) + return "demodata" in normalized + + if any(_is_demo_data(app) for app in installed_apps): + raise CommandError( + "Demo data plugin detected in INSTALLED_APPS. Remove all demodata plugins before launch." + ) + + if any("dummy" in app.lower() for app in payment_apps): + raise CommandError( + "Dummy payment app detected in INSTALLED_APPS. Use a real provider plugin before production launch." + ) + if any("mollie" in app.lower() for app in payment_apps): + mollie_settings = ( + getattr(settings, "PAYMENT_MOLLIE", None) + or getattr(settings, "payment_mollie", None) + or {} + ) + config_key = str(mollie_settings.get("api_key", "")).strip() + env_key = str(os.environ.get("MOLLIE_API_KEY", "")).strip() + effective_key = env_key or config_key + if not effective_key or effective_key.upper() == "CHANGE_ME": + raise CommandError( + "Mollie payment provider is enabled but no valid API key is configured. " + "Set MOLLIE_API_KEY or settings.payment_mollie.api_key to a real key." + ) + if not effective_key.startswith("live_"): + raise CommandError( + "Mollie key must be a live key for production launch (expected prefix 'live_')." + ) + + config_path = Path(__file__).resolve().parents[2] / "ocyan.json" + if config_path.exists(): + with config_path.open("r", encoding="utf-8") as handle: + config_payload = json.load(handle) + config_plugins = [str(plugin) for plugin in config_payload.get("ocyan_plugins", [])] + if any(_is_demo_data(plugin) for plugin in config_plugins): + raise CommandError( + "Demo data plugin detected in ocyan.json. Remove it before launch." + ) + settings_payload = config_payload.get("settings", {}) + domain = str(settings_payload.get("django", {}).get("domain", "")).strip() + shop_base_url = str( + settings_payload.get("oscar", {}).get("shop_base_url", "") + ).strip("/") + if not domain or domain.upper() == "CHANGE_ME": + raise CommandError( + "settings.django.domain is missing/placeholder in ocyan.json." + ) + if not shop_base_url: + raise CommandError( + "settings.oscar.shop_base_url is missing in ocyan.json." + ) + + currency = getattr(settings, "OSCAR_DEFAULT_CURRENCY", "EUR") + if currency != "EUR": + raise CommandError(f"OSCAR_DEFAULT_CURRENCY must be EUR, got '{currency}'.") + + product_class = ProductClass.objects.filter(name=IDEA_PRODUCT_CLASS_NAME).first() + if product_class is None: + raise CommandError(f"Missing ProductClass '{IDEA_PRODUCT_CLASS_NAME}'.") + if product_class.requires_shipping: + raise CommandError("Idea Product class requires_shipping must be False.") + + short_attr_exists = ProductAttribute.objects.filter( + product_class=product_class, code=SHORT_DESCRIPTION_ATTRIBUTE_CODE + ).exists() + full_attr_exists = ProductAttribute.objects.filter( + product_class=product_class, code=FULL_DESCRIPTION_ATTRIBUTE_CODE + ).exists() + if not short_attr_exists or not full_attr_exists: + raise CommandError( + "Missing required idea product attributes: short_description and/or full_description." + ) + + expected_titles = {item.title for item in IDEA_PRODUCTS} + expected_prices = {item.title: item.price_eur for item in IDEA_PRODUCTS} + found_products = Product.objects.filter(product_class=product_class) + found_titles = set(found_products.values_list("title", flat=True)) + missing_titles = sorted(expected_titles - found_titles) + if missing_titles: + raise CommandError(f"Missing seeded idea products: {', '.join(missing_titles)}.") + + non_public_idea_titles = list( + found_products.filter(title__in=expected_titles, is_public=False).values_list( + "title", flat=True + ) + ) + if non_public_idea_titles: + raise CommandError( + "Seeded idea products must be public to appear in the storefront. " + f"Examples: {', '.join(sorted(non_public_idea_titles))}" + ) + + invalid_shipping_products = [ + product.title + for product in found_products + if getattr(product, "is_shipping_required", False) + ] + if invalid_shipping_products: + raise CommandError( + "Some idea products still require shipping; expected digital-only products: " + + ", ".join(invalid_shipping_products) + ) + + # Validate each seeded idea has EUR stockrecord pricing in the expected range. + invalid_stockrecords: list[str] = [] + missing_stockrecords: list[str] = [] + for product in found_products.filter(title__in=expected_titles): + stockrecord = ( + StockRecord.objects.filter(product=product) + .order_by("id") + .first() + ) + if stockrecord is None: + missing_stockrecords.append(product.title) + continue + + if stockrecord.price_currency != "EUR": + invalid_stockrecords.append( + f"{product.title} (currency={stockrecord.price_currency})" + ) + continue + + expected = expected_prices[product.title] + actual = stockrecord.price_excl_tax + if actual is None or actual != expected: + invalid_stockrecords.append( + f"{product.title} (price_excl_tax={actual}, expected={expected})" + ) + continue + + if actual < 29 or actual > 149: + invalid_stockrecords.append( + f"{product.title} (out-of-range price_excl_tax={actual})" + ) + + if missing_stockrecords: + raise CommandError( + "Missing stockrecords for seeded idea products: " + + ", ".join(sorted(missing_stockrecords)) + ) + if invalid_stockrecords: + raise CommandError( + "Invalid stockrecord pricing for seeded idea products: " + + "; ".join(sorted(invalid_stockrecords)) + ) + + non_idea_public_titles = list( + Product.objects.exclude(title__in=expected_titles) + .filter(is_public=True) + .values_list("title", flat=True)[:10] + ) + if non_idea_public_titles: + raise CommandError( + "Non-idea products are still public. Retire them before launch. " + f"Examples: {', '.join(non_idea_public_titles)}" + ) + + demo_page_filter = Q() + for marker in DEMO_MARKERS: + demo_page_filter |= ( + Q(title__icontains=marker) + | Q(slug__icontains=marker) + | Q(search_description__icontains=marker) + ) + + live_demo_pages = ( + Page.objects.live() + .public() + .exclude(depth__lte=2) + .filter(demo_page_filter | Q(slug__in=BLOCKED_DEMO_PAGE_SLUGS)) + .values_list("title", "slug")[:10] + ) + if live_demo_pages: + formatted = ", ".join(f"{title} ({slug})" for title, slug in live_demo_pages) + raise CommandError( + "Demo-like pages are still live/public. Purge them before launch. " + f"Examples: {formatted}" + ) + + self.stdout.write( + self.style.SUCCESS( + "Idea marketplace launch validation passed: " + f"{len(found_titles)} products, EUR currency, checkout apps={checkout_apps}, " + f"payment apps={payment_apps}." + ) + ) diff --git a/mandelstudio/templatetags/idea_marketplace.py b/mandelstudio/templatetags/idea_marketplace.py new file mode 100644 index 0000000..84c2486 --- /dev/null +++ b/mandelstudio/templatetags/idea_marketplace.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from django import template + +from mandelstudio.idea_marketplace import ( + get_idea_full_description, + get_idea_short_description, + get_unlockable_description, + is_idea_product, + user_has_unlocked_idea, +) + +register = template.Library() + + +@register.simple_tag +def idea_is_product(product): + return is_idea_product(product) + + +@register.simple_tag +def idea_is_unlocked(product, user): + return user_has_unlocked_idea(user, product) + + +@register.simple_tag +def idea_payments_enabled(): + from django.conf import settings + + return getattr(settings, "IDEA_MARKETPLACE_PAYMENTS_ENABLED", False) + + +@register.simple_tag +def idea_short_description(product): + return get_idea_short_description(product) + + +@register.simple_tag +def idea_full_description(product): + return get_idea_full_description(product) + + +@register.simple_tag +def idea_description_for_user(product, user): + return get_unlockable_description(product, user)