JSON Overview
What It Is
A standalone, client-side HTML page builder. No backend server required for core editing — pages are built in the browser, persisted to localStorage, and exported as static HTML via ZIP download or FTP upload. When served from an ACCID backend (PHP bridge or ACCID Vault), persistence extends to server-side storage.
The builder runs at any URL with ?edit in the query string. Without ?edit, the same page renders in read-only preview mode.
Architecture at a Glance
index.html
|-- Phase 1: Data layer (blobber, saver, data-adapters, data-point-registry)
|-- Phase 2: Module definitions (manifest -> dynamic loader -> per-module JS files)
|-- Phase 3: Persistence bridge (accid-saver.js)
|-- Phase 4: Renderer (page-renderer.js)
+-- Phase 5: Editor (editor-loader.js -> creator/ directory, only on ?edit)
Scripts load in strict dependency order. The editor is entirely optional — the renderer works standalone for published sites.
Entry Point: index.html
Loads all scripts in phases. Key containers in the DOM:
#navigationContainer— Fixed-position navigation modules (sticky top bar)#moduleContainer— Main content grid (all non-fixed modules)#topDropZone— Drop target above the grid for adding modules at position 0
The ?edit URL parameter triggers editor-loader.js, which dynamically loads the entire creator UI (panels, tabs, drag system, palette).
Module System
Registry and Manifest
js/modules/module-manifest.js defines window.CORE_MODULES — an array of { type, path, class } entries. The module loader reads this manifest and dynamically loads each .js file.
Each module file attaches itself to window (e.g., window.TextModule = { ... }). The module loader then registers each into window.moduleRegistry, a Map<string, ModuleDefinition>.
Module Interface
Every module implements two methods:
{
type: 'text',
create: (options) => ({ id, type, layout_class, order, ...defaults }),
render: (data, registry, isEditMode) => '<div class="module-text">...</div>'
}
- create() — Returns a new module data object. Always sets
order: 0(the editor’saddModule()overrides this withmax + 1). - render() — Returns an HTML string. Receives the module’s data, the full registry, and a boolean for edit vs preview mode. Edit mode typically adds inline controls.
Active Module Types
| Type | File | Purpose |
|---|---|---|
| meta | meta_module.js | Page metadata (title, description, SEO) |
| text | text-module.js | Rich text content |
| image | image-module.js | Images with captions |
| button | button-module.js | Styled CTA buttons |
| link | link-module.js | Hyperlinks |
| row | row-module.js | Flex container for child modules |
| section | section-module.js | Layout sections (hidden from palette) |
| navigation | navigation-module.js | Nav bar (horizontal/vertical/bullets) |
| form | form-module.js | Contact/input forms |
| code-screenshot | code-screenshot-module.js | Code display blocks |
| hero | hero-module.js | Full-width hero sections |
| gallery | gallery-module.js | Image galleries |
| background | background-module.js | Background layers (solid/gradient/image) |
| spacer | spacer-module.js | Invisible positioning filler |
| row-breaker | row-breaker-module.js | Grid section splitter |
Commented out (future): waypoint-image, sidebar.
Hidden from palette via HIDDEN_TYPES in creator.js: section, waypoint-image, sidebar, and all admin-* types.
Grid Layout System
CSS Grid v2 (.page-grid-v2)
The main grid uses CSS Grid with auto-fill columns and CSS custom properties:
.page-grid-v2 {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--grid-min-col, 180px)), 1fr));
grid-auto-rows: minmax(100px, auto);
column-gap: var(--grid-gap, 16px);
row-gap: var(--grid-gap, 16px);
}
The #moduleContainer base CSS also sets grid-auto-flow: dense, which allows smaller modules to fill gaps in the grid.
Layout Classes
Each module has a layout_class that controls its grid span:
| Class | Effect |
|---|---|
card |
1 column, 1 row (default) |
card-wide |
2 columns, 1 row |
card-tall |
1 column, 2 rows |
card-full |
Full width (grid-column: 1 / -1) |
half |
50% width |
hero-full |
Full width, tall |
feature-block |
2 columns, 2 rows |
row-span-2/3/4 |
Height spanning |
sidebar-left/right |
Sidebar positioning |
Users cycle through sizes via the resize handle (drag) or layout cycling functions (cycleLayoutClass, cycleRowSpan, cycleLayoutFull).
Row Breaker (Grid Splitting)
Row breakers split #moduleContainer into multiple independent .page-grid-v2 sections. This prevents grid-auto-flow: dense from flowing modules across section boundaries.
How it works:
- Row breaker is added as a regular
card-sized module and positioned via Shift+drag renderPageV2()detects row breakers in the module list- Container switches from grid to
display: flex; flex-direction: column - Modules between row breakers are placed into separate
.page-grid-v2 .grid-sectiondivs - Row breaker itself renders as a
.row-breaker-dividerbetween sections (thin orange dashed line in edit mode, invisible in preview) - When no row breakers exist, the original flat grid rendering is used (backwards compatible)
Export: page-manager.js generateHTMLForPage() uses the same splitting logic — renderContentLayer() creates separate grid sections in the exported HTML.
Module Ordering
All modules have an order integer. Modules are sorted by order before rendering. Order is global across all layers (a single incrementing sequence). The addModule() function always sets order to max(existing orders) + 1.
Reordering uses Shift+drag, which calls swapModulesV2() to swap order values between two modules.
Layer System
Four Layers
| Layer | Z-Index | Purpose |
|---|---|---|
| background | 1 | Full-page backgrounds (solid, gradient, image) |
| mid | 10 | Decorative overlays, mid-ground elements |
| content | 100 | Main page content (default layer) |
| ui | 1000 | Fixed UI elements (nav bars, overlays) |
Filter-in-Place Architecture
All modules live in a single data.modules array and a single #moduleContainer DOM tree. Each module is tagged with module.layer (defaults to 'content').
The layer switcher (js/accid-layers.js) shows/hides modules by toggling the .layer-hidden CSS class (display: none). window.ACCID_ACTIVE_LAYER tracks which layer is being edited.
This design means:
querySelectorAll('.module-wrapper-v2')finds all modules regardless of layerelementsFromPoint()naturally skipsdisplay: noneelements (layer-aware drag for free)- No separate DOM containers or data arrays per layer
- Order values are global — no per-layer ordering conflicts
Layer Switcher UI
The layer switcher panel (in creator-ui-template.html) lets users pick which layer to edit. Switching layers dispatches a layerChanged CustomEvent, which triggers palette re-rendering with layer-appropriate module types:
- Background: background, image, text, spacer
- Mid: image, text, spacer, code-screenshot
- Content: all module types
- UI: nav, button, link, text, spacer
Export Behavior
On export (generateHTMLForPage()), modules are separated into actual z-index stacked containers (#layer-background, #layer-mid, #layer-content, #layer-ui), each with appropriate position and z-index CSS.
Persistence (3-Tier)
Data Flow
In-Memory (HTMLModuleBlobber)
| saveData() / getData()
localStorage (AccidSaver, key: accid_edits_{pageId})
| bridge save/load (if backend available)
Server / Baked-In (accid_dropper.json or PHP bridge)
HTMLModuleBlobber (js/html-module-blobber.js)
Static class that holds the in-memory data cache. Key methods:
getData()— Returns current builder datasaveData(data)— Writes to memory + triggers AccidSaver debounced save (1500ms)saveDataImmediate(data)— Writes to memory + flushes AccidSaver immediatelyaddModule(module)— Adds module to data.modulesdeleteModule(id)— Removes module by IDupdateModule(id, updates)— Merges updates into existing module
AccidSaver (js/accid-saver.js)
Handles persistence to localStorage and optional server bridge:
- Load order: 1. In-memory cache -> 2. localStorage -> 3. accid_dropper.json (baked-in)
- Save: Debounced (1500ms) writes to localStorage. If a bridge URL is configured, also POSTs to server.
- Page-scoped: Each page gets its own localStorage key (
accid_edits_{pageId})
initCreator() Sync
When the editor initializes, initCreator() loads data via AccidSaver, then syncs it back into HTMLModuleBlobber via setData(). This ensures the in-memory cache matches the persisted state.
Authentication (AccidAuth)
js/accid-auth.js implements a two-part authentication system:
- Key — Embedded in the page, stored in localStorage, or fetched from ACCID Vault
- Passcode — Known only to the user (never stored)
- Hash — SHA-256 of
key + passcode, compared against stored hash
Behavior
- Localhost: Auto-unlocks (no prompt)
- Live server: Shows a modal with passcode input
- First-time setup: User creates a passcode, hash is generated and stored
- Returning user: User enters passcode, hash is verified
Credential Sources (tried in order)
- localStorage
- Cookies
- Content file (configurable URL, default
null) - ACCID Vault API
Integration
editor-loader.js calls initAccidAuth() before loading the editor. If auth fails or is cancelled, the editor stays in view mode with an “Edit” button.
Editor System
editor-loader.js
Detects ?edit in the URL. If present:
- Calls
initAccidAuth()for authentication - Sets
window.ACCID_EDIT_MODE = true - Dynamically loads creator CSS and JS
- Fetches
creator/creator-ui-template.htmland injects the editor UI - Calls
initCreator()to boot the editor
creator.js
The main editor logic file. Key functions:
initCreator()— Boots editor, syncs data, sets up panelsaddModule(type)/addModuleAtPosition(type, position)— Creates and inserts modulesrenderModulePalette()— Renders the module type picker, filtered by active layer andHIDDEN_TYPESrenderModulesForEditing(data)— Re-renders the grid with edit controls (MUST receive data argument)makeModuleEditableInline(el, module, event)— Opens inline editor overlay for a modulesetupDragAndDrop()— Initializes Shift+drag reordering
creator-ui-template.html
HTML template for all editor UI: toolbar, side tabs, module palette, page manager panel, site style panel, layer switcher, data streams panel.
creator.css
All editor-only styling: panels, tabs, toolbar, overlays, drag states.
Navigation Module
The navigation module has a dual rendering mode:
- In-grid (default): Renders inside
#moduleContaineras a regular grid module. Sizable vialayout_class. - Fixed position: When
module.fixedPosition = true, renders in#navigationContaineras a sticky top bar, full-width, outside the grid.
The editor provides a “Fixed Full-Width Position” checkbox. renderPageV2() splits modules into fixedNavModules and gridModules at render time.
Data Streams (Scraper Integration)
For importing scraped website data into the builder:
Components
js/data-adapters.js—AccidDataAdapters: Normalizes JSON/CSV/arrays intotag_groupsformatjs/data-streams-panel.js—AccidStreamsPanel: Right-side slide-in panel for browsing and binding data streamsjs/template-replicator.js—AccidTemplateReplicator: Save layout as template, replicate across pagescss/data-streams.css— Panel styling (z-index: 11000, translateX slide animation)
How Streams Work
- User loads JSON or pastes CSV into the streams panel
- Data is normalized into
tag_groups(key-value pairs per “page”) - User drags a stream tag onto a module -> sets
module.dataSource = streamName - Template replicator can duplicate a page layout across all data entries
Gotcha
Tag group values from scrapers are often nested objects ({text, html, links, images}), not plain strings. Always check typeof and extract .text or .html.
Multi-Page Management
creator/page-manager.js manages multiple pages within a single builder instance:
- Pages stored in
this.pagesobject (loaded fromaccid_dropper.json) - Each page has:
id,name,slug,title,description,modules[] switchToPage()saves current page via AccidSaver, creates new AccidSaver for target page, loads modules- Global modules (
module.showOnAllPages = true) are injected into every page - Page select dropdown in the toolbar
Export
Two export modes:
- ZIP Download — Generates HTML for each page, bundles with CSS and
accid_dropper.json, downloads via JSZip - FTP Upload — Same file generation, uploads via
cdn.accid.cloud/ftp-proxy.php
Export generates fully static HTML with no JavaScript dependencies. Layer separation happens at export time (modules sorted into z-index containers). Row breakers split grids in export just like in the live editor.
Drag and Drop System
js/accid-grid-drag.js handles all drag interactions:
- Shift+Drag to reorder modules (swaps order values)
- Module palette drag to add new modules at specific positions
- Resize handle drag to cycle through layout classes
- Uses
elementsFromPoint()for hit detection (naturally layer-aware) - After any re-render:
AccidStreamsPanel.attachDropTargets()is called (with 50ms setTimeout)
Module Data Shape
Every module in data.modules has at minimum:
{
id: "text-1705123456789-abc123def", // Unique ID (type + timestamp + random)
type: "text", // Module type key
order: 5, // Global render order (integer)
layout_class: "card", // Grid sizing class
layer: "content", // Layer assignment (default: content)
// Optional universal properties:
hidden: false, // Visibility toggle
showOnAllPages: false, // Global module flag
dataSource: null, // Tag group binding
fixedPosition: false, // Navigation-only: fixed top bar
// Cell-level appearance:
cellBgImage: null, // Wrapper background image
cellBgColor: null, // Wrapper background color
cellBgOverlay: null, // Overlay on wrapper background
cellBgSize: "cover", // Background size
cellBgPosition: "center", // Background position
cellMinHeight: null, // Minimum wrapper height
// Module-level appearance:
backgroundColor: null, // Module background color
bgGradientColor: null, // Gradient end color
bgImage: null, // Module background image
bgOpacity: 100, // Background opacity (0-100)
textColor: null, // Text color override
// CSS Filters:
filterBlur: 0,
filterBrightness: 100,
filterContrast: 100,
filterSaturate: 100,
filterHueRotate: 0,
filterGrayscale: 0,
filterSepia: 0,
// Per-module spacing (BaseModule.getSpacingCSS):
marginTop: null,
marginBottom: null,
marginLeft: null,
marginRight: null,
paddingTop: null,
paddingBottom: null,
paddingLeft: null,
paddingRight: null
}
Plus type-specific properties (e.g., content for text, src/caption for image, items[] for navigation).
Key Patterns
| Pattern | Usage |
|---|---|
window.moduleRegistry.get(type) |
Look up module definition |
HTMLModuleBlobber.saveData(data) |
Persist to memory + AccidSaver |
window.renderModulesForEditing(data) |
Re-render grid (MUST pass data) |
window.dispatchEvent(new CustomEvent('layerChanged', {...})) |
Cross-module events |
window.functionName = functionName |
Global exposure pattern |
m.order ?? index |
Nullish coalescing for order defaults (keeps 0) |
Known Gotchas
- Module
create()always setsorder: 0—addModule()MUST override withmax + 1 renderModulesForEditing()requires data argument — All callers must passbuilderDatagrid-auto-flow: densecauses modules to backfill gaps — row breakers solve this by splitting into separate gridsrenderModulePalette()handles palette clicks — Do NOT add click handlers insetupDragAndDrop()(causes double-add)- Nested tag group values — Always check
typeofbefore using scraper data elementsFromPointskipsdisplay: none— Layer filtering via CSS class works automatically with drag system
File Map
htmlbuilder_local/
|-- index.html Entry point, script loading phases
|-- js/
| |-- html-module-blobber.js In-memory data cache (static class)
| |-- accid-saver.js 3-tier persistence (localStorage/bridge/baked-in)
| |-- accid-auth.js Two-part authentication (key + passcode)
| |-- page-renderer.js renderPage() -> renderPageV2(), grid splitting
| |-- editor-loader.js Dynamic editor loading (?edit detection)
| |-- accid-grid-drag.js Shift+drag reorder, resize, drop targets
| |-- accid-layers.js Layer system (show/hide/filter)
| |-- data-adapters.js JSON/CSV -> tag_groups normalization
| |-- data-streams-panel.js Stream browsing & drag-to-bind panel
| |-- template-replicator.js Layout template save & replicate
| +-- modules/
| |-- module-manifest.js CORE_MODULES array (single source of truth)
| |-- text-module.js
| |-- image-module.js
| |-- button-module.js
| |-- link-module.js
| |-- row-module.js
| |-- section-module.js
| |-- navigation-module.js
| |-- form-module.js
| |-- code-screenshot-module.js
| |-- hero-module.js
| |-- gallery-module.js
| |-- background-module.js
| |-- spacer-module.js
| |-- row-breaker-module.js
| +-- meta_module.js
|-- creator/
| |-- creator.js Editor logic (addModule, palette, inline editing)
| |-- creator-ui-template.html Editor UI panels and tabs
| |-- creator.css Editor-only styling
| +-- page-manager.js Multi-page management + ZIP/FTP export
|-- css/
| |-- accid_grid.css Grid layout, responsive breakpoints, layout classes
| |-- modules-styles.css Module-specific styling
| |-- accid-layers.css Layer system CSS (.layer-hidden)
| +-- data-streams.css Streams panel styling
+-- accid_dropper.json Baked-in page data (source of truth for published sites)
