Technical Documentation
Zineroo
A browser-based React print shop for making seven kinds of printable zines, booklets, brochures, accordions, and folded maps, with local uploads, open-archive and Pinterest image intake, layered photos, rich text, editable shapes and icons, portrait or landscape documents, undo/redo, end-to-end encrypted cloud projects, exact imposition previews, and PDF, PNG, ZIP, or editable output
The Idea
Zineroo is a focused publishing tool for turning a small image set into a printable folded object. It keeps the core workflow in the browser: upload, paste, or find source images; choose one of seven physical formats; auto-fill the editor; add photo, text, shape, and icon layers; reorder pages or spreads where the format supports it; tune the composition; preview the finished object or imposed sheet; and export the files. The editor remains local-first by default. Images are kept in the browser, autosaved to IndexedDB, and exported by the user as files unless the user signs in for optional Plus cloud projects.
The interface is built around the physical object. The app has fixed page maps, paper presets, document-orientation controls, format-aware spreads, full-sheet imposition views, and one consolidated export surface for the printable artifact. The workspace separates the loop into Grid, Editor, and Preview modes, with an image portal, project manager, account flow, and public product site around the editor. It is not a general design canvas. Its job is to make the zine-making loop fast enough that layout decisions can stay visual and tactile. Cloud persistence follows that same principle: a project keeps working in the browser while an encrypted, resumable sync job moves it to the user's private workspace.
Make the folded sheet the source of truth.
Features
Folded formats
- Eight-page Foldy Zine format with front cover, six interior pages, and back cover
- Square Foldy Zine format using the same folded page map with square page geometry
- Two-sided 16-page Foldy format with sixteen editable faces, seven reader spreads, and short-edge duplex imposition
- Fourteen-page Accordion format with twelve interiors, covers, six reader spreads, and a one-sided 4-by-4 construction map
- Saddle Stitch Booklet format with page counts from 4 to 100 pages in four-page increments
- Trifold Brochure format with outside and inside sheet sides
- Unigrid (NPS Map) format with two 6-by-2 sides, side switching, large-format paper presets, and title band support
- Portrait and Plus landscape document orientations update editable-face geometry, reader-spread direction, hit testing, and imposition without changing the physical paper
- Format switching updates page labels, editor grid, preview views, valid spreads, physical sheet orientation, and export sides
- Info dialog browses finished frame dimensions by format or paper size and groups keyboard shortcuts by workspace
- Drag-and-drop page assignment from the image tray
- Auto-Distribute fills the editor from the sorted asset tray
- Re-Order mode moves or swaps single pages, two-page spreads, and valid trifold spread groups
- Undo and redo controls cover layout edits, placement changes, text edits, imports, and reordering
Image placement
- Add Images portal accepts JPEG, PNG, and WebP files by picker, drag-and-drop, or clipboard paste
- Fill and fit modes for fast page framing
- Manual placement mode supports direct pan, stepped or continuous zoom, rotation, and image opacity
- Layered photo placement supports a background image plus additional foreground photo layers
- Rotation-aware freeform crop constraints and snap guides keep images inside the printable frame
- Per-page margin and image stroke controls with apply-to-all actions
- Image overlay colors can tint the whole page or only the placed photo
- Freeform and aligned crop states are preserved when switching modes
Source image Library
- Plus Library searches Openverse, the Library of Congress, and Internet Archive inside the Add Images portal
- License, collection-source, image, and document filters narrow results while themed searches surface zines, ephemera, maps, posters, botanical art, and protest graphics
- Multi-page archive items open in a page browser with region clipping where the source supports it
- Pinterest intake accepts public pins, pin.it share links, pinimg media, and public boards through the Library, paste, or drag-and-drop paths when enabled
- Imports retain creator, collection, license, source URL, resolution, parent-page, and crop provenance in the project
- Thumbnail-first background imports show immediate progress, then upgrade the asset to the original when the provider makes it available
Text and color layers
- Non-destructive text overlays stored with each page or spread
- Full-frame editor with direct dragging over the image and separate design/text panes
- Font, per-selection foreground color, opacity, highlight, size, tracking, line-height, padding, alignment, rotation, and curve controls
- Fixed or auto width and height modes, draggable box handles, overflow warnings, and top, middle, or bottom vertical alignment
- Left, center, right, and justified composition share source-index-aware wrapping, fallback line breaking, and English hyphenation
- Shared palettes provide saved swatches, direct hex entry, screen eyedropper support, a native browser picker, opacity, and reset actions for text, images, shapes, icons, paper, and frames
- Self-hosted display, blackletter, serif, mono, script, and typewriter font options
- Per-range colors and text geometry stay consistent across the live editor, Grid, Page Preview, Full Sheet, print, PDF, and PNG output
Shapes and icons
- Plus design elements include rectangles, circles, triangles, regular polygons, lines, arrows, stars, and speech bubbles
- Searchable Iconify access exposes more than 300,000 open-source icons without leaving the editor
- Shapes support direct move, resize, rotation, opacity, fill, stroke, stroke weight, rounded corners, and polygon-side controls
- Compatible icons support recoloring and stroke-weight adjustment; vector data is embedded when placed so saved projects and exports do not depend on the remote catalog
- Shift-selection can combine text and shape elements into one move operation while preserving each layer's own edit controls
Layer stacking
- Layers panel lists every photo, text, shape, and icon on the active page or spread in a single stacking order
- Drag-and-drop reordering moves any layer above or below another, freely interleaving all element kinds
- A canonical bottom-to-top layer order per page slot drives canvas rendering, hit-testing, and PDF/PNG export from the same stack
- Pages can contain text and design elements without requiring a background photo
Preview and export
- Grid view for the active format's complete editable page set
- Booklet and brochure preview modes with format-specific navigation
- Full-sheet imposition preview uses the exact physical size, cell map, artwork rotation, side order, and document orientation required by each format
- One Export dialog handles Quick Print, standard PDF, PNG, print-shop PDF, editable project files, and project bundles from the Print button, app menu, or Cmd/Ctrl+P
- Standard 144 PPI PDF export via jsPDF for sharing and simple printing
- Free printable output carries a per-face Zineroo watermark; Plus and Pro output is unwatermarked
- Print shop PDF export at 300 PPI with optional bleed, crop marks, and local CMYK conversion through uploaded ICC or ICM profiles
- Single-sheet PNG export and zipped multi-side PDF or PNG export for booklets, brochures, 16-page Foldies, and Unigrid output
- Optional ZIP export bundles the rendered PDF or PNG files with a portable project JSON archive
- Format-aware print guidance calls out details such as short-edge duplex printing for the 16-page Foldy
- Each export row reports its own generating and error state, supports multiple downloads in one visit, and links to the folding guide
- Lightweight preview images keep editing responsive while exports render from original uploads
Local and cloud persistence
- Autosaves the working project to IndexedDB with visible save status
- Restores the saved project on reload
- Stores original image blobs locally and regenerates runtime preview URLs when restored
- New Zine and project import preserve cloud projects in Projects while warning before a browser-only draft is replaced
- Object URLs and image caches are cleaned up on reset, removal, and import
- Project import reports progress and leaves an actionable recovery path when an asset cannot be restored
- Optional Plus cloud projects encrypt project state, originals, previews, thumbnails, titles, and text in the browser before upload; Zineroo and its storage providers cannot read the project contents
- A private cloud key, recovery key, and optional trusted-device unlock keep encrypted projects available across devices without a server-side content key
- Cloud autosave persists a local sync queue with byte-level progress, resumable upload plans, bounded retry backoff, offline resume, and generation-conflict handling
- The Projects library separates regular work from interrupted uploads, so failed or conflicted syncs can be retried, resolved in the editor, checked again, or removed without cluttering the library
- Projects supports search, recent and favorite views, grid or list layouts, opening progress, renaming, duplicating, manual versions, version restore, and a 30-day Trash before permanent deletion
- Desktop beta gate blocks phones and too-small windows while desktop-responsive scaling keeps the editor usable across larger viewports
Accounts, plans, and public workflows
- Supabase Auth powers sign in, email-code verification, sign up, password reset, session revocation after a password change, subscriptions, entitlement grants, and admin role checks
- Friendly Captcha or Turnstile protects authentication according to deployment configuration
- Stripe checkout and billing portal routes support monthly and yearly Plus and Pro plans
- Contextual upgrade dialogs preserve the current zine, explain the exact gated feature, carry checkout intent through account creation, and finalize entitlements after Stripe returns
- Access codes, gifts, campaigns, and manual grants can unlock entitlements without changing account roles
- Admin APIs manage users, manual Pro grants, revocation, access codes, and audit events server-side; hardened request paths keep implementation details out of client-facing errors
- Public React routes cover Home, Pricing, Projects, Classrooms, Teams, Support, Terms, Privacy, and the editor, with route metadata and sitemap coverage
- Zineroo for Teams adds a commercial-use license inquiry path, annual seat pricing, a one-event Workshop Kit, and reciprocal Classrooms navigation without inventing a self-service team workspace
- Version checks detect a newer deployed build and show a full-screen reload modal without risking local project data
Tech Stack
| Framework | React 18, React Router, and Vite |
| Language | TypeScript |
| Rendering | Shared DOM and HTML Canvas geometry for live editing, page previews, booklet spreads, imposition sheets, print, and export |
| Persistence | IndexedDB local autosave, resumable sync jobs, and workspace bindings with runtime URL rehydration; optional end-to-end encrypted Supabase cloud projects, versions, and 30-day trash for Plus users |
| Export | jsPDF for standard PDF output, Canvas blob export for PNG sheets, local CMYK PDF generation with ICC output intents, JSON archive/import, and a small in-app ZIP writer for bundled exports |
| Image pipeline | Canvas-generated preview blobs for editing, original uploads for print export |
| Image input | JPEG, PNG, and WebP via file picker, drag-and-drop, clipboard paste, Openverse, Library of Congress, Internet Archive, or optional Pinterest intake |
| Design elements | Native SVG/canvas shape rendering plus server-proxied Iconify search; selected vectors are normalized and embedded in the project |
| Text layout | Self-hosted fonts, source-indexed color runs, TeX-style line breaking, and English hyphenation shared by DOM and Canvas renderers |
| Layout sizing | ResizeObserver-driven editor frame sizing for stable page previews |
| Paper presets | US Letter, US Legal, US Tabloid, A5, A4, A3, and Unigrid-specific 18" x 24", 21" x 35", 24" x 36", 30" x 42", and 420 x 594 mm presets |
| Formats | Foldy Zine, Foldy Zine (Square), 16-page Foldy, 14-page Accordion, Saddle Stitch Booklet, Trifold Brochure, and Unigrid (NPS Map) |
| Typography | Self-hosted Anton, Bebas Neue, Bungee, Alfa Slab One, UnifrakturMaguntia, Playfair Display, Alegreya, Lora, EB Garamond, Space Grotesk, Courier Prime, Special Elite, and Caveat web fonts |
| Hosting | Vercel-hosted Vite build with serverless API routes |
| Auth and billing | Supabase Auth, Postgres, and Storage; browser-side Web Crypto vault and recovery keys; Stripe Checkout, webhooks, access-code entitlements, and configurable captcha protection |
| Observability | Vercel Analytics and Speed Insights plus product events for exports, upgrade gates, checkout, Library use, and public conversion paths |
Architecture
Source structure
api/ Vercel API routes for admin, billing, auth, Library providers, Pinterest, Iconify, cleanup, and access codes server/ protected billing, open-archive adapters, provider validation, design-element proxying, and shared security helpers src/ main.tsx React Router shell for public pages, editor, Projects, account returns, and admin routes App.tsx editor state, formats, image intake, clipboard and Library flows, history, and export orchestration canvas/ shared preview, booklet, imposition, and export rendering components/ image portal, Library, grids, frame editor, text, photos, shapes, icons, inspector, previews, and dialogs context/ auth, private cloud vault, update state, and the active cloud-project workspace/sync bridge dom/ drag targets and text-overlay geometry shared by DOM and canvas rendering domain/ project model, seven format maps, text-color ranges, shape/icon geometry, Library provenance, and interactions hooks/ viewport gating, checkout finalization, update checks, and confirmation helpers pages/ Home, Pricing, Projects, Classrooms, Teams, Support, policy pages, and admin console crypto/ browser-worker encryption format, key hierarchy, recovery keys, and authenticated cloud objects services/ assets, clipboard, Library/Pinterest/Iconify clients, IndexedDB sync, cloud crypto, entitlements, JSON, ZIP, PDF, ICC, and analytics styles/ app shell, public site, media portal, Library, grid, frame editor, inspector, preview, account, projects, and export UI
Core project model
type ZineProject = {
version?: number;
format?:
| 'zine'
| 'zine-square'
| 'foldy-16'
| 'accordion-14'
| 'saddle-stitch'
| 'trifold'
| 'unigrid';
pageCount?: number;
unigridTitleBand?: {
enabled: boolean;
text: string;
};
title: string;
assets: ZineAsset[];
pages: PageSlot[];
settings: DesignSettings & {
orientation: 'portrait' | 'landscape';
};
};
type TextOverlay = {
id: string;
text: string;
fontFamily: string;
fontSize: number;
color: string;
colorRanges?: Array<{ start: number; end: number; color: string }>;
opacity: number;
backgroundColor?: string;
backgroundOpacity: number;
tracking: number;
lineHeight: number;
padding: number;
x: number;
y: number;
rotation: number;
widthMode?: 'auto' | 'fixed';
boxWidth?: number;
heightMode?: 'auto' | 'fixed';
boxHeight?: number;
verticalAlign?: 'top' | 'middle' | 'bottom';
curve: number;
align: 'left' | 'center' | 'right' | 'justify';
};
type ShapeOverlay = {
id: string;
kind: 'rectangle' | 'circle' | 'triangle' | 'polygon' | 'line' |
'arrow' | 'star' | 'speech-bubble' | 'icon';
x: number;
y: number;
width: number;
height: number;
rotation: number;
fillEnabled: boolean;
fillColor: string;
strokeColor: string;
strokeWidth: number;
cornerRadius?: number;
sideCount?: number;
icon?: IconifyIconData;
iconStrokeScale?: number;
opacity: number;
};
type PhotoLayer = {
id: string;
assetId?: string;
fit: 'cover' | 'contain' | 'spread';
crop: { x: number; y: number; zoom: number; rotation: number };
freeformCrop?: { x: number; y: number; zoom: number; rotation: number };
freeform: boolean;
margin: number;
imageBorder?: number;
imageStroke?: string;
opacity?: number;
overlayColor?: string;
overlayOpacity: number;
overlayPhotoOnly?: boolean;
};
type ZineAsset = {
id: string;
name: string;
type: string;
blob: Blob;
width: number;
height: number;
previewBlob?: Blob;
previewWidth?: number;
previewHeight?: number;
provenance?: AssetProvenance;
};
type PageSlot = {
id: number;
assetId?: string;
fit: 'cover' | 'contain' | 'spread';
crop: { x: number; y: number; zoom: number; rotation: number };
freeformCrop?: { x: number; y: number; zoom: number; rotation: number };
freeform: boolean;
margin: number;
imageBorder?: number;
imageStroke?: string;
opacity?: number;
overlayColor?: string;
overlayOpacity: number;
overlayPhotoOnly?: boolean;
photoLayers?: PhotoLayer[];
textOverlays?: TextOverlay[];
shapeOverlays?: ShapeOverlay[];
layerOrder?: string[];
spreadId?: string;
spreadSide?: 'left' | 'center' | 'right';
spreadPageIds?: number[];
spreadIndex?: number;
spreadLength?: number;
spreadColumns?: number;
spreadRows?: number;
spreadColumnIndex?: number;
spreadRowIndex?: number;
};
The project remains a compact, portable document: title, physical format, local asset library, page slots, optional Unigrid title-band state, and design settings including the finished-face orientation. Page slots own background and foreground photo placement, text and per-range color state, vector design elements, one cross-kind stacking order, overlay color, and spread geometry. Assets can also retain source and license provenance. Runtime object URLs are stripped before autosave, project export, and cloud persistence, then rebuilt when the project is restored. Uploaded or downloaded originals stay available for export while generated preview blobs keep the editor responsive.
Layer model
type SlotLayer =
| { id: string; kind: 'photo'; placement: PhotoPlacement }
| { id: string; kind: 'text'; overlay: TextOverlay }
| { id: string; kind: 'shape'; overlay: ShapeOverlay };
function allLayersForSlot(slot: PageSlot): SlotLayer[];
function reorderLayer(slot: PageSlot, id: string, beforeId: string | null): PageSlot;
Each page slot keeps a single bottom-to-top layerOrder list of element ids spanning both its photo layers, text overlays, shapes, and icons. allLayersForSlot resolves that order into a typed stack that canvas rendering, hit-testing, and the Layers panel all read from instead of walking element families separately, so dragging a layer in the panel changes the same order the page actually renders in. normalizeLayerOrder reconciles the stored order against each slot's current layer ids on every edit, so older projects and pages missing a stored order still resolve to a sensible default stack.
Preview asset pipeline
const PREVIEW_MAX_LONG_EDGE = 2560;
function imageForAsset(asset: RuntimeAsset, source: 'preview' | 'original' = 'preview') {
const url = source === 'preview' ? asset.previewUrl ?? asset.url : asset.url;
return loadImageFromUrl(url, `Could not load ${asset.name}`);
}
The editor renders from preview-sized blobs when possible, but PDF and PNG export request the original image source. That keeps drag, crop, and preview interactions light without lowering print output quality.
Library intake pipeline
interface LibrarySourceAdapter {
search(query: string, filters: LibrarySearchFilters, cursor?: string): Promise<LibrarySearchResult>;
getDocumentPages(externalId: string, cursor?: string): Promise<LibraryDocumentPages>;
resolveMedia(externalId: string, variant: LibraryMediaVariant, options?: ResolveOptions): Promise<ResolvedLibraryMedia>;
}
Openverse, Library of Congress, and Internet Archive integrations share one server-side adapter contract, while provider-specific validation, allowlists, redirect limits, bounded reads, caching, authentication, and rate limits stay behind the API. The client receives normalized result cards and materializes the chosen image or clipped document region into a normal Zineroo asset. Pinterest uses the same normalized item and provenance model, but has a separately gated URL classifier and unfurl path for pins, share links, media URLs, and boards. Remote results never become permanent runtime dependencies: imported bytes and their source metadata are saved with the project.
Text and vector rendering parity
Text layout preserves source-string offsets while wrapping, justifying, hyphenating, and breaking long tokens, so a foreground-color range applied to a selection reaches the same characters in the textarea mirror, curved DOM glyphs, flat canvas runs, and print output. Shapes and Iconify vectors use normalized page coordinates and document-space stroke widths. Every view resolves them through the same layer order and geometry helpers; selected icon data is embedded in the project so an archive outage cannot change an existing composition.
Format-aware sheets
const IMPOSITION_PAGES = [7, 6, 5, 4, 8, 1, 2, 3];
const TRIFOLD_OUTSIDE_PAGES = [2, 6, 1];
const TRIFOLD_INSIDE_PAGES = [3, 4, 5];
const ACCORDION_PRINTED_SIDE = { columns: 4, rows: 4, cells: /* explicit 14-face map */ };
const FOLDY_16_SHEET_SIDES = [
{ id: 'foldy-16-side-a', columns: 4, rows: 2, cells: /* explicit map */ },
{ id: 'foldy-16-side-b', columns: 4, rows: 2, cells: /* explicit map */ },
];
const UNIGRID_COLUMNS = 6;
const UNIGRID_ROWS = 2;
const UNIGRID_SIDE_PAGE_COUNT = 12;
The full-sheet preview and PDF export use the same format definitions, so the visual preview matches the file that is saved. Foldable zines render one imposed sheet with the top row rotated for folding. The Accordion leaves its two construction cells blank; the 16-page Foldy defines two explicit duplex maps. Trifolds render separate outside and inside sides for double-sided printing. Saddle Stitch booklets render the required imposed sheet sequence so longer booklets can be exported as printable PDF sheets. Unigrid projects render two large-format sides with explicit 6-by-2 cell geometry and format-specific paper choices. A saved document orientation changes logical face geometry and adds the required imposition rotation while the physical sheet dimensions remain stable.
Reorder model
type ReorderDropPosition = 'before' | 'after' | 'on';
type ReorderUnit = {
kind: 'single' | 'spread';
length: number;
startIndex: number;
};
Reordering treats a spread as one unit, whether it spans two pages or a full three-panel trifold side. Boundary drops move content before or after another slot, while direct drops swap compatible blocks. Spread metadata is normalized after each move so paired and grouped pages stay together. Unigrid uses side-aware spread controls instead of free reordering, because its cells are tied to a fixed folded-map grid. The two new fixed formats reuse the same machinery for six Accordion reader pairs and seven 16-page Foldy pairs instead of adding view-specific reorder rules.
History model
The editor keeps an undo/redo stack around complete project snapshots, with grouping for continuous text and control edits so sliders and typing do not flood history. The frame editor keeps temporary draft state while open, then commits the finished overlay state back into project history when it closes. Document-orientation changes are one reversible history step, and mixed text-and-shape moves commit as one operation rather than a sequence of unrelated element updates.
Export flow
async function renderPdfBlob(
options = STANDARD_PDF_EXPORT_OPTIONS,
sides = sheetSidesForProject(project),
) {
const paper = sheetSizeForProject(project);
const pageSize = pdfPageSizeForExport(paper, options);
const orientation = pageSize.width > pageSize.height ? 'landscape' : 'portrait';
const pdf = new JsPDF({
orientation,
unit: 'pt',
format: [pageSize.width, pageSize.height],
});
for (const [index, side] of sides.entries()) {
if (index > 0) pdf.addPage([pageSize.width, pageSize.height], orientation);
const sheet = await renderImpositionCanvas(project, pdfTrimCanvasWidthForExport(paper.width, options), {
showGuides: false,
imageSource: 'original',
sideId: side.id,
watermark: exportWatermark,
});
const preparedSheet = preparePrintCanvas(sheet, options);
pdf.addImage(preparedSheet.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, pageSize.width, pageSize.height);
}
return pdf.output('blob');
}
Export is canvas-first. The app renders the printable side or sides, then routes that same rendering to PDF, PNG, browser print, or ZIP. The physical sheet helper preserves portrait Accordion output and landscape output for the other formats, while a capability-based side list prevents two-sided formats from silently losing a side. Standard PDFs prioritize quick sharing and home printing; Free output is watermarked. Print shop PDFs render at 300 PPI, can add bleed and crop marks, and can flatten through a local CMYK conversion path when the user supplies a printer profile. Project export is separate but can be bundled with the rendered artifact: it serializes the project state and embeds original image blobs as data URLs so the work can move between browsers without runtime preview blobs or object URLs. Plus cloud save uses a separately encrypted manifest and authenticated objects for originals, previews, and thumbnails; the browser hydrates them back into blobs only after the private vault is unlocked.
Private cloud sync model
type CloudSyncJob = {
projectId: string;
expectedGeneration: number;
revision: number;
state: 'pending' | 'syncing' | 'error' | 'conflict';
retryCount: number;
retryAt: number;
project: ZineProject;
};
type CloudSyncStatus =
| 'Saved in browser'
| 'Syncing to cloud…'
| 'Saved in cloud'
| 'Offline — will resume'
| 'Cloud sync needs attention';
The cloud workspace is deliberately queue-based instead of treating a save as a one-shot request. After a short idle delay, a meaningful edit is serialized into IndexedDB with its expected cloud generation and an upload plan. The app reports preparation, upload, and commit progress; retries transient failures with a capped exponential delay; wakes again when the browser comes back online; and restores the active cloud-workspace binding after a reload. A generation mismatch becomes an explicit conflict rather than an overwrite, with an editor path to open the newer version or retain the local work as a separate copy.
Development and release paths
Local feature work runs in one Docker-backed Test Studio that owns its loopback Supabase stack, migrations, seed data, production-secret isolation, and switchable Free, Plus, Pro, gift, and campaign personas. Release classification is separate: presentation-only registered copy or CSS can use a validated live-presentation lane, while React structure, behavior, auth, APIs, editor rendering, and security changes use the normal application build, tests, and deployment path. That separation keeps fast public copy updates possible without pretending product-code changes are low risk.
Design Notes
The UI uses a print-shop vocabulary: heavy rules, square controls, hard shadows, paper tones, and a narrow red accent. The panels map to the workflow: image tray on the left, working sheet or frame editor in the center, design controls on the right, and a compact top bar for Grid, Editor, Preview, account, project, and export actions. A focused Add Images portal keeps uploads, paste, open archives, and Pinterest intake in one place; the full-frame Add control keeps text, shapes, and icons next to the composition. The app favors visible mechanics over hidden menus because the user is making a physical object and needs to see the consequences of each change immediately.
The most important view is the full sheet. Individual pages matter, but the final object is the folded imposition. Keeping that view one click away makes mistakes visible before the user prints, especially for the two-sided 16-page Foldy, the Accordion's blank construction cells, and top-bound landscape variants. A beta desktop gate and responsive editor scaling are also part of that design decision: until the mobile editor is ready, the app explicitly blocks phones and windows below the editor's minimum scale instead of offering a cramped, unreliable canvas.