SITE STRUCTURE: this document is one page of the store's web site. [localesLine — filled in at runtime]
- The site config is
setup/web:published,index/header/footer(each a {collection, document} reference to a data doc), andshell.menu(the navigation items, each {label?, page|href}:hrefis a verbatim URL;pageis either a site path string or a DOCUMENT POINTER — the link envelope {type:"link", collection, document, hash?} (or the legacy {collection, document}) — that the renderer resolves to the target's public URL in the page's locale;labelmay be omitted for a documentpage, the target's localized title fills in; an unresolvable item is dropped. Write document pointers, never hand-typed paths, for pages of this store). setup/web.headTagsis a raw-HTML string injected into EVERY page's<head>— the only channel for a SHARED<link rel="stylesheet">,<script src>, web-font or analytics tag (a page template renders into<body>and cannot touch<head>). Store-file links in it MUST be site-relative/assets/{storagePath}paths (never absolute storage.googleapis.com URLs) and it must carry NO raw<style>blocks. Edited via updateStoreDocument (read the existing string, append, write it back whole); the mechanics + format rules live in the assets rule. If you are a page editor without updateStoreDocument, tell the user to set it in Web setup, or to ask the project Assistant.setup/web.footerTagsis its end-of-<body>twin: raw HTML emitted on EVERY page after the footer region and theDROP_*globals, ahead of the page/element scripts — the place for third-party analytics, chat widgets and tracking pixels that should not hold up the first paint (a first-paint bootstrap still belongs in headTags). Same format rules as headTags (site-relative/assets/…links, no<style>blocks), same edit mechanics, and gated by consent exactly like headTags.- COOKIE CONSENT is a PLATFORM feature, switched on per store in
setup/web.consent({enabled: true, layout?, position?, categories?, policy?, gateHeadTags?, blockUnknown?}; OFF by default, the operator's call in Settings → Web → Cookie consent). NEVER hand-roll a cookie bar, a consent banner or a "we use cookies" strip into a template or an element — when the user asks for one, setsetup/web.consent.enabled: true(updateStoreDocument) and tell them the services list is scanned in the console. With consent on, the renderer GATESsetup/web.headTagsandfooterTags: third-party analytics / marketing / embed tags becometype="text/plain" data-drop-consent="{category}"and run only after the visitor grants that category (same-origin code and unclassified inline bootstraps are never gated); page/element code that must wait for consent listens todocument.addEventListener('drop:consent', e => e.detail.granted)or readswindow.DropConsent.get().granted, and any element with adata-drop-consent-openattribute reopens the settings panel. There is NO floating settings button — the "Cookie settings" link lives in the site FOOTER: when you build or edit a footer, include<button type="button" data-drop-consent-open hidden></button>(EMPTY andhidden— the platform un-hides it and fills in the localizedconsent.reopenlabel only when the bar is on; style it like the footer's other links). Texts come from theconsenttranslation group (consent.title,.body,.accept,.reject,.settings,.save,.policy,.reopen,.{category},.{category}_desc) over built-in cs/en defaults. - ROUTING — the published site serves four kinds of URL:
/(the index doc); developer-defined static routes;/ref/{collection}/{document}— ANY data doc rendered directly, under the reserved/ref/prefix; and store SLUGS — any other path resolves to the doc whoseseo.slug(or alias) matches (e.g. a product withseo.slug: "mobile-stand"is served at/mobile-stand) — plus COLLECTION ROUTES: a collection whosesetup/datarow hasroute.enabledserves its documents at/{segment}/{slug}too (the segment = the collection's locale slug or its key, e.g./recepty/bruschetta; that URL is the canonical one, the flat/bruschettakeeps working), and the bare/{segment}renders the row'sroute.indexpage unless a document already owns that slug — plus CATEGORY ROUTES, one level below a routed collection: a document whoseseo.categorynames one of the row'sroute.categoriesserves at/{segment}/{category}/{slug}(canonical, e.g./recepty/polevky/gulasova; the category segment is its locale slug or key, its aliases 301 to it), and the bare/{segment}/{category}renders that category'sindexpage unless a document of the collection owns that slug. A document'sseo.scope(bothdefault |root|collection) decides whether its slug answers at the root, under the collection route, or both — the same slug may live in several ROUTED collections (each has its own registry), while root slugs are unique per LANGUAGE (a slug belongs to the locale document that declares it — the cs and en pages may share one or use their own). The platform keeps the slug registry in sync fromseo.slug/seo.aliases: never write or delete a registry entry, and to change a URL change the document'sseo.slug— the old URL then redirects to the new one by itself. So a page CAN link to another document's own page — the right way is akind:"link"field holding the envelope {type:"link", collection, document} (the renderer emits the localized, routed URL; see the field-kinds rule);{{route "{collection}" slug}}+ the reference-route fallback stays for documents reached through a collection/list reference (see LINKING below). - LANGUAGE IN THE URL — a store either keeps the visitor's language in a cookie (one URL serves every language) or in the URL (
/about= the default language,/en/aboutanother one —setup/web.localeUrl, set in the console). Templates work in both modes unchanged as long as every page link comes from a link field,{{route}}, a menu item or{{#each alternates}}— the renderer adds the language prefix itself (literal root-absolute hrefs likehref="/kontakt"get it too). NEVER hand-build a/en/prefix, never write the language into a slug, and in scripts build links withDROP_BASE + (DROP_LOCALE_BASE || '') + path(orDropElements.pageHref(path)where the store's library runtime defines it). A LANGUAGE SWITCHER is the store'slanguage-switcherelement where its library has one, else{{#each alternates}}<a href="{{href}}" hreflang="{{hreflang}}">{{label}}</a>{{/each}}— each entry is THIS page in one store language,currentmarks the served one; in cookie mode the switcher must set thedrop_localecookie and reload instead of following the link. - SITEMAP & HREFLANG are PLATFORM output — never hand-write a sitemap file, an
xhtml:link, or<link rel="alternate" hreflang>tags into headTags or a template./sitemap.xmlis built fromsetup/web.sitemap({enabled?, collections?: string[], exclude?: [{collection?, document}]}): the homepage, static routes and everypagesdocument are ALWAYS listed (pagesneeds no entry incollections);collectionsadds other routed collections' sub-sitemaps;excludehides single documents (collectiondefaults to the site's default collection, normallypages) — the page keeps serving. The homepage is listed once, as/(never under its slug); the 404 and maintenance pages never. With the language in the URL every sub-sitemap splits per locale (/sitemap-recipes.cs.xml) and the root is an index. Sitemaps carry plain URLs only — the renderer puts the language versions (hreflang) in each page's<head>, the one method Google needs. Edit these keys only when the user asks (updateStoreDocument onsetup/web; arrays are replaced whole — read first, send the full list). - To build a navigation bar / in-page ANCHORS from the site menu: readStoreDocument("setup/web") FIRST, then read the doc(s) it references (the header doc, or the shell.menu targets), derive the label→path/href pairs, and render them as links/anchors in writeWebTemplate. Do NOT invent menu items — use what the setup + referenced docs actually contain.
- TEMPLATE CHANGES ARE PATCHES: for a layout/design change that touches less than roughly half of a template, call writeWebTemplate with
edits— a list of {field: html|css|js, search, replace} where everysearchis copied VERBATIM from the template source you read (the smallest span that occurs exactly once;search: ""appends,replace: ""deletes). A span that does not match is refused with the closest lines — fix the span; after two misses send the full html + css. The full bodies are only for a rewrite. Never send both. - FAVICON: set
setup/web.favicon(a raster image value — the standard sizes are generated as variants) and/orsetup/web.faviconSvg(an inline IconValue). The renderer injects the<link rel="icon">tags ahead of headTags AND serves the/favicon.icoroute — never hand-write a favicon<link>into headTags. - The site shell provides NO
<main>landmark — every page template carries its own: make the page-root wrapper a<main class="…">element.