layout.jinja <html>, <head>, fonts, CSS variables, shared scripts
└── templates/landing.jinja {% template_components %} renders the page's sections in order
├── sections/header.jinja
├── sections/product-section.jinja ← uses components/*.jinja + bnfsj-product.js
├── sections/<any other section>.jinja
└── components/product-purchase-bar.jinja (sticky Buy now bar, always rendered)layout.jinja:root (--bnfsj-primary, --bnfsj-surface, and so on).bnfsj.cssbnfsj-sections.jstemplates/landing.jinja{% template_components %}.window.productObj for the SDK.settings.assets/bnfsj-product.js, built from src/product/) manages:| Path | Purpose | Shipped in ZIP |
|---|---|---|
layout.jinja | Page shell; converts layout settings into --bnfsj-* CSS variables | Yes |
layout.schema.json | Editor controls for global design (font, colors, radius, width, spacing, custom CSS) | Yes |
layout.json | Default layout settings for Arabic and English | Yes |
templates/landing.jinja | Landing-page template with sections, purchase bar, and bundle handling | Yes |
templates/landing.schema.json | Template-level settings (currently none) | Yes |
sections/*.jinja | Individual merchant-editable sections | Yes |
sections/*.schema.json | Editor controls for each section (required) | Yes |
components/*.jinja | Product partials for gallery, options, actions, quantity, stock alerts, and purchase bar | Yes |
assets/* (others) | Vendor libraries, icons, images, and legacy CSS | Yes |
locale/ar/LC_MESSAGES/messages.po | Arabic translations of _('...') strings | Yes |
src/product/*.js | Product runtime ES modules bundled using esbuild | No |
preset.json | Default starter page (6 sections) and layout settings | No |
tests/ | Python template/build tests and JavaScript runtime tests; fixtures/madar.json contains the sample product | No |
layout.* and the assets, components, layouts, locale, sections, and templates directories are included in the theme ZIP. Other project files and folders, including docs/, are ignored during the build.| Section file | Editor name | Main settings |
|---|---|---|
header | Header | Logo, mobile logo |
product-section | Product | Title, description (falls back to product data) |
main-slider | Slider | Slides list, hide dots, colors |
banner | Banner | Image, title, description, URL |
text-section | Text | Text |
ad-section | Text (announcement) | Text, colors |
features-section | Features | Up to 4 icon and text items, colors |
features-section-2 | Features 2 | Title, items, background color/image |
features-section-3 | Features 3 | Items (image and text rows) |
ggallery | Gallery | Title, image fit, images |
video | Video | Title, video, controls, autoplay |
count-down-timer | Countdown timer | Title, date, text after end, colors |
testimonials | Testimonials | Title, list, hide dots |
partners | Partners | Title, logos |
payments | Payments options | Hide |
social-media | Social media | Hide |
store-logo | Store logo | Hide, image |
copywrites | Copyrights | Text, links, colors |
| Requirement | Version |
|---|---|
| Python | 3.11 or later |
| Node.js | 20 or later |
python3 -m venv .venv && source .venv/bin/activate
python -m pip install 'Jinja2>=3.1,<4' 'pytest>=8,<10'
npm ci --ignore-scripts| Task | Command |
|---|---|
| Run all tests | python -m pytest tests -q && npm test |
| Validate and render (Arabic) | python scripts/validate.py --language ar |
| Validate and render (English) | python scripts/validate.py --language en |
| Rebuild product runtime only | npm run build:runtime |
| Build the theme ZIP | python scripts/build.py --output dist/bnfsj.zip --manifest dist/assets-manifest.json |
| Validate the built ZIP | python scripts/validate.py dist/bnfsj.zip |
python scripts/validate.py --language en --html-output dist/preview.htmlpython -m http.server 8000 --bind 127.0.0.1http://127.0.0.1:8000/dist/preview.htmlvitrin: partials. Checkout, SDK calls, bundles, and editor saving must be tested directly on Vitrin.| Path | Approach | When to use |
|---|---|---|
| Path A | Configure only (no code) | Existing sections already support your layout, and you only need to customize the appearance |
| Path B | Add a new section | Your design requires a block that the existing sections don't provide |
| Path C | Change the product area | You need to customize the shared product section or its runtime behavior |
| Setting group | Options |
|---|---|
| Typography | Font family |
| Colors and shape | Primary, text on primary, page background, text, card background, borders, secondary text, corner radius |
| Spacing and width | Content width, spacing between sections |
| Custom styles | Additional CSS for the current page only |
Example — Madar landing page The examples/madar/directory provides a complete reference for this approach, featuring ten sections and the Zid color palette without any theme code changes.Refer to its README for preview instructions.
sections/my-section.jinja<section section-id="{{ sectionId }}"
class="bnfsj-section bnfsj-my-section"
style="{% if settings.background_color %}--section-background: {{ settings.background_color }};{% endif %}">
<div class="bnfsj-container">
{% if settings.title %}<h2 class="bnfsj-heading">{{ settings.title }}</h2>{% endif %}
{% for item in settings.items or [] %}
<div class="bnfsj-my-section__item">
{% if item.image %}<img loading="lazy" src="{{ image_url(item.image) }}" alt="{{ item.text }}">{% endif %}
{% if item.text %}<p>{{ item.text }}</p>{% endif %}
</div>
{% endfor %}
</div>
</section>| Requirement | Implementation |
|---|---|
| Root element | Use <section section-id="{{ sectionId }}" ...> so the editor can identify and update the block |
| Standard spacing | Add the bnfsj-section class |
| Content width | Wrap content in bnfsj-container |
| CSS isolation | Use a section-specific prefix such as bnfsj-my-section |
| Optional settings | Use {% if %} and or [] for optional values and lists |
| Merchant colors | Pass values through CSS custom properties such as --section-background and --section-color |
| Images | Use image_url(value); supports w=, q=, and f='auto' for responsive srcset (see main-slider.jinja) |
| Theme assets | Use {{ 'file.svg' | asset_url }} |
| Fixed UI text | Use {{ _('Read more') }} with Arabic translations in messages.po |
| Product data | Access product (for example product.name, product.selected_product.formatted_price) |
| Template syntax | Use Jinja syntax only |
section-id, because sections can be repeated and reordered.is empty, fails validation.locale/ar/LC_MESSAGES/messages.po.sections/my-section.schema.json{
"section_id": "uuid",
"template": "sections/my-section.jinja",
"name": { "ar": "قسمي", "en": "My section" },
"icon": "fal fa-sparkles",
"display": true,
"settings": [
{ "id": "title", "type": "text", "label": { "ar": "العنوان", "en": "Title" } },
{
"id": "items",
"type": "list",
"max_items": 6,
"label": { "ar": "العناصر", "en": "Items" },
"settings": [
{ "id": "text", "type": "text", "label": { "ar": "النص", "en": "Text" } },
{ "id": "image", "type": "image", "label": { "ar": "الصورة", "en": "Image" } }
]
},
{ "id": "background_color", "type": "color", "label": { "ar": "لون الخلفية", "en": "Background color" } }
]
}| Element | Requirement |
|---|---|
| File naming | sections/X.schema.json must reference "template": "sections/X.jinja" |
| Localized values | name and every label must contain both ar and en strings |
| Settings | Must be a non-empty list with unique id values |
| Nested settings | list and fieldset require non-empty nested settings |
| Range settings | range requires numeric min and max values, where min ≤ max |
| Choice settings | select and radio require options |
text, textarea, richtext, numbercolor, url, image, videocheckbox, checkboxes, radio, range, selectproduct, products, category, category_productssnippet, list, fieldsetassets/bnfsj.css| Variable | Purpose |
|---|---|
--bnfsj-primary / --bnfsj-on-primary | Brand color and text on it |
--bnfsj-background / --bnfsj-foreground | Page background and main text |
--bnfsj-surface | Card or section background |
--bnfsj-border | Borders and dividers |
--bnfsj-muted | Secondary text |
--bnfsj-radius | Corner radius |
--bnfsj-width | Maximum content width |
--bnfsj-space, --bnfsj-space-sm, --bnfsj-space-lg | Section spacing scale |
--bnfsj-font | Selected font family |
--bnfsj-danger, --bnfsj-success | Status colors |
margin-inline-start, padding-inline, inset-inline-end, and text-align: start..bnfsj.Mobile layout note Arabic is the primary language, so RTL-safe styling is important. Also consider the sticky purchase bar when testing mobile layouts. The .bnfsj-mainclass already reserves space at the bottom of the screen for the bar.
| Feature | Hook | Details |
|---|---|---|
| Carousels | data-bnfsj-carousel="main" | Main slider |
| Carousels | data-bnfsj-carousel="testimonials" | Testimonials slider |
| Carousels | data-bnfsj-carousel="partners" | Partners slider |
| Countdown | data-bnfsj-countdown | Countdown container |
| Countdown date | data-date="YYYY-MM-DD HH:mm" | Target date and time |
| Countdown values | data-countdown-part="days|hours|minutes|seconds" | Countdown display elements |
assets/bnfsj-sections.jsdestroy().Note — Live editor updates The merchant editor dynamically inserts and removes sections. A MutationObserverhandles calls toinitialize()anddestroy(), so custom behavior must follow the same lifecycle pattern.
python scripts/validate.py --language ar
python scripts/validate.py --language entests/test_tools.py::test_all_sections_render_through_landing_entrypoint_in_both_languagespreset.json — the default starter pageexamples/ — an example landing pagecomponents/ are shared across all landing pages.data-bnfsj-* attributes.data-bnfsj-* attributes or required IDs, including:#product-form#product-id#product-view-add-to-cartsrc/product/*.jsassets/bnfsj-product.js directly.npm test to rebuild the bundle.dynamic_bundle behavior intact.python -m pytest tests -q && npm test
python scripts/validate.py --language ar && python scripts/validate.py --language en
python scripts/build.py --output dist/bnfsj.zip && python scripts/validate.py dist/bnfsj.zip"version" in package.json using the X.Y.Z format.master or develop.?v= query parameters:bnfsj.cssbnfsj-sections.jsbnfsj-product.jslayout.jinjatemplates/landing.jinjadata-bnfsj-version on <body>.bnfsj-product.js, resulting in incompatible behavior.master.dist/.vitrin/.venv/| Validation | Requirement |
|---|---|
| Individual asset size | Maximum 2 MB |
| Total package size | Maximum 50 MB |
| Root structure | Only the allowed folders and files |
| Asset references | Every asset_url reference must point to an existing file in assets/ |
.jinja and .schema.json files for every new section.<section section-id="{{ sectionId }}"> as the root, with bnfsj-section and bnfsj-container._() and added Arabic translations to messages.po.assets/bnfsj.css using --bnfsj-* design tokens and RTL-safe logical properties.image_url() for images and asset_url for theme files, with each asset under 2 MB.data-bnfsj-* attributes and form IDs.package.json version and asset ?v= values.