./ACCID HTML Builder – Technical Overview

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’s addModule() overrides this with max + 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:

  1. Row breaker is added as a regular card-sized module and positioned via Shift+drag
  2. renderPageV2() detects row breakers in the module list
  3. Container switches from grid to display: flex; flex-direction: column
  4. Modules between row breakers are placed into separate .page-grid-v2 .grid-section divs
  5. Row breaker itself renders as a .row-breaker-divider between sections (thin orange dashed line in edit mode, invisible in preview)
  6. 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 layer
  • elementsFromPoint() naturally skips display: none elements (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 data
  • saveData(data) — Writes to memory + triggers AccidSaver debounced save (1500ms)
  • saveDataImmediate(data) — Writes to memory + flushes AccidSaver immediately
  • addModule(module) — Adds module to data.modules
  • deleteModule(id) — Removes module by ID
  • updateModule(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)

  1. localStorage
  2. Cookies
  3. Content file (configurable URL, default null)
  4. 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:

  1. Calls initAccidAuth() for authentication
  2. Sets window.ACCID_EDIT_MODE = true
  3. Dynamically loads creator CSS and JS
  4. Fetches creator/creator-ui-template.html and injects the editor UI
  5. Calls initCreator() to boot the editor

creator.js

The main editor logic file. Key functions:

  • initCreator() — Boots editor, syncs data, sets up panels
  • addModule(type) / addModuleAtPosition(type, position) — Creates and inserts modules
  • renderModulePalette() — Renders the module type picker, filtered by active layer and HIDDEN_TYPES
  • renderModulesForEditing(data) — Re-renders the grid with edit controls (MUST receive data argument)
  • makeModuleEditableInline(el, module, event) — Opens inline editor overlay for a module
  • setupDragAndDrop() — 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.


The navigation module has a dual rendering mode:

  • In-grid (default): Renders inside #moduleContainer as a regular grid module. Sizable via layout_class.
  • Fixed position: When module.fixedPosition = true, renders in #navigationContainer as 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.jsAccidDataAdapters: Normalizes JSON/CSV/arrays into tag_groups format
  • js/data-streams-panel.jsAccidStreamsPanel: Right-side slide-in panel for browsing and binding data streams
  • js/template-replicator.jsAccidTemplateReplicator: Save layout as template, replicate across pages
  • css/data-streams.css — Panel styling (z-index: 11000, translateX slide animation)

How Streams Work

  1. User loads JSON or pastes CSV into the streams panel
  2. Data is normalized into tag_groups (key-value pairs per “page”)
  3. User drags a stream tag onto a module -> sets module.dataSource = streamName
  4. 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.pages object (loaded from accid_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:

  1. ZIP Download — Generates HTML for each page, bundles with CSS and accid_dropper.json, downloads via JSZip
  2. 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

  1. Module create() always sets order: 0addModule() MUST override with max + 1
  2. renderModulesForEditing() requires data argument — All callers must pass builderData
  3. grid-auto-flow: dense causes modules to backfill gaps — row breakers solve this by splitting into separate grids
  4. renderModulePalette() handles palette clicks — Do NOT add click handlers in setupDragAndDrop() (causes double-add)
  5. Nested tag group values — Always check typeof before using scraper data
  6. elementsFromPoint skips display: 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)