# Squarespace

7.1 markup is observed, not documented: null-check lookups, and inspect rather than guess.

## Page structure

```text
#siteWrapper
  header#header
  main#page                                  the stable root
    article#sections > section.page-section
    or article#page-regions > section.region > section.page-section
  footer#footer-sections
```

- A `.page-section` carries `data-section-theme` (may be `""`, meaning the site default) and
  `data-section-id` (a rendered id, not Squarespace's backend id). Fluid Engine sections have
  `data-sqsp-section="fluid-engine"`.
- The mount is a plain `div` inside a Code Block. Find its section with
  `el.closest(".page-section")`; sections below are its `nextElementSibling`s that match
  `.page-section`.
- Squarespace buttons aren't always links: match `a[href]` and `button[data-href]`.

## The editor

A signed-in owner visiting their site lands in `/config`, with the site in an iframe
(`window.self !== window.top`). Squarespace disables scripts embedded in Code Blocks while the
owner is logged in and editing, so they may not see the component there; the accurate view is
logged out. A browser holding the `seamble_preview` cookie shows drafts in the editor too.

Clicking Edit adds `sqs-edit-mode-active` to `<body>` without a reload. If you hid, moved or
rewrote Squarespace's markup, undo it then, or the owner can't edit it:

```js
function onEditMode(undo) {
  if (window.self === window.top) return;
  const on = () => document.body.classList.contains("sqs-edit-mode-active");
  if (on()) return undo();
  const obs = new MutationObserver(() => { if (on()) { obs.disconnect(); undo(); } });
  obs.observe(document.body, { attributes: true, attributeFilter: ["class"] });
}
```

## Taking over the page

- **Sections into a new layout** (slider, stack): mount in the first section. Insert your
  container into the page first, then move the sections into it; anchoring on a section already
  moved inside throws (`HierarchyRequestError`). Give the container the first section's
  `data-section-theme`. Undo: move them back in reverse order.
- **Hiding sections** (tabs): `hidden` alone doesn't hide a `.page-section`
  ([Styling](styling.md#gotchas)).
- **List sections** (auto layouts: team, testimonials):
  `section.user-items-list-section[data-sqsp-section="auto-layout"]` holds
  `[data-controller^="UserItemsList"][data-current-context]`: JSON with `userItems` (`title`,
  `description` HTML, `button`, `image.assetUrl`). Render from it beside `.user-items-list`; hide
  the original in the editor, remove it live if you like.
- **Popups**: sections clip their content, so append the overlay to `#siteWrapper`. Give it a
  theme: the mount's `el.closest("[data-section-theme]")` value, or the first non-empty one in
  `#page` (the header and cookie banner often have `""`). The site's default theme is defined
  only on `:root`: set on an element inside a differently themed section, it doesn't apply.

## Inserted markup doesn't start

Squarespace starts its blocks once, at load. Its markup inserted later (a fetched or cloned
section) keeps its HTML, but carousels, lazy images and background video never run, and
calling Squarespace's init functions doesn't start them. Build your own UI from the data.
Moving sections that are already running is fine.

## Reading content

**Collections** (blog, events, products): add `?format=json` to the collection URL.

- `items[]`, for a blog: `title`, `fullUrl`, `excerpt` and `body` (HTML), `assetUrl`,
  `categories`, `tags`, `author`, `publishOn` (epoch ms). Blog-wide: `collection.categories`, `.tags`.
- 20 items per page. Follow `pagination.nextPageUrl` while `pagination.nextPage`, adding
  `format=json` back (it's dropped). Never compute offsets; don't trust `collection.itemCount`.
- The response can be HTML with status 200: parse in try/catch, show an empty state.
- The mount's `data-source` (e.g. `/blog`) says where, if set.
- Images: `assetUrl + "?format=750w"`, never the original.
- A list section's data is already in `data-current-context`: no fetch.

**Another page**: `?format=json` doesn't include a normal page's sections. Fetch its HTML, parse
with `DOMParser`, take what you need from `#page`, and use it as static HTML.

**Site context**: `window.Static?.SQUARESPACE_CONTEXT`. Signed in, it holds account details
and a CSRF token: never send it anywhere.
