# Lolly - full documentation > Lolly: constraint-first, template-driven platform for generating production-ready creative and content assets at scale. Every English docs page in one file, in the order https://lolly.tools/llms.txt lists them. Each section opens with the page title, the URL of its markdown twin and the URL of the HTML page. Engine 1.184.0. The short guide for agents is https://lolly.tools/agents.md. --- Source: https://lolly.tools/info/index.md Page: https://lolly.tools/info/index.html # Lolly - Landing page copy Free creative tools, with your rules. Create studio-quality media files: no account, no card, private, and 100% open source. --- Source: https://lolly.tools/info/start/quickstart.md Page: https://lolly.tools/info/start/quickstart.html # Quickstart Lolly turns your rules - colours, type, layouts, logic - into tools anyone can use to make finished files: images, PDFs, social cards, video, by filling in a few fields. There's little to learn and nothing to upload: making and exporting runs on your device, online or off. This is the one page to read first. Two things get you productive: **make Lolly yours** and **bring in what you already have** (your design files and tokens). Everything else is a link away. > New to Lolly and just want to make something? [Make something in 60 seconds](/info/make-something.html) walks you through three, or [open the app](/#/), pick any tool from the gallery, fill in the blanks and hit **Export**. Come back here when you want it wearing *your* brand. ![The Utilities view - the on-device workhorses like Strip Hidden Data, Compress PDF and Convert Image, all in one place](/t/url-shot?url=%2F%23%2Fu&width=1440&height=900&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone!important%7D&tolerance=0.03&format=svg&walker=1&dark=1&filename=use-utilities&try=1) ## 1. Make it yours - configure your Design System Your brand in Lolly is a small **design-tokens** document - colours, fonts and a few rules - that every tool renders against. Set it once and everything you make is on-brand by construction, not by review. There are three ways in; pick the one that matches where your brand already lives. ### Start from scratch (the design system builder) First run drops you on the **gallery**, with a short welcome dialog over it offering three ways in - **Make it yours** (the Brand Studio at `#/start`), **Bring your design** (drop a Figma, Penpot, InDesign or PDF file and it opens as an editable layout - the fastest route to [Bring in what you already have](#2-bring-in-what-you-already-have) below) and **Explore the community tools** - plus a row of languages if English isn't yours. Take the first card and you land in the [**Brand Studio**](/info/brand-studio.html). Give it a name and a primary colour and Lolly *derives* a complete, accessible palette from it - light/dark surfaces, text, accents - using the same colour maths the engine uses everywhere else. ![The Brand Studio's Colours room on a fresh install - one live chip, one field, and the whole of the first decision](/t/url-shot?url=%2F%23%2Fstart%3Farea%3Dcolor&width=1440&height=740&dpi=192&waitMs=1800&format=svg&walker=1&localize=1&dark=1&filename=brand-colours) Pick a font, and you have a working brand in under a minute. From there the studio's six rooms - Overview, Colours, Type, Logos, Tokens, Files - let you take it as far as you like, in any order, refining any of it whenever you come back. The dashboard's **Design system** tab (`#/d`) shows the result read-only and points back at `#/start`, which is where the editing happens (unless you're on a brand-locked build of Lolly, where the brand is fixed and there's nothing to change). ### Import a brand you already have If your brand is already captured as design tokens - from **Penpot**, **Tokens Studio** (Figma) or any plain **DTCG** file - bring it in wholesale rather than retyping it. Two routes: - **In the app:** the [design system builder: Brand Studio](/info/brand-studio.html) (`#/start`) takes it through **Add from…** at the foot of its room rail - a token file, a Penpot export, an SVG or a `LollyBrand` pack. Drop it in and the palette lights up. - **From the command line**, to stand up a reusable brand pack: ```bash # a monolithic tokens.json, a one-file-per-set directory, or a Penpot project archive npm run ingest:brand -- ./my-tokens.json --name acme --label "Acme" --activate ``` `ingest:brand` accepts all three containers Penpot / Tokens Studio export the same document in - a single `tokens.json`, a directory (`$metadata.json` + per-set files) or a `project.penpot` archive. With `--activate` it registers the brand as a profile, switches to it and rebuilds the catalog. See [Configuration](/info/configuration.html) for how brand packs and profiles fit together. ### Tune it in the app Once a brand is active, keep shaping it in the [**Brand Studio**](/info/brand-studio.html) (`#/start`) - change a colour or a role and every preview across the app updates as you type. (The dashboard's **Design system** tab at `#/d` *shows* the brand read-only; the Studio is where you edit it.) ![The Dashboard's Design-system tab - the active brand shown read-only](/t/url-shot?url=%2F%23%2Fd%3Ftab%3Dbrand&width=1440&height=900&dpi=192&waitMs=1800&format=svg&walker=1&localize=1&dark=1&filename=dashboard-brand) The same brand is summarised on the **Profile → Your brand** card. Fonts are real: pick from Google Fonts and Lolly stores the file **on your device** as a brand asset, so your typography travels offline and nothing is fetched at render time. When you're happy, **export the brand as a `LollyBrand` pack** - a single file a colleague can import to get the exact same palette, fonts and rules. That's how a brand moves between people and machines without a server in the middle. > **Brand tokens round-trip both ways.** Because Lolly's brand *is* DTCG tokens - the format Penpot reads and writes natively and Tokens Studio brings to Figma - the palette you design *with* and the palette Lolly *enforces* are one document, not two lists you keep in sync by hand. See [Design Tokens](/info/design-tokens.html). ## 2. Bring in what you already have You don't start from a blank page. Lolly opens the design work and the open formats you already own. ### Open-source design files Finished work in **Figma, Penpot, Illustrator, InDesign or any SVG app** doesn't have to stay locked in the app you drew it in. Open **Design**, click **Import a design**, and the file opens as a *living layout* - not a flattened picture. Every layer becomes an editable box: text stays retypable, shapes stay shapes, images land in your library and complex vector art is preserved faithfully. It arrives already conformed to your brand faces and colour rules. | You have | Bring it in as | |---|---| | A Figma frame | Native `.fig` (File → Save local copy), or an SVG export | | A Penpot design | Its `.penpot` export, or any SVG | | An Illustrator file | Native `.ai` (PDF-compatible) or `.pdf` - opens directly | | An InDesign layout | `.idml` (File → Export → InDesign Markup) | | Anything else | **Any SVG** - the universal door in | The whole import happens **on your device** - the file is parsed in your browser and nothing is uploaded. Full details, and exactly what carries over, are in [Import a design](/info/design-import.html). Got a **PowerPoint deck** instead? Drop the `.pptx` on any upload surface to file its slides as assets, write a new deck from Markdown in **Markdown Slides**, or run **Rebrand** to get the same deck back re-themed, charts and animations intact. ### From a one-off to a template Here's the payoff: an imported layout is an ordinary Design session, so once you **save** it, it lives at a URL. Anyone with Lolly can open that URL, change the words, swap an image and render their own version - no design app, and the locked parts stay locked. A once-off design becomes a reusable tool. That's the whole idea, reached without writing a line of configuration. ### Open data and open tools The [community tool set](/info/builders.html) is open source and brand-agnostic - QR codes, street maps, filters, privacy utilities - and it renders against *your* brand the moment you activate it. Feed tools your own open data too: paste or drop a **CSV** or **JSON** table and a tool's repeating fields fill from it, one finished asset per row. ## 3. Make something, then share or automate it With a brand active and your material in hand, every tool produces a finished file: - **Render** any tool to **SVG, PDF, PNG, JPG, WebP, video** and more - at true print sizes and physical units when you need them. See [Exporting & formats](/info/exporting.html). - **Share a link.** Every tool state is a URL, so a finished asset is reproducible and parameter-addressable - commit the link, regenerate on demand. - **Do it in bulk.** Drive a template from a spreadsheet in the [batch grid](/info/exporting.html): one finished asset per row. - **Automate it.** The same render runs from the [CLI](/info/cli.html) and from an [AI agent](/info/ai-agents.html) - a URL is the API. "A URL is the API" is literal. The chart below was not drawn by anyone: its type, its heading and its whole data table were typed into the address bar, and the same link renders the same chart on any device. ![An area chart of monthly signups, every value of which arrived as a query parameter rather than a click](/t/url-shot?url=%2F%23%2Ftool%2Fchart%3Fct%3Darea%26d%3DMonth%2CSignups%250AJan%2C120%250AFeb%2C180%250AMar%2C265%250AApr%2C340%250AMay%2C455%26t%3DSignups%2520this%2520year%26full&width=1440&height=900&dpi=192&waitMs=2600&cropSelector=%23tool-canvas&walker=1&format=svg&dark=1&filename=vt-d3-url-chart) ## Where to go next Three pathways, depending on what you're here to do: - **[Lolly for Creators](/info/creators.html)** - you make things. The advantages, and how to get the most out of the app. - **[Lolly for Builders](/info/builders.html)** - you author tools, integrate and deploy. The technical documentation. - **[Lolly for Operators](/info/operators.html)** - you're responsible for brand, security and rollout across an organisation. --- Source: https://lolly.tools/info/start/make-something.md Page: https://lolly.tools/info/start/make-something.html # Make something in 60 seconds By the end of this page you will have one finished file on your own device: a picture for the noticeboard, a PDF for the printer or a vector a print shop can use at any size. You need no account, no design training and nothing set up first, and after your first visit it all works with the internet off. Open [the app](/#/) and you land on the **gallery** - every tool as a card. Click one and it opens in two halves: the controls down one side, a live preview on the other. Change a control and the preview changes as you type, and what the preview shows is what the file will be. A few things save time before you start: - **The little pictures on a card are a shortcut.** Where a card shows a strip of small examples, click the one you like and the tool opens already filled in with exactly that look. Changing a few words beats starting from nothing. - **Getting the file out is the same move every time.** Press **Export** on the **Export | Save** pill - at the foot of the controls on a computer, floating at the bottom of the screen on a phone. Pick a format at the top of the panel that opens (it lists only the ones that tool makes), then press **Download**. - **Or skip the download.** **Copy**, in the same panel, puts the picture straight on your clipboard to paste into a message. The full format list is in [Exporting and formats](/info/exporting.html). Three ordinary jobs follow. Pick the one that sounds like yours. ## A code for the invitation A square code people point a phone at: on a party invite, a noticeboard poster or a card on your stall. 1. Open **QR Code Generator** from the gallery, or type `qr` in the search box to find it. 2. Put the web address in **URL** - the map, the sign-up form, your shop page. 3. Set **Module color** and **Background** to the two colours already on the invitation. 4. Leave **Quiet zone** at 4. That is the plain margin a phone camera needs to find the code, so scan it yourself before you print anything. 5. **Export**, choose **SVG** for print or **PNG** for a screen, then **Download**. About twenty seconds, once you have the web address to hand. If the code is going somewhere it might get scuffed or rained on, set **Error correction** to **High (30%)** first. It packs in enough spare information that a damaged code still reads. ## Feeling shy? Your voice is plenty A voice note becomes a finished video: a moving waveform, your words on the cover, your colours. No camera, no makeup needed. 1. Open **Audiogram** and drop your recording into **Audio** - a voice memo from your phone works as it is. 2. Type the **Title** and **Subtitle** - the episode name, who is speaking, the date. 3. Pick a **Style** for the waveform - bars, a ring, a wave - and press play to watch it move with the sound. 4. **Export**, choose **MP4** for most places or **WebM**, then **Download**. A couple of minutes, most of it listening back to your own clip. The same file also exports as plain audio (MP3, M4A) or a still cover (PNG), so one recording covers the podcast feed, the social post and the artwork without touching another tool. ## A photo, ready for the poster One photo, treated so it prints well and matches everything else you have made. 1. Open **Filter**. Under **Image**, press **Choose asset…** and pick a photo, or **Upload your own…** to use one from your device. 2. Pick an **Effect**: **Halftone** breaks the photo into printed dots, **Colour treatment** re-lays it in two flat colours and **Posterize** flattens it into a few blocks of tone. 3. Work the sliders that appear under it. On Halftone, **Grid size** sets how far apart the dots sit and **Dot size** how fat each one gets. 4. Open the **Colour** section and nudge **Contrast** and **Saturation** until the picture reads clearly from across a room. 5. **Export**. Pick **PNG** for anything, or **SVG** on an effect badged *vector* so it stays sharp at poster size. Then **Download**. About a minute, most of it spent choosing the photo. Click down the **Effect** list once to see what your own photo does under each. There are seven, and they look very different on a face, a building and a plate of food. ## Send it or keep it - **Send it.** Every control you touched is written into the page address, so **Share** in the export panel hands you a link that reopens the same design on someone else's screen. One thing it does not carry: a photo you uploaded from your own device stays there. See [Using Lolly: Sharing your work](/info/using.html#sharing-your-work). - **Keep it.** **Save**, the tick beside **Export**, stores what you typed as a session you reopen with **Continue** on the tool's card, and it stays on your device. See [Using Lolly: Saving and continuing](/info/using.html#saving-continuing). Between them that covers most of day one: hand the finished thing to someone and be able to change a date on it next month without building it again. ## Make it wear your colours Everything above works before you have told Lolly a thing about yourself. The second visit's job is to hand it your colours and your fonts once, so every tool arrives already filled in with them and you stop typing the same hex code into every field. The [Quickstart](/info/quickstart.html) walks that through, starting from a single colour it builds a full palette out of. This is polish, not a prerequisite. The codes, names and posters you made today go on working exactly as they are, and nothing you make is waiting on a setting you have not found yet. When you want the rest of what the app does - the other tools, printing properly, video, doing fifty of something at once - [Lolly for Creators](/info/creators.html) is the tour. [Using Lolly](/info/using.html) is the practical manual for driving the app once it is open. --- Source: https://lolly.tools/info/start/faq.md Page: https://lolly.tools/info/start/faq.html # FAQ Frequently asked questions shown in the accordion on the `/info` landing page. **How to maintain:** each `##` heading below is a question; everything beneath it (up to the next `##`) is the answer. Answers use the same lightweight markdown as the rest of the site - separate paragraphs with a blank line. Add, remove or reorder questions here and re-run `npm run build:info` (or `npm run dev:web`). Everything above the first `##` (this title and these notes) is ignored by the build. ## What happens when I opt-in on the /profile page? When you first use Lolly, everything you type anywhere is fully private until you deliberately want that information out there via media or a share link (if online). With the opt-in selected, the profile details you choose are sealed into what you make, naming you as the source. Nothing is included without you picking it. Lolly produces a large volume of content. We take a strict data minimization approach to prevent risk. ## Was Lolly "vibe coded"? Lolly was developed with AI-assisted coding, AI-assisted discovery and, in many places, AI-assisted content, using a mix of models and vendors, including those from public-cloud frontier companies. As of this writing Lolly contains zero known security vulnerabilities in its supply chain, and commits to rapid security response practices when CVEs surface. A human created the architecture, curated the code with intention and art directed the experience. Most importantly, Lolly stands on the shoulders of decades of open source innovation from real experts around the world. A deterministic build-gate exists in Lolly's codebase to keep code and documentation coherent to the average reader and "de-slop" the experience. This may make it difficult for proprietary synthetic enumeration of origin. That is unintentional. **Generative AI disclosure:** - **LLM-written code:** Opus 4.8, Gemini 3.1, Qwen3-Coder-Next (this list may expand) - **LLM discovery:** Gemini 3.1, Fable - **Documentation:** Sonnet 5 - **Open source libraries:** their respective authors, expressed in the SBOM, comments and file headers This list does not include models vendored into Lolly. **Human claims:** - **Architecture:** Andy Fitzsimon - **Art direction:** Andy Fitzsimon - **Human-written code:** Andy Fitzsimon - **Ideation, review and feedback:** Ravan Naidoo, Matthias Eckermann, Kelly Andrews, Ryan Kleeman, Peter Chamalian, the Penpot Community (list not exhaustive) ## What are the feature flags? Feature flags turn parts of Lolly on or off. Usually an administrator controls these - with Lolly, you are in control. ![Every feature flag is a switch you own, sitting in your own profile rather than an administrator's console](/t/url-shot?url=%2F%23%2Fprofile%3Ffocus%3Dfeature-flags&width=1440&height=1800&dpi=192&waitMs=2000&format=svg&cropSelector=%23feature-flags-section&walker=1&dark=1&filename=pd-feature-flags) ## How do I get the mobile or desktop apps? Anybody can distribute their own apps, the tools and configuration of those apps should vary widely depending on what audience it's intended for. So there's no one app unless you made it or someone relevant gives it to you. ## Why the name "Lolly Tools"? **Lolly** because freedom is sweet, and because in Australia, New Zealand and Britain a lolly is a sweet. **Tools** because a tool sits still until you pick it up. It does not run when you are not using it, and it does not watch you while you are. ## What hurdles could I expect adopting Lolly? Lolly slots in wherever you already generate files - the CLI is the same engine as the App, so a pipeline run at 2am can't drift from what a person previews in a browser. The friction to adoption is rarely technical; it's organisational. Expect these: **A curated brand catalog has to be authored.** Lolly is a platform, not a finished pack of your templates. For a *governed rollout*, someone defines the shared asset catalog (logos, palettes, fonts as permanent IDs) and writes the manifest + template for each output type. Individuals don't have to wait for that, though - in the open app anyone can ingest their own files into the catalogue and build tools in Design from day one. **No git required to contribute.** Designers make their own tools and templates in the app, then share them with peers or submit them to whoever owns the deployment for default inclusion. **It's deliberately narrow - frame it that way.** Lolly is not for bespoke or hero content. It *is* your personal DAM - hydrated and supercharged by your design system, tools and catalog - and it *does* have an open canvas (Design), but even there colours, type and assets conform to the active design globals, so free arrangement stays inside the system. Judged against Figma or Canva it will look limited. Judged as what it is - operationalised, recurring, massive-scale asset generation - nothing competes. The wrong framing is the most common set-back. **Change management on the producing side.** Existing processes work today, even if the output is off-brand. Re-pointing them at the engine means re-testing re-learning, and "we can already make files" becomes the excuse not to migrate. Start by converting one high-visibility production quality output and showing the before/after side by side. Lolly lifts everything up. ## What makes utilities different from tools? **Basic Answer →** Utilities don't always need to render and therefore can get a different UX. **Real Answer →** The reason utilities are hostable inside Lolly Tools is to add yet-another 'convenience layer' of defence to disincentivise data-exfiltration. Why? Because it is known that every day, people take **confidential content they already have** and hand it to a random website to perform one small mechanical operation: - "**Compress this PDF**" → uploads a contract / payslip / board deck to unknown entities. - "**convert HEIC to JPG**" → uploads personal photos (with GPS EXIF) to an ad-funded host - "**crop / resize this image**" → uploads a product screenshot or unreleased asset - "**format this JSON**" / "decode this JWT" → pastes API responses, tokens, secrets into a formatter - "**merge these PDFs**" → uploads **two documents that should never share a server** These sites and their massive clone long-tail are **not trustworthy by default** with unknown retention, unknown jurisdictions, unknown subprocessors and an ad/affiliate business model that has every incentive to keep what you give them. The operation is trivial; the **content is the cost.** We win the war for governance with excellent convenience and service. ![The Utilities view collects the mechanical jobs people usually hand to a random website, all running inside Lolly instead](/t/url-shot?url=%2F%23%2Fu&width=1440&height=900&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone!important%7D&tolerance=0.03&format=svg&walker=1&dark=1&filename=use-utilities&try=1) ## Can Lolly edit and render my Figma, Penpot, Illustrator or InDesign files? Yes. Open **Design** and click **Import a design**: it accepts a native Figma **.fig** (Save local copy), a Penpot **.penpot** export, an Illustrator **.ai** or **.pdf**, an InDesign **.idml** (File → Export → InDesign Markup) or **any SVG** (the wide door - almost any design app exports it). No account, no plugin and no design app licence needed. ![Design's open canvas - Import a design sits in the toolbar's Lolly menu](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17VVRa9swEP41ekyQLrbjPOyhVeeOrSsbG3lXbLkYZCnISmn264tO9irFhdJCoZQIpBNn9N2dvu_k3cOX1opeEuCTpeNkG0BDy8ljZe28pyDA74UlUC4WOyt0sxgOthW1JJAD9wv1y6rFQWCDMB6BAK-NdqLTBLg2thcqeAnk68vaKGOXg-yFdl29dPLBef8VAZ6V_qjUTloCvO-aRvlcc0QdhB58iCWDMVNnD1iSUIOcAvASY2qsEUeex-XSOEJ6koaT_6Q1iY89IY4mw4TCmmJYc9BNZKPQ00ywxxJS-BlqcnL2KfJF3xhZf3WdwwsMd8xZ4VEgEF6EGornaH-ByW_iHkVAj-aAtpcO7Y1R6kggX1Uz7ext1wt7PNEOk7BZ7UbtYCpKtj4NZ_YE-Doh_hOwHnff-1K_M83xlPmyiJhn8AbmL5C61nZSN8g00OlFoK2xkyjCRofOBro3g5N2OdPE2PqxIGjL1iCCIPA1SPSQpQ_B6qyIVyiCsp8s-_Gn2P76fpHfVlt6WV3_Lv5e5_6mul7cSVRFhiWhOIrsaaVTMbKZaUWNbQ-VMncGN__b_fwn-CAKeAQ&width=1360&height=850&dpi=192&waitMs=3000&format=png&localize=1&dark=1&filename=design) Layers arrive as editable boxes on the open canvas: text stays retypable, shapes stay shapes, images join your own image library and type and colours conform to the brand globals. Save it and the layout becomes a reusable, URL-addressable template anyone with Lolly can refill - and you can mix in live tools (a QR code, a chart) that re-render on load. From there it renders like anything else in Lolly - SVG, PDF, PNG and the rest, reproducible from its URL. See [Import a design](/info/design-import.html). ## Can I share my work as a file instead of a link? Yes. When a link can't carry everything (your own photos, long text), the Share dialog says exactly what would go missing and offers a **.lolly** file instead: one file holding the design, the images it uses and, if you choose, the tool itself. You decide how much travels - your name and details go in only if your profile opts in, licensed art is held back unless you include it, and whoever opens a file that carries a tool is asked whether they trust it before it can run. See [Sharing your work](/info/using.html#sharing-your-work). ## Can two people work on the same design without the internet? Yes. One person shares an invite (a link, a QR code or a short code), the other accepts, and both devices hold the same session live - presence, focus rings and all. It works on any shared network, including a phone hotspot in a basement, because there is no server in the middle. See [Working together](/info/collaborate.html). ## Where did the SUSE-branded tools go? They already live in a separate, private repository. A public clone doesn't fetch the SUSE brand pack at all, so a public build runs the neutral `lolly-start` profile - the brand-agnostic community tools plus a blank brand you fill in with your own. SUSE operates its own instance to protect its trademarks. ## Why is it free? What's the catch? **We built Lolly for ourselves.** SUSE needed thousands of on-brand files, each with its name sealed inside, made without handing anything to outside services. So we built a tool that does all of it on the device, and released it as open source, like everything else we make. We keep maintaining it because we use it every day. **There is no obligation:** everything here works with or without us. That line is drawn in the licence, not in a promise: anything that runs locally is free, forever. A version that has been released is licensed so it cannot be taken back, and there is no contributor agreement that could relicense anyone's work. See [positioning](/info/positioning.html) for the full statement. ## How much is SUSE keeping private? (aka when is the rug-pull) The engine, the shells, the schemas and the brand-agnostic tools are open source; SUSE's trademarks and branded tools are the part that stays private, and they are already separated out. You can find an unbranded instance of Lolly at [lolly.ART](https://lolly.art). The boundary is structural rather than promised. Every released version is open source and cannot be un-released, there is no contributor agreement that could relicense anyone's work, and the only thing held back is the trademark. When another company closed its enterprise Linux sources in 2023, SUSE co-founded [OpenELA](https://openela.org) to keep that code open - the same posture this project inherits. Full disclosure: SUSE *is* building out internal tooling to integrate Lolly within its IT systems - that's about SUSE's internal set-up, not public vs. private development. Lolly also aims to be built through [Open Build Service](https://openbuildservice.org/), with secure supply-chain artifacts delivered by the [SUSE Application Collection](https://apps.rancher.io/applications). ## What flavour is that Lolly logo? Some say Lime, others say Mint and sometimes Apple, Lolly brings the sweetness, you make the flavour happen! --- Source: https://lolly.tools/info/start/install.md Page: https://lolly.tools/info/start/install.html # Install Lolly Lolly runs in a browser with nothing to install, and it also ships as a real app for the machine in front of you. This page is the one list of every packaged build: what each one is, how to install it, and how to check you got the file we made. The app is the same either way. Same tools, same brand packs, same files out - on the web, macOS, Windows, Linux, iOS and Android, plus the [CLI](/info/cli.html) and the terminal [TUI](/info/tui.html). Install it, open a hosted address, or run both at once. The packaged builds add native file dialogs, a home in your application menu and a copy that keeps working with the network unplugged. > **Direct downloads are live.** Every packaged build below links straight to the > file on `lolli.li`. Each platform has a stable `lolly-latest.*` link that always > points at the newest build, alongside versioned files that never change. Signed > system repositories (openSUSE OBS, Flathub) are planned and will land here when > they do; until then these direct files - and the build-it-yourself route in each > section - are the way in. Prefer no install at all? Use [the web app](/); it > needs no install and is the same application. --- ## macOS **Apple silicon (M1 and later), `.dmg`.** Download the disk image, open it, and drag Lolly to Applications. Nothing else is needed - the app carries its own engine and tool catalogue. - **Download:** [`lolly-latest.dmg`](https://lolli.li/lolly-latest.dmg) - always the newest build. - **Versioned:** `https://lolli.li/Lolly__aarch64.dmg` (for example `Lolly_1.0.0_aarch64.dmg`) - pinned, never overwritten. - **Intel Macs:** not published. Tauri does not cross-compile, so an `x86_64-apple-darwin` build has to run on Intel hardware or a rosetta CI runner; if you need one, build it yourself with the steps below. Build it yourself: ```bash git clone --recurse-submodules https://github.com/lolly-tools/lolly.git cd lolly && npm install npm run build:desktop # → shells/tauri-desktop/src-tauri/target/release/bundle/macos/ ``` Full prerequisites are in the [Build Guide](/info/build-guide.html). --- ## Tumbleweed **openSUSE Tumbleweed, `.rpm`, x86_64.** ```bash sudo zypper install https://lolli.li/lolly-latest.rpm ``` Or download [`lolly-latest.rpm`](https://lolli.li/lolly-latest.rpm) and install the file directly. The versioned build sits beside it, for example `https://lolli.li/lolly-desktop-1.0.7-0.x86_64.rpm`. A signed OBS repository - so updates arrive with the rest of your system - is planned; until it is live a direct `.rpm` does not auto-update, so check back here for a newer build. The package build itself is described in `shells/tauri-desktop/rpm/README.md`. --- ## Leap **openSUSE Leap 16, `.rpm`, x86_64.** ```bash sudo zypper install https://lolli.li/lolly-latest-leap16.rpm ``` Leap 16 has its own build, and you need it: the Tumbleweed `.rpm` (`lolly-latest.rpm`) is compiled against a newer glibc and refuses to install here with `nothing provides 'libm.so.6(GLIBC_2.43)'`. The versioned build sits beside the alias, for example `https://lolli.li/lolly-desktop-1.0.7-0.leap16.x86_64.rpm`. The two packages are otherwise identical in content; only the toolchain they were built against differs. **Leap 15.6 is not a target.** The dependency tree needs Rust 1.88 or newer and 15.6's default toolchain is older, so supporting it would mean pinning a second Rust toolchain into the project and maintaining two build stories. Tumbleweed ships 1.97.1 and Leap 16 is current; those two are the supported pair. On 15.6, use the Flatpak below. --- ## Flatpak **Any Linux distribution, `.flatpak`.** The Flatpak is the right choice on Fedora, Debian, Ubuntu, Arch, Leap 15.6, an immutable desktop, or anything else that is not one of the two RPM targets. It brings its own runtime, so it does not care what your distribution ships. Download the single-file bundle and install it: ```bash curl -LO https://lolli.li/lolly-latest.flatpak flatpak install --user ./lolly-latest.flatpak flatpak run tools.lolly.Desktop ``` The app id is `tools.lolly.Desktop` and the runtime is `org.gnome.Platform` version 49. A Flathub listing - which would carry updates automatically - is planned; the manifest and the local build steps are in `shells/tauri-desktop/flatpak/README.md`. --- ## Arch **Arch and its derivatives, from the Lolly pacman repository.** Add the repository to `/etc/pacman.conf`: ```ini [lolly] SigLevel = Optional TrustAll Server = https://lolli.li/arch/$arch ``` then install - and from here on, update - with pacman itself: ```bash sudo pacman -Syu lolly-desktop-bin ``` The `SigLevel` line is needed because the repository is not yet GPG-signed; it scopes the exception to this one repository only. An AUR package under the same name is planned once AUR account registration reopens; the recipe is maintained in `shells/tauri-desktop/linux/arch/`, and you can build it by hand today: ```bash git clone https://github.com/lolly-tools/lolly-desktop.git cd lolly-desktop/linux/arch && makepkg -si ``` The package repacks the official 1.0.7 desktop build and installs the whole desktop integration - the `.lolly` MIME type, `lolly://` links, the thumbnailer, the GNOME Shell search provider, the D-Bus services, and the KDE service menu. On Hyprland and other wlroots compositors the colour picker and the wallpaper feature go through the desktop portal, so an `xdg-desktop-portal` backend must be running (Hyprland ships its own); the desktop search integration is a GNOME/KDE feature. --- ## Debian / Ubuntu **Debian, Ubuntu and derivatives, `.deb`.** Pick the build for your architecture and install it with `apt`: - **amd64 (Intel/AMD):** [`lolly-latest.deb`](https://lolli.li/lolly-latest.deb) - **arm64 (ARM servers, Raspberry Pi, ARM VMs):** [`lolly-latest-arm64.deb`](https://lolli.li/lolly-latest-arm64.deb) ```bash # amd64 curl -LO https://lolli.li/lolly-latest.deb && sudo apt install ./lolly-latest.deb # arm64 curl -LO https://lolli.li/lolly-latest-arm64.deb && sudo apt install ./lolly-latest-arm64.deb ``` Versioned files sit beside them, for example `https://lolli.li/lolly-desktop-1.0.7_amd64.deb` and `lolly-desktop-1.0.7_arm64.deb`. A direct `.deb` does not auto-update, so check back here for a newer build, or use the Flatpak above, which updates in place once its Flathub listing is live. --- ## Android **Phone and tablet, `.apk`, installed directly.** No store account, and no store. Download the APK, open it, and allow your browser or file manager to install applications this once. Android will ask - that prompt is the system working correctly, not a warning about this file. - **Download:** [`lolly-latest.apk`](https://lolli.li/lolly-latest.apk) - always the newest build. - **Versioned:** `https://lolli.li/Lolly-.apk` (for example `Lolly-1.0.0.apk`). - **Minimum:** Android 8.0 - **Architecture:** `arm64-v8a` Check the file before installing it. Every release publishes a checksums file at `https://lolli.li/SHA256SUMS.txt`: ```bash sha256sum lolly-latest.apk ``` Compare the result with the line for that filename. If they differ, do not install it. Build it yourself from `shells/tauri-mobile` with `npm run build:android`; the Android SDK and NDK prerequisites are in the [Build Guide](/info/build-guide.html). --- ## Everything else | Surface | How you get it | Notes | |---|---|---| | **Web** | [Open it](/) | Nothing to install. Add it to your home screen or dock and it keeps working with the network unplugged. | | **Windows** | `.msi` or an NSIS `.exe` | Built by `npm run build:desktop` on Windows. Not yet published as a signed release. | | **iOS** | `.ipa` | Needs an Apple developer identity to sign with, so there is no download to hand out. See [Building for iOS](/info/ios-build.html). | | **Linux, other** | `.AppImage` | Falls out of the same desktop build. For Debian/Ubuntu use the `.deb` above; the Flatpak is the supported cross-distro package. | | **Terminal** | `npm i -g @lolly-tools/cli` | The same engine, headless, as `lolly`. It ships no tools and no catalog, so point it at one with `LOLLY_ROOT`, or let the desktop app bring its own. `npm run cli` is the in-repo spelling. See [the CLI guide](/info/cli.html#install). | | **Terminal, interactive** | `lolly tui` | A full-screen terminal interface, from the same package (`npm run tui` in a checkout). See [the TUI guide](/info/tui.html). | | **Tool-author SDK** | `npm i -D @lolly-tools/core` | Published on npm. The `HostV1` contract types, the manifest validator and a mock host for testing a tool without the app or a clone of the repository. See [Authoring Tools](/info/authoring-tools.html#try-it-without-the-monorepo). | | **Browser extension** | Load unpacked | See [Browser Extension](/info/extension.html). | | **Your own server** | Self-host the web shell | A static bundle behind any web server, including an air-gapped one. See [Deployment](/info/deployment.html). | --- ## Checking what you downloaded Every release publishes a checksums file at [`https://lolli.li/SHA256SUMS.txt`](https://lolli.li/SHA256SUMS.txt). Put it next to your file, then: ```bash curl -LO https://lolli.li/SHA256SUMS.txt sha256sum --check --ignore-missing SHA256SUMS.txt ``` A line reading `OK` means the file on your disk is byte-for-byte the file the build produced. A `lolly-latest.*` file is a copy of the current versioned build, so it carries that build's hash - compare the value, not the name, if you kept the `latest` name. A mismatch means it changed somewhere between there and here, and the answer is always to download it again rather than to install it anyway. The files Lolly itself makes carry their own seal, which is a separate and stronger check - see [Verify It Yourself](/info/verify-yourself.html). --- ## Which one should I pick? - **You are on a Mac.** The DMG. - **You are on Tumbleweed or Leap 16.** The RPM repository, so updates arrive with the rest of your system. - **You are on any other Linux.** The Flatpak. - **You are on a phone.** The APK on Android; the web app on iOS. - **You do not want to install anything.** [The web app](/). It is the same application, and you can add it to your dock or home screen. - **You are rolling this out to other people.** Read [Deployment](/info/deployment.html) first - it covers the three delivery postures and which package suits each one. --- Source: https://lolly.tools/info/create/creators.md Page: https://lolly.tools/info/create/creators.html # Lolly for Creators You fill in a few fields and a finished file comes out, with the fonts, the colours and the layout already decided on your own device. You ship in minutes and it looks like you spent hours, and because the brand rules are baked into the tool, it is never your name on the off-brand file. Pick the lane below that matches what you are doing today. You are one hand in a relay: a developer can scale the tool you use into a pipeline and a colleague can run it self-serve, so [The lifecycle of a campaign](/info/overview.html#the-lifecycle-of-a-campaign) follows one asset through all three pairs of hands. New here? Start with **[Make something in 60 seconds](/info/make-something.html)** and come back once you know which part you want to go deeper on. [Questions & answers](/info/faq.html) covers what people ask before they trust a free tool. [How Lolly compares](/info/positioning.html) is the honest account of what to reach for something else for. ## Make Pick a tool, fill in a few fields and download the finished file. Or open the Design canvas and lay it out freehand: either way the colours, the type and the spacing come from your brand rather than from memory, and nothing needs an account. - **[Make something in 60 seconds](/info/make-something.html)** - three short walkthroughs with nothing to set up, if you have never opened Lolly before. - **[Using Lolly](/info/using.html)** - the canvas, the controls, saving, Projects and phone use: how the app itself behaves. - **[The Brand Studio](/info/brand-studio.html)** - when the logos, colours and type should be yours rather than the defaults. - **[Import a design](/info/design-import.html)** - when the artwork already exists as a `.fig`, `.penpot`, `.ai`, `.idml` or SVG file and you want it editable rather than flattened. - **[Utility views](/info/utilities.html)** - the spreadsheet, the converter, the Colour Lab, the PDF extractor and Script audio, for the jobs that are not a tool render. - **[Browser Extension](/info/extension.html)** - capture a live web page and reuse it as an asset in your own designs. - **[Search](/info/search.html)** - one field that reaches your tools, your saved work, the catalogue and your settings. - **[Ask Lolly](/info/ask.html)** - type a question and get the matching section of these guides back, with a link straight into it. - **[The Dashboard](/info/dashboard.html)** - what this device can do, your design system read-only and a glance at storage. - **[Your favourites](/info/favourites.html)** - star what you use often so it gets a tile of its own above the grid. - **[Profiles](/info/profile.html)** - the details tools pre-fill from and the four accessibility switches that calm the app without moving a pixel of what you export. ## Animate Text that moves, scenes on a timeline and motion that stays on-brand. A motion export is composited frame by frame from a driven clock rather than recorded off the screen, so the timing never drifts and the same link renders the same clip at any length. - **[Animating](/info/animating.html)** - keyframes, real depth and a scene camera to fly over the result. - **[The sequence editor](/info/sequence-editor.html)** - the timeline itself: clips, overlay lanes, trim and split, transitions and the audio mix. - **[Exporting & Formats](/info/exporting.html)** - the video, GIF and animated PNG side of the export panel, once the motion is right. ## Record Record a clean take with a count-in, a level meter and gentle coaching, or turn a voice note into a finished video that moves with the sound. There is no separate Record page yet: Using Lolly carries it, in the timeline section. - **[Using Lolly](/info/using.html#timeline-sequence)** - recording a voiceover straight onto the timeline, where the take is saved and how it reaches the mix. - **[Make something in 60 seconds](/info/make-something.html#make)** - the audiogram scene, for when you would rather be heard than seen. - **[Utility views](/info/utilities.html)** - Script audio, when the words come first and the voice comes after. ## Collaborate Two people, two devices, one session, edited live. Scan a code across the table and both screens hold the same work, with no account and no server in the middle - and no internet at all when both devices are on the same network. - **[Working together](/info/collaborate.html)** - starting a session, what travels between the devices and what happens when the network drops. - **[Using Lolly](/info/using.html#sharing-your-work)** - the quieter ways to hand work on: a share link that carries the whole state, or one `.lolly` file. ## Post Send it where it was asked for: a link that carries the design, a print-true PDF, a video, a deck or a folder of variants. The same source makes every one of them, so you build it once. - **[Exporting & Formats](/info/exporting.html)** - print units, DPI, bleed and crop marks, video, batch runs and the export panel itself. - **[Every format Lolly can open and make](/info/formats.html)** - the full in-and-out list, with a plain-language card behind every chip. - **[Data Transfer](/info/data-transfer.html)** - the backup bundle, for moving everything you have made to another device. --- Source: https://lolly.tools/info/create/using.md Page: https://lolly.tools/info/create/using.html # Using Lolly A practical guide to actually *using* the app - opening a tool, working the canvas, exporting, saving and sharing. Everything here runs **on your device**: no account, no upload, no internet required after the first load. > New here? The [Quickstart](/info/quickstart.html) gets you making things in minutes, and [Lolly for Operators](/info/operators.html) covers installing/deploying the app; this page is about driving it once it's open. ## Opening a tool The home screen is the **gallery** - every tool, grouped by category. Click a card to open the tool; if you've worked on it before, a **Continue** button resumes your most recent session. Use the search box to filter by name - or [Search](/info/search.html) from the bar at the foot of the six listing screens (the gallery, Utilities, Projects, the Catalogue, the Dashboard and Profile), which reaches your saved work, the catalogue and your settings as well as the tools. Inside a tool the bar steps aside for the tool's own chrome. ![The tool gallery - every tool as a card, grouped by category](/t/url-shot?url=%2F%23%2F&width=1440&height=900&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone!important%7D&tolerance=0.03&waitSelector=.gallery-view%5Bdata-shots-settled%5D&walker=1&format=svg&localize=1&dark=1&filename=gallery&try=1) Each tool is a split view: **controls** on one side, a live **preview** (the canvas) on the other. Change any control and the preview updates instantly. ![A tool's split view - the control stack on the left, and the live grouped bar chart it draws on the right](/t/url-shot?url=%2F%23%2Ftool%2Fchart%3Fct%3Dbar%26t%3DExample%2520data%26st%3DSample%2520values%252C%2520not%2520a%2520real%2520dataset%26d%3DMonth%252CSeries%2520A%252CSeries%2520B%252CSeries%2520C%250AJan%252C12%252C9%252C5%250AFeb%252C18%252C14%252C7%250AMar%252C24%252C17%252C11%250AApr%252C29%252C23%252C15%26lg%3D1&width=1440&height=900&dpi=192&waitMs=2500&walker=1&format=svg&dark=1&filename=vt-tool-split-view) > A few tools (like **Design**) instead open as a **free canvas** - a chromeless, direct-manipulation surface where you drag, resize, rotate and snap boxes of text, shapes and images and double-click to edit text in place. It exports through the same render path as every other tool, so the canvas *is* the file. See [The free canvas](#the-free-canvas-design) below. Two ways to shape the grid itself into the one you want: - **Star what you use.** ★ a card and it gets a big tile of its own in a strip above the grid - see [Your favourites](/info/favourites.html). - **Hide a tool you never use.** Right-click a card (or select several and use the selection bar) → **Hide tool**. It drops out of the grid, and out of what typing in the grid finds; a grey **Show hidden tools (N)** tile at the very end reveals them again, dimmed, each with **Unhide tool** in its own menu. Hiding is only about your grid - the tool still opens from a saved link or a bookmark, and it stays exactly where it was for everyone else. ![The end of the Tools grid with hidden tools revealed: the dimmed QR Code Generator card, and beside it the grey tile that toggled it back into view, now reading Hide hidden tools](/t/url-shot?url=%2F%23%2F&width=1440&height=680&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone!important%7D&drive=click%3A%5Bdata-select%3D%22qr-code%22%5D%3Bclick%3A%5Bdata-bulk%3D%22hide%22%5D%3Bwait%3A300%3Bclick%3A.gtile--hiddenbox%3Bpress%3AEnd%3Bwait%3A800&waitSelector=.gallery-view%5Bdata-shots-settled%5D&walker=1&format=svg&rasterDpi=96&dark=1&filename=misc-hidden-tools) ### Ask Lolly When you'd rather ask than hunt, **Ask Lolly** (`#/ask`) takes a typed question and hands back the matching section of this documentation **verbatim** - the guides' own words, not a summary and not a generation - with the page it came from cited and an **Open in docs** link beside it. Under the answer sit the places in the app the same question matches: a tool, a setting, a saved project, each as a button that simply navigates there. The transcript is session memory: ask a follow-up and the thread builds up as you go, then reload and it starts fresh. Search results carry an **Ask Lolly: *your query*** row at the bottom - under whatever concrete hits the other groups found - which hands the question straight over, so you can start in the bar and finish here. ## The canvas (preview) The preview always shows exactly what will export. **Desktop** - **Zoom:** Cmd/Ctrl-scroll, or pinch on a trackpad - zoom centres on your pointer. - **Pan:** hold **Space** and drag, or drag with the **middle mouse button**. (Plain clicks stay free for clicking parts of the design.) - **Keyboard:** `0` = fit to window · `1` = 100% · `+` / `−` = zoom. - **Zoom HUD:** the small `− NN% + Fit` control in the corner. Click the percentage to toggle Fit ↔ 100%. ![The zoom HUD in the corner of the canvas - minus, the live percentage, plus, Fit, then the theme and sound toggles](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools&width=1440&height=900&dpi=192&waitMs=2500&css=.stage-nav%7Bopacity%3A1!important%7D&cropSelector=.stage-nav&walker=1&format=svg&dark=1&filename=use-zoom-hud) **Touch** - **Pinch** to zoom, **drag** to pan, **double-tap** to reset to fit. **Click to jump to a control:** click any element in the design and the matching sidebar input gets focus and scrolls into view - for a repeating row group it folds open the exact row you clicked, so editing what you see is one tap away. A dimension change always snaps the view back to a clean fit. ### The free canvas (Design) Free-canvas tools add a working surface *around* the artboard, like a designer's pasteboard: - **Off-canvas staging.** Drag a box past the frame edge and it stays fully **visible and selectable** - park elements off to the side while you arrange the composition, then drag them back in. Everything outside the frame is **gently faded** so the export area always reads at a glance, and the frame keeps its shadow to mark exactly where the file begins. - **Only the frame exports.** The exported file is bounded by the artboard - anything left outside (or the part of a box hanging over the edge) is simply cropped out of the output, in raster and vector formats alike. - **Zoom out past Fit** (down to 20%) to see the whole pasteboard when you've staged things far outside the frame. - **Resizable artboard.** Changing the export dimensions resizes the frame in place; boxes keep their positions, so you can reframe a layout around existing content. - **Before you export.** The inspector's Document section checks the saved layer structure, then reads the settled canvas for clipped text and flat-colour contrast. It also asks the same font registry used by SVG/PDF outlining whether each text run has embeddable font bytes; image and gradient backgrounds are named as visual checks instead of being given a made-up contrast score. ![Design's free canvas - the artboard with its surrounding pasteboard](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17VVRa9swEP41ekyQLrbjPOyhVeeOrSsbG3lXbLkYZCnISmn264tO9irFhdJCoZQIpBNn9N2dvu_k3cOX1opeEuCTpeNkG0BDy8ljZe28pyDA74UlUC4WOyt0sxgOthW1JJAD9wv1y6rFQWCDMB6BAK-NdqLTBLg2thcqeAnk68vaKGOXg-yFdl29dPLBef8VAZ6V_qjUTloCvO-aRvlcc0QdhB58iCWDMVNnD1iSUIOcAvASY2qsEUeex-XSOEJ6koaT_6Q1iY89IY4mw4TCmmJYc9BNZKPQ00ywxxJS-BlqcnL2KfJF3xhZf3WdwwsMd8xZ4VEgEF6EGornaH-ByW_iHkVAj-aAtpcO7Y1R6kggX1Uz7ext1wt7PNEOk7BZ7UbtYCpKtj4NZ_YE-Doh_hOwHnff-1K_M83xlPmyiJhn8AbmL5C61nZSN8g00OlFoK2xkyjCRofOBro3g5N2OdPE2PqxIGjL1iCCIPA1SPSQpQ_B6qyIVyiCsp8s-_Gn2P76fpHfVlt6WV3_Lv5e5_6mul7cSVRFhiWhOIrsaaVTMbKZaUWNbQ-VMncGN__b_fwn-CAKeAQ&width=1360&height=850&dpi=192&waitMs=3000&format=png&localize=1&dark=1&filename=design) **Flip a selection.** Right-click any box and choose **Flip horizontal** or **Flip vertical** to mirror it in place, or press `Shift+H` / `Shift+V` from the keyboard - Shift, because a bare `V` is the Pointer tool. Each selected box mirrors on its own axis in one undo step, and the mirror is a real transform, so it holds in the exported SVG, PDF and PNG rather than only on the canvas. ### Drawing your own shapes (the pen) Boxes, circles and rounded frames cover most layouts. When you need a shape that isn't in that list, draw it: the rail's **Pen** button (or the `P` key) puts you in drawing mode. Three single keys move between the modes - **`V`** back to the Pointer, **`P`** for the Pen, **`N`** for the node tool (**Edit points**) - and the Pointer is always the way out of whatever you're in. ![The free-canvas tool rail: a drag grip, the Lolly menu, then Pointer, Add a box, Pen, Edit points, Line, Timeline, Artboards and Auto-arrange](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Ftemplate%3D__blank__&width=1440&height=900&dpi=192&waitMs=2600&css=.fc-toolbar%7Bopacity%3A1!important%7D&cropSelector=.fc-toolbar&walker=1&format=svg&dark=1&filename=pen-editor-rail) - **Click** to place a point. On the default curve type, **click and drag** pulls that point's handles out, which is how you draw a curve rather than a corner - hold **Alt** as you click for a hard corner instead. (On the other curve types every placed point is a corner and the drag does nothing; see **Spline type** below.) - Points snap to the artboard and to your other boxes as you place them, drawing the same guides a normal drag does. Alt suppresses the grid while you draw, and both the grid and the edges while you drag a point afterwards. - **Click your first point** to close the loop and finish in one move. Otherwise press **Enter**, double-click or just switch tools - the drawing is kept, not thrown away. - **Escape** works one rung at a time: the first press abandons the drawing and writes nothing, and a second leaves the pen. - **Delete** while drawing drops the last point you placed. The result is an ordinary box on the canvas. Move it, resize it, rotate it, group it, align it, restack it, give it a fill, a gradient, a shadow or an opacity - a path behaves like every other box, and none of those controls treat it differently. It arrives painted, too. The first path you draw takes the fill and stroke your brand gives a path, and after that each new path takes **whatever you last used** - set a fill once and keep drawing, rather than recolouring every shape. (In a tool whose brand says nothing about paths, a drawn path is stroked in the colour you watched it being drawn in, so it's never invisible.) **Editing the points again.** Double-click the shape (or use **Edit points** on the object bar) and the points come back. Drag a point to move it, drag a handle to re-aim it, click anywhere on the curve to insert a point, rubber-band a group of points and press Delete to remove the selected ones. A path always keeps at least two points, so you can't accidentally delete it out of existence. **Spline type** decides what kind of curve runs through your points, and it's the choice worth understanding: | Type | What it does | |---|---| | **Smooth (auto)** | The default. Works its own handle lengths out, so plain click-click-click gives a genuinely smooth curve with no handle-wrangling. If you do set a handle, it pins the *direction* and the curve keeps ownership of the length. | | **Bezier handles** | The classic pen. Handles are the control points, and inserting a point never moves the curve. | | **Through the points** | Passes exactly through every point you placed, no handles. | | **B-spline** | Flows near the points rather than through them, for a softer shape. | | **Straight lines** | A polyline. | Switching an existing path to a type that works out its own handles asks first, because the handle lengths you set can't be recovered - switching to **Bezier handles** is always lossless. Mid-drawing there's no prompt: the switch applies straight to the draft, and any handles you'd already pulled go with it. On the types that own their handles, inserting a point reshapes the curve very slightly; on **Bezier handles** it doesn't. Each point also carries a continuity rule, shown by its shape on canvas - square for **Corner** (handles move independently), round for **Smooth** (handles stay in line), round-with-a-ring for **Symmetric** (in line and equal length). Set it for any selected points and the curve re-satisfies it immediately. ![Two pen paths rendered straight from a link: a stroked S-curve and a closed filled blob](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fboxes%3D%5B%7B%22id%22%3A%22paper%22%2C%22kind%22%3A%22box%22%2C%22x%22%3A0%2C%22y%22%3A0%2C%22w%22%3A1080%2C%22h%22%3A1080%2C%22shape%22%3A%22rect%22%2C%22bg%22%3A%22%23ffffff%22%7D%2C%7B%22id%22%3A%22curve%22%2C%22kind%22%3A%22path%22%2C%22x%22%3A140%2C%22y%22%3A180%2C%22w%22%3A800%2C%22h%22%3A420%2C%22shape%22%3A%22rect%22%2C%22bg%22%3A%22%22%2C%22stroke%22%3A%22%234f84ba%22%2C%22strokeW%22%3A14%2C%22path%22%3A%221!cubic!0_.02!.85!!!.25!-.45!s_.5!.5!-.22!.32!.22!-.32!y_.98!.12!-.25!.45!!!s%22%7D%2C%7B%22id%22%3A%22blob%22%2C%22kind%22%3A%22path%22%2C%22x%22%3A340%2C%22y%22%3A620%2C%22w%22%3A400%2C%22h%22%3A320%2C%22shape%22%3A%22rect%22%2C%22bg%22%3A%22%23d1e7ff%22%2C%22stroke%22%3A%22%234f84ba%22%2C%22strokeW%22%3A6%2C%22fillRule%22%3A%22nonzero%22%2C%22path%22%3A%221!catmull-rom!1_.5!0_1!.42_.78!1_.22!1_0!.42%22%7D%5D&width=1440&height=900&dpi=192&waitMs=2600&cropSelector=%23tool-canvas&walker=1&format=svg&dark=1&filename=pen-path-geometry) A drawn path travels in the link like everything else, so a shape you draw reopens from a share link and renders identically from the CLI. Nothing about it depends on the editor. ### Combining shapes (path operations) Select two or more shapes, **right-click** the canvas (two-finger tap on touch) and the menu offers the operations you'd expect from a drawing app: - **Union** merges them into one shape, keeping the topmost one's paint. - **Subtract** cuts everything above away from the bottom shape. - **Intersect** keeps only the overlap. - **Exclude** keeps everything except the overlap. Three more work on a single shape: **Outline stroke…** turns a stroke into a filled shape of the same outline (useful when you want to keep a weight exactly as drawn), **Offset path…** grows the silhouette outwards or, with a negative number, shrinks it inwards and **Simplify** rebuilds a path with fewer segments at the same shape. ![A crescent and a ring with a real hole, both produced by Subtract](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fboxes%3D%5B%7B%22id%22%3A%22paper%22%2C%22kind%22%3A%22box%22%2C%22x%22%3A0%2C%22y%22%3A0%2C%22w%22%3A1080%2C%22h%22%3A1080%2C%22shape%22%3A%22rect%22%2C%22bg%22%3A%22%23ffffff%22%7D%2C%7B%22id%22%3A%22crescent%22%2C%22kind%22%3A%22path%22%2C%22x%22%3A120%2C%22y%22%3A330%2C%22w%22%3A374%2C%22h%22%3A400%2C%22shape%22%3A%22rect%22%2C%22bg%22%3A%22%234f84ba%22%2C%22path%22%3A%221!cubic!1_.750491!.957645!-.128211!-.062578!-.065985!.027232!c_.534759!1!.076762!!-.295339!!c_0!.5!!.276142!!-.276142!c_.534759!0!-.295339!!.200102!!c_1.000984!.254923!-.09169!-.152161!-.021205!-.003239!c_.935829!.25!.022185!!-.221505!!c_.534759!.625!!-.207107!!.14459!c%22%7D%2C%7B%22id%22%3A%22ring%22%2C%22kind%22%3A%22path%22%2C%22x%22%3A580%2C%22y%22%3A330%2C%22w%22%3A400%2C%22h%22%3A400%2C%22shape%22%3A%22rect%22%2C%22bg%22%3A%22%234f84ba%22%2C%22fillRule%22%3A%22nonzero%22%2C%22path%22%3A%221!cubic!1_1!.5!!-.276142!!.276142!c_.5!1!.276142!!-.276142!!c_0!.5!!.276142!!-.276142!c_.5!0!-.276142!!.276142!!c*1!cubic!1_.5!.7!-.110457!!.110457!!c_.7!.5!!.110457!!-.110457!c_.5!.3!.110457!!-.110457!!c_.3!.5!!-.110457!!.110457!c%22%7D%5D&width=1440&height=900&dpi=192&waitMs=2600&cropSelector=%23tool-canvas&walker=1&format=svg&dark=1&filename=path-ops-boolean-result) The result is a new path you can keep editing with the pen. Holes are real holes - a **Fill rule** control on the stroke panel decides whether overlapping contours fill (*non-zero*) or punch through (*even-odd*). Two things these operations deliberately don't do. They **refuse rather than destroy**: ask to intersect two shapes that don't overlap and you're told there's nothing to keep, and nothing changes. And text and image boxes have no outline to work with, so they're left alone rather than approximated by their frame. A combined result is stored as plain Bezier curves, which is what a drawing app does too - the original spline type doesn't survive the operation. ## Timeline (Sequence) **Sequence** is Design's timeline: it adds *time* to the free canvas. Every box can start at a moment, run for a length and animate in and out, and a timeline docked under the artboard is where you arrange them. Open it and there's already a sequence playing - a title card, a clip, an end card, a lower-third and a music bed - so the model is visible before you change anything. ![The Sequence timeline: the transport, the ruler, an overlay lane, the magnetic sequence row with its clips and seam chips and the Always on strip](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A252px!important%7D&cropSelector=.tl-panel&format=svg&walker=1&tolerance=0.03&dark=1&filename=seq-studio-timeline) There are two kinds of row, and the difference is the whole idea: - The **sequence row** is *magnetic*. Clips sit gapless, one after another, and dragging one reorders the run rather than leaving a hole. Delete a clip and the rest close up. This is your spine. - **Overlay lanes** are free. A lower-third, a logo, a caption - anything that floats over the spine at its own time - gets its own lane and its own start. - Below those, **Always on** collects the boxes with no timing at all: scenery that's simply present the whole way through. The `+` on a chip promotes one onto a lane; **Make always on** sends it back. ![The editing stage: the artboard front and centre, the tool rail on the left and the zoom HUD in the corner](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.fc-toolbar%7Bopacity%3A1!important%7D&format=svg&walker=1&tolerance=0.03&dark=1&filename=seq-studio-stage) Opening the timeline gives it the keyboard, so Space and the arrow keys drive the playhead rather than the page - and because it opens by itself on a composition that already has timing, that's true the moment Sequence loads. > **[The sequence editor](/info/sequence-editor.html)** goes deeper on the four things that decide whether editing in time feels predictable: which clip a canvas click edits, onion-skin ghosts of the neighbouring clips, split scope and the Join that undoes a cut and trimming (including the keyboard set). Press `?` with the timeline focused for the shortcut sheet. **Editing.** Drag a clip's middle to move or reorder it, drag within a few pixels of either end to trim it and press **Split at playhead** (or `S`) to cut one clip into two. Split needs a clip with a real **Length** and the playhead a little way inside it, so an open-ended clip (the music bed, for one) can't be split. **Snap to edges** is on by default and snaps to clip edges, the playhead and whole seconds, with Alt to override. Every drag is a single undo step, and the drag preview runs the same arithmetic as the commit, so what you see while dragging is what you get. Select a clip and the inspector gives you the same edits as numbers: **Length**, **Trim in** (how far into the source it starts), **Speed** as a set of fixed multipliers from ×0.25 to ×4, **Animate in** / **Animate out** with their lengths and **Mute clip**. A clip on the magnetic row has no **Start** field on purpose - the row owns the order, so you drag to move it. **Transitions** are presets, not keyframes: Fade, Pop, Grow, Rise, Drop, the four Slides, Zoom in and out, Tilt, Swoop, Spin, Drift or **Cut (no animation)**. Distances scale with the object, so the same preset reads correctly on a full-frame card and a small badge. Between two adjacent clips on the sequence row there's a **seam chip**: click it and choose **Cut** or **Crossfade**, which applies at once and closes. Open the same chip again to change the **Length (ms)** and press **Done**. A crossfade is stored as a fade out of one and a fade into the next, and the export derives the actual dissolve from that pair - which is why a crossfade looks like two fades in the preview and a true handover in the file. **Sound.** Add an **Audio** clip and it lives on the timeline like any other clip: waveform, trim, mute. (The generated bed the default session ships with is the one exception - it's synthesised at export time, so its bar stays plain and silent until you render.) Press the mic to **record a voiceover** straight onto the timeline, with a count-in and a level meter, and the take is saved as your own asset at the point you started. Press the camera beside it to **record a video** the same way: the take is cropped to the artboard's export size as it records, so the small self-view shows exactly what goes onto the sequence at the playhead, full frame - the way to gather a colleague's clip from a shared link. Music, dialogue and a clip's own soundtrack all reach the exported mix. (The export panel's **Audio track** is a different thing: one bed laid under the whole clip, with fade and ducking. The two coexist.) **The audio strip.** Select any clip that carries sound and a compact strip opens under the timeline: a **Volume** fader, **Pan** for stereo position, a three-band **EQ** (**Low**, **Mid**, **High**), a **Pitch** control that transposes in semitones while the voice keeps its character, and **Normalize volume**, which brings the clip to broadcast loudness (BS.1770) so a quiet voice note and a loud track sit level. Where two clips meet, **Crossfade** blends the junction instead of cutting. An **Effect** slot runs on-device processing on the clip - **Voice cleanup** takes the room and the hiss out of a recording. Speed changes keep pitch too: a clip slowed or hurried is time-stretched, not chipmunked. At every mix the export ducks music under speech as the speech comes and goes and holds the whole programme under a true-peak limiter, so nothing clips on the way out; a waveform that would have clipped is drawn with a warning where it happens. ![The timeline with the music clip selected: its strip runs along the bottom with Speed, Fades, Volume, Pan, EQ, Pitch, Normalize volume and the Effect slot](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Ftemplate%3Dvideo%26_sel%3Dbed&width=1440&height=900&dpi=192&waitMs=5000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D&cropSelector=.tl-panel&walker=1&format=svg&dark=1&filename=tl-audio-strip) **Rendering it.** A motion export is a **deterministic composite**, not a screen recording - each frame is decoded, drawn and encoded at an exact time, so the file doesn't depend on your machine keeping up, and there's no practical frame ceiling on MP4 or WebM. The timeline's own length sets the duration unless you type one. Content Credentials are stamped as on any other export. A still export gives you the frame at the playhead, or a whole contact sheet from the **Frames** field beside the output size - see [Exporting](/info/exporting.html#stills-from-a-timed-composition). A few limits to keep in mind: a sequence is capped at one hour, GIF and animated PNG buffer their frames so they stay short, a clip played faster or slower keeps its pitch (the audio strip time-stretches it, and a **Pitch** control transposes in semitones with the voice's character kept) and **Record live** is hidden here because the compositor is the better path. **Beyond presets: keyframes, depth and a camera.** A transition animates a clip as it arrives and leaves. To pose a box *within* a clip - drift it, fade it, blur it, lift it off the page and settle it back - add keyframes: select the clip, press **+Keyframe** (the diamond in the timeline's tool cluster, the diamond on the canvas object bar or `K`) and the playhead's position decides which pose your next edit writes. The same keyframe system gives every timed composition a **camera** that pushes in, pans across and pulls focus and turns one flat SVG into a stack of layers you can fly between. **[Animating](/info/animating.html)** is the full guide. The Design tool has the same timeline, so you can time a layout without moving to another tool, and it exports motion too. ## Presenting A Design document made of **artboards** is already a deck. Open the **Lolly menu** on the tool rail and choose **Present** - the last row - and each artboard becomes a full-screen slide, in the order the artboards sit on the canvas. The deck runs on a copy of the rendered artboards, so the editor underneath is never touched and leaving puts you back exactly where you were. - **Advance** with **Space**, `→`, **Page Down** or a click on the strip at the right edge of the screen; go back with `←`, **Page Up** or the strip at the left edge. **Home** and **End** jump to the first and last slide. A small bar of controls fades in whenever you move the pointer and hides itself again once you stop. - **Overview** (`O` or the grid button) lays every artboard out at once in the arrangement you gave them on the canvas; click one to open it. - **Reveal steps.** Right-click a box and choose **Reveal at step 1**, **2** or **3** instead of the default **Always visible**. That box then waits until you advance onto its step, so a slide can arrive in pieces; boxes sharing a number arrive together. - **Speaker view** (`S`) opens a second window with the current slide, the one coming next, your notes for that slide and a running clock. If the browser blocks the pop-up it falls back to a panel over the deck. Notes are set per artboard and never appear on the slide itself. - `B` holds a black screen (any key brings the slide back), `F` returns to fullscreen and **Escape** peels one layer at a time: overview back to the deck, deck back to the editor. - **Kiosk.** Give an artboard a **Length** and the deck holds there for that long, then advances itself behind a thin progress bar; `K` (or the pause button, which appears only once something has a length) stops and restarts that. Add `kiosk` to the link and the deck wraps at the end, which is what makes it signage. - **Sub-slide stacks.** Right-click an artboard and choose **Stack under the previous slide** and it becomes a step of that slide rather than a slide of its own: the overview shows one card, the deck walks through the stack in order, and the inspector's **Stack** row says which slide it belongs to. - **Morph.** When two consecutive slides both carry a box with the same **Morph match** name (right-click a box, or the inspector's **Morph match** row - `hero`, say), the transition moves that box from where it was to where it is, resizing and recolouring on the way, instead of cutting. A deck-wide **Morph** transition does the same for every matched pair. - **Narration.** Each artboard's **Speaker notes** can be read aloud. In the inspector's **Document** section pick a **Voice**, optionally a second voice to **Blend with**, the reading **Speed**, and a **Lead-in** and **Tail** in milliseconds around each slide; turn on **Show captions when presenting** and the words appear as they are spoken. The voice runs on your device. The same notes become the film in a video export, real slide audio in a PowerPoint export, and the narrated film inside a [SCORM package](/info/create/exporting.html#scorm-course-packages). ![The inspector's Document section: Voice, Blend with, Speed, Lead-in, Tail and Show captions when presenting](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Ftemplate%3Dfeature-tour&width=1440&height=900&dpi=192&waitMs=3500&cropSelector=.fc-insp&walker=1&format=svg&dark=1&filename=design-narration) The deck is a link as well. `?present` opens straight into it, `s=` names the slide - a position, an artboard id or `id.step` for a build step - and the address updates as you move, so what you send is the slide you're on. Tool authors: those parameters are documented on the [URL Mode](/info/url-mode.html#reserved-parameters) page. ## On a phone On narrow screens the layout reflows to one column: - The **controls become a sheet** at the top with a **drag grip** on its lower edge. Drag the grip to resize it - it snaps to **peek / half / full** - or **tap** the grip to toggle collapsed ↔ expanded. The preview fills the space below and stays visible while you edit. - A floating **Export** button opens the export sheet - all the format, size, copy, save and download controls in one place. Dismiss it by tapping the backdrop. ![A tool on a phone-width screen - controls as a sheet up top, the generated palette filling the preview below and the render pill floating bottom-centre](/t/url-shot?url=%2F%23%2Ftool%2Fcolor-palette%3Fseed%3Df97316%26harmony%3Dadjacent-3%26steps%3D9&width=430&height=900&dpi=192&waitMs=2200&walker=1&format=svg&dark=1&filename=vt-phone-palette) ## Controls (inputs) Tools expose only the inputs that are meant to vary - everything else (colours, layout, typography, logic) is locked in by the tool author, so whatever you make meets the rules the author set. Inputs include text, sliders, colour pickers, dropdowns, dates, image pickers and repeating row groups. Some are grouped under collapsible sections. ![A tool's control stack - a text field, colour triggers and a slider and nothing else the author chose to lock down](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools&width=1440&height=900&dpi=192&waitMs=2500&cropSelector=%23tool-inputs&walker=1&format=svg&dark=1&filename=use-tool-inputs) **Reset:** *Clear changes* returns every input to its defaults. ### Undo and redo **Cmd/Ctrl-Z** steps back and **Cmd/Ctrl-Shift-Z** (or **Cmd/Ctrl-Y**) steps forward again. The same pair sits as **Undo** and **Redo** buttons in the row above the controls - on the free canvas they're on the tool rail instead - and each greys out while there's nothing left to take back. Every step says what it was: undo a colour and a small message names the input it just restored, with a **Redo** button in it for the way back. - **A drag is one step.** Repeated changes to the same control within half a second merge together, so pulling a slider across its range is a single undo rather than two hundred. - **The last 100 steps are kept** - older ones drop off the end. Making a fresh edit after undoing clears the forward stack, as it does everywhere else. - **While your caret is in a text box**, Cmd/Ctrl-Z belongs to the field itself, character by character. Lolly takes over for the controls with no useful undo of their own: sliders, dropdowns, colours and switches. - **Choosing a file** in a **file** input isn't a step - those bytes are held for the session only, so there would be nothing to put back. In a live [collaboration](/info/collaborate.html) the history stays yours alone. A change arriving from the other device never goes onto your stack, so undo can only ever take back something you did. ## Your details & headshot **Profile** (top-right of the gallery) holds your name, contact details and an optional **headshot**. Tools that ask for those fields pre-fill them automatically - set them once and your email signature, lockups and badges fill themselves in. You can still override any field per session. Opt in with **Use my details to create** so your details ride along as the author on what you export. Your headshot and details live **only on this device**. A profile can be more than just you - a team or a role you step into now and then. See **[Profiles](/info/profile.html)** for the full picture, including keeping more than one. ## Saving & continuing Click **Save** to store the current inputs as a session for that tool. You can keep multiple named sessions per tool; each tool's **Continue** button reopens your most recent, and the **history button** (top-right, beside your profile) lists every saved session across all tools. Sessions are device-local. To organise them, open **Projects** (below). ![The two-half render pill - an up arrow that opens the export panel, and a tick that saves the session in place](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools&width=1440&height=900&dpi=192&waitMs=2500&css=%23tool-inputs%7Bdisplay%3Anone%7D&cropSelector=.render-pill&walker=1&format=svg&dark=1&filename=use-render-pill) ## Projects **Projects** - open it from the **Projects** tab beside **Tools**, or from **Profile → Storage → Organise in Projects** - is a home for everything you've saved, and it works like a file manager: ![Projects - saved sessions organised into nestable folders](/t/url-shot?url=%2F%23%2Fp&width=1440&height=900&dpi=192&waitMs=1200&walker=1&format=svg&localize=1&dark=1&filename=projects) - **Folders that nest.** Group saved sessions into folders, and folders inside folders, as deep as you like. Create a folder, rename it or drag a tile onto another folder to move it; a breadcrumb walks you back up. An always-present **Uncategorised** folder holds anything not yet filed. - **Sort your own way.** **View & sort** offers **Name**, **Date added**, **Last modified** (the default) and, inside a folder, **By tool**. Folders always come first regardless of which sort is active - the sort only orders the sessions and folders within their own group. - **File new work straight in.** **New asset** ("Start a fresh creation" at the root, "Add to *folder*" inside one) opens a tool and files its first save into that folder automatically. - **Multi-select (desktop).** Tick a tile's checkbox, drag a selection box across empty space or **Shift/Cmd-click**; **right-click** a tile for its context menu. Then act on the whole selection at once - the same gesture and the same floating action bar work on the Tools gallery, Utilities, the Catalogue and Projects, not just here. - **Render a whole folder or selection.** **Render folder** exports every saved session in a folder - including its sub-folders - as one nested `.zip`. **Render selection** does the same for any multi-selection, and a single session renders straight to its own file. No Batch/Pro needed. - **Jump straight to a tool's saved work.** Tick one or more tools on the Tools gallery and choose **View sessions** from the selection bar - Projects opens showing only the sessions made with those tools, with a **Clear** to get back to the full view. - **Share a saved session.** Right-click a session → **Share link** to copy a link that reopens it with the exact same inputs (the full Share dialog - see below). ![The View and sort popover in Projects open, with a theme row, a View choice of Preview or List and Name, Date added and Last modified under Sort](/t/url-shot?url=%2F%23%2Fp&width=900&height=700&dpi=192&waitMs=1400&drive=click%3A.projects-viewopts&cropSelector=.projects-viewmenu&walker=1&format=svg&dark=1&filename=misc-projects-sort) **What the selection bar offers** differs a little by view, since not every action makes sense everywhere: - **Tools / Utilities:** Favourite (or Unfavourite), Hide (or Unhide), Available offline (or Remove from offline), **View sessions** (the jump described above) and Copy link when exactly one card is selected. - **Catalogue:** Favourite and Hide apply to any selection; Duplicate, Download and Delete only appear once every selected item is one of your own uploads - a shared design-system asset is a permanent contract, so those three stay off it even in bulk. - **Projects:** **Render selection**, **Move to…**, **New folder**, **Delete**, **Edit together** when the selection is between two and eight single-tool sessions (it opens them side by side under one combined sidebar) and **Edit as sheet**, which opens the whole selection as rows in the batch grid instead. That one has **no size limit** and doesn't care whether the sessions came from the same tool, so it's the escape hatch when a selection is bigger or more mixed than Edit together's two-to-eight. > One label trap: **View sessions** only exists once something is *selected*. Right-clicking a single unselected card instead offers **N saved sessions**, which opens that tool's own history dialog rather than navigating to Projects. ![Two tool cards ticked in the Tools gallery, with the floating selection bar reading 2 selected and offering Available offline, View sessions, Favourite and Hide](/t/url-shot?url=%2F%23%2F&width=1440&height=900&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone!important%7D&drive=click%3A%5Bdata-select%3D%22qr-code%22%5D%3Bclick%3A%5Bdata-select%3D%22gradient%22%5D&waitSelector=.gallery-view%5Bdata-shots-settled%5D&walker=1&format=svg&dark=1&filename=misc-bulkbar-gallery) ## Sharing your work A design goes out one of two ways: as a link or as a file. The Share dialog offers both. Open it with **Share** in the export controls; **Share link** on a saved session in Projects opens the same dialog for that session. ### The link Every input is captured in the page URL, so a link *is* the design. At the top of the dialog sits the ready-to-copy link, with two collapsed sections under it. - **Link options** holds **Open in the installed app** (switches the field to a `lolly://` URI for Shortcuts, launchers and automation, with every parameter unchanged), **Shortest link** (a big design makes a long URL, so this packs the whole state into a compact token and shows you the saving in characters; the readable form is always there too), **Password-protect this link** (AES-256 over the whole link, the password never in it) and **Pin this tool version** - the `_v` flag, which nails the link to the tool version you're looking at so a later update can't change what it renders. - **Link behaviour** is what happens when the recipient opens it: fullscreen, the export panel already expanded, download-on-open with `&export` or copy-to-clipboard with `©`. Paste the link to a colleague, bookmark it or commit it. (Full details: [URL Mode](/info/url-mode.html).) **Some tools make the link the whole product.** Jump Page gathers your links into one page to hand out - a bio link, a conference talk, a shop front. There is nothing to host and no account behind it: the page is the link, so it opens as fast as the URL travels. In the editor you see the finished page beside the fields; a visitor who opens the link gets it full width, one link per scene as they scroll. ![Jump Page in the editor - the heading, three link scenes each with its own wash and a Made with Lolly footer, laid out as one page in the canvas](/t/url-shot?url=%2F%23%2Ftool%2Fjump%3Ffull&width=900&height=1300&dpi=96&waitMs=2000&cropSelector=%23tool-canvas&walker=1&format=svg&dark=1&filename=use-jump-page) **The dialog says what a link cannot carry.** Three things don't fit in a URL: an image or file you added from this device, a very long text value or a very large list. Each one is counted as the link is built. If anything had to be dropped the dialog names it and points you at the file below, instead of handing you a link that opens with the picture missing. A link that is merely *long* gets a milder note with its character count, since packing can still rescue length. ### The .lolly file `.lolly` is Lolly's portable-bundle extension, not a promise that every file contains the same thing. The `format` in `manifest.json` is the authority. The app reads that tiny manifest first and shows the size, contents and action before it writes anything: - A **shared design** (`lolly-share`) contains one saved tool session, its embedded files and a receipt for anything that still resolves by reference. It may also carry the tool and the design system used to make it. Opening adds a new Project; it never overwrites an existing session. - A **design-system pack** (`lolly-brand`) contains tokens and may contain fonts, logos, published versions and retained resources. Opening adds it as a separate named design system, then switches to it; systems already on the device remain. - A **brand workspace / instance pack** is a `lolly-brand` with declared tools, catalogue assets and optionally an instance address. The preflight names those device-wide effects because loading it replaces the single previously loaded workspace overlay. A full **device/profile backup is not a `.lolly`**. It remains a `LollyTools-….zip` with format `lolly-backup`, and restores only through **Profile → Storage**. A plain zipped tool folder also remains `.zip`. In other words, session and design-system bundles own `.lolly`; backup and loose archive workflows do not. **Download .lolly**, in the Share dialog of the tool you're working in, writes the current design as a shared-design bundle. It carries the saved session together with the images and files available on this device. Ordinary catalogue art rides along too. Licensed art is held back unless you explicitly include it, and a stale or unavailable file remains an external reference rather than disappearing. The prepared receipt shows the actual `.lolly` size, embedded-file count, external-reference count and whether the tool is included. Where your device has a share sheet, **Send to…** hands that file straight to it (AirDrop, an Android share) rather than saving it to disk. A `.lolly` is an ordinary zip. Rename it `.zip` and open it: your own images are under `assets/uploads/` and catalogue art under `assets/catalog/`, each with its real name and extension, `manifest.json` lists every one and a README at the top says what the file is. Three things are yours to decide before it goes: - **Whether your name goes in.** Your name, email and organisation are written into the file only when **Use my details to create** is on in your profile. With it off, the file records that it was made with Lolly and when - nothing about you. - **Whether licensed art goes in.** Licensed and brand-locked assets are held back by default. If the design uses any, the dialog says how many and offers two buttons - *Download without them* or *Include and download* - because including them hands the actual files to whoever opens the `.lolly`. - **Whether the tool goes in.** **Include the tool** packs the tool's own files alongside the design, so it opens on a device that doesn't have that tool. It arrives ticked for a custom tool - a fork or a private brand tool your recipient is unlikely to have - and unticked for a tool the signed catalogue lists, since their copy comes from the same source. (On a build with no signed catalogue, every tool counts as custom and the box starts ticked.) **Opening one.** On an installed desktop or mobile app, double-click or tap a `.lolly`, choose **Open with Lolly**, or send it to Lolly from the system share sheet. macOS, Windows, Linux, iOS and Android all register the format; desktop file managers show it as a Lolly document (and GNOME Files can show a saved session's own thumbnail). In the web app, use **Open** or drop the file onto Lolly. Every door uses the same manifest-first preflight. Opening from Brand Studio recommends the design-system action when a shared design carries one, but it never re-labels the file or hides **Open shared design**. An iOS or Android document handed in from another app is capped at 48 MB because the native hand-off has to copy its bytes across the app boundary. The mobile app says so instead of silently ignoring an oversized file. **Open** inside Lolly does not use that hand-off; it is the route to try for a larger bundle. After confirmation the selected reader inflates and verifies the bundle once. A shared design's assets go to your library, its session goes to Projects and its tool opens when available. An asset already on the device is matched by checksum and reused. A design-system pack is stored in its own namespace before the app switches to it. Files over 100 MB are called out as large, and the preflight warns when browser storage reports less free space than the declared payload needs. Every integrity-covered part is checked before the operation commits; a damaged copy is refused and the newly-created destination is rolled back. If the file carries a tool you don't have, Lolly asks before that tool can run: **Trust this tool?** names it and its author and says plainly that opening it runs the tool's own code on your device, with **Trust & install** as the way through. Decline and the shared work is still saved to your projects, waiting there for the day you add the tool. (One kind of tool can't be sideloaded yet - one whose code ships as a module - and it's turned away the same way.) A link and a file both hand over a snapshot. To work on the same session *at the same time* as someone else - two devices, no server, no internet needed if you're on one network - see [Working together](/info/collaborate.html). ## Live camera (motion-reactive tools) Every photo **Filter** - Halftone, Scanline, Posterize, Voronoi cells, Colour treatment, Pixel stretch and Imperfections - shows a **Go live** button where a camera is available. Turn it on and the effect tracks your webcam frame by frame, so it reacts to movement; you can record the result to GIF, WebM or MP4. Frames are read and processed **on your device** and never leave it, and the camera is released the moment you stop or leave the tool. (Any image picker also has **Take a photo** to grab a single frame as an on-device image.) ## My images When a tool lets you add an image from your device, it's kept exactly as it arrived - so a Content Credential on it still verifies - and saved to your personal **My images** library (under **Profile → Storage**). Only a genuinely huge file asks whether to keep or resize it. Reuse it across any tool. To scrub EXIF/GPS as images come in, turn on **Strip metadata from uploads** in your profile. There's no cap: the library is entirely local and limited only by your device's storage - manage or delete images there. ## The Catalogue - your asset library The **Catalogue** (`#/c`, or the **Catalog** segment of the Projects · Tools · Utilities · Catalog switch at the top of every listing view) gathers everything your tools can draw on - brand logos, images, audio and motion, grouped by kind - and it's where your **own creative files** live too. No server, no admin console, no pull request: it's all on your device. ![The Catalogue - brand assets, swatches and fonts, plus your own uploads](/t/url-shot?url=%2F%23%2Fc%3Fsection%3Dswatches%2Cfonts&width=1440&height=900&dpi=96&waitMs=2400&css=.plat-swatch-grid~%2A%7Bdisplay%3Anone%7D&walker=1&format=svg&localize=1&dark=1&filename=catalogue) - **Bring your files in.** Drag any image, SVG, audio clip, video, Lottie, PDF or PowerPoint deck onto the upload area - or click to choose - and it arrives in your catalogue instantly, ready in every tool's asset picker. A multi-page PDF or a `.pptx` asks which pages or slides to keep - each becomes an SVG asset. Ingest as much as you like; it never leaves your device. - **Favourite what you reach for.** ★ an asset (or a brand swatch) and it pins to the top of every picker, so your go-to logo or colour is one click away. - **Tidy up.** Recategorise an asset into a different group, hide a shared brand asset you don't use (with **Show hidden** to bring it back) or delete your own uploads outright. The same multi-select gesture and floating action bar as Projects work here too, so any of that can be done to a whole selection at once. - **Lift a video off its background.** Open a video's detail or right-click its card in any asset picker and choose **Remove background…** to save a transparent alternative - an animated WebP or PNG with real alpha. Pick a **Method**: an **On-device model** cuts a subject out of a busy scene, or a **Colour key** keys out an evenly lit, flat backdrop like a green screen or a plain wall, with **Tolerance**, **Softness** and **Spill removal** to tune the edge. The colour key needs no model download and no network, so **Remove background** is offered on any video and is often cleaner on tidy footage. A **Resolution** control (360, 480, 720 or 1080p, never past the source) trades detail for a smaller, faster file. It runs as a background job on your device. The finished cut-out is saved beside the original as its own asset and the source video's Content Credential rides along as an ingredient. (See [Generated once, rendered the same](/info/ai-features.html) for why removing a background stays a plain edit.) ### Take your palette and fonts anywhere The Catalogue's **Swatches** panel does more than display - click a colour to copy it, or **download the whole brand palette** in the format your other tool speaks: - **Design tokens (JSON)**, **CSS variables** or **CSS classes** - drop the brand straight into a stylesheet or a build; - **Adobe Swatch Exchange (.ase)** - load it into Illustrator or Photoshop; - **GIMP palette (.gpl)** - for GIMP or Inkscape. ![The Swatches panel - the five palette download buttons across the top, then every brand colour as a copyable chip](/t/url-shot?url=%2F%23%2Fc%3Fsection%3Dswatches&width=1440&height=900&dpi=96&waitMs=1800&css=.cat-group%3Anot%28%5Bdata-group%3Dswatches%5D%29%7Bdisplay%3Anone%7D&cropSelector=%5Bdata-group%3Dswatches%5D&walker=1&format=svg&dark=1&filename=use-swatch-downloads) The **Fonts** panel lists your brand faces with a **download** beside each, to install locally or hand to a print shop. (The [Brand Studio](/info/brand-studio.html)'s Colours room offers the same palette download.) Assets are one half of the open, do-it-yourself path; the other is **making your own tools** - the free canvas (Design, described above) lets you build one visually, no code required. ## Sound & accessibility Lolly aims to be comfortable to use for everyone. The interface is keyboard-navigable, custom controls carry proper labels for screen readers and every tool's live preview is exposed as a single labelled image describing what it's making. A gentle layer of **assistive sounds** confirms what you do - arriving in the gallery, a valid vs. invalid Content Credentials check, closing a panel, switching a filter. It's **off by default**: turn **Sound** on anywhere the switch appears (each view's options popover, or **Profile**), and the choice is remembered. Four opt-in comfort settings live under **Profile → Accessibility**: **Reduce motion** (drops the app's transitions and flourishes), **Hide colourful previews** (calm icon-and-text gallery cards, and quieter project thumbnails), **High contrast** (stronger borders, text and focus rings) and **Large text** (bigger app type - labels, menus, button text). All four settle the app *around* your work: they never reach inside a tool canvas or change a pixel of what you export, and each is off until you turn it on. Full detail in [Your profile → Accessibility](/info/profile.html#accessibility). Beside the Sound switch is **Neurospicy Mode** - an optional, calming background focus track that plays quietly while you work. Turning it on opens a small **player dock** in the bottom corner that follows you across the app; from it you can search and pick a track, skip forward and back, set the volume and minimise or close it. The track list spans a few categories - procedural *Lolly Sings* tunes, ambient loops and beats, your own uploaded audio and a handful of live internet **radio** stations (these need a connection; everything else plays offline). It's **off by default** and, like Sound, is remembered across sessions and devices. Turning Sound off mutes the focus track too. ## Storage & privacy Everything is stored in your browser's local database (IndexedDB): your profile, saved sessions, uploaded images and a cache of downloaded catalog content. **Profile → Storage** shows usage and lets you: - **Clear cache** - drop downloaded catalog content (re-syncs next load). - **Clear all my data** - wipe profile, sessions and images. *Cannot be undone.* ![The storage card on a phone-width screen: every category of on-device data named, with the Clear all my data button at the bottom](/t/url-shot?url=%2F%23%2Fprofile%3Ffocus%3Dstorage-section&width=430&height=1600&dpi=192&waitMs=2400&css=.welcome-dialog%2C.personalize-nudge%2C.store-manages%2C.storage-subsection%2C.store-selbar%2C.store-chip-val%2C%23store-hero-num%2C%23store-headroom%2C%23store-quota%2C%23store-reclaim%7Bdisplay%3Anone%7D&format=svg&walker=1&cropSelector=%23storage-section&dark=1&filename=pv-storage-clear) None of this local data is transmitted anywhere - no telemetry, no cloud rendering. The complete list of what the app ever fetches or sends is in the [Privacy Policy](/info/privacy.html), and [Server Surface](/info/server-surface.html) inventories the optional server components. ## Moving to another device Because everything lives on your device, **Profile → Storage → Move to another device** lets you carry it all to a second install - no account, no cloud: - **Export my data** downloads a single `LollyTools----.zip` (the name parts come from your profile and are dropped if unset; `` is a per-day counter so same-day exports don't collide) containing your profile, every saved session (with its thumbnail), your uploaded images and your preferences (theme, sidebar width, local activity stats). - **Import data…** on the other install reads that file back in. It **merges**: anything with the same name (your profile, a session slot, an image) is replaced by the imported copy; everything else on that device is kept. Saved sessions re-link to your imported images automatically. The catalog cache isn't included - it re-downloads itself on the new device. The bundle is a plain zip (`manifest.json` + `profile.json` + `sessions.json` + `assets.json` + `assets/blobs/…` + `prefs.json`, format id `lolly-backup`), so it survives email, USB or AirDrop intact and is the same format every shell reads. Each part is checksummed, so a file damaged in transit is caught on import rather than restored half-broken. (Full format spec: [Data Transfer](/info/data-transfer.html).) ## Importing a design (Figma, Penpot, Illustrator, InDesign) You can bring an existing design into Lolly and keep working on it: open **Design**, click **Import a design** in the canvas toolbar, and choose a Figma **.fig** or SVG, a Penpot **.penpot**, an Illustrator **.ai** / **.pdf** or an InDesign **.idml**. Layers become editable boxes on the free canvas - text stays retypable, images land in **My images** and type and colours conform to the brand globals - then the result saves, shares and renders like any other session. The parse happens entirely on your device. Full detail: **[Import a design](/info/design-import.html)**. ## Exporting See **[Exporting & Formats](/info/exporting.html)** for the full story - choosing a format, output size and print units, transparency, video and copy/share. In short: pick a format, set the size if you need to and **Download** (or **Copy** to the clipboard). ## Batch (Pro) mode For power users, **Batch** (linked from the gallery, gated behind the Pro feature flag, which defaults on) renders many variations at once - a grid where each row is a set of inputs, exported together. Ideal for localising a card into a dozen languages or generating every size variant in one pass. Fill rows by typing, pasting straight from a spreadsheet or importing a CSV (you can export one back too), and set per-row format, size and output filename. Save a whole grid as a named **batch session** that reopens from the gallery, and download every row as a single `.zip`. ![The batch toolbar - zip name, units, DPI and the format every row inherits, with Sessions and Render on the right](/t/url-shot?url=%2F%23%2Fbatch&width=1440&height=900&dpi=192&waitMs=3500&cropSelector=.pro-toolbar&walker=1&format=svg&dark=1&filename=use-batch-toolbar) Batch is for generating **many variants of one template** at once. To re-render sessions you've **already saved**, use **Projects → Render folder / Render selection** (above) - no Pro needed. ## Editing side by side (Multi-edit) Batch is many variants of *one* design. **Multi-edit** is the other half of the job: several **different** saved designs open at once, so one change applies to all of them. Tick between **two and eight** saved sessions in **Projects** and pick **Edit together** from the selection bar; they open as live cards side by side at `#/multi?s=,…`. Each card is a real render of that session, not a stored thumbnail, so what you see is what it will export. One sidebar drives the lot: - **Shared** leads it - every input that two or more of the selected sessions declare the *same way* (same id, same type, same constraints - the same merge rule the batch grid uses on its columns). Edit a shared control once and the value fans out to every session that declares it, live on every card. Two sessions of the same tool share everything; two different tools share whatever they happen to have in common, and nothing else. - Under it, **one collapsed card per session** with all of that session's own inputs, at the same fidelity as the tool's own sidebar - asset pickers, repeating row groups, colour fields - plus a compact export block: **Format**, **W** / **H**, **Unit**, **DPI** and its own **Download**. That Download saves the session first and then renders it through the ordinary session-export path, so the file carries the same filename, format and Content Credentials it would straight from the tool. - **Filter inputs…** at the top narrows the controls across *every* card at once - which is how you get to "the headline" in eight sessions without scrolling for it. Click any canvas (or press Enter on it) and that session's sidebar card opens and scrolls into view. **Save all** writes every session back to its own slot. **Download all** saves first, then renders the whole set through the same pipeline as Projects' **Render selection** - one zip, with the optional password lock offered on the way. Two honest limits. The two-to-eight cap is real: every card mounts its own live runtime, and that's the number that stays responsive - a link asking for more (or for a session that no longer exists) says so rather than half-loading. And the link names *your* saved slots, so it reopens that set on this device; it is not a share link. When the selection is bigger than eight, mixes tools or includes images as well as sessions, the escape hatch is **Edit as sheet** in the same selection bar: it opens the whole selection as **rows in the batch grid** (`#/pro?s=…`), with no size limit and no same-tool rule. Folders stay out of both - they have their own open-in-grid path. ([Search](/info/search.html) is the one thing that doesn't reach in here yet: Multi-edit is the single view the search bar doesn't know about.) ## Offline & install Lolly is a PWA. After the first load it works **offline** - install it from your browser's address bar (or *Add to Home Screen* on mobile) for an app-like, full-screen experience. It updates itself when you're back online. About updates: if a view ever fails to load right after one (a blank panel, a "failed to fetch" in the corner), reload the page once - the app picks up the new version cleanly and your work, sessions and brand are untouched. It stores everything on your device, not in the page. --- Source: https://lolly.tools/info/create/brand-studio.md Page: https://lolly.tools/info/create/brand-studio.html # The Brand Studio The **Brand Studio** at `#/start` is the one place you shape your brand - its logos, colours, type, the rest of your tokens and the files it keeps. Set it here once and every tool, page and export follows it *by construction*, not by review. Changes preview **live across the whole app** as you make them, so you can see a colour or a font land everywhere before you commit it. It's all on-device: your brand files and tokens never leave your machine (picking a Google Font fetches that one family from Google, once, after a consent dialog), and the brand travels in a single [brand pack](#move-a-brand-between-devices) file. > **This is the editor. The dashboard is the mirror.** The **Design system** tab on the Dashboard (`#/d`) *shows* your brand read-only; you *edit* it here at `#/start`. If you want to change a colour later, come back to the Brand Studio. ## The rooms The studio is a set of **rooms** listed in a rail down the side - not steps. Nothing is numbered, nothing is gated on anything else and arriving in any of them is legitimate: - **Overview** - the hub. What exists right now, at a glance, with a door into each room. - **Colours** - add colours one at a time, assign roles or generate a whole palette from one. - **Type** - the four faces the app, the tools and every export read. - **Logos** - your marks, in every orientation and treatment. - **Tokens** - corner radius, spacing, shadows and the rest of the system. - **Files** - the image, audio and motion files your brand keeps. On a phone the same list becomes a horizontal chip strip pinned under the header. Switching room never reloads anything - the editor keeps all its panels mounted and simply shows the one you asked for. **Deep-link a room** with `#/start?area=`. The keys are `overview`, `color` *(note the US spelling in the URL)*, `type`, `logos`, `tokens`, `catalogue` (the Files room - the panel key is a permanent contract, so the URL keeps the old name) and `versions`. `?tab=` is the long-standing alias for the same thing and still resolves, so old links and bookmarks keep working; anything unrecognised opens Overview rather than dead-ending. Pinned to the **foot of the rail** are the actions that belong to the whole design system rather than to one room: - **Add from…** - the source picker, for bringing a brand in from a file, a PDF, an image, a font or a website. See [Bring a brand in](#bring-a-brand-in) below. - **Tray** - the candidates a scan turned up but hasn't committed. It stays hidden until a scan actually keeps something, and carries a count when it does; nothing in it changes your brand until you press Add on that row. - **Export** - writes the whole design system as one `LollyBrand-….lolly`. - **Tokens (.json)** - the plain design-tokens document on its own, for a repo, a build step or another tokens tool. - **Versions** - publish, activate and restore named copies of the design system. Hidden until there's something of your own to publish (or a `?area=versions` link asks for it by name). ![The studio room rail - Overview, Colours, Type, Logos, Tokens and Files](/t/url-shot?url=%2F%23%2Fstart&width=1440&height=900&dpi=192&waitMs=1600&cropSelector=.ds-rail&format=svg&walker=1&localize=1&dark=1&filename=brand-studio&try=1) ## Overview Overview is the first room, and it has two faces. With **nothing chosen yet** it says **Nothing here yet** and offers three doors, in the order they cost: **Pick a colour** (one press), **Choose a face** (a search) and **Add a logo** (a file). Each opens its room with the control that makes the decision already up, rather than changing the room and leaving you to find it - `#/start?area=color&focus=pick` opens the Colours room with the picker on the chip, `#/start?area=type&focus=stage` opens the Type room's stage for the primary face, and `#/start?area=logos` opens the Logos room. None of the three writes anything. Under the doors sits one quiet line: **Bring a file** for design tokens, a Penpot project, a PDF or an SVG, and **explore the tools**, because leaving is a legitimate answer too. Once anything is your own, the same room shows **what you have**, with the counts you made leading. Colours reads the number of colours the design system carries, and adds a muted `· N starter` only where there are inherited colours on show; the strip beside it puts the colours you chose first, then a hairline and the faded starter ones. Type reads by role (*Inter for headings*, with *Starter for the rest · SUSE, SUSE Mono* under it). Logos reads how many slots are filled, or **Not set**. Tokens carries the corner radius, tagged *starter* until you move it. Files says **Nothing yet** while the library is empty. Every block is a door into its room. There are counts here, never a progress bar and never a finish card - nothing in this studio is owed. ## Logos Start by emptying your folder of marks into the drop zone at the top: **"Drop marks here, or choose several at once"** takes as many files as you have in one go. Each file is read for its shape and its ink, then queued under **Waiting for a slot** as a chip that says what it thinks - *"Looks like the Horizontal primary"*, with the measurement it went on, and a **Place** button (**Replace**, where that slot is already filled). Where it isn't sure the chip says so plainly and offers **Change slot** instead, which lists all eight. Nothing is placed until you press something. Two things happen around that queue. A mark with surplus empty margin gets a **trim offer** first - answer it or press Escape and the original file goes in untouched. And where a mark can furnish an empty sibling slot, the room offers the derived **mono** or **reverse** version as its own chip, marked *Generated*, which disappears again if you fill that slot another way. Below that sits the grid every mark ends up in - **orientation × treatment** slots: - **Orientations:** Horizontal (wordmark + symbol in a row) and Vertical (stacked, for square and tall spaces). - **Treatments:** Primary, Primary reverse (for dark backgrounds), Mono (one colour) and Mono reverse. That's eight optional slots. Click a slot to add a PNG, SVG, JPEG or WebP; click a filled slot to replace it. Every slot is optional and everything stays on this device. ![The logo matrix - each orientation across the top, each treatment as its own dashed slot, all of them optional](/t/url-shot?url=%2F%23%2Fstart%3Ftab%3Dlogos&width=1440&height=1600&dpi=192&waitMs=1600&cropSelector=.be-logo-grid&format=svg&walker=1&dark=1&filename=bs-logo-slots) - **Custom marks** - add marks your brand names its own way (an icon, a crest, a favicon) under **Custom marks**; name it and choose a file. - **More identities** - a sub-brand, product or event can have its own full set of logos. Use **+ Add another logo** and name it; your main set is simply "Your logo". - **Upload an SVG and Lolly reads its colours.** On a brand-new install it quietly sets your primary colour from the logo and says so. On an existing brand it offers the colour as a suggestion instead - *"Found in the logo: #…"* with a **Use as primary** button beside it - over in the Colours room, where you can take it or dismiss it. ## Colours The room grows with the design system. Nothing you have not needed yet is on the page, so a first visit is one decision and the rest arrives as the palette does. ### The first colour A design system with no colours of its own opens on one centred column: **Start with one colour**, a large live chip, a field, and a quiet line saying that roles, shades and print settings arrive as the system grows. - **The chip is the picker.** Press it and the studio's own OKLCH card opens on the chip, seeded with whatever the field is holding: a name, the wheel, the four dials, alpha and **Stored as**, with **Cancel** and **Add colour** at the foot. Dragging a dial paints the chip and rewrites the field as you go, and nothing reaches the design system until you press **Add colour**. - **The field takes any notation** - `#e0452b`, `rgb(224 69 43)`, `oklch(58% .19 32)` or a plain colour name - and a whole *list* of colours becomes a row of chips you add one at a time. - **Two more doors sit beside it.** The eyedropper (on a browser that has one) takes a colour off the screen, and **From an image** reads a screenshot or a photo on this device and offers the colours it finds. - **Add is never disabled.** With nothing readable in the field it opens the picker, which is what an empty press usually means; text it cannot parse gets a line under the field saying so, rather than a dead button. The first colour becomes the **primary**, and the chip that answers the add says so - *"Primary is now Vivid Violet"* - with **Fine-tune** beside it. ![The Colours room with nothing chosen yet - one big live chip, one field and one line about what arrives later](/t/url-shot?url=%2F%23%2Fstart%3Farea%3Dcolor&width=1440&height=740&dpi=192&waitMs=1800&format=svg&walker=1&localize=1&dark=1&filename=brand-colours) ### Starter **Starter** is the word for anything that came with the app instead of being chosen. A fresh install carries no colour at all: what it has is one neutral ramp, ink through paper, so surfaces, text and hairlines render before anybody has decided anything. Those neutrals are scaffolding, so they are not counted as colours and they are not drawn in the palette pane. They live in the [Tokens](#tokens) room as **Neutrals · starter · 9**, with an **Open** that shows them in the Colours pane as one folded, tagged group (`#/start?area=color&group=neutral`). The same word carries through every room: a role standing on a starter colour reads *"Starter Paper stands in"* and its picker offers **Choose…**; a starter face wears a **Starter** tag and no tint; a starter corner radius is tagged on the Overview. Inherited material is never drawn with a dashed border, because a dashed border means a drop target here. ### As the palette grows One colour brings the room's two panes back. The left is where you work, the right is the live palette; drag the divider between them to resize (Enter on it collapses the palette out of the way). At one colour the left pane holds three things - the compact add row, a panel offering **Generate a palette from *Vivid Violet*** with one line about what that would do, and **Roles** - while the right pane holds the colours the design system carries, their count, and a line about what arrives later. Generate a set of shades, or add six colours by hand, and the rest of the room opens: the four expert wings, the colour chart, gradients, the download pill and selection across the whole pane. ![The Colours room after one colour - the two panes back, the generate offer, roles reading in three registers and the pane at one colour](/t/url-shot?url=%2F%23%2Fstart%3Farea%3Dcolor%26focus%3Dpick&width=1440&height=840&dpi=192&waitMs=1800&drive=click%3A%5Bdata-be-editor-add%5D%3Bwait%3A900&format=svg&walker=1&dark=1&filename=bs-colour-first) ### Roles - what tools read **Roles** are the layer over the swatches: which colour plays each part in every tool and export. Roles are optional (a design system of three loose colours and no roles is a perfectly good one), any swatch can take one and the contrast readout is measured against the surface, APCA first. A row reads in one of three registers, so the strip never claims a decision nobody made: - an own colour serving the role, at full strength; - **Starter *Paper* stands in** - muted, with **Choose…** on its picker; - **↳ follows Primary** - the role resolves through the primary rather than to a colour of its own. Once the palette has shades, the strip grows to all seven slots a tool can read: Primary, Secondary, Surface, Text, Muted, Edge and On primary. On primary is derived from the primary, reads as **Derived** and carries no picker. **The app's own accent is a preference, not a token.** By default the interface follows the design system and the chrome accent takes the primary colour. That is an Appearance setting on [your profile](/info/profile.html) - **Interface follows the design system** - and turning it off leaves the chrome neutral. Tools, canvases and exports are unaffected either way, and the fonts and the corner radius follow the design system whether the setting is on or off. ### The expert wings Four folded sections sit under the add row and the roles, once the palette has shades. Open the one you want; each is deep-linkable as `#/start?area=color&focus=`, which opens it whatever the room is otherwise showing: - **Generate a starter palette** (`focus=generate`) - one colour into a full set of shades. Described below. - **Shade curves** (`focus=curves`) - reshape a ramp point by point. Lightness, chroma and hue each get their own curve, switched with L / C / H, and the shades below rebake live as you drag. - **Contrast** (`focus=contrast`) - **Contrast-lock** retones a ramp to hit APCA targets against a background you pick, each step keeping its own hue and chroma; **Rotate hue** turns the whole ramp bodily around the wheel, every shade keeping its lightness and chroma. - **Print** (`focus=print`) - what the primary becomes on press: its automatic screen value, or a pinned CMYK build or a named spot ink instead. ### One colour, a whole palette Inside **Generate a starter palette**, pick a **Primary colour** and Lolly works out a complete palette - light and dark surfaces, text, accents and full tint/shade ramps - using the same perceptual colour maths (OKLCH) the engine uses everywhere. Tune the derivation: - **Scheme** - Mono, Complement, Analogous or Triad - sets how the secondary colour relates to the primary. - **Shades** - a slider from 3 to 20 (default 5) controls how many steps each ramp generates. - **Fine-tune** (folded) - **UI intensity** (Muted / Deep), **Contrast** (Comfort / High) and **Text on brand** (Auto / Light / Dark). Nothing in this wing writes anything to the design system. It is a preview, live across the app so you can judge it, right up until you press **Replace palette** (below). Below the primary you'll see live **Primary / Neutral / Secondary / Blend** ramps and Light and Dark specimen cards, each carrying its own contrast readout - the WCAG ratio with the APCA `Lc` figure beside it. **Click a step in the Neutral or Secondary ramp** to anchor that shade instead of the derived default. ![The four ramps stacked above light and dark specimen cards, each card carrying its own WCAG contrast ratio](/t/url-shot?url=%2F%23%2Fstart%3Farea%3Dcolor%26focus%3Dgenerate%26seed%3D%2523e0452b&width=1440&height=1400&dpi=192&waitMs=1800&css=.start-head%7Bdisplay%3Anone%7D&cropSelector=.be-preview&format=svg&walker=1&dark=1&filename=bs-colour-ramps) ### Build the palette (harmony generator) Still in the same wing, **Build the palette** generates matching accent colours from the primary. Pick a **Harmony** - **Complementary**, **Adjacent**, **Triad**, **Tetrad** or **Analogous** (which brings its own **Accents** count, 2 to 5, and a hue **Angle** from 10° to 45°) - and each candidate arrives with an auto-generated human-readable name and a **+ Add** button. Adding one puts that colour in the palette immediately, one press to one token. *"The palette, applied"* previews the whole set on real graphics. ![Generated accents, each with a swatch, an auto-generated name, its hex and an Add button](/t/url-shot?url=%2F%23%2Fstart%3Farea%3Dcolor%26focus%3Dgenerate%26seed%3D%2523e0452b&width=1440&height=900&dpi=192&waitMs=1800&css=.start-head%2C.be-colour%7Bdisplay%3Anone%7D&cropSelector=.be-candidates&walker=1&format=svg&dark=1&filename=bs-harmony-candidates) ### Committing a generated palette **Replace palette** is the one control in this wing that writes anything, and it never writes straight away. Press it and a review card opens first, headed **"Replace the palette?"**, itemising exactly what is about to happen: how many roles stay as you assigned them, how many colours you added yourself are kept, how many shade curves get re-anchored, how many print locks re-pinned, how many hidden shades stay hidden, how many gradient stops keep their colour. **Replace palette** on that card commits it; **Cancel** walks away and changes nothing. Once it has run, the card becomes **"Palette replaced."** with a single **Undo** already focused - and a checkpoint of the whole design system is taken *before* the swap, so "put it back the way it was" is a restore rather than a lost afternoon. ### The palette, the chart and each swatch The right pane lists every colour the design system carries, grouped (Primary, Neutral, Secondary, Spectrum, Custom), each group foldable with its own **+ Add**. A role never makes a second tile: one token is one tile, and a tile a role points at wears a small corner mark instead (**P**, **S**, **Su**, **T**). Below the tiles, **Colour chart** folds open on two views of the same swatches: the **Wheel** (the OKLCH wheel - drag a dot to recolour it, click a dot to edit it or click empty space to drop a new swatch) and the **Gamut** chart, which shows where the displayable range actually ends. `#/start?area=color&focus=chart` opens the card directly, as `?wheel` always has. ![The palette pane, every group foldable, with the download pill parked at its bottom edge](/t/url-shot?url=%2F%23%2Fstart%3Farea%3Dcolor%26focus%3Dpick&width=1440&height=1000&dpi=192&waitMs=1800&drive=click%3A%5Bdata-be-editor-add%5D%3Bwait%3A500%3Bclick%3A%5Bdata-be-generate-cta-go%5D%3Bwait%3A900%3Bclick%3A%5Bdata-be-replace-palette%5D%3Bwait%3A800%3Bclick%3A%5Bdata-be-review-go%5D%3Bwait%3A1400&cropSelector=.be-split-side&walker=1&format=svg&dark=1&filename=bs-palette-pane) ![The OKLCH wheel - angle is hue, distance out is chroma and the greys ride a lightness rail down the side](/t/url-shot?url=%2F%23%2Fstart%3Farea%3Dcolor%26focus%3Dpick&width=1440&height=900&dpi=192&waitMs=2400&css=.start-head%2C.be-pal%2C.be-gradients%7Bdisplay%3Anone%7D&drive=click%3A%5Bdata-be-editor-add%5D%3Bwait%3A500%3Bclick%3A%5Bdata-be-generate-cta-go%5D%3Bwait%3A900%3Bclick%3A%5Bdata-be-replace-palette%5D%3Bwait%3A800%3Bclick%3A%5Bdata-be-review-go%5D%3Bwait%3A1400%3Bclick%3A%5Bdata-be-chart%5D%20summary%3Bwait%3A900&cropSelector=.be-pal-wheel&walker=1&format=svg&dark=1&filename=bs-colour-wheel) Click any swatch to open its editor: - **Rename** it. - **Set the colour** - the picker opens on perceptual **OKLCH** sliders, with modes for **Hex**, **HSL**, **RGB** and **CMYK**; the value field reads *and* writes in whichever space is active, so you can paste a hex or type ink percentages. Note that entering CMYK sets the *screen* colour by conversion - to pin exact inks, use the print lock below. - **Stored as** - choose how the swatch is persisted: **LCH** (the default - perceptual, wide-gamut, the best choice for editing), Hex, RGB or HSL. Override it when you need to pin an exact legacy hex or match an sRGB value. - **Use as** - hand this swatch one of the brand roles directly, without going back to the Roles panel. (A role's own tile doesn't offer it - a role can't take a role.) - **Print substitutes** (folded) - lock the colour's print behaviour: - **CMYK** - switch it from **Auto** to **Locked** to override the automatic sRGB→CMYK conversion with exact ink values (C/M/Y/K, 0–100). - **Spot colour** - switch it from **None** to **Set** to lock the swatch to a spot colour; give it a **Name** (e.g. `PANTONE 186 C`), an optional **Book** and an optional **Finish** (Ordinary ink by default) for when the ink is not an ink at all - a foil, an emboss or deboss, a spot varnish, a soft touch or a die cut, crease or perforation. - **In other spaces** (folded) - the same idea widened: each row is a space this swatch can be expressed in, either derived from the canonical value or authored by you, and an authored one wins at export. These print locks are what a press uses when you export a CMYK PDF or TIFF - see [Exporting](/info/exporting.html#colour-profiles). **Deleting a swatch** is safe: derived ramp steps and theme roles are *hidden* (the underlying token keeps resolving, so nothing downstream breaks), while colours you added yourself are removed outright. ### Working with many swatches Selection in the palette pane is a gesture, not a mode. There is no button to press first, and the bar arrives with the first selected tile and leaves with the last. - **Drag on the pane's empty space** to draw a rectangle: every tile it touches joins the selection, across group boundaries. A folded section contributes nothing, and a drag that never moves clears the selection. - **Shift-click** takes the range in reading order; **Cmd/Ctrl-click** toggles one tile; a plain click still opens that tile's editor. - Every group header carries **Select all**, and **Cmd-A** with a tile focused takes every colour the design system owns - never a starter one. - The grid has one tab stop. Arrows walk it, Shift-arrows extend the selection, Space toggles a tile, Delete removes the selection and Escape clears it. (Arrows only move the focus: to nudge a channel, press `l`, `c` or `h` first, as the readout says.) - On a touch screen there is no rectangle. Press and hold a tile to start a selection, then tap to add; per-group **Select all** carries the rest. The bar itself reads **{n} selected**, then **Move to** (an existing group, or a new one you name inside the menu), **Give a role** (each selected colour takes the next role in turn, so four tiles fill all four roles in one press), **Download** (the selection in any of the six palette formats), **Copy values** (one line per colour in its stored notation) and **Delete**. Move to and Give a role appear once the palette has shades to move around. One Ctrl/Cmd-Z undoes a whole bulk action - a move of forty, a role walk, a delete - and a delete says what it kept, because a selection reaches tiles this room does not remove. ### Gradients An optional **Gradients** panel builds blend tokens from the palette for backgrounds and accents. Skip it entirely if the design system doesn't do gradients. Each gradient has a preview, named stops (2–8) and an angle. The key behaviour: **a stop references a swatch**, so recolour that swatch and the gradient follows. Interpolation runs in OKLCH for clean blends. Delete a stop to trim the run. ### Take the palette elsewhere The floating pill parked at the bottom edge of the palette pane downloads the whole palette as **Design tokens (JSON)**, **CSS variables**, **CSS classes**, **SCSS variables**, a **GIMP palette (.gpl)** or an **Adobe Swatch Exchange (.ase)** - so the design system drops straight into Illustrator, Figma, GIMP or a stylesheet. It sits outside the pane's scroller, so it keeps its seat however far the palette scrolls, and it appears once the palette has shades. (You can also download the palette from the [Catalogue](/info/using.html) view.) ## Type This room grows the same way. With no face of its own it is one card and one decision: **Primary**, set at reading size in the face serving it today, a **Starter** tag beside the name, a filled **Choose a face** and the line *"Nothing installs until you choose one."* Under the card sits *"Headings, code and italic follow the primary until you choose them"*, with **Choose them separately** revealing the other three cards for the rest of the visit. ![The Type room with no face chosen yet - one card at reading size, a Starter tag on it, and one filled Choose a face](/t/url-shot?url=%2F%23%2Fstart%3Ftab%3Dtype&width=1440&height=740&dpi=192&waitMs=1800&format=svg&walker=1&dark=1&filename=brand-type) Choose one face and the room opens out into **four role cards**, the Fonts list and the live specimen. The four faces are the ones the app, the tools and every export actually read: - **Primary** - body copy, buttons and every tool. - **Headings** - the display face for `h1`/`h2`. - **Code** - a monospace face for code and data. - **Italic** - a true italic companion for emphasis, quotations and asides. Headings, code and italic each fall back to the primary until you assign them, so a one-face design system needs no decisions here at all. **A tint means you chose it.** A card is tinted only where you installed that face. A starter face carries the same **Starter** tag the palette's inherited groups wear, in the muted register and with no tint, and a role nobody has chosen reads **↳ follows Primary** rather than repeating the primary's name as though it had been picked. The button says **Change** on a face of your own and **Choose a face** everywhere else. Nothing on a card commits anything: the button opens the **compare stage** scoped to that role. ![The four role cards revealed - each set in the face that serves it, with a Starter tag where nobody chose one and Italic following the primary](/t/url-shot?url=%2F%23%2Fstart%3Ftab%3Dtype&width=1440&height=1000&dpi=192&waitMs=2600&drive=click%3A%5Bdata-be-typemore-toggle%5D%3Bwait%3A600&cropSelector=.be-typecard-grid&walker=1&format=svg&dark=1&filename=bs-type-specimen) ### The compare stage ![The compare stage open under its card, with the search row, the pinned families and the cards folded to a one-line strip](/t/url-shot?url=%2F%23%2Fstart%3Farea%3Dtype%26focus%3Dstage&width=1440&height=740&dpi=192&waitMs=1800&format=svg&walker=1&dark=1&filename=bs-type-stage) The stage opens **inline in the room**, not in a dialog, and directly under the card you pressed. While it is up the cards fold to a one-line strip of role and face, so the stage is on the first screen even on a phone. Escape cancels and hands the keyboard back to the card you opened it from. Choosing a face is three presses: 1. **Choose a face** on the card. 2. Type a family name and press **Preview** - or press one of the six **Pinned** families under the field, one press each. The card appears already loading, with a skeleton bar where the specimen will be instead of the interface face standing in for a face you have not seen yet. 3. **Use this face**. **Consent is asked once, on the press you made.** The first time a preview reaches Google Fonts, a dialog says what happens: *Google learns the family name and your IP address. The file is then kept on this device and used offline. This is the one step in the studio that reaches a third party.* **Fetch from Google** goes ahead and is remembered. **Cancel** leaves the card saying *"Not fetched. Nothing was sent to Google."* with its own live **Fetch from Google**, so changing your mind is one press on the card itself. No card ever shows a dead button: whatever state it is in, its one primary names the next step. **Drop a font file on the stage** and it previews at once - **TTF**, **OTF** or **WOFF** off your own machine, which is the path for a licensed corporate typeface you already own. That drop zone is the only file door in the room. Either way the face stays on this device, renders in the app, in the tools and in every export, offline forever, and travels in the design system file - nothing is fetched at render time. Everything on Google Fonts ships under an open licence (OFL/Apache/UFL). ### Fonts on this device The **Fonts** panel lists every face this device holds and the role it serves. Faces you added lead under **In the design system**, each with its roles and a delete, and the one serving Primary carries the badge. The starter faces follow in one folded row - *Starter · SUSE, SUSE Mono · serving Primary and Code until you choose* - muted, with no delete and nothing to promote, because neither is a decision anybody made. **Add a face** opens the same compare stage unscoped. The **Type roles** panel at the foot shows a live specimen of each role - body and UI in the primary, an optional display face for the top headings, an italic for emphasis, a mono for code and data - with the family and its state beside each one (*Inter*, *SUSE · starter*, *SUSE · follows Primary*), so the whole set can be read at once. ## Tokens The rest of the design system, editable without touching code: ![The Tokens room - a corner-radius slider plus spacing, sizing, shadows and the rest of the system](/t/url-shot?url=%2F%23%2Fstart%3Ftab%3Dtokens&width=1440&height=740&dpi=192&waitMs=1600&format=svg&walker=1&dark=1&filename=brand-tokens) - **Rounded corners** - a single radius slider (0–1.5rem) that cards, buttons and panels across the app follow. - **Neutrals** - the ink-through-paper ramp a fresh install ships with, listed as **Neutrals · starter · 9** with its nine steps and an **Open** into the Colours pane. It is the one place the starter neutrals are managed, and the *starter* tag goes the moment the ramp is generated rather than inherited. - **More tokens** - add and edit **spacing**, **sizing**, **stroke width**, **opacity**, **rotation**, plain **numbers** and **shadows**. Pick a type, name it (*Gutter, Card shadow…*) and set its value. These are stored as standard [design tokens](/info/design-tokens.html) (DTCG) and travel with the design system. ## Files Drop the files your brand keeps - logos aside - here: **vector**, **image**, **audio** and **motion** (video, Lottie, animated) assets. They land in your [Catalogue](/info/using.html), sorted into sections and ready in every tool's asset picker. Everything stays on this device. (The rail labels the room **Files**; the URL key stays `catalogue`, because a panel key is a permanent contract.) ## Bring a brand in **Add from…** at the foot of the rail opens a two-stage picker. The first stage asks what you *have*, not what format it is: - **Design tokens or a design file** - DTCG or Tokens Studio JSON, a Penpot project, a **zip of token sets**, a Lolly design system pack or an SVG. - **PDF** - a deck or a guidelines file, read on this device for its colours, its marks and its embedded typefaces. - **Image** - a screenshot or a photo; its colours are read on this device and nothing is uploaded. - **Font file** - TTF, OTF or WOFF. Opens the Type room, where the face installs. - **Website** - one page, read for its colours and type. This tile only appears on a device that can actually read a page, because a disabled tile advertising something nobody can press is worse than no tile at all. Where it does appear it names its reader plainly: fetched by the app on this device, or read through the browser extension in a background tab, signed in as you. Naming a URL only *prefills* the field - the fetch button is the consent, so a link somebody sends you can never start a read. Pick the design-file source and the second stage is the card below: the accepted formats lead as icon tiles in preference order, and the whole card is one drop target - click anywhere on it or drag a file onto it. You can also drop a file straight onto the studio. ![The import card - the accepted formats lead as icon tiles, and the whole card is one drop target](/t/url-shot?url=%2F%23%2Fstart%3Fsource%3Dfile&width=1440&height=900&dpi=192&waitMs=1600&css=.start-import-modal%20.modal-msg%2C.start-import-modal%20.modal-title%7Bdisplay%3Anone%7D&cropSelector=.start-import-drop&walker=1&format=svg&dark=1&filename=bs-brand-import-formats) What each design file gives you: - a **Lolly design-system pack** (`.lolly`; legacy `.zip` is still accepted) - installs in one step; - a **Penpot** export (`.penpot`) - pulls in its design tokens; - a **Design Tokens** file (`.json`) - W3C DTCG; - a **Tokens Studio** file (`.json`) - Tokens Studio; - a **plain SVG** (`.svg`) - Lolly scans its colours and lets you pick which to keep, the first becoming your primary. A source install takes a **checkpoint first**, so "revert to before the import" is one restore. And what a scan finds doesn't go straight in: candidates land in the **Tray**, where each one is added by its own press, through the room that owns that kind of material. `#/start?source=` opens the picker on a given source (`file`, `pdf`, `image`, `font`, `url`), and `?import` opens it on the plain list. ## Move a brand between devices **Export** at the foot of the rail writes a single **`LollyBrand-….lolly`** - your tokens, fonts, logos and theme preference, with an integrity manifest it verifies on the way back in. Web releases before 1.0.7 named the same payload `.zip`; that legacy spelling is still accepted. Beside it, **Tokens (.json)** writes the plain design-tokens document on its own: no fonts, no logos, just the tokens, which is what a repo, a CI step or another tokens tool actually reads. Bringing one back is **Add from… → Design tokens or a design file** (above), or a drag-and-drop onto the studio. This is how a colleague hands you a brand, or how you carry one to a second install - no account, no cloud. To bring in a brand from the command line instead, see [`ingest:brand`](/info/configuration.html#brand-packs). ## Versions **Versions** at the foot of the rail is where a design system stops being a moving target. Publish one and you get a **permanent, named copy** kept on this device: it never changes afterwards, so a tool that pins it keeps drawing the same thing. The panel stays hidden until there's something of your own to publish, so a studio that never publishes never sees the controls. Three things to know before you press anything, and the panel says all three before the press rather than after: - **A version is permanent.** There's no delete yet, so the panel states what has been kept and that it stays kept rather than offering a button that lies. - **Removals lead the compatibility card.** Added and changed tokens are news; a *removed* one is the thing that breaks a tool, so it's named first and called what it is. - **Publishing can't be undone; restoring can.** *Restore latest from this version* is an ordinary edit to the head, so it goes onto the studio's undo stack and the panel offers you the **Undo** straight away. You can **Publish only**, or **Publish and make active** - the difference being whether tools and the app follow that version from now on or keep following your latest edit. **Follow the latest again** puts every edit live the moment it's made. `#/start?area=versions` opens the panel directly. ## When the brand is fixed Some builds ship a **locked brand** - its colours, fonts and tokens are what every tool and export use, and there's nothing to change. In that case the studio is replaced with a short note explaining that this build ships with a fixed brand and editing is turned off. This is deliberate: it's how an organisation guarantees everything stays on-brand. ## Where to go next - **[Using Lolly](/info/using.html)** - the canvas, saving, projects and the catalogue. - **[Design Tokens](/info/design-tokens.html)** - the token model your brand is expressed in. - **[Exporting & formats](/info/exporting.html)** - print units, CMYK and the formats your brand renders into. --- Source: https://lolly.tools/info/create/profile.md Page: https://lolly.tools/info/create/profile.html # Profiles - who you are when you create A **profile** is the working identity Lolly creates *as*. It's the small set of details a tool can pull from so you don't retype them every time - your name, contact details, an optional headshot, a few preferences - plus everything you accumulate while you work: saved sessions, uploaded images and the local activity tally. Everything in a profile lives **on the device**, in the browser's local database (IndexedDB on the web PWA, the filesystem on the Tauri apps). There's no account and nothing is uploaded. You manage it under **Profile** (top-right of the gallery); tools only ever *read* it, and only the specific fields they were built to pre-fill. > A profile is about *you* (or whoever's creating here). It's distinct from the **Platform** - the brand's colours, fonts and global settings - and from **Capabilities**, the catalogue of what the app can do. See [Profile vs Platform vs Capabilities](#profile-vs-platform-vs-capabilities) at the end. ## What's in a profile | Part | What it is | |---|---| | **Name** | First and last name. | | **Contact** | Email and phone. | | **Location** | City and country. | | **Headshot** | An optional photo, cropped to a square and kept as a local image. Used by tools like email signatures, quote cards, org charts and dynamic layouts. | | **Use my details to create** | A single opt-in switch (it reads **Using my details** once it's on). It controls whether your personal details ride along as **provenance** - the author/credit line embedded in exported files - and as the author on **/pro** batch runs. (It doesn't gate pre-fill: see [How tools use your profile](#how-tools-use-your-profile).) | | **Preferences** | Your theme (Light, Dark or Brand - the brand theme paints the app in your own palette) and which parts of the app you've enabled via **Feature flags**. | | **Accessibility** | Four comfort switches - *Reduce motion*, *Hide colourful previews*, *High contrast*, *Large text* - kept on the profile record, so they ride along in a profile export. See [Accessibility](#accessibility). | | **Your work** | Saved sessions (with thumbnails) - organised into nested folders in **[Projects](/info/using.html)** - your **My images** library and the local activity stats, all keyed to this profile. | None of this is required. A blank profile is a perfectly good profile; you fill in only what saves you typing. ![The Profile screen - name, contact, an optional headshot and your preferences](/t/url-shot?url=%2F%23%2Fprofile&width=1440&height=900&dpi=192&waitMs=1600&format=svg&walker=1&localize=1&dark=1&filename=profile-details) Preferences are the one part that changes how the app looks back at you. The theme cards are live previews and apply the moment you pick one, on this device only. The page is a long one, so it carries its own **settings rail** down the side - Your details, Appearance, Accessibility, Lolly instance, Your activity, Storage, Available offline, Feature flags, Content Credentials - with a **Search settings** field above it that filters the list as you type. Every section is deep-linkable as `#/profile?focus=`, which opens it and scrolls it into view (`#/profile?focus=storage-section`, `?focus=feature-flags-section`, and so on), so a link can point at one setting rather than at the top of the page. ![Three theme cards, each previewing its own type and colour, with the active one flagged](/t/url-shot?url=%2F%23%2Fprofile&width=1440&height=1400&dpi=192&waitMs=1600&walker=1&format=svg&cropSelector=.profile-card--appearance&dark=1&filename=pd-theme-picker) ## A profile is a context, not just a person The word "profile" suggests one fixed person, but in Lolly it's really a **creating context** - *who you are while you make this thing*. That context can be three different shapes, and Lolly handles all of them the same way. ### As an individual The default. The profile is you: your name, your email, your headshot. Set it once and your signature, your badge, your conference lockup all fill themselves in. This is what most people will ever need. ![The headshot control, empty until you upload a photo that then stays on this device](/t/url-shot?url=%2F%23%2Fprofile&width=1440&height=900&dpi=192&waitMs=1600&format=svg&cropSelector=.profile-side&walker=1&dark=1&filename=pd-profile-headshot) ### As a team A profile doesn't have to be a single human. It can stand in for a **team or function within an org**: the team's shared name, a group inbox address (`events@…`), a department, the team's headshot or unit mark. One person sets it up, exports it (see below) and the rest of the team loads the same profile - so everything the team produces carries consistent details without anyone re-entering them. A shared kiosk or a checked-out demo laptop can run a single team profile that everyone behind it creates as. ### As a function - a role you wear sometimes This is the case the rigid "one person, one profile" model misses. You might be an **event manager three days a year** and something else entirely the rest of the time. Those three days you want event details, the event inbox, maybe an event sub-brand to fill in your badges and signage; the other 362 you want your normal identity back. In Lolly, that role is just **another profile you keep on hand** - a saved bundle (next section) you load for the event and set aside afterwards. The role is a hat, not a new account. Wear it when you need it, take it off when you're done. ## One install, one active profile - many you can keep At any moment an install has **one active profile** - the details a tool sees right now. There's no in-app profile switcher; instead, each profile is a **portable bundle** (a single `.zip`, see [below](#moving-a-profile-to-a-new-device)). That's deliberately the same mechanism as moving to a new device - a profile is a file you can save, copy and load. So if you genuinely juggle several contexts (you, your team, the event-manager hat), you keep several bundles and load the one you need: - **Cleanest switch:** **Profile → Storage → Clear all my data**, then **Import** the bundle for the context you're stepping into. You're now creating purely as that profile. - **Layering:** importing *without* clearing first **merges** - the imported profile, sessions and images land on top of what's already there, replacing anything with the same name and leaving the rest. Handy for pulling one team's saved sessions into your own setup; not what you want if you need a clean role boundary. - **Side by side:** because everything is device-scoped, a separate browser profile, a separate user account or a second installed PWA each carries its own independent Lolly profile. Run your personal install and the event kiosk install at once, no switching. Storage is where both halves of that live: the meter accounts for every byte this install is holding, category by category, and the buttons under it are how you clear or carry it. ![The storage meter, breaking down saved sessions, images and cache against what the browser actually reports](/t/url-shot?url=%2F%23%2Fprofile%3Ffocus%3Dstorage-section&width=1440&height=1800&dpi=192&waitMs=2400&css=.store-manages%2C.storage-subsection%2C.storage-actions%7Bdisplay%3Anone%7D&walker=1&format=svg&cropSelector=.store-meter&dark=1&filename=pd-storage-meter) > Keep a bundle per context and rename the files for what they are (`LollyTools-events-2026.zip`, `LollyTools-me.zip`). The file *is* the profile. ## Accessibility **Profile → Accessibility** holds four comfort settings for the app *around* your work. Each is off until you turn it on, and none of them reach inside a tool canvas or an export - a calmer app must not move a pixel of the file you ship. - **Reduce motion** - turns off the transitions, slides and animated flourishes in the app. Your tool canvas and any animated export keep moving exactly as designed. - **Hide colourful previews** - swaps the gallery preview artwork for calm icon-and-text cards, and lowers the colour and contrast of your project thumbnails so they stay recognisable without shouting. Inside a tool everything shows in full colour. - **High contrast** - strengthens the borders, text and focus rings of the app. Your brand colours and everything on the canvas stay exactly as you set them. - **Large text** - grows the app type: labels, menus and button text. The controls keep their size, so only the words inside them get bigger, and type inside your designs is untouched, so nothing you export reflows. These live on the profile record itself, which is why they travel in a profile export and land on the next install alongside your name and your sessions. (The device also keeps a small local mirror so the setting applies before the first paint; that mirror is device-only and doesn't travel.) ## Your Lolly instance **Profile → Lolly instance** says where this install gets its tools and catalogue from - the address of the instance, or *Bundled with this app* when everything ships inside the build. Where a deployment offers one, an **Instance console** link opens its admin surface, and **Change** / **Disconnect** re-point the install or cut it loose. Re-pointing at another instance needs the **desktop app**: a browser blocks a page from loading tools and assets across origins, so on the web the section reports where you are and leaves it there. ## Available offline Lolly caches as you go, but caching-as-you-go only covers where you've already been. **Profile → Available offline** is for the trip you can see coming: an hour on airport wifi before a flight with none. Download the parts you'll need, watch one progress bar, and everything you took keeps working with the connection gone. Seven parts, each with its size stated before you commit: - **The app** - every view, editor and font, including the ones you haven't opened yet. Without this, a screen you never visited online can't load offline. - **Catalogue** - brand assets beyond the essentials. Take all of it, or open *Choose by tag* and take only the tags you use. - **Guides & docs** - this documentation site, in your language, screenshots included. - **Speech voices** - the voice models behind Script audio and narration. Downloaded once, then it runs on-device. - **Upscaling models** - the AI image upscalers: photo, illustration/anime and face. - **Background removal** - the on-device cut-out models behind *Remove background*. - **Verify deep scan** - the on-device watermark scanner, for checking Content Credentials away from a connection. The last four are marked **large download**, and they are deliberately individual opt-ins: **Download everything** at the top takes the app, the catalogue scope you chose, the docs and all tools in one pass and nothing else. Speech voices, the upscalers, background removal and the deep scan each download only when you ask for that row by name - a few hundred megabytes hiding inside one button would be dishonest. Below the parts sits the per-tool list: each tool downloads individually (the tick means ready offline), or **Download all** sweeps the lot. Downloads are resumable - cancel or lose the connection and the next run picks up where it stopped, fetching only what's missing - and they refresh themselves when you're back online, pulling just what a new release changed. If the browser hasn't granted persistent storage, the section says so and offers **Protect downloads**, which asks for it - the difference between "downloaded" and "downloaded until the browser wants the space back". ## Moving a profile to a new device Because a profile is entirely local, the only way to get it onto a blank install - a new laptop, a freshly reset browser, a colleague's machine, an offline box - is to **carry the file**. No login restores it for you, and that's the point: nothing ever left your device to begin with. Under **Profile → Storage → Move to another device**: - **Export my data** downloads one `LollyTools----.zip` - named for the profile it belongs to, with a per-day sequence number so repeat exports don't collide (name parts are dropped when the profile doesn't have them). It contains your profile, every saved session (with its thumbnail), your uploaded images - your brand tokens and installed fonts ride along as user assets - and your preferences (theme, layout, local activity stats). - **Import data…** on the other install reads that file back in and you pick up exactly where you left off. - **Export my data & render everything** writes that same backup *plus* a second zip that renders every saved session to its finished output file, in folders that mirror your Projects. A complete offline archive of both the sources and the results - and it can be large and slow with a lot of sessions. ![The two buttons that move a whole install: Export my data writes one zip, Import data reads it back](/t/url-shot?url=%2F%23%2Fprofile%3Ffocus%3Dstorage-section&width=1440&height=1800&dpi=192&waitMs=2400&css=.store-manages%7Bdisplay%3Anone%7D&walker=1&format=svg&cropSelector=.storage-subsection&dark=1&filename=pd-transfer-controls) The bundle is a plain, self-contained zip, so it travels by **any** means - USB, AirDrop, a network share, email-to-yourself - and the target can be completely offline. Each part is checksummed, so a file damaged in transit is caught on import rather than restored half-broken. Import **merges** (same-named profile/session/image is overwritten; everything else is kept), so it never wipes a target that was already in use. What doesn't travel: the catalogue cache (it re-downloads itself on the new device) and the tools themselves (assumed already present). For the exact bundle layout, version policy and integrity rules, see **[Data Transfer](/info/data-transfer.html)**; for the end-to-end walkthrough, **[Using Lolly → Moving to another device](/info/using.html#moving-to-another-device)**. ## How tools use your profile A tool only ever *pre-fills* the profile fields it was explicitly built to bind: **Explicit binding.** A tool author marks an input as drawing from the profile (`bindToProfile: "firstname"`, `"email"`, `"headshot"`, …). When the tool opens, that input pre-fills from your profile - and you can still override it for that one session without changing the profile. Pre-fill is a local convenience and happens whether or not **Use my details** is on. **The opt-in (provenance).** When you export an asset, your details optionally ride along as **provenance** - an author/credit line embedded in the file's metadata (PNG, PDF, SVG, …) - so a finished asset can say who made it. *This* is what **Use my details to create** governs: leave it off and the export still carries the "Made with Lolly" tool/platform attribution, but no personal author/contact line is embedded. (The same opt-in sets the author on **/pro** batch runs.) (Tool authors: see [Authoring Tools → `bindToProfile`](/info/authoring-tools.html#bindtoprofile) and [Host API → `host.profile`](/info/host-api.html#host-profile).) ![The single Use my details to create switch, sitting beside Save Profile and off until you turn it on](/t/url-shot?url=%2F%23%2Fprofile&width=1440&height=900&dpi=192&waitMs=1600&format=svg&cropSelector=.profile-check&walker=1&dark=1&filename=pd-use-my-details) ## Profile vs Platform vs Capabilities Three things sit near each other in the UI and are easy to confuse: - **Profile** - *you* (or your team, or the role you're in): name, contact, headshot, your saved work. Personal, device-local, portable as a bundle. - **Platform** - the *brand*: colours, fonts and global settings every tool renders against. Shared and consistent, not personal. - **Capabilities** - *what the app can do*: the full feature set and the tools available to you. A profile changes who an asset is *from*; the platform changes what it *looks like*; capabilities are *what you can make*. ### "Profile" means two other things elsewhere - not this one The word is overloaded across the project. Neither of these is the personal profile this page is about: - **Content profile** - a build-time configuration in `profiles.json` that binds a set of tool packs to a brand catalog (e.g. `suse`, `lolly-start`). It's what an operator picks when deploying, and it's what the `profile` **URL/CLI parameter** also selects a *colour* variant of at export time (the ICC/CMYK press condition - see [URL Mode](/info/url-mode.html)). Both are about the *build/output*, not about *you*. See [Configuration](/info/configuration.html). - **Identity profile** - the optional **verified Content Credentials identity** you can enrol (a short-lived certificate that ties your email to your signed exports). That's a signing identity, separate from the personal profile's name/contact fields, though **Use my details to create** governs whether either is embedded. See [Content Credentials Identity](/info/content-credentials-identity.html). ![The Verified identity card, phone-width: the certificate lifetime picker and the enrolment step beneath it - the identity profile, separate from your personal details](/t/url-shot?url=%2F%23%2Fprofile%3Ffocus%3Didentity-section&width=430&height=1600&dpi=192&waitMs=2400&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone%7D&format=svg&walker=1&cropSelector=%23identity-section&dark=1&filename=pv-identity-enrol) ## Privacy Outside the optional identity enrolment above (which sends the email you enrol to the certificate service - see [Server Surface](/info/server-surface.html)), a profile is never transmitted, uploaded or used to identify or track you - there's nothing to consent to, only this notice so you know what's kept. Wipe all of it at any time with **Profile → Clear all my data**. See the [Privacy Policy](/info/privacy.html). --- Source: https://lolly.tools/info/create/design-import.md Page: https://lolly.tools/info/create/design-import.html # Import a design (Figma, Penpot, Illustrator, InDesign) Bring a finished design **out of Figma, Penpot, Illustrator or InDesign and into Lolly** - not as a frozen picture, but as a live, editable layout you can keep working on, mix with tools and render through Lolly's deterministic export path. Like everything else, the whole import happens **on your device**: the file is parsed in your browser and nothing is uploaded. The good news for everything you've already made: none of it is stranded in the app you drew it in. A design you spent hours on in another tool arrives as an editable session, already conformed to your design globals - and once you save it, it's a reusable template anyone with Lolly can open and refill. Import lives in **[Design](/info/using.html)**, Lolly's free canvas: open it, click **Import a design** in the canvas toolbar and choose a file. The artboard resizes to the file's frame and every layer becomes an editable box on the same open canvas - so an imported artboard is just an ordinary Design session from the first click. ![Design's free canvas - Import a design sits in the toolbar's Lolly menu](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17VVRa9swEP41ekyQLrbjPOyhVeeOrSsbG3lXbLkYZCnISmn264tO9irFhdJCoZQIpBNn9N2dvu_k3cOX1opeEuCTpeNkG0BDy8ljZe28pyDA74UlUC4WOyt0sxgOthW1JJAD9wv1y6rFQWCDMB6BAK-NdqLTBLg2thcqeAnk68vaKGOXg-yFdl29dPLBef8VAZ6V_qjUTloCvO-aRvlcc0QdhB58iCWDMVNnD1iSUIOcAvASY2qsEUeex-XSOEJ6koaT_6Q1iY89IY4mw4TCmmJYc9BNZKPQ00ywxxJS-BlqcnL2KfJF3xhZf3WdwwsMd8xZ4VEgEF6EGornaH-ByW_iHkVAj-aAtpcO7Y1R6kggX1Uz7ext1wt7PNEOk7BZ7UbtYCpKtj4NZ_YE-Doh_hOwHnff-1K_M83xlPmyiJhn8AbmL5C61nZSN8g00OlFoK2xkyjCRofOBro3g5N2OdPE2PqxIGjL1iCCIPA1SPSQpQ_B6qyIVyiCsp8s-_Gn2P76fpHfVlt6WV3_Lv5e5_6mul7cSVRFhiWhOIrsaaVTMbKZaUWNbQ-VMncGN__b_fwn-CAKeAQ&width=1360&height=850&dpi=192&waitMs=3000&format=png&localize=1&dark=1&filename=design) ## What you can import Several file kinds go in - a native Figma binary, a Penpot export, an Illustrator or PDF file, an InDesign markup file or **any SVG**. That last one is the wide door: SVG is a universal export (or native save) format, so almost any design app can reach Lolly through it. | File | What it is | |---|---| | **`.fig`** | A native Figma file (File → Save local copy). Decoded entirely in the browser - no Figma account, plugin or API key involved. | | **`.penpot` / `.zip`** | A Penpot export (its standard export bundle). | | **`.ai` / `.pdf`** | An Illustrator file (saved with the default *PDF-compatible* option) or any PDF. Vector paint, text and images are read straight out of the page. | | **`.idml`** | An InDesign layout (*File → Export → InDesign Markup*). A raw `.indd` can't be read directly - export IDML first. | | **`.svg`** | Any SVG - Figma and Penpot SVG exports, or hand-written markup. *Tip: for editable text from a Figma SVG, untick "Outline text" on Figma's export dialog.* | Bringing a **PowerPoint deck** instead? Decks have their own doors - file slides as assets, rebrand in place, or write a new deck in Markdown Slides. See [Decks and documents](#decks-and-documents) below. ### What each app is for - **Figma** - *File → Save local copy* for a native `.fig`, or export a frame as SVG. (Untick *Outline text* on the SVG export to keep headlines retypable.) - **Penpot** - its standard `.penpot` export, or any SVG export. Penpot is also where the **brand tokens** round-trip both ways - see below. - **Illustrator** - a native `.ai` (kept PDF-compatible, the default) or a `.pdf` opens directly; no SVG step needed. - **InDesign** - *File → Export → InDesign Markup* for an `.idml`. A print layout becomes an editable, brand-conformed Lolly session. (A raw `.indd` isn't readable - export IDML.) ## What happens to the layers The importer maps design layers onto the free canvas's box model, keeping as much editable as it can: - **Text stays text.** Frames of type become real text boxes - content, size, alignment, weight, line-height and per-run colours carry over - so you can retype a headline, not repaint it. - **Shapes stay shapes.** Rectangles, rounded rects, ellipses and pills become native shape boxes with their fills, opacity, rotation and corner radii. - **Images are extracted.** Bitmaps land in **My images** (your on-device library) and are placed as image boxes. - **Complex vector art is flattened faithfully.** Paths, gradients and other paint Lolly's box model can't express are baked into crisp standalone SVG image boxes, so the visual result survives even where editability can't. - **Geometry is exact.** Positions, sizes and rotations are resolved by the browser itself (the same maths that rendered the original), so the imported layout lines up with the source. ## It conforms to the brand This is the point of importing rather than pasting a screenshot: the result is a **governed** layout, not a foreign artboard. - **Type remaps to the brand faces.** Every imported font becomes one of the faces your brand pack ships (monospace families map to your brand's mono face, weights clamp to the cuts that exist). That's intended behaviour - the design arrives already on-brand. - **Colours are guarded.** Every imported fill passes the same colour sanitiser native boxes use; from there you can snap them to brand swatches with the normal pickers. - **Everything else is a normal session.** Drag, rotate, snap, group, clip; place [themable icons](/info/authoring-assets.html) and library assets; save it, share it as a URL, run it through [Batch](/info/exporting.html). ## Rendering An imported design exports exactly like a native one: **SVG, PDF, print CMYK PDF, CMYK TIFF, PNG, JPG, WebP** through the same deterministic pipeline - true page sizes and physical units, outlined type in vector output, [Content Credentials](/info/exporting.html) on every stampable format. The design's new home is its URL, so a once-off Figma file becomes a reproducible, parameter-addressable asset. ## Frames become a video The same files open on **Design's timeline** - and there the import means something different: **every frame becomes a scene on the timeline**. A Figma file's top-level frames, a Penpot file's boards, a PDF's pages - each arrives as its own timed clip, already playing through in order. From that first play-through you're in an ordinary sequence session: drag clips to reorder, trim and retime them, add a music bed, record a voiceover, drop text overlays on top and export **MP4, WebM, GIF or APNG** through the same deterministic pipeline. Two ways in: - In **Design**, open **Import a design** from the canvas menu and choose the file. - Drop the file anywhere on the gallery or dashboard and pick **Make a video from its frames**. A deck that stays a click deck can still leave as a video. **Export slides as video** in the Present menu opens the export sheet on MP4, and the render places the slides on a timeline in order for that export only: each slide plays for its own dwell, or for the Duration in the export sheet when it has none, and the deck's slide transition plays between them as a dissolve. The document itself is not changed. Each frame is baked through the Design tool's own renderer into a crisp vector still (text as outlines, images embedded), so scenes stay sharp at any export size and need no fonts at playback. A storyboard drawn in Penpot or Figma becomes a finished cut - titles, soundtrack, provenance and all - without a video editor in sight. ## From a one-off design to a reusable template This is where import stops being a conversion and starts being *authoring*. An imported layout is an ordinary [Design](/info/using.html) session, so it inherits everything a native session can do: - **It's a template anyone can reuse.** Save it and the layout lives at a URL. Anyone with Lolly can open that URL, change the words, swap an image and render their own version - no design app, no design skill and the parts the author locked stay locked. The person who imported the artboard becomes its author; everyone else just fills in the blanks. - **It mixes with tools.** Any box can hold another Lolly tool as a live asset - a badge that renders its own QR code, a card with a live chart, another render dropped in through the asset picker. Those stay live and re-render on load; they're never flattened pictures. So an imported poster can carry generated, always-current content the original file never could. - **It scales like any session.** Drive it from the [batch grid](/info/exporting.html) (one finished asset per spreadsheet row), keep it in a [Projects folder](/info/using.html) or render a whole folder as one zip. A single imported design turns into a whole run of consistent variants. For a *fully declarative* tool - sidebar inputs, hard-coded constraints, the works - you'd still author a `tool.json` by hand (see [Authoring Tools](/info/authoring-tools.html)). Import gets you the fast, visual 90%: a governed, shareable, tool-embedding layout without writing a manifest. ## Round-tripping the brand itself Import travels one direction; the **brand primitives travel both ways**. Lolly's colours are [DTCG design tokens](/info/design-tokens.html) - the same format Penpot imports/exports natively and Tokens Studio brings to Figma - so the palette you design *with* in Figma/Penpot and the palette Lolly enforces are one document, not two hand-synced lists. ![The Brand Studio's import card - a Penpot export, a DTCG or Tokens Studio file or a plain SVG all come in through the same door](/t/url-shot?url=%2F%23%2Fstart%3Fsource%3Dfile&width=1440&height=900&dpi=192&waitMs=1600&css=.start-import-modal%20.modal-msg%2C.start-import-modal%20.modal-title%7Bdisplay%3Anone%7D&cropSelector=.start-import-drop&walker=1&format=svg&dark=1&filename=bs-brand-import-formats) ## Limits & safety - Imported SVG is treated as untrusted: scripts, event handlers and foreign objects are stripped **before** parsing, and anything flattened to disk is sanitised a second time on ingest. Nothing executes, nothing leaves the device. - Very large files are capped at **2,000 elements** - anything past that is dropped with a warning rather than locking the tab. - Effects outside the box model (complex blend stacks, exotic strokes) flatten into the image fallback rather than round-tripping as editable properties. ## Every format Lolly can read Design files are one way in - the table above covers them. For the complete picture, here is everything Lolly ingests. As with a design import, **every file is parsed on your device and nothing is uploaded**. ### Images Drop a photo or graphic into any image picker or your **My images** library. | Format | Notes | |---|---| | **`png` · `jpg`/`jpeg` · `webp`** | Decoded natively and kept **verbatim** in **My images**, so a Content Credential on the file still verifies; only a genuinely huge image prompts you to keep or resize it. Turn on *Strip metadata from uploads* in your profile to scrub EXIF/GPS on the way in. | | **`gif` · `apng` · animated `webp`** | Animated rasters are recognised and stored **verbatim** - frames intact - so they stay animated when placed. | | **`avif`** | Read wherever your browser decodes it natively (no bundled fallback). | | **`heic` / `heif`** (`.heic`, `.heif`) | iPhone photos decode even where the browser can't, via a bundled libheif fallback. | | **`svg`** | Sanitised (scripts, `on*` handlers and `javascript:` URLs removed) and normalised to a clean viewBox. | ### Decks and documents | Format | Notes | |---|---| | **`pptx`** (PowerPoint) | Three ways in, all on-device. Drop a deck on any upload surface and pick the slides you want - each becomes an SVG asset (text, shapes, images and tables drawn; charts and SmartArt as labelled placeholders) to place in a layout or grade. **Markdown Slides** writes a new branded deck from Markdown. The **Rebrand** utility re-themes a deck *without* re-authoring it: the theme palette, hardcoded colours and fonts swap to your brand while charts, SmartArt and animations pass through untouched, and you get a `.pptx` back. And dropping a `.pptx` on any upload area asks which slides to keep - each is stored as an SVG asset in your library. | | **`pdf` / `.ai`** | Besides the Design import above, dropping a PDF on an upload area asks which pages to keep - each page becomes a self-contained SVG asset (vector paint, text and images preserved). | ### Data Paste or drop a table and a tool's repeating blocks fill from it (up to 1,000 rows). | Format | Notes | |---|---| | **`csv`** | RFC 4180 - quoted fields, `""` escaping, embedded commas/newlines, CRLF/LF, BOM. | | **`json`** | An array of row objects, headerless positional arrays or a `{ "data": [...] }` / `{ "rows": [...] }` wrapper. | | **Lottie** (`.json`, `.lottie`) | Bodymovin JSON and dotLottie animations validate and place as live vector animations. | ### Video | Format | Notes | |---|---| | **`mp4` · `mov`** | ISO-BMFF containers, stored **verbatim** (never transcoded); dimensions probed locally. | | **`webm`** | Matroska/EBML, stored verbatim. | ### Audio Audio arrives in the asset picker - ready as a **music bed** under a video export, or in Neurospicy Mode's ambient player. | Format | Notes | |---|---| | **`mp3` · `wav` · `ogg`/Opus · `m4a`/AAC · `flac`** | Kept byte-for-byte and decoded on-device. | | **`midi`** (`.mid`) | Converted on import to a tiny on-device synth track. | | **Tracker modules** (`mod` · `xm` · `it` · `s3m` · `stm` · `mtm`) | A few kilobytes of song data, decoded on-device by a bundled player. | ### Content Credentials (verify) Lolly reads and cryptographically verifies a signed [C2PA](https://c2pa.org) manifest embedded in **PDF, PNG/APNG, JPG, GIF, SVG, TIFF, WebP, AVIF, MP4, WebM/MKV** and the audio containers **MP3, WAV, M4A and OGG/Opus** - entirely on-device, against the signing certificate. The [/verify](/verify) view goes further: it flags AI-generated content, detects Lolly's own pixel **Imprint**, verifies **SEAL** signatures, optionally deep-scans for third-party pixel watermarks and surfaces hidden data - none of it uploaded. See [Content Credentials](/info/exporting.html#content-credentials-c2pa) and [Content Credentials Identity](/info/content-credentials-identity.html#beyond-the-credential-what-else-verify-shows). (HEIC/HEIF is read as an image but carries no credential.) ### Metadata (to strip it) The **Strip Hidden Data** utility *reads* embedded metadata so it can remove it - EXIF/GPS/IPTC/XMP from **JPEG**, text and time chunks from **PNG**, comments and editor namespaces from **SVG** and document info from **PDF**. The cleaned file never leaves your device. --- Source: https://lolly.tools/info/create/formats.md Page: https://lolly.tools/info/create/formats.html # Every format Lolly can open and make Lolly reads dozens of source formats and writes dozens more. This is the whole set, grouped by what each one is, with a card behind every chip that says in plain language what that format is for and what Lolly supports in it. **How to read the table.** Each row is a family of formats. Anything Lolly only *reads* sits at the left edge, anything it only *writes* sits at the right edge and the formats it does **both ways** sit once in the middle - so a format Lolly reads and writes appears one time, never twice. Tap or click any chip for the full name, a plain description and the properties Lolly handles (transparency, colour profiles, layers, credentials and the rest), plus anything it does not handle yet. ## Why "both ways" matters A format in the middle column is one you can bring a file in as, work on and send back out in the same shape. That is what stops a file becoming a dead end: a deck arrives as a deck and leaves as a deck, a design file arrives as editable layers, a spreadsheet goes back out as one. Nothing in Lolly needs a proprietary container to keep working. ## Where to go next - [Exporting](/info/exporting.html) - each export format in detail: page sizes, colour, credentials and what to pick for print, for screen and for video. - [Import a design](/info/design-import.html) - what happens to layers, type and colour when a Figma, Penpot, Illustrator or InDesign file comes in. - [Parser inventory](/info/parser-inventory.html) - for reviewers: every parser Lolly runs, because a parser is where an untrusted file meets a machine. --- Source: https://lolly.tools/info/create/sequence-editor.md Page: https://lolly.tools/info/create/sequence-editor.html # The sequence editor **Sequence**, Design's timeline, puts time under the free canvas: every box can start at a moment, run for a length and animate in and out. [Using Lolly](/info/using.html#timeline-sequence) covers the layout of the timeline - the magnetic sequence row, the free overlay lanes, the Always on strip, transitions and rendering. This page is about the four things that decide whether editing in time feels *predictable*: what a canvas click edits, seeing what is about to arrive or leave, cutting a clip and trimming one. Everything here happens on your device, and none of it changes what a render produces. The chrome described below - ghosts, badges, banners, outlines - lives outside the exported node, so a file exported with onion skin on is byte-identical to the same file exported with it off. ![The timeline, tool bar first: add, record, the split blade with its resolved label, snap, onion skin, zoom, fit and the keyboard shortcuts sheet, over the ruler, the overlay lane, the magnetic sequence row and the Always on strip](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A252px!important%7D&cropSelector=.tl-panel&format=svg&walker=1&tolerance=0.03&dark=1&filename=seq-studio-timeline) ## The one rule A timed canvas is a window onto **one instant**. One sentence governs everything you can touch, and it is written into the editor's own source at `shells/web/src/views/free-canvas.ts` so the code and this page cannot say different things: > "The canvas edits exactly what the canvas shows at the playhead. Moving the playhead never changes the selection; selecting in the timeline moves the playhead so the selection stays live; when a selection is nevertheless off-playhead, the canvas says so and offers to reconcile. The timeline inspector and the sidebar are the precision fallbacks and are never gated by time." Read the three clauses as three promises. **Moving the playhead never changes what is selected.** Scrubbing is looking, not choosing. This is the direction that goes wrong in other editors: when time drives selection, you type a value into a panel and it hits whichever clip happened to be under the playhead rather than the one you picked. **Selecting in the timeline moves the playhead.** Click a clip's bar and the playhead steps inside it, so the thing you just selected is the thing on screen. It only moves when it has to: never while playing, never for an Always on box (which has no time of its own) and never when the playhead is already inside the clip. **An off-playhead selection says so.** Selection can still end up pointing at something the canvas is not showing - you selected a clip, then scrubbed away. Rather than leave editing furniture floating over nothing, the canvas takes the outline, the eight resize handles, the rotate handle and the contextual bar down, and raises a small banner with a **Go to it** button that seeks to the clip's start. The banner is suppressed during playback, where scenes coming and going is the point. While a selection is off-playhead the keyboard refuses every key that would change the model - the arrows, Delete, duplicate, group, z-order and starting a text edit. Escape, Tab and Select all keep working. Nothing is silently dropped: either the edit applies to what you can see, or it does not happen. **The fallbacks are never gated.** The timeline's own inspector (Length, Trim in, Speed, the two transitions, Mute) and the tool sidebar edit the selected clip whatever the playhead is doing. They are the precision route, and the accessible one. ![A clip selected in the sequence row: the playhead has stepped inside it, the canvas shows that scene with its selection outline and handles, and the timeline inspector fills with the clip’s Length, Trim in, Speed and transitions](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px%21important%7D%23tool-stage%7Bbackground-image%3Anone%21important%7D&drive=click%3A.tool-canvas%7Cat%3D0.5%2C0.5%3Bclick%3A.tl-ruler%7Cat%3D0.92%2C0.5&walker=1&format=svg&dark=1&filename=seq-rule-selection) ### What a click on the canvas hits Clips that are not live at the playhead are not painted, and a click **falls through** them to the topmost visible box underneath - the same resolution a click already uses for a stack of overlapping boxes, so it needs no explanation in the moment and produces no interruption. Ghosted onion-skin frames are drawn in a layer that ignores the pointer entirely, so they can never be clicked either. The one state that does get words is the stuck one: a selection that is off-playhead, which is exactly the case you cannot reason your way out of by clicking somewhere else. ![The canvas at one instant: only the clip live at the playhead is painted, so a click hits it or falls through to whatever is underneath](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px%21important%7D%23tool-stage%7Bbackground-image%3Anone%21important%7D&drive=wait%3A600&cropBottom=0.35&walker=1&format=svg&dark=1&filename=seq-click-live-scene) ## Onion skin Animators want to see the frame before and the frame after. **Onion skin** shows the neighbouring clips as ghosts over the live one. It is **off by default and stays off until you turn it on**, and the preference is remembered on this device. That is not caution for its own sake. No mainstream video editor ghosts adjacent clips, and the reason is visible the moment you try it: a Lolly scene is usually an opaque, full-frame, brand-coloured composition that entirely replaces its neighbour, so laying two of them over each other at a third opacity produces colour mud and hides the one thing you are judging. - The **Onion skin** button in the timeline's tool bar toggles it. `O` does the same from the keyboard. - **Long-press** the button, right-click it or press `Shift+O` for its options: the mode, how many clips to ghost before and after (up to two each, independently) and the strength. ![The onion skin options popover: the mode, how many scenes to ghost before and after and the strength](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D.fc-toolbar-dock%7Bdisplay%3Anone!important%7D&drive=click%3A.tl-onion%3Bpress%3AShift%2BO&cropSelector=.tl-onion-pop&format=svg&walker=1&dark=1&filename=seq-onion-options) - **Outlines** is the default mode - each neighbour as a plain rectangle where its boxes sit. It stays readable *over* an opaque scene, which a filled ghost cannot. **Filled** adds each ghost's own colour and picture, for the animation-style work that wants it. - Past clips are drawn warm, future clips cool blue. Colour is never the only signal: each ghost carries a small `-1` / `+2` chip in its corner saying how far away in the sequence it is, so the direction survives any kind of colour vision. With **Hide colourful previews** on (see [Inclusive Design](/info/inclusive-design.html)) filled mode falls back to outlines. ![Onion skin on: the neighbouring scenes drawn as ghosts over the live one, each carrying a small -1 or +1 chip saying how far away in the sequence it is](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px%21important%7D%23tool-stage%7Bbackground-image%3Anone%21important%7D.fc-toolbar-dock%7Bdisplay%3Anone%21important%7D&drive=click%3A.tl-onion%3Bhover%3A.tl-ruler%7Cat%3D0.01%2C0.5&cropBottom=0.35&format=png&dark=1&filename=seq-onion-ghosts) **Onion skin cannot reach a file.** The ghosts are drawn in the editor's overlay layer, which is a sibling of the exported canvas rather than a part of it, and they are additionally tagged so the export path strips them before any format is written. They never set a class or a style on a real box. An export taken with ghosts on screen is the same bytes as one taken without. ## Splitting a clip **Split at playhead** cuts one clip into two at the current instant. The blade is in the tool bar, `S` does it from the keyboard and it is in a clip's right-click menu. **The button says what it will cut before you press it.** It reads *Split clip* when the playhead is inside one, *Split 3 clips* when a selection spans it and *Split at playhead* - greyed out - when there is nothing to cut. A refusal you can see beforehand beats a refusal announced afterwards. ![The split blade naming its own scope before it is pressed: with the playhead inside a clip the tooltip reads Split clip](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1100&height=760&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px%21important%7D%5Bdata-tip%5D%3A%3Aafter%7Bbottom%3Aauto%21important%3Btop%3Acalc%28100%25%20%2B%208px%29%21important%7D%5Bdata-tip%5D%3A%3Abefore%7Bbottom%3Aauto%21important%3Btop%3Acalc%28100%25%20%2B%203px%29%21important%3Bborder-top-color%3Atransparent%21important%3Bborder-bottom-color%3Ahsl%28var%28--foreground%29%29%21important%7D&drive=click%3A.tl-ruler%7Cat%3D0.42%2C0.5%3Bhover%3A.tl-split&cropSelector=.tl-panel&format=svg&walker=1&dark=1&filename=seq-split-blade) The scope resolves in one order, everywhere: 1. every **selected** clip the playhead is inside, so a deliberate multi-selection cuts through all of it in one press; 2. failing that, the **sequence clip under the playhead** - the "just cut here" case, which should not need selecting anything first; 3. failing that, nothing is written and the panel says why. `Shift+S` (or Shift-clicking the blade) is the wider variant: every timed clip the playhead is inside, on every lane, ignoring the selection. Whichever route you take, the whole cut is a **single undo step**. The cut snaps to clip edges and whole seconds like every other timeline gesture, so pressing it twice at the same spot snaps exactly onto the existing cut and does nothing at all - no write, no undo entry. ### Through edits, and Join A cut you have not acted on yet is a **through edit**: the two halves still run continuously, and the sequence plays as if the cut were not there. Those seams are marked with a hairline rather than left to look like every other edit, so at a glance you can tell which cuts are decisions and which are just "I cut here and then changed my mind". ![A through edit: the two halves of a fresh cut still run continuously, and the seam is marked with a hairline rather than looking like every other edit](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1100&height=760&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D.fc-toolbar-dock%7Bdisplay%3Anone!important%7D&drive=click%3A.tl-ruler%7Cat%3D0.42%2C0.5%3Bclick%3A.tl-split%3Bhover%3A.tl-ruler%7Cat%3D0.01%2C0.5&cropSelector=.tl-panel&format=svg&walker=1&dark=1&filename=seq-through-edit) Click the seam and the transition dialog offers **Join clips** alongside Cut and Crossfade. Join is also in a clip's right-click menu, and it works from either side - select one half, join and the two become one clip again with the second half's ending restored. It is only offered where it is real: a seam whose sides have been trimmed apart, sped up differently or given a transition is a decision, and it gets no Join. ![The transition dialog opened from a seam: Cut, Crossfade, a length and Join clips - offered only where the two sides are still continuous](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D.fc-toolbar-dock%7Bdisplay%3Anone!important%7D&drive=click%3A.tl-ruler%7Cat%3D0.42%2C0.5%3Bclick%3A.tl-split%3Bclick%3A.tl-seam.is-through&cropSelector=.tl-junction-modal&format=svg&walker=1&dark=1&filename=seq-junction-dialog) ## Detaching a clip's audio **Detach audio** pulls a video clip's sound onto its own overlay lane, where it can be trimmed, moved and mixed on its own. It is in the clip's right-click menu and on `Shift+D`. The two halves stay **linked**, and the link is written on both of them: - the picture is muted and carries a chain mark saying where its sound went; - the sound carries the same mark back; - selecting either selects both, so they move together by default. Alt-click selects just the one you clicked. - **Re-attach audio** puts it back, from either side, un-muting the picture and removing the detached sound. This is deliberately not the one-way detach some editors ship, where the only way to resync is Undo. A link that survives being split - split the muted picture and both halves still name the sound - is what makes detaching a safe thing to try. Detach is only offered where it means something: the tool has to declare the field that stores the link and have an audio clip kind to create, and the clip has to be a video that is not already linked. A video clip's menu carries a second video-only entry, **Remove background…**, which makes a transparent alternative of the clip on your device - an animated cut-out with real alpha - so a track can shed its backdrop without leaving the timeline. It offers the same on-device model and colour-key methods documented for the [Catalogue](/info/using.html#the-catalogue-your-asset-library), and the cut-out is saved as its own asset. ![A clip’s right-click menu. This clip is a card, so it offers Split at playhead, Make always on and Delete - Detach audio is absent because there is no sound to detach](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D.fc-toolbar-dock%7Bdisplay%3Anone!important%7D&drive=click%3A.tl-clip-seq%7Cright%7Cat%3D0.5%2C0.15&cropSelector=.tl-ctx-menu&format=svg&walker=1&dark=1&filename=seq-clip-menu) ## Sound Select any clip with audio - an audio box, or a video's soundtrack - and the inspector grows a sound strip. Every control is an icon with its name on hover, and the two level controls open a **tall fader over the timeline** when pressed, where there is room to be precise; the number beside each takes typed values. - **Volume** (0-200%). Above 100% boosts the exported file; the preview plays at 100% and the control says so. The diamond beside it **keys the level at the playhead** - volume automation that rides the same keyframe grammar as motion, so splitting or trimming a clip carries its volume curve with it. The bolt **normalizes**: one press measures the clip and sets its level so it plays at -16 LUFS. - **Pan** (-100..100). Equal-power, the same law in preview and file. - **Under other audio.** A music bed asked to sit under speech drops wherever the *other* clips actually make sound - measured from their audio, not just their length - and comes back up in the gaps. - **Effect.** Fourteen presets, from **Voice cleanup** (an on-device model that removes background noise; nothing is uploaded) through tone shapes, reverb spaces and a telephone. A preset writes its full settings into the clip, so a later re-tune of a preset never changes what a link you already shared sounds like. The sliders button beside it opens the **EQ** - Low, Mid and High as dB faders, editing the same chain directly. - **Pitch** (semitones, an octave each way, voices kept natural) - and on a speed-changed clip, a **Preserve pitch** toggle. It is on by default: slow motion stays in key. Off plays it tape-style. ![The sound strip on a selected audio clip: icon-labelled Volume with its keying diamond and Normalize, Pan, the ducking select, the Effect rack with its EQ door and Pitch - and the waveform tinted where it will play hot](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fbx%3Dt1%252Ctext%252C200%252C140%252C1500%252C220%252C0%252Crect%252C16%252C%252C100%252C%252Ccontain%252Cnormal%252CVoiceover%252520session%252C%25257Bcolor.semantic.text%25257D%252C48%252Ccenter%252Cmiddle%252C500%252Csans%252C1.12%252C0%252Ctrue%252Cfalse%252C%252C%252C8%252Cnone%252C00000055%252C0%252C0%252C10%252Ccenter%252Cfalse%252C%252C%252C0%252Cnonzero%252C0%252C3.3%252C0%252C1%252Cnone%252Cnone%252C400%252C400%252Cfalse%252Cseq%252C%252Cround%252Cround%252C%252C0%252C0%252C0%252C0%252C%252C%252C%252C0%252Ctrue%252Cnone%252Cnone%252C%252Cfalse%252C%252C%252C%252C0%252C%252C%252Cfalse%252C%252C%252C%252C%252Cfalse%252Cfalse%252C%252C1%252C%252Cfalse%252C%252C60%252C%252C%252C1%257Ea1%252Caudio%252C200%252C500%252C400%252C80%252C0%252Crect%252C16%252C%252C100%252Clolly%25252Floops%25252F3-am-echoes%252Ccontain%252Cnormal%252C%252C%25257Bcolor.semantic.text%25257D%252C48%252Ccenter%252Cmiddle%252C500%252Csans%252C1.12%252C0%252Ctrue%252Cfalse%252C%252C%252C8%252Cnone%252C00000055%252C0%252C0%252C10%252Ccenter%252Cfalse%252C%252C%252C0%252Cnonzero%252C0%252C3.3%252C0%252C1%252Cnone%252Cnone%252C400%252C400%252Cfalse%252C%252C%252Cround%252Cround%252C%252C0%252C0%252C0%252C0%252C%252C%252C%252C0%252Ctrue%252Cnone%252Cnone%252C%252Cfalse%252C%252C%252C%252C0%252C%252C%252Cfalse%252C%252C%252C%252C%252Cfalse%252Cfalse%252C%252C1.3%252C%252Cfalse%252C%252C60%252C%252C%252C1%26_sel%3Da1&width=1800&height=720&dpi=180&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D.fc-toolbar-dock%7Bdisplay%3Anone!important%7D&cropSelector=.tl-panel&format=png&dark=1&filename=seq-sound-strip) ![The EQ door open over the timeline: Low, Mid and High as vertical dB faders with live readouts, editing the effect chain directly](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fbx%3Dt1%252Ctext%252C200%252C140%252C1500%252C220%252C0%252Crect%252C16%252C%252C100%252C%252Ccontain%252Cnormal%252CVoiceover%252520session%252C%25257Bcolor.semantic.text%25257D%252C48%252Ccenter%252Cmiddle%252C500%252Csans%252C1.12%252C0%252Ctrue%252Cfalse%252C%252C%252C8%252Cnone%252C00000055%252C0%252C0%252C10%252Ccenter%252Cfalse%252C%252C%252C0%252Cnonzero%252C0%252C3.3%252C0%252C1%252Cnone%252Cnone%252C400%252C400%252Cfalse%252Cseq%252C%252Cround%252Cround%252C%252C0%252C0%252C0%252C0%252C%252C%252C%252C0%252Ctrue%252Cnone%252Cnone%252C%252Cfalse%252C%252C%252C%252C0%252C%252C%252Cfalse%252C%252C%252C%252C%252Cfalse%252Cfalse%252C%252C1%252C%252Cfalse%252C%252C60%252C%252C%252C1%257Ea1%252Caudio%252C200%252C500%252C400%252C80%252C0%252Crect%252C16%252C%252C100%252Clolly%25252Floops%25252F3-am-echoes%252Ccontain%252Cnormal%252C%252C%25257Bcolor.semantic.text%25257D%252C48%252Ccenter%252Cmiddle%252C500%252Csans%252C1.12%252C0%252Ctrue%252Cfalse%252C%252C%252C8%252Cnone%252C00000055%252C0%252C0%252C10%252Ccenter%252Cfalse%252C%252C%252C0%252Cnonzero%252C0%252C3.3%252C0%252C1%252Cnone%252Cnone%252C400%252C400%252Cfalse%252C%252C%252Cround%252Cround%252C%252C0%252C0%252C0%252C0%252C%252C%252C%252C0%252Ctrue%252Cnone%252Cnone%252C%252Cfalse%252C%252C%252C%252C0%252C%252C%252Cfalse%252C%252C%252C%252C%252Cfalse%252Cfalse%252C%252C1.3%252C%252Cfalse%252C%252C60%252C%252C%252C1%26_sel%3Da1&width=1440&height=760&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D.fc-toolbar-dock%7Bdisplay%3Anone!important%7D&drive=click%3A.tl-eq-btn%3Bwait%3A600&cropSelector=.tl-eq-popover&format=svg&walker=1&dark=1&filename=seq-sound-eq) Two things back the strip that you never have to operate: - **What plays is what renders.** One envelope drives the preview and the export mix, so the file sounds like the timeline did. The waveform bars are part of the same promise: a stretch that will play within 1 dB of full scale is tinted amber, and over it, red - level feedback across the whole timeline with no meter to watch. - **A true-peak limiter guards every export** at -1 dBTP, always on and transparent until needed - so a boosted, stretched, effected mix cannot clip at the encoder. The export bar's **Loudness** select adds a platform target on top: Off by default, or -14 LUFS (streaming), -16 (podcast), -23 (broadcast), stated in the correct unit because almost nobody shipping does. ### Subtitles and the transcript A clip with speech carries two doors in its right-click menu, both on-device: - **Generate subtitles** listens to the clip and writes timed caption boxes. The first run downloads the speech model once (it says so, with the size); after that it is instant to start and runs in the background. - **Edit transcript** opens the transcript as flowing text - click a word to jump the playhead there, select a sentence to cut it. On a clip with no transcript yet it offers the same background transcription first, and opens the editor when it finishes. ## Trimming Drag either end of a clip to trim it. The grip is a narrow bar, but the **area that responds is wider than it looks** - and wider again for a finger or a pen, where it is at least the 24px that the accessibility guidelines ask of any target. A clip too narrow to carry two grips without swallowing its own middle offers neither, and says so in its tooltip: zoom in, or use the inspector. While you drag: - the edge lights up, and a **readout** at the dragged end shows the clip's new length and the signed change (`4.2s +0.6s`); - a **ghost extent** shows how much source is still reachable past the edge; - the edge turns to a **limit** state the moment you ask for more than the file has. Before this, dragging past the end of the media simply stopped with no explanation; - on the sequence row the clips downstream **move as you drag**, not when you release, so you can see the ripple you are causing; - hold **Alt** to override snapping mid-drag. Nothing is written until you let go: one drag is one undo step. ![A trim in flight: the dragged edge lit, a readout showing the clip’s new length and the signed change and a ghost extent showing how much source is still reachable past the edge](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1100&height=760&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D&drive=drag%3A.tl-clip-seq%7Cdx%3D90%7Cat%3D0.99%2C0.15%7Chold&cropSelector=.tl-panel&format=svg&walker=1&dark=1&filename=seq-trim-drag) ### Trimming from the keyboard Every trim is reachable without a pointer, which is also the fastest route once you know it. | Key | What it does | | --- | --- | | `[` / `]` | Aim at the in edge or the out edge of the selected clip | | `,` / `.` | Nudge that edge one frame earlier or later (hold Shift for ten) | | `E` | Pull that edge to the playhead | | `Esc` | Let the edge go (then a live recording, then close the panel) | Each press is one write and one undo step. ![Trimming from the keyboard: the in edge of the selected clip is aimed and has been nudged two frames earlier](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1100&height=760&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D&drive=click%3A.tl-clip-seq%7Cat%3D0.5%2C0.15%3Bpress%3ABracketLeft%3Bpress%3AComma%3Bpress%3AComma&cropSelector=.tl-panel&format=svg&walker=1&dark=1&filename=seq-trim-keyboard) ## The shortcut sheet Every shortcut the timeline binds is a bare letter or a punctuation key. That is not a style choice: the familiar editor chords (`Ctrl/Cmd+B`, `Ctrl/Cmd+K`) collide with browser bindings that a web page cannot reliably take over, and a shortcut that silently does nothing is worse than one you have to learn. So the list is printed in the app. Press `?` with the timeline focused, or click the keyboard button at the end of the tool bar, and every key the panel handles is listed with what it does. The sheet is generated from the same list the key handler is written against, so it cannot fall out of date. Escape closes it and puts focus back where you were. ![The shortcut sheet, printed in the app: every key the timeline binds with what it does, generated from the same list the key handler is written against](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D11dZBb5swFADgX8MOiRYZB0J76GFpNO2wnbr7ZMwDrBg7s01C8usngmNwSqJszaT2aD8_G54_PUgJXRdK1iJ7CvAcpSHG6FMqG9BPQbwMkmWAMcsCjIP5lwDjUsp1O8DPAcZrJvpIKhsXaLpZ1I323mjXjcJHbCdKO4Ee7ISSxsvQJdmAO0cBNe6gtHDzQbKkkks101ARYRidaaBSZETtg2TlMgw0xuX8LBXANCN7PTVyWki3Kr-6b61yQmG4ay6FeWEHOL1K1E0TzgrhdqIgDCiXs_WjFcsyDi66A1aU_ZMuEPIOcwFNhHYRzgR8GySGs9CW0BDlFzWrVTe2qdCwftOcZP2TtJEfuovFyKZzIvor0fD7WKhVGzsXo2ALhH8UMxvFqmtiXmQFpmSimArYBfGzYHpKZcVEcS87-OHedpK72cHndmYWunu6rtxM-3wuwGqTXskacov-nhs15KNYG7HgWZtMvpNa0LJtUJNJi-2rYhnZ3ybtuNUVZuj9MotOrAbQFmPQbuB0uxwud4NX9-ycrmWIJw59Pg9h5AF6RGd-5vgWPhvG-2b5xs8bl5zvZ0ZK3tf_bd0pxu_lw2YDG2Jv6VRdj9Hi__WrKB7pV3OELuBKIRunReqM9f8d1tbnKPFsJVHs2Zqf9SaMLrSmASCjiNAbokD0lD0t_95Wxu-MVaQ4uT6WQ8ta0V46Z6lq9br1fVEOh7ypju-FFyjBC7fmVy0UaMm3YBcbVYMt-dhfTlUbe2BOuD6ujFd_AA&width=1440&height=900&dpi=192&waitMs=7000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D.fc-toolbar-dock%7Bdisplay%3Anone!important%7D&drive=click%3A.tl-panel%7Cat%3D0.5%2C0.02%3Bpress%3A%3F&cropSelector=.tl-keys-modal&format=svg&walker=1&dark=1&filename=seq-shortcut-sheet) --- Source: https://lolly.tools/info/create/animating.md Page: https://lolly.tools/info/create/animating.html # Animating: keyframes, depth and a camera **Design** puts a timeline under the free canvas - **Sequence** is its name when a video composition is on it. [Using Lolly](/info/using.html#timeline-sequence) describes the timeline itself - the magnetic sequence row, the overlay lanes, the Always on strip, transitions and rendering - and [The sequence editor](/info/sequence-editor.html) covers editing in time. This page is about the layer above that: posing a box at one instant, lifting it off the page and flying a camera over the result. Everything here runs on your device. Nothing plays on its own: the preview moves when you press play or drag the playhead, and what you see at any instant is exactly what an export of that instant contains. ## A keyframe is a pose at a moment A keyframe records a box's position, its size, how see-through, how blurred and how far it lifts off the page - all at one instant. Two keyframes make a move, and Lolly fills in everything between them. **+Keyframe** has two homes and one behaviour: - the diamond at the end of the timeline's tool cluster, after the keyboard button; - the diamond on the selected object's bar on the canvas, beside Duplicate and Delete. `K` does the same from the keyboard. Whichever you use, the pose is written at the playhead for everything keyframable in the selection, in one commit - so one undo takes the whole thing back, even across a multi-selection. If the thing you selected has no timing yet, that same press adds it to the timeline **and** writes its first keyframe together. One press, one undo step. Each keyframe appears as a **diamond** on the clip's bar. `Alt + ←` and `Alt + →` step the playhead from one diamond to the next. Nothing else creates a keyframe. Dragging a box, typing in a field or scrubbing the playhead never quietly adds one, which is the rule the whole feature is built on: nobody keyframes by accident. A box that has never been animated shows one control instead - **Animate**, in its Keyframes group. Press it and a rest pose is written at the start of the clip, with everything exactly where it already was. From then on the box has a track, and the rest of this page applies to it. ## The playhead is the arm There is no record button and no stopwatch. The playhead's position decides what an edit writes. - **Parked on a diamond**, the canvas and the pose fields edit *that keyframe*. Drag the box, rotate it, resize it or type a number, and only that pose changes. A box resized on a diamond tweens its size between keyframes, and text inside it reflows as it goes. - **Parked off every diamond**, the same edits change the box itself, which moves the whole animation with it. This is how you reposition a finished move without re-keying it. The Keyframes group says which one you are in: its first line reads **Scene pose** off a diamond and **Keyframe @ 0:01.8** on one. Off a diamond the pose fields show the values the box is passing through at that instant, and all but Depth are inactive, because there is no keyframe to write to. Scrubbing **latches onto diamonds**: drag the playhead near one and it snaps to it, so "edit the pose I can see" is the easy thing to do. Hold **Alt** to park between them. Closing the timeline turns all of this off. The playhead is the arm, so the arm has to be visible. ## The Keyframes popup The clip inspector is a row of grouped segments - **Time**, **Animate**, **Keyframes**, a speaker toggle for mute and the timed or always-on switch. Each segment shows one glance-value summary; press it and its controls open as a panel above the transport, wide enough to read. The **Keyframes** panel holds everything one track can be edited with: - the latch line, saying which keyframe you are on; - the pose fields - **Depth**, **Scale**, **Opacity** and **Blur** (position, rotation and size are authored on the canvas instead); - one row per keyframe: its time in milliseconds, its curve, **Duplicate** and **Delete**; - **Remove N keyframes**, which is how you un-animate a box - the track is the animation, so removing it removes the state; - the curve editor, docked at the bottom of the same panel. Curves are the shipped set: **Linear**, **Ease out**, **Ease in**, **Ease in and out**, **Overshoot**, **Anticipate**, **Smooth**, **Snappy** and **Hold**, which keeps a value until the next keyframe. Drag a handle on the docked plot and the row switches to **Custom** with the curve you drew, so the presets are a way to learn the shape rather than a limit. A curve shapes movement; a fade always ramps evenly, whichever curve the keyframe carries. Clicking a diamond selects it and opens this panel on that keyframe in one gesture. Dragging one moves it in time, and Alt-dragging leaves the original in place and drops a copy where you let go. Right-clicking one offers **Keyframe curve**, **Duplicate keyframe** and **Delete keyframe** without opening anything. The keyframe rows are the keyboard and screen-reader route to the diamonds. Everything a diamond can do by pointer is a labelled button in that list. ![The Keyframes popup on a choreographed swatch: its diamonds listed with their times, the curve between two of them, and the pose fields for the one the playhead is parked on](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17ZjNj5pAFMD_Gi8mkjdvPjn0sKLuoWnT7qFJTwR1mpooNMAm6l_f-QBFdLMkLWQPiAwz7803v7w3j_XxUwETjNbZ0aQE4ZIic6nL2zvPXtOt3jqNFUUUMbK1wWrNf5OlZbJLTS7N8kOy99IJcjnfZPssDwp9SNJytwlKfSytfGH0TNmmOi11bjKH3Xa71ybDXa9FkhZ2iIBgNYsyf7XqX8m-0PUAke1iq_-Uv20l9-O8qu_WA80hbpuCm2561nlWlWjdymv09cHcnHx624vbm8azMXh934xYreK2-7te68qExzqLjzOCGHAen2aEsICq-GxeVFyQOIN4LaYlwVCYYgDITEExHh8hPkF89pVIvIY3RmnIGjpyV1tcF0ImclmQCzisOzgU5k9SjeAMAI704IBFBvGOF8orXmwhhP55wQsvsjsvyDhdrUZehjA0MnTAvGdomAAHDgmnJQKEvYNDWx7Kb8074KyWMqIjOEOAg_ziokJHQgsYTr2lIcICo_q3NKzlmToBs1zZawRmAGCosSq1a0J2x4sgofdMyvBChOqdF97yTJ14ISGCxJGXAXhhFCvP9NC-SEI9L9TwggJ750W0HJJQXRwSr3Zu5KVvXjhTtzHTGycZhQ1DQ2X_hka2HFMncJYcCV-O4AwAjmCsdkyPQqYQuefFnnyZZL3zolqOqRMvks6fkI-8DBFiGz6aIdNjO2OCJOK54VN7WIa-uQHyhayQ_fzx_WUBavXtmYoFRJ9fvj7b7U4OOk9an_uoj8XhgpTelFYpJh_pK1_1Ov4JpKvs42AEtdExTMxMdJ2bfH6CaUnNOq-wVOL_yMpf%26w%3D1100%26h%3D1040%26_sel%3Ds4%26_t%3D1.1&width=1440&height=1000&dpi=192&waitMs=5000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A320px%21important%7D&drive=click%3A.tl-group%5Bdata-group%3D%22keyframes%22%5D%20.tl-group-head%3Bwait%3A600&cropSelector=.tl-group-pop-body&walker=1&format=svg&dark=1&filename=anim-keyframes-popup) ## Depth lifts a box off the page **Depth** is the first pose field, and it is the one that stays live even when you are parked between keyframes. It says how far the box stands above the surface: `0` is flat on the page, the slider scrubs the useful band of 0 to 300 and the number beside it takes anything from `-300` (sunken) to `900`. - Off a diamond, Depth writes the box's own depth, so the box is simply lifted for as long as it runs. - On a diamond, Depth poses that keyframe, so a box can rise and settle again. Depth on its own changes how the camera sees the box: lifted layers move further and faster than the page behind them. To make the lift readable when nothing is moving, give the box a shadow that follows its depth. The object bar's **More** panel has a **Shadow** section whose **Apply to** row offers **Depth** beside Box, Text and Content; it derives a soft shadow from the box's own height above the page, so the higher the box, the further it casts. The first time you move Depth, a **Camera** appears in the Always on strip. Depth already works without it - the camera box is what makes the shot itself editable. ## Tilt a box A box can hold a **perspective pose** of its own: pitch and yaw, up to 75 degrees each way, with the vanishing point at the box's centre. The object bar's **More** panel has a **Perspective tilt** section with the two sliders; the timeline's Keyframes popup carries the same pair as pose fields, so a tilt can be keyframed like depth - the keyed value replaces the authored one for its segment, and everything the arc grammar says about coming home applies. A tilted box renders with real perspective everywhere - the canvas, a still, a frame of video - and one honest trade: because a tilted plane is beyond what the vector formats can say, an SVG or PDF export embeds that box as an image (the export says so), and a **video** export of a tilted scene runs on the slower capture path. Choreograph's **Tumble** option rides this feature and is off by default for exactly that reason. ## The camera Select the **Camera** chip and the inspector swaps its Time and Animate groups for a **Camera** group. Five moves write a complete track in one press, each one an ordinary set of keyframes you can then edit or delete: | Move | What it does | | --- | --- | | **Push in** | Travels toward the artwork and stops. | | **Pull back** | Starts close and settles at the resting frame. | | **Pan across** | Slides sideways, so lifted layers part at different speeds. | | **Rise** | Climbs over the composition, easing a little closer as it goes. | | **Reveal** | Starts close and soft, then settles sharp at the resting frame. The depth-of-field demo. | **Orbit** sits beside them, dimmed, and says why: it needs the angled camera that is still being built. ![The Camera group with the camera selected: the five moves as one-press buttons, and the pan, tilt, dolly, focus, aperture and FOV strength channels under them](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17ZhLi9swEMc_jS8Bm5mRRo9DD7vepKVQaLdQ6ElkE2-7kMTF9kKST19kOe8VCbROe0gedqL5z0hWfsxk_LR8V0NC-VO5TChHgu2RZHtsP_t3Vb4upsW0tfihXBDlXg3emlA-KRfN-GWRUL4oq_l4FkYTYn0_KWdlldXFfLxoXiZZUywbP_6QUC6Ndy0WTVEllM9fptNZkVDObdR6vKj9FBlSt4qmevXm5_Gs9ucfGGbxQabFr-anl7UP5s6jvSLYn2TjHF7QLnixLqqy-6Y2XsFS7E6yXVU4HkZpd2fvvDf55n0wY3cdh-FPom7EQrMrarcEtwK3BlejK9E9waBBIdgtUwSdMbtVigCZMG6NtK8S1gcozwulFuhqzIDkoGFgPp0zss69sT0bnqjVbisw0cMat_DJy-ETcH-nzQ2-q8AnRRw-GwBJEU5RAhGYi9m17VBTg4YR-0eNtqjpy1EjyWI0uqF2FdRY2Bhq0lq3PJe90PuX53XGcose2kHDhLZ39MRRiQ1bewa90VDn4obeddBTGM1yrNsSi7I1HaFEuKmsb9utDlkOfZYj23-Wk0cF9SLUhiP_vKF2HdSsiaGm2ISCSfKUNO9WxswMLEM5NYOGhTa9g8ZH5fQi0NASaLqBdhXQtKUYaJrJLSMpS3i3MmZmlCaAJgYNS029g6aOiqcylxRP7jb8Blr_oFmIZjSjzF7bGfk_xmAO29O3dUxqL8ex6T_H6aNiehF6Qybk4Q29q6CHADLGnlWyHX-r-1TerYyZWSgIpPkOQRnZO2nmqJpeRJoW93fEN9KuRJqBCGkEEnZdZyTJKe9enpOxFCqQx4OGtYK-yQP8hCMyj9--PXz5_FV-eC8_GkHfH63vWSfjeVGNj-4Mi3DfBLZQFpPGG1UX9F-yuCOx-zn_CMTd2P-D4SGD1RJctQL3DG7cllsI9lSAzmhz5y1F5aUpGVetkNyzZ87rhc9zXt_Jjcqkl1MrRy9PUe30W4h9EyJFRm6dhtAmLGMjVAAQWeVfxPc3%26w%3D1100%26h%3D1040%26_t%3D0.5&width=1440&height=1000&dpi=192&waitMs=5000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A320px%21important%7D&drive=click%3A.tl-chip%3Bwait%3A500%3Bclick%3A.tl-group%5Bdata-group%3D%22camera%22%5D%20.tl-group-head%3Bwait%3A600&cropSelector=.tl-group-pop-body&walker=1&format=svg&dark=1&filename=anim-camera-moves) Under the moves are the camera's own channels - **Pan X** and **Pan Y**, **Dolly**, **Focus**, **Aperture** and **FOV strength** - each with the gesture that drives it. With the camera selected and the playhead inside its window, dragging empty canvas pans the shot and a plain scroll dollies it. Cmd/Ctrl-scroll still zooms *your view*, and Space-drag still pans your view: moving the shot and moving your own window onto it stay separate gestures. Clicking any box hands both back. Two things to know about the model: - A camera holding a **single pose** is the scene default. Pan it, dolly it or change its focus and the whole shot changes, with no keyframes involved. - **Cuts come from a second camera.** Park the playhead where you want the cut, choose **Camera** from the timeline's `+` menu and click the canvas to drop it (the same two steps every kind added from the timeline takes). It arrives as a clip starting at that instant, and from there the shot looks through it. **Focus** is a distance and **Aperture** is how shallow the focus is; at `0` everything is sharp, and raising it softens whatever sits away from the focus distance. **FOV strength** changes the perspective, not the magnification - it is a dolly (the camera's own Depth) that makes things bigger. > Today's camera moves stay square to the page: it travels, but it does not tilt. Angled, point-of-view glides low over the artwork are being built. ## Lift layers: one SVG into a stack A flat SVG is one picture. **Lift layers** turns it into a stack of boxes, one per layer of the drawing, so each layer can carry its own depth, keyframes and blur. Right-click a box holding an SVG and choose **Lift layers** (it is also in the **More** panel on the object bar). The dialog reads the artwork, then says what it found - **6 layers found**, with the number of shapes in each. Press **Lift layers** to accept, and the box is replaced by that many boxes, in the same place, sharing a group, with their depth staggered and the Depth shadow already set. It is one commit, so one undo puts the original picture back. The list is a plan, not a promise. On a screenshot exported from a Lolly canvas the layers really are the objects the editor knows about. On anything else - a page you captured, a drawing from another app - the split is read off the artwork's own groups and geometry, so read the list before you accept it. Artwork that is genuinely one layer says so and offers nothing to do. ## Choreograph: one click, a whole motion arc A lifted stack, a grouped selection or a grid of swatches is a set of boxes that want to move together. **Choreograph** writes that motion for you: every box gets its own keyframes, staggered a little after the box before it, and the camera gets a move of its own, all in one commit. Nothing is stored by name - what you get is ordinary keyframes, so you can retime, split or delete any of them afterwards, and one undo takes the whole arc back. ![Choreograph: nine swatches selected, and the picker offering the six showcases with Buildup checked, Length 3, Stagger 90, Order, Camera move and Float](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17ZTNboQgEMefhquBUcQeelipvoertDVRaJBNTJ--AV3Xj0O5kqACZob5D8Mvmfv8PmEE_K5mBJwA3mbI3Oz-7dDqITvROY818RSA293YehHwVknT9BIBl0qPzbBYEVBWtmpQOpnE2EjTt4kRs7H2DwQ8K2yokEZoBHzsu24QCDh1qlMjJ5siIbCewuiHdX82w2TXL7JksSKd-DHfdpt7KF0jXEV4n-QZvHzYHVj-Cq0ONrLYxWvJ3JmW-ajh7ma37lI_x0F7reIof1E9RF5cO9vORy6785cGQayayEY786ed4vLGikg7ONqw0Wb-tCGjaV1H2sHRTk-dfKnnH9p1xXgaaYdHOzt1ci_aVW3fSDs42vTUyb1okzfADCLt4Gjnp06eFz6dnK51R9ph0WanTu5Fu6JAaBVpB0e7OHVyL9osLW9AI-2gaP8B%26w%3D1100%26h%3D1040%26_sel%3Ds0%2Cs1%2Cs2%2Cs3%2Cs4%2Cs5%2Cs6%2Cs7%2Cs8%26_panel%3Dchoreograph&width=1360&height=1240&dpi=192&waitMs=3500&css=.fc-flash%7Bdisplay%3Anone%21important%7D%23tool-stage%2C%23tool-canvas%2C.tool-canvas%7Bbackground-image%3Anone%21important%7D&format=png&dark=1&filename=anim-choreograph-picker) Select two or more boxes, right-click and choose **Choreograph…** (it is also in the **More** panel). Pick a showcase: | Showcase | The arc | | --- | --- | | **Buildup** | Assembles from nothing: each box fades in from a little way out and settles into place, in reading order. | | **Deconstruct** | The reverse: everything rests, then flies apart, and stays apart. | | **The Loop** | Assembles, holds, flies apart. It ends exactly where it began, so an exported GIF or APNG cycles without a jump. | | **Hero arc** | Explodes outward while the camera flies through the stack at an angle, then everything comes home. | | **Trench run** | A small lift, with the camera inside the stack drifting along it so the layers stream past above and below. | | **Map-scan** | The boxes hardly rise; the camera starts far back, glides in at an angle, then pans across the page in a few thrown-map moves before settling. | ![Buildup at 1.1 s: the first swatches have settled into place, the middle of the grid is still fading in from its exploded offset, and the last are yet to appear](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17ZjNj5pAFMD_Gi8mkjdvPjn0sKLuoWnT7qFJTwR1mpooNMAm6l_f-QBFdLMkLWQPiAwz7803v7w3j_XxUwETjNbZ0aQE4ZIic6nL2zvPXtOt3jqNFUUUMbK1wWrNf5OlZbJLTS7N8kOy99IJcjnfZPssDwp9SNJytwlKfSytfGH0TNmmOi11bjKH3Xa71ybDXa9FkhZ2iIBgNYsyf7XqX8m-0PUAke1iq_-Uv20l9-O8qu_WA80hbpuCm2561nlWlWjdymv09cHcnHx624vbm8azMXh934xYreK2-7te68qExzqLjzOCGHAen2aEsICq-GxeVFyQOIN4LaYlwVCYYgDITEExHh8hPkF89pVIvIY3RmnIGjpyV1tcF0ImclmQCzisOzgU5k9SjeAMAI704IBFBvGOF8orXmwhhP55wQsvsjsvyDhdrUZehjA0MnTAvGdomAAHDgmnJQKEvYNDWx7Kb8074KyWMqIjOEOAg_ziokJHQgsYTr2lIcICo_q3NKzlmToBs1zZawRmAGCosSq1a0J2x4sgofdMyvBChOqdF97yTJ14ISGCxJGXAXhhFCvP9NC-SEI9L9TwggJ750W0HJJQXRwSr3Zu5KVvXjhTtzHTGycZhQ1DQ2X_hka2HFMncJYcCV-O4AwAjmCsdkyPQqYQuefFnnyZZL3zolqOqRMvks6fkI-8DBFiGz6aIdNjO2OCJOK54VN7WIa-uQHyhayQ_fzx_WUBavXtmYoFRJ9fvj7b7U4OOk9an_uoj8XhgpTelFYpJh_pK1_1Ov4JpKvs42AEtdExTMxMdJ2bfH6CaUnNOq-wVOL_yMpf%26w%3D1100%26h%3D1040%26_t%3D1.1&width=1360&height=1000&dpi=192&waitMs=4500&cropSelector=%23tool-canvas&walker=1&format=svg&dark=1&filename=anim-buildup-mid) ![Hero arc at 2.4 s: the camera pushed in and tilted, inside the exploded stack, every swatch lifted and leaning as it passes](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17ZhLi9swEMc_jS8Bm5mRRo9DD7vepKVQaLdQ6ElkE2-7kMTF9kKST19kOe8VCbROe0gedqL5z0hWfsxk_LR8V0NC-VO5TChHgu2RZHtsP_t3Vb4upsW0tfihXBDlXg3emlA-KRfN-GWRUL4oq_l4FkYTYn0_KWdlldXFfLxoXiZZUywbP_6QUC6Ndy0WTVEllM9fptNZkVDObdR6vKj9FBlSt4qmevXm5_Gs9ucfGGbxQabFr-anl7UP5s6jvSLYn2TjHF7QLnixLqqy-6Y2XsFS7E6yXVU4HkZpd2fvvDf55n0wY3cdh-FPom7EQrMrarcEtwK3BlejK9E9waBBIdgtUwSdMbtVigCZMG6NtK8S1gcozwulFuhqzIDkoGFgPp0zss69sT0bnqjVbisw0cMat_DJy-ETcH-nzQ2-q8AnRRw-GwBJEU5RAhGYi9m17VBTg4YR-0eNtqjpy1EjyWI0uqF2FdRY2Bhq0lq3PJe90PuX53XGcose2kHDhLZ39MRRiQ1bewa90VDn4obeddBTGM1yrNsSi7I1HaFEuKmsb9utDlkOfZYj23-Wk0cF9SLUhiP_vKF2HdSsiaGm2ISCSfKUNO9WxswMLEM5NYOGhTa9g8ZH5fQi0NASaLqBdhXQtKUYaJrJLSMpS3i3MmZmlCaAJgYNS029g6aOiqcylxRP7jb8Blr_oFmIZjSjzF7bGfk_xmAO29O3dUxqL8ex6T_H6aNiehF6Qybk4Q29q6CHADLGnlWyHX-r-1TerYyZWSgIpPkOQRnZO2nmqJpeRJoW93fEN9KuRJqBCGkEEnZdZyTJKe9enpOxFCqQx4OGtYK-yQP8hCMyj9--PXz5_FV-eC8_GkHfH63vWSfjeVGNj-4Mi3DfBLZQFpPGG1UX9F-yuCOx-zn_CMTd2P-D4SGD1RJctQL3DG7cllsI9lSAzmhz5y1F5aUpGVetkNyzZ87rhc9zXt_Jjcqkl1MrRy9PUe30W4h9EyJFRm6dhtAmLGMjVAAQWeVfxPc3%26w%3D1100%26h%3D1040%26_t%3D2.4&width=1360&height=1000&dpi=192&waitMs=4500&cropSelector=%23tool-canvas&walker=1&format=svg&dark=1&filename=anim-hero-pov) Below the showcases: **Length** in seconds (each showcase has its own, and follows the one you pick until you type a length), **Stagger** in milliseconds between one box and the next, **Order** (reading order, reversed, from the centre, by depth or random), **Camera move** and **Float** - a breath of scale as each box settles, so a grid never ticks like a metronome. Two rules the generated moves keep. Every showcase except Deconstruct ends at rest: each box back at its own depth, the camera home. And a showcase over boxes that already have a timeline runs on that timeline - it starts where the earliest of them starts, and a box that enters late joins the move at the pose it has reached rather than replaying the opening. Boxes with no timeline yet are given one: each becomes a clip from the start of the sequence for the arc's length, which is what makes the motion play and export at all. ![The timeline after a Buildup: nine clips from 0 to 3 s, one lane each, every one carrying its own diamonds, and the playhead parked mid-arc](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Fz%3D17ZjNj5pAFMD_Gi8mkjdvPjn0sKLuoWnT7qFJTwR1mpooNMAm6l_f-QBFdLMkLWQPiAwz7803v7w3j_XxUwETjNbZ0aQE4ZIic6nL2zvPXtOt3jqNFUUUMbK1wWrNf5OlZbJLTS7N8kOy99IJcjnfZPssDwp9SNJytwlKfSytfGH0TNmmOi11bjKH3Xa71ybDXa9FkhZ2iIBgNYsyf7XqX8m-0PUAke1iq_-Uv20l9-O8qu_WA80hbpuCm2561nlWlWjdymv09cHcnHx624vbm8azMXh934xYreK2-7te68qExzqLjzOCGHAen2aEsICq-GxeVFyQOIN4LaYlwVCYYgDITEExHh8hPkF89pVIvIY3RmnIGjpyV1tcF0ImclmQCzisOzgU5k9SjeAMAI704IBFBvGOF8orXmwhhP55wQsvsjsvyDhdrUZehjA0MnTAvGdomAAHDgmnJQKEvYNDWx7Kb8074KyWMqIjOEOAg_ziokJHQgsYTr2lIcICo_q3NKzlmToBs1zZawRmAGCosSq1a0J2x4sgofdMyvBChOqdF97yTJ14ISGCxJGXAXhhFCvP9NC-SEI9L9TwggJ750W0HJJQXRwSr3Zu5KVvXjhTtzHTGycZhQ1DQ2X_hka2HFMncJYcCV-O4AwAjmCsdkyPQqYQuefFnnyZZL3zolqOqRMvks6fkI-8DBFiGz6aIdNjO2OCJOK54VN7WIa-uQHyhayQ_fzx_WUBavXtmYoFRJ9fvj7b7U4OOk9an_uoj8XhgpTelFYpJh_pK1_1Ov4JpKvs42AEtdExTMxMdJ2bfH6CaUnNOq-wVOL_yMpf%26w%3D1100%26h%3D1040%26_t%3D1.1&width=1440&height=1000&dpi=192&waitMs=5000&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A420px%21important%7D&cropSelector=.tl-panel&format=png&dark=1&filename=anim-timeline-choreographed) ## Text that arrives in pieces A text box can enter and leave as a whole, or a piece at a time. In the timeline's **Animate** section, **Text** chooses the tier - **Whole text**, **Letter**, **Word** or **Line** - and every piece then plays the box's own **Enter** and **Exit** with its curve. **Offset starts by** is the gap between one piece starting and the next, in milliseconds, so a heading can drop in letter by letter or a list can fade in line by line. **Text order** decides who goes first: **First to last**, **Last to first**, **From the centre** or **Random**. ![The timeline with a text clip selected: its strip carries Time, Speed and the Motion group, where Text chooses the split tier and Offset starts by, Text order and While on screen follow](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Ftemplate%3Dfeature-tour%26_sel%3Dfeat1&width=1440&height=900&dpi=192&waitMs=4500&waitSelector=.tl-clip&css=.tl-panel%7Bheight%3A300px!important%7D&cropSelector=.tl-panel&walker=1&format=svg&dark=1&filename=tl-split-text) Pieces are shaped glyphs, not a span per character, so a letter that kerns into its neighbour keeps that kerning while it moves, and the exported video, animated SVG and PowerPoint deck all play the same split. In PowerPoint the split becomes native per-letter, per-word or per-paragraph animation, so a deck stays editable in PowerPoint and still moves. ## While on screen Between its entrance and its exit a box usually sits still. **While on screen** gives it something to do in the meantime: **Pulse**, **Bob**, **Sway** or **Flicker**, at a **Hold speed** in cycles per second. It is the same feature whether the box is a heading or a call-to-action sticker, and it plays in every motion export. ## Exporting a move The preview and the file are the same arithmetic, so what you see is what you get. - **Video** - MP4 or WebM, rendered frame by frame at exact times rather than recorded off the screen. See [Exporting & Formats](/info/exporting.html). - **A still** is the frame at the playhead. Park the playhead where the shot looks best and export. - **A contact sheet** comes from the **Frames** field beside the output size: raise it and you get that many stills sampled across the sequence - see [stills from a timed composition](/info/exporting.html#stills-from-a-timed-composition). - **SVG stays vector.** A posed frame exports as real transforms with real blur and shadow filters, not a picture of a moment. - **PDF is partly vector.** Box and text shadows stay vector; the Depth shadow and the content shadow follow the shape of the artwork itself, so a box using either exports as an image embedded in the PDF, as does a blurred one. A keyframe track is an ordinary part of the design, so it travels in the link like everything else: share the URL and the recipient opens the same move. (A long one packs itself into the short form - see [URL Mode](/info/url-mode.html).) --- Source: https://lolly.tools/info/create/collaborate.md Page: https://lolly.tools/info/create/collaborate.html # Working on something together Two people, two devices, one tool session, edited live. No account, no sign-in, no server in the middle and no internet needed if both devices are on the same network. This page is the whole feature: how to start one, the three ways to hand the invite over, the reply leg that trips most pairs up, the six characters that tell you the connection is private, what you see while you work, how to send files and sessions down the same link and what to do when a network refuses to let two devices talk. > This is the individual path. It pairs exactly two devices, directly, and it is yours to start whenever you want one. Nothing about it asks permission from anything. ## What a private collab is A **private collab** is a live editing link between two devices. One person invites, the other joins and from that moment both are typing into the same tool session: change a field on one device and it appears on the other. The link is made by the two browsers talking to each other directly. Your work does not travel through a service on the way, because there is no service - the invite and the reply are the whole of the setup, and you are the one who carries them across. Two things follow from that, and it helps to have them straight before you invite anyone. - **The person who invites owns the session.** The saved session lives on the inviting device. The joining device gets a working copy that is deliberately never written into its own Projects. That copy is real, editable and exportable on the joining device, but it is not filed there and it does not survive the collab. - **Anyone holding the invite can join and edit.** The invite is the key. Send it through a channel you would send the work itself through, and treat a re-sent invite as a re-shared document. The feature is on by default. It carries a **beta** pill in the profile settings, which is where you can also turn it off. Turning it off means an invite link opened on that device offers to turn it back on rather than dead-ending. ![The Feature flags section of the profile, with Private collab and its beta pill at the end of the list](/t/url-shot?url=%2F%23%2Fprofile%3Ffocus%3Dfeature-flags&width=1440&height=1800&dpi=192&waitMs=2400&cropSelector=%23feature-flags-section&walker=1&format=svg&dark=1&filename=collab-flag-setting) **What both devices need.** The same tool, present locally on each. A collab sends values, never code, so the template and the logic always come from the catalogue on each device. If the joining device does not have the tool, the join is refused at the moment the invite is read, by name, rather than opening something that renders nothing. ## Start a collab Open the tool and get the session to the state you want to share. Then: 1. Press **Share** in the export controls, the same button that copies a share link. 2. Scroll to **Private collab** and press **Start a collab**. 3. Give yourself a name for this collab and press **Create the invite**. ![The Share dialog's Private collab section - one line saying what it does, then Start a collab and Join with a code](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools&width=1100&height=900&dpi=192&waitMs=2600&drive=click%3A%23render-fab%3Bclick%3A%5Bdata-action%3D%22copy-url%22%5D&cropSelector=.share-private-collab&walker=1&format=svg&dark=1&filename=collab-share-section) The name is chosen here, per collab, and it is the only thing about you that crosses the link. It is not read from your profile, and nothing else from your profile goes anywhere. Leave it empty and you appear as **Host** to the other person, or **Invitee** if you are the one joining. ![Step 1 of 3 - naming yourself for this collab, with the note that the other person sees this name while you work together](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools&width=1100&height=900&dpi=192&waitMs=3000&drive=click%3A%23render-fab%3Bclick%3A%5Bdata-action%3D%22copy-url%22%5D%3Bclick%3A%5Bdata-act%3D%22start-private-collab%22%5D&cropSelector=.collab-ceremony&walker=1&format=svg&dark=1&filename=collab-invite-name) The whole ceremony is numbered **1 of 3**, **2 of 3**, **3 of 3** on every screen, on both sides. That is deliberate: the hand-over has two legs, both people are looking at different screens and the numbers are how you tell each other where you are. ## Handing over the invite Once the invite is minted you get the same invite in three forms at once. They are the same thing wearing different clothes, so pick whichever suits the two of you and wherever you happen to be. ### A link The invite as a `#/join?inv=...` URL. Send it through any channel the two of you already use. The other person clicks it, their device opens Lolly at the join screen with the invite already read and they go straight to naming themselves. Nothing to paste. **When it fits:** you have any messaging channel at all between the two devices, and the joining device can reach the same address you are using. ### A QR code The invite as a QR on screen. The other device points its camera at it and scans. **When it fits:** the two devices are in the same room and there is no channel between them at all. It is also the leg that works when one device is a phone and the other is a laptop across a desk. Two things to know. The reply comes back the same way, so this is genuinely two scans and not one: your device shows the invite, theirs scans it, then theirs shows the reply and yours scans that. Numbering the steps 1-2-3 is what stops that going wrong. And scanning is only offered where the browser can actually decode a QR, which today means Chromium-family browsers. Where it cannot, there is no **Scan a code** button at all rather than a button that opens a camera and never finds anything - the code beside the QR is the same payload, so pasting is always available. ### A code The invite as a block of text. Copy it, send it however you like and the other person opens `#/join` on their device with nothing in the URL and pastes it into the field there. The Share dialog has a **Join with a code** button that opens exactly that screen, so both entrances are one place. **When it fits:** the invite came through a channel that mangles links, or the other person is typing an address in by hand, or you are reading it out. ![The bare join screen - one field, one button and the note that a code or a link both work here](/t/url-shot?url=%2F%23%2Fjoin&width=1100&height=760&dpi=192&waitMs=2400&cropSelector=%5Bdata-collab-join%5D&walker=1&format=svg&dark=1&filename=collab-join-code-door) > **The invite does not last forever.** While you are waiting, the screen counts down how long the current invite still works. After ten minutes a fresh one is minted automatically and the screen says so, so send the new one rather than the one you already sent. That happens twice; after that the wait gives up and tells you nothing came back in time. **Make a new invite** sits on the waiting screen throughout, if you want to start the clock again yourself. ## Sending the reply back This is the leg pairs give up on, so it is worth being explicit: **an invite on its own does not connect anything.** The joining device makes a reply, and that reply has to get back to the waiting device before either of you is connected. Same three forms, same choice. - **As a link.** The reply is a `#/join-reply?ans=...` URL. Opening it on the same device that made the invite hands the reply straight to the window that is waiting, and that window moves on by itself. The tab that did the handing says so and can be closed. If no window on that device is waiting, it says that too and leaves the code on screen to copy. - **As a code.** Paste it into the **Paste the reply here** field on the waiting screen and press **Connect**. - **As a QR.** The waiting screen has its own **Scan a code** button, so the reply can be scanned back exactly like the invite was scanned across. **Testing it with two tabs on one device works.** If you open your own invite in another tab, the join screen notices and says so in one dismissible line. It is information and never a refusal - the pairing is real, it just happens to be between two windows of one browser. If the reply arrives in a window that was not expecting it, nothing silently goes wrong: a reply pasted into the invite door is named as a reply and you are told which window it belongs in, and a device with more than one invite waiting says so and leaves the code for you to paste into the right one rather than guessing. ## The matching plates The moment the two devices connect, both screens show the same six characters, grouped as three and three. Under them, one sentence: > Both screens show the same plate when the connection is private. Read the plate out loud and check it against the other screen. If they match, the two devices are talking to each other and to nobody in between. ![The Connected screen with the six-character plate across it, and the line saying both screens show the same plate when the connection is private](/info/shots/collab-plate.svg) Here is what the check is actually doing, because it is the one security property of this feature that needs a person rather than code. The invite carries a fingerprint of the inviting device, and the reply carries one back. The plate is derived from **both** fingerprints together, ordered so that each device computes the same answer without either of them having to be told who is who. Anyone who wanted to sit in the middle would have to terminate the connection on both sides with certificates of their own, which is two fingerprints neither of your devices ever saw, and there is no substitution they can make in the invite or the reply that produces two matching plates on your two screens. This is the same idea as the short authentication string in ZRTP (RFC 6189 section 7): the humans are the part of the channel that cannot be forged. It is not a formality. Comparing the plates is what turns "the invite reached them" into "the invite reached them and nothing changed it on the way". A few details that follow from how it is built: - **The alphabet has no ambiguous characters.** No 0 or O, no 1 or I or L, no B against 8. The plate exists to be compared out loud, and "oh" against "zero" is exactly the confusion the comparison must not absorb. - **A screen reader says it character by character**, spaced, with a pause at the group break. Read as a word it would sound like a match when it is not. - **A new pairing has a new plate.** If the connection drops and you pair again, compare again. The old plate belongs to a connection that no longer exists. ## Editing together Once you are connected, the tool opens on both devices with the session in it and you both just work. **What you see.** A **collab pill** sits over the canvas: the people in the collab as a stack of initials, and a dot for the state of the link - Connecting, Live, Reconnecting, Away, Disconnected. Open it for the roster, which names everyone and tags who is you, who is away and who is observing. ![The collab pill: a status dot and two stacked initials over the canvas](/info/shots/collab-pill.svg) ![The roster open under the pill - Ada tagged You and Host, and Grace below her](/info/shots/collab-pill-roster.svg) **Where the other person is working** shows as a coloured ring on the control they are in, with their name on a chip beside it, and as a matching outline on the part of the render that control draws. Colour is never the only signal - the ring is always paired with the name, the canvas outline carries a hairline that reads as a shape rather than a hue and every handover is spoken through the live region for screen readers. ![The whole editing window on one device while the other person works: the Quiet zone row in the sidebar carries a coloured ring and a name chip reading Grace](/info/shots/collab-focus-ring.svg) **What you do not see is a floating mouse pointer.** That is a decision rather than a gap: the canvas here is a rendered preview and not a freeform surface, so "Priya is editing the Headline" is both truer and cheaper than an arrow drifting over a picture. Nothing about presence is written into the render - the rings and outlines are painted on a layer above it - so someone else working alongside you cannot change a single byte of what you export. **Undo stays yours.** Your undo history is a record of your own edits and nothing else. A change arriving from the other device never goes onto your undo stack, so you can never undo something you did not do. When you do undo, the value goes back the way you meant and that change travels to the other device like any other edit. **Two people in the same field.** The last write wins, per field. There is no locking and no queue, and both of you will see the same final value. Two people typing into the same text field at the same moment is the one case that behaves poorly, for the same reason it does in every design tool: you get one of the two versions, not a merge of both. In practice this is what the focus rings are for - you can see where the other person is. **If the connection wobbles**, the dot says Reconnecting and nothing is torn down. A brief drop heals on its own. A real drop needs a fresh invite, because a private collab has no server to resume from: the state lives on the two devices and nowhere else. **If the two devices are running different versions**, you are told rather than left to wonder. A minor difference in the tool says some fields may not match. A larger difference in the collab format puts the older device into **Observing**: it keeps seeing everything, and its own edits are not sent. ## Sending files and sessions The same link that carries your edits will also carry things that are too big to be edits. This is a **beam**, and it works like handing someone a file across the table. Press **Send this session** on the collab pill. The other device gets a card naming what is being offered, how many items it contains and how large it is, with **Accept** and **Decline**. Nothing moves until they accept. Both of you watch the same progress card, and either of you can cancel. ![Both devices at the same moment: on the left the sender waiting to be let in, on the right the card naming what is offered, who it is from and Decline beside Accept](/info/shots/collab-beam-consent.svg) **What travels.** The session itself, plus the files you brought to it - uploads, recordings, captures. Those exist on one device only, so without them the session would arrive with holes in it. **What does not travel.** Anything already in the catalogue on both devices. Those are listed by reference and resolved locally, which keeps the transfer to the size of your own work rather than the size of a design system. If the two devices are set up differently and a reference cannot be resolved on the other side, the manifest says plainly which ones those were rather than pretending the render is faithful. **What happens on arrival.** A received session is filed as a **new** session, always, labelled with who it came from. It never overwrites anything. Received files are stored byte for byte as they arrived, with no re-encode and no downscale, and the transfer is checked against its declared size and digest before and after the write - which is also what keeps content credentials intact across the hop. A file the other device already has is recognised by its checksum and not stored twice. The transfer runs on its own channel, so a large beam never queues your edits behind it. Editing stays responsive for the whole transfer. > Today the one control wired up is **Send this session**, and it appears only while the bulk channel is actually open. The wider shapes the format already supports - a hand-picked set of files, a whole project, everything under a tag - are not reachable from the interface yet. ## When there is no internet at all This is the case the feature was built for. **Same network is the first-class path.** Two laptops on the same Wi-Fi, a laptop and a phone on the same router, two machines on a wired switch: the two devices find each other on the local network and the pairing completes without anything leaving it. No internet is involved at any point in the editing. **The hotspot trick.** If there is no network to share, make one. Turn on the personal hotspot on a phone and connect the other device to it. That is a network with exactly two devices on it, no route to anywhere, and it is enough - a plane, a basement, a site with no coverage. This is also the standing answer when a venue's Wi-Fi will not let two of its own clients talk to each other, which happens more often than you would like. **What needs what**, honestly: | The part | What it needs | |---|---| | Editing together | Both devices reachable from each other on the same network. Nothing else. | | Opening an invite link | The joining device has to be able to load Lolly at the address in the link. Already installed as an app, or already open on that device, and it loads offline. A first-ever visit needs to be able to reach the address. | | A code or a QR | Nothing beyond the app being open on both devices. This is the fully cold path. | | Scanning a QR | A camera, and a browser that can decode barcodes - Chromium-family today. | | A beam | Both people in the tool. There is no queue for a transfer offered before the other side has the tool open. | **Across the open internet**, be realistic. The pairing uses the addresses each device can see on the network it is on, and this build configures no external address-discovery server. Two devices on different networks, in different buildings, is not something to plan around. Get onto one network, or onto a hotspot. ## When it will not work The failures are named rather than shrugged at, and each screen offers the one thing worth doing next. **This network blocks direct connections.** Both devices gathered addresses and no route between them ever formed. This is client isolation - a guest network, a corporate Wi-Fi, a hotel, a conference floor - where the network deliberately stops its own clients from talking to each other. The screen says so and offers **Try again**. > **What to try:** a personal hotspot from a phone, with both devices on it. Or a wired network. Or any network you control. Nothing about the app can talk its way past a network that has been configured to prevent exactly this, and pretending otherwise would waste your time. **Nothing came back in time.** The reply never arrived. Make a new invite and send it again. Nine times in ten this is the reply leg: the invite was sent, the other person opened it and the reply is still sitting on their screen waiting to be sent back. **The connection dropped.** The other device stopped answering. A new invite is needed to carry on - there is no server holding the session, so there is nothing to resume from. Your work is untouched on your own device. **This device does not have that tool.** The collab needs a tool the joining device does not have. Add it there, then ask for a fresh invite. **The two versions of that tool do not match.** One device has a version of the tool the other cannot read. Update both. **This device could not open the connection.** The browser refused to make a direct connection at all. Reload and try again. **This link carries no invite**, or **this invite could not be read.** The link arrived incomplete or something changed it on the way. Ask for a fresh one. If the code came through a channel that wraps lines, the code form pastes more reliably than the link form. --- **Related:** [Using Lolly](/info/using.html) for the tool, the canvas and saving sessions. [Trust](/info/trust.html) for how the rest of the app treats your work and your data. [Privacy](/info/privacy.html) for what is stored and where. --- Source: https://lolly.tools/info/create/search.md Page: https://lolly.tools/info/create/search.html # Search # Search One search field, always in the same place at the bottom of the screen, that reaches your tools, your saved work, the catalogue and your own settings. Type from wherever you happen to be: either the list already in front of you narrows, or a small panel opens above the field with what matched. ## Where it lives The field sits in the middle of the bar fixed to the bottom of the window, the same bar that carries the Dashboard and Verify links. It's one field for the whole app rather than a different box on every screen, and it follows you: it shows on the Tools gallery, Utilities, the Catalogue, Projects, the Dashboard and Profile. ![the persistent search bar at rest: Pro and Dashboard on the left, the search field with its magnifier icon and a Ctrl-Space hint chip, Verify and What? on the right](/t/url-shot?url=%2F%23%2F&width=1440&height=900&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone!important%7D&waitSelector=.gallery-view%5Bdata-shots-settled%5D&walker=1&format=svg&cropSelector=.gallery-footer&dark=1&filename=srch-footer-closed) It doesn't show everywhere. Once you're inside a tool's own canvas, Batch mode, Verify, Convert, the spreadsheet, the Colour Lab, the PDF tool, Multi-edit, the Design System studio, the component library, Script audio or Ask Lolly, the bar is gone and the shortcut below does nothing - those surfaces keep their own chrome for now (Ask Lolly has its own composer). That's a narrower gap than the list makes it sound: every one of those except Multi-edit is still a destination you can search your way *to*, from any screen that does show the bar - they're in the Places group below, and Ask Lolly gets a group of its own. Multi-edit is the one place search doesn't know about yet. Search also reaches Lolly from outside the app. The installed apps own the `lolly://` URL scheme, so a launcher's "open URL" action (Raycast, Alfred, PowerToys Run), a macOS Shortcut or a terminal `open` opens an exact tool with its inputs filled, and on Linux the GNOME Shell search provider and KRunner list Lolly's tools by name. The scheme and its grammar are in [URL mode](/info/url-mode.html), under "The lolly:// scheme". ## What it finds A match is sorted into groups, always shown in the same order: - **Tools** and **Utilities** - by name, description and tag. Matching folds accents and case (typing "cafe" finds something named "Café"), and a name stays findable by its original English word even when you're using the app in another language. - **Projects** - your saved sessions, by their name, their filename and the tool that made them, and your folders by name. It matches the session's own label, not anything inside it: it can't find a session by a word you typed into one of its fields. - **Catalogue** - the design system's images, logos, audio and fonts, and anything you've uploaded yourself, by name, id, tag, category and format. Again, this is metadata, not contents: it can't find an image by what's actually in the picture. - **Settings** - every settings section, the four accessibility preferences and the feature flags, matched on their names and on the words people actually search for ("dark mode", "calm", "storage"). - **Places** - the app's own destinations, most of which don't carry the bar themselves: Verify, Convert, the spreadsheet, the Colour Lab, the PDF tool, the Design System studio, the component library, Script audio and Batch mode. Typing "verify" or "oklch" takes you there directly. - **Docs** - this site: every published page and heading. - **Ask** - always last, under whatever the other groups found: a single **Ask Lolly** row carrying what you typed into the in-app help view, where the answer is a documentation section quoted verbatim with its citation. It's the "didn't see it above?" row, not a result of its own. What happens with a match depends on where you're standing. On the Tools gallery, Utilities and the Catalogue, the grid you're already looking at filters itself, exactly as it always has - that grid *is* the result, so its own group is left out of the floating panel. On Projects, the Dashboard and Profile, the screen behind never changes shape while you type: everything, including your own projects and your own settings, shows in the panel instead, so nothing you own can look like it quietly disappeared. Inside the Tools, Utilities, Projects and Catalogue groups, a "See all in ..." row hands off to a proper results view when a handful of matches isn't enough; Settings, Places, Docs and Ask just end. The panel itself floats centred over the whole window, not tucked under the field, so it reads as one thing with the bar beneath it rather than a dropdown off one corner. ![the search panel open above the bar, with results grouped by kind for a typed query, the tools grid behind it already filtered to match](/t/url-shot?url=%2F%23%2F&width=1440&height=820&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone!important%7D&waitSelector=.gallery-view%5Bdata-shots-settled%5D&drive=press%3Ac%7Con%3D.gallery-search%3Bpress%3Ah%3Bpress%3Aa%3Bpress%3Ar%3Bpress%3At%3Bwait%3A900&walker=1&format=svg&dark=1&filename=srch-spotlight-results) Every hit is an ordinary link. A **Places** result goes to a plain hash route - `#/verify`, `#/lab`, `#/start` - so you can bookmark it, paste it into a doc or send it to a colleague, and several take you further in than the front door: `#/lab?c=` opens the Colour Lab already reporting on that colour, `#/ask?q=` opens Ask Lolly with the question already asked and `#/d?tab=` opens the Dashboard on that tab. A hit in **Projects** is a link too, but it names a saved slot on this device (`#/tool/?slot=…`), so that one travels no further than the device does. ## Keyboard Press **Control-Space** from any screen that shows the bar and it jumps straight to the field. (Cmd-Space is quietly wired in too, for anyone whose Mac muscle memory reaches for it, but it's never the one advertised: on an unmodified Mac the OS keeps that combination for Spotlight before the browser ever sees it, so Ctrl-Space is the one that reliably works, everywhere.) A small `⌃␣` chip sits inside the empty field as the reminder, on a wide enough screen with a mouse or trackpad. Once the panel is open: the arrow keys walk every row across every group, Enter opens whichever is highlighted (Cmd/Ctrl-Enter opens it in a new tab instead) and Escape backs out one step at a time - first the panel closes, then a second press clears what you typed, then a third leaves the field. ## What it doesn't do - It searches names, labels and tags, never contents. It won't find a session or an asset by something inside it, only by what it's called. - Results only navigate. Nothing in the panel deletes, renames or flips a setting on your behalf - it takes you to the place you'd do that yourself. - No history and nothing shown before you start typing. Below two characters the panel stays closed. - Matching is exact substrings, folded for accents and case, not fuzzy - it won't correct a typo or guess at a word you didn't type. - It's answered entirely on your device, from data already there. The one exception is Docs: the first time you search, it quietly fetches this documentation site's own search index, the same plain file its own on-page search already uses, so page and heading titles are searchable too. What you type is never part of that fetch, only matched locally once the file is in; offline, that one group just doesn't appear, without complaint. --- **Related:** [Your favourites](/info/favourites.html) for keeping the handful you use where you can see them. [Using Lolly](/info/using.html) for the gallery, Projects and the Catalogue that search reaches into. [Privacy](/info/privacy.html) for what stays on your device. --- Source: https://lolly.tools/info/create/ask.md Page: https://lolly.tools/info/create/ask.html # Ask Lolly Ask Lolly is the in-app help view: you type a question, and it hands back the section of this documentation that answers it, word for word, with the page it came from cited beside it. Nothing is generated. The answer is retrieved, quoted and attributed, and the whole exchange happens on your device. ## What it is One job: find the part of these guides that answers your question, and show it to you unchanged. There is no model and no summarising step - the words in an answer are the documentation's own words, the citation under them names the page and section and an **Open in docs** link goes straight there. Under the quoted section sit the places in the app the same question matches: a tool, a utility, a saved project, a catalogue asset, a setting, a page of the app. Each is a row that only navigates. Nothing in an answer flips a setting, opens a file or acts on your behalf - the same rule [Search](/info/search.html) follows. ## Asking a question Type into the composer at the foot of the view and press Enter. Anything shorter than two characters is ignored. Answers build up in the transcript above, newest last, and you can keep asking. Follow-ups are answered on their own. The transcript is a record of what you asked, not a conversation that carries context forward, so "what about PDF?" after a question about exporting will be read as a question about PDF and nothing else. Ask the whole question each time. The first word of your question decides what leads the answer: | You start with | What leads | |---|---| | *where*, *find*, *open*, *show*, *go to*, *jump*, *switch* | the places in the app that match | | *make*, *create*, *convert*, *resize*, *compress*, *remove*, *strip*, *export*, *render*, *add* | the tools that match | | anything else - including *how do I* and *what is* | the documentation section, which is the default | Both halves are always retrieved, so a misread question reorders an answer rather than losing half of it. Matching drops the common question words (*how*, *do*, *I*, *the*, *my*) and ranks a section first by how many of your remaining words it covers, then by where they matched - a heading counts for more than body text. It is substring matching, folded for case and accents, not fuzzy: it will not correct a typo or guess at a word you did not type. When nothing matches, it says so and points at the documentation index rather than inventing an answer. ## What comes back An answer card is assembled in a fixed order: 1. **The section itself**, rendered from the documentation source - paragraphs, lists, tables, code and quotes, with in-app and `/info` links live. A long section is capped with a **Show more** that opens it in place. Images are dropped, and a link out to another site renders as plain text rather than an anchor. 2. **The citation** - *Page › Heading* - and the **Open in docs** link to that exact section. 3. **More in the docs** - up to four other sections that matched, as links. 4. **The in-app rows**, under the same group headings Search uses. The full text comes from the plain-markdown twin each documentation page publishes at `/info/.md`, and those twins are English only. In another language - and on the handful of pages not built from markdown - the answer falls back to the short extract held in the search index instead of the whole section. The citation and the **Open in docs** link are there either way, pointing at the page in your own language. ## Where it lives The route is `#/ask`. It has no tab and no tile in the Utilities grid; the ways in are the search bar and the address itself. - **From search.** Every result panel carries an **Ask Lolly: *your query*** row at the very bottom, under whatever concrete hits the other groups found. It hands the question over as `#/ask?q=…` and the view asks it once on arrival. The seed arrives folded by the shared tokenizer - lower case, punctuation dropped - so it will not look exactly as you typed it; the composer is where you refine it. - **From a link.** `#/ask?q=how%20do%20I%20export%20a%20transparent%20png` opens the view with that question already asked. The hash is the entry seed only: questions you type afterwards are answered in place and never rewrite the address. The shell's search bar steps aside inside this view, since the composer takes Enter, and the back pill returns you to wherever you came from. The transcript is session memory. It survives navigating away and coming back, and a reload clears it. Nothing is written to storage, so there is no history to manage and nothing to clear. ## Offline Answers are assembled from two static files the documentation site already publishes: the per-language search index and the page's markdown twin. Both are fetched by name, so Ask keeps working with no connection once they are cached. Reading the guides in the app caches them as you go, and **Profile → Available offline → Guides & docs** takes the whole site, screenshots included, in one download - see [Your profile](/info/profile.html#available-offline). Without them the documentation half of an answer simply does not appear, with no error. The in-app rows still do, because those are matched against data already on the device. ## Privacy Your question never leaves the device. Matching runs locally against files fetched by name, so what you typed is not part of any request; there is no server that sees it, no account it is attached to and no record of it after you reload. See the [Privacy Policy](/info/privacy.html) for the complete list of what the app ever fetches or sends. --- **Related:** [Search](/info/search.html) for the bar that hands questions over. [Using Lolly](/info/using.html#ask-lolly) for how Ask works within the rest of the app. [Your profile](/info/profile.html#available-offline) for taking the documentation offline first. --- Source: https://lolly.tools/info/create/dashboard.md Page: https://lolly.tools/info/create/dashboard.html # The Dashboard The Dashboard (`#/d`) is the instrument panel for a Lolly install: the brand it is wearing, what this device can do, the platform's capability map and a glance at your activity and storage. Four tabs, everything on them a read-out rather than a control - the one exception is the sound switch. It opens on **Design system**. The other three are one click away, and every tab and every section on them has an address of its own. | Tab | Address | What it shows | |---|---|---| | **This device** | `#/d?tab=device` | A live readout of the browser and machine this session is running on, plus sound and storage | | **Design system** | `#/d?tab=brand` | The brand in force - logo, colours, type, tokens, print reference. Read-only | | **Capabilities** | `#/d?tab=caps` | The whole platform as searchable cards, grouped by what they do | | **Activity & stats** | `#/d?tab=activity` | What ships in this build, plus your own local counters and recent work | Switching a tab rewrites the address as you go, so the link you copy is the tab you are looking at. ## This device - **This Machine** - screen, input, graphics, memory, codecs, storage backend and the rest, read live from this session. Nothing is stored and nothing is sent. It opens by itself on a wide screen, where the tab lays out in two columns, and stays folded on a narrow one. - **Sound** - interface sounds and the Neurospicy focus loops. This is the one switch on the page that writes anything, and the choice follows you across the app. - **Storage** - what Lolly is keeping on this device, category by category. A read-only view of the meter on your [Profile](/info/profile.html), which is where you clear or carry it. ## Design system The brand as it is actually loaded, rendered wearing its own variables: the name, the horizontal logo, the primary colour as a copyable value and the faces currently loaded on the device. Below the hero sit the palette on a hue/chroma wheel (greys have no hue, so they ride a lightness rail beside it), a live type specimen, the full colour palette, the brand token primitives - radius, spacing, effects, gradients - and a print and CMYK reference panel. **Nothing on this tab writes brand state.** It is a mirror: the editing happens in the [Brand Studio](/info/brand-studio.html) at `#/start`, and the tab links there from the hero and from the tokens section. On a brand-locked build there is nothing to edit at all, and a **Brand locked** panel says so - the brand ships with the build, comes from the catalogue and is authoritative on that install. Personal preferences live elsewhere too: theme and sound are on your [Profile](/info/profile.html), not here. > `/b` and `/brand` are shortlinks straight to this tab. The retired `#/platform` and `#/capabilities` addresses both fold into the Dashboard, deep-link flags intact. ## Capabilities Every part of the platform written up as a card, in eleven groups: Experiences, Platforms & runtimes, Export formats, Import formats, Print production, Automation & AI, Determinism & reproducibility, Brand & design system, Privacy & data ownership, Security & access control and Architecture. Each group folds, carries its own card count and opens a card into a dialog with that capability's feature list and - for about half of them - the same vector screenshot this documentation site uses. A search field above the groups filters the cards as you type and force-opens whichever groups still hold a match, so a query turns the accordion into a flat result list. It says how many capabilities it is searching, and a query that matches nothing says that in words rather than leaving you with a column of empty sections. This map is written documentation of what Lolly can do, kept in step with the guides and the export bridge. It is not a probe of the machine you are on - that is the **This device** tab, one click to the left. ## Activity & stats The build first: how many tools are loaded, how many export formats exist, how many surfaces the platform runs on and how many brand assets the catalogue carries, with a **Catalogue** panel breaking down what ships in this build. Then your own side of it: **Your activity** counts what you have made on this device (local counters, nothing recorded remotely), and **Recent creations** and **Latest exports** are swipeable stacks of your saved sessions and downloaded files, each appearing only once there is something to show. Both open an item exactly as it was. ## Deep links Every destination on the page is addressable, and the same registry the Dashboard renders from is what [Search](/info/search.html) points its **Settings** results at - so a section can never be renamed out from under a search result. - **A tab:** `#/d?tab=brand`, `#/d?tab=caps`, `#/d?tab=device`, `#/d?tab=activity`. - **A section, exactly:** its own id as a bare flag - `#/d?dash-storage`, `#/d?dash-tokens`, `#/d?cap-formats`. This is the precise form. - **A section by keyword:** `#/d?print`, `#/d?formats`, `#/d?palette`, `#/d?tokens`. Convenient, but keywords are shared - `print` belongs to both the brand tab's print reference and the Print production capability group - and the first one in page order wins. Use the id when it matters which. A deep link switches to the tab that owns the target, opens it (and any group it is folded inside), then scrolls to it, re-landing for a moment or so while the asynchronous sections above it finish laying out. --- **Related:** [The Brand Studio](/info/brand-studio.html) for editing what this page shows. [Your profile](/info/profile.html) for the settings, storage and offline downloads it mirrors. [Search](/info/search.html) for reaching any of these sections by typing. --- Source: https://lolly.tools/info/create/utilities.md Page: https://lolly.tools/info/create/utilities.html # Utility views A utility view is a workbench built into the app rather than a tool: you bring a file to it, do one job and take the result away. The spreadsheet, the converter, the Colour Lab, the PDF extractor and Script audio are the five, and they all run on your device. ## What a utility view is (and is not) A tool is data - a manifest, a template, inputs that live in the URL - so it saves sessions, reopens from a link, renders from the CLI and exports through the shared pipeline. A utility view is none of those things. It is a page of the app with no manifest, no session store and no export panel: what you take away is a download, and when you leave, the file you dropped is gone. That trade buys the things a tool cannot have. A view can use the shell's own controls (the Colour Lab is built on the app's multi-space colour picker), it can simply be a very tall page instead of a fixed canvas and it can hold a 200,000-row grid or a 400-page document that no render path would want. The cost is honest and easy to name before you start: no saved sessions, and nothing to keep offline per view, since they ship inside the app shell and are available whenever it is. They live in the **Utilities** tab (`#/u`), alongside the on-device utility *tools* - Strip Hidden Data, Compress PDF, Pages, Trim, Sign, Clean, Convert Image, Convert Font, Redact, Screen Capture, Text Helper and the rest - because to anyone using them they are the same kind of thing: something you open from that grid. A view's tile can be starred like a tool's ([Your favourites](/info/favourites.html) covers the strip that puts starred things on top), and each carries a details dialog. What a view's tile does not offer is *keep offline* or *saved sessions*, for the reasons above. ![Strip Hidden Data: the file arrives on the canvas and the badge states plainly that nothing is uploaded](/t/url-shot?url=%2F%23%2Ftool%2Fstrip-data&width=1200&height=750&dpi=192&waitMs=2000&walker=1&format=svg&dark=1&filename=aud-strip-data) > **Verify & Inspect** (`#/verify`) is the sixth member of the family and has pages of its own: [Verify It Yourself](/info/verify-yourself.html) and [Security & Verification](/info/security.html). ## Everyday file tools **Pages** is the PDF page manager: give it a 1-based expression such as `1-3,7,10-` to reorder, rotate, extract or delete those pages, append a second PDF, or split comma-separated ranges into separate files. The page strip shows the selection before download. It preserves the primary document's own metadata, adds none of Lolly's, and refuses encrypted PDFs and XFA forms rather than risking a partial document. **Trim** cuts audio and video at exact in and out points, keeps or changes the container, removes sound, extracts an audio track, and makes short GIFs. It uses packet copy when the chosen boundary and container permit it; when an exact video boundary sits between keyframes it re-encodes that span instead of silently moving the cut. The result names the real container and any codec the device could not handle. **Sign** places a transparent signature on a chosen PDF page in PDF-point coordinates; drag it on the page or enter the numbers. Initials and today's date are optional. A Content Credential is on by default and names the profile signer only when **Use my details** is enabled; a password lock is available at download time, but cannot be combined with that credential because encrypting afterwards would invalidate it. This is a visible signed document with optional provenance, not a regulated e-signature or identity-verification service. **Clean** reduces voice noise with the on-device speech model, trims silent edges sample-exactly, normalises to podcast, streaming or broadcast loudness, and holds the output under a -1 dBTP ceiling. Audio can leave as WAV, MP3, M4A or Opus. For a video, the picture is carried into the new container while the cleaned audio replaces its original track. The terminal path accepts WAV without pretending Node has codecs it does not; other containers move to the browser tier or are refused by name. ## Spreadsheet - `#/data` Open, read and lightly edit a spreadsheet with no Excel, no LibreOffice and no internet. Drop an `.xlsx`, `.csv`, `.tsv` or `.json`, or choose one; a multi-sheet workbook gets a tab bar and you can switch sheets without re-picking the file. Cells are editable in place, and **Download as** writes the grid as it stands - CSV, Excel, JSON or TSV. The grid renders only the rows in view and recycles them as you scroll, so a very large file stays responsive: the viewer reads up to 200,000 rows, and a pathological workbook is bounded at two million cells by the reader itself. Where either bites, the banner above the grid says how much you are looking at. **It shows values.** A formula arrives as its current computed result, and styles, merged cells, charts and every sheet but the one you downloaded do not survive the download. The banner above the grid states this before you edit anything, because the failure would otherwise be silent. Use it to read a file, fix some numbers and hand the data on - not to round-trip a formatted workbook. Nothing is uploaded, and nothing is kept: the file lives in the page until you leave it. ## Convert - `#/convert` Drop a file, pick a target, get a download. Every conversion runs in the app with the engine's own codecs - no upload, no service. | You bring | You can get | |---|---| | TrueType, OpenType or WOFF | any of the other two container formats | | SVG or SVGZ | its compressed or uncompressed twin, plus the raster list below | | Any image the app can decode | PNG, JPEG, WebP, AVIF, TIFF, BMP, PDF (one page, sized to the pixels) or ICO | | `.xlsx`, `.csv`, `.tsv` or `.json` | any of the other three | Some edges to know. The prompt and the file chooser's filter name fonts, images and SVG only, so a spreadsheet or a `.json` has to be **dropped** onto the view rather than picked - it converts perfectly well once it arrives. A font swap and an SVG⇄SVGZ swap are exact byte work, so an embedded Content Credential and outlined text come through untouched; anything on the raster row is drawn to a canvas first, which is a re-encode. WOFF2 is recognised but has nothing to convert to, so it reports that rather than pretending. Vector-to-vector transcoding (SVG to EPS or DXF) is not offered at all, because the engine's vector writers walk a rendered canvas rather than arbitrary source SVG and would misconvert it. An `.xlsx` converts from its first sheet. And where a browser cannot encode a format it is asked for, the view says so instead of handing back a PNG wearing an `.avif` name. **Convert the view versus Convert Image the tool.** The tool is the photo path: it decodes HEIC and HEIF with a bundled decoder, gives you a quality setting and a longest-edge resize, saves sessions and travels in a link like every other tool. The view is format plumbing with no settings - one click per target, whatever the browser can decode - and it reaches fonts, SVG and tabular data, which the tool does not. There is a **Convert Font** tool too, for the same swap as a saveable, linkable session. Convert has no tile in the Utilities grid. Type "convert" into [Search](/info/search.html) from any listing screen, or go to `#/convert` directly. ## Colour Lab - `#/lab` One colour, comprehensively. `#/lab?c=` opens the report on any CSS colour, and the address tracks what you pick, so a link reproduces the page you are looking at. The report runs in five steps: **set a colour** (the app's tabbed multi-space picker, or your brand's own swatches), **plotted on a colour space** (four charts, governed by a comparison target you choose), **every notation** (the same colour written for each space, copyable, with a *clamped* mark where a space is too narrow to hold it), **tones and blends** (a perceptually even ramp through the colour, and a blend across to a second one at a step count you set) and **displayable range and readability** (the gamut verdict, then the contrast scores - APCA first because it models polarity, WCAG second because people still have to report it, with a foldable grid of every brand colour against every other and a colour-vision simulation over it). The colour is never collapsed to sRGB on the way in. `color(display-p3 1 0 0)` is described at its real chroma and its real gamut rather than flattened to `#ff0000` and then declared safe, and every swatch is painted from the value you authored, so a wide-gamut display shows the real thing. **Your own ICC profiles.** The comparison target is sRGB, Display-P3 or Rec.2020 or a press profile you load yourself. A stored profile rides the same rail as an uploaded font - it is a user asset, so the storage meter counts it, a data export carries the bytes and *Clear all my data* removes it - and its id is derived from the file's own content, so a `&limit=icc:…` link matches the same profile on someone else's device rather than depending on a filename. Two files are refused rather than stored: one the parser cannot read, and one no rendering intent can be asked a gamut question of. This is also where you load the CMYK profile a PDF/X-4 export has to embed - see [URL Mode](/info/url-mode.html). A **This screen** panel at the foot reports the display the charts are being judged against, read live and stored nowhere. There is no export here. The page is the document: no canvas, no render, no CLI equivalent - copy the notations you need, or share the link. ## Unpack - `#/unpack` A design file is a container, and most software treats it as one opaque thing. This view opens it: the words, the vector marks, the images, the fonts, the colours and the attachments, each viewable and downloadable. Drop a PDF or Illustrator file, an SVG, an InDesign `.idml`, a Penpot `.penpot`, a Figma `.fig`, a PowerPoint `.pptx` or a Photoshop/GIMP `.psd`/`.xcf`. Nothing is uploaded. (The older `#/pdf` link still works.) Each format gives up what it honestly holds. A PDF and a slide deck carry their glyphs, so the words come out; an SVG or `.idml` names its fonts rather than embedding the files, so those come back as names-only rows you cannot download; a Penpot or Photoshop file embeds its images, so those travel as real bytes, while an InDesign document only links its images, so their pixels stay where they live and are counted, not fetched. A layered PSD/XCF gives every layer as its own named PNG; its text was flattened to pixels by the reader, so it has no words to extract here (which is not the same as the file having none). The **Text** pass rebuilds each page in reading order with the page's own vector picture beside it, noting how many columns it was read as and how many rotated runs were left out, so you can judge the reconstruction. Take it away with **Copy all**, **Download .md** or **Download .txt** or copy one page at a time. For an SVG the text is its `` runs in document order; an SVG whose type was outlined to paths honestly yields nothing, the same answer a PDF gives when its text was converted to curves. **Unpack extracts, it does not read pixels.** A born-digital PDF already contains its glyphs and their positions, which is why the extraction works offline at all - but a scanned page holds a picture of text and nothing else, and it is reported as exactly that, page by page. A document where every page is a scan gets that as a banner at the top rather than a footnote, because the next thing you need is a different surface: Lolly's **on-device text reader**. Drop the image into [Verify](/#/verify) and press **Read the text in this image**, or open it in the Catalogue and press **Read text** - the recognition model downloads once and runs on the device, nothing is uploaded. The same reader powers **Snap to text** in Annotate and **Suggest** in Redact. See [Generated once, rendered the same](/info/ai-features.html) for what it is and is not marked as. The tabs sit in a raised strip pinned to the top of the report, so as you scroll the pages you keep seeing that your images, fonts and colours came out too. Each tab appears only when a pass found something, so the strip describes your document rather than what a container could theoretically hold: **Palette** (the distinct colours it paints with, each with a copy button), **Logos** (vector marks, downloadable as SVG and usually the most useful thing in a guidelines file, since they stay sharp at any size), **Images** (embedded rasters - undecodable or linked-from-elsewhere ones are counted rather than hidden, and a linked image's pixels are never fetched), **Fonts** (each with its own embedding caveats stated plainly - a subset is called a subset, a names-only face carries no bytes and "no embedding restriction" is not called a licence) and **Attachments**. One check runs before any of it: text painted underneath an opaque shape is reported at the top of the report, with the hidden words shown. A black bar that does not actually remove the words underneath is worth seeing rather than trusting. Three hand-offs go straight into the brand: a font row installs its face, a mark goes to the [Brand Studio](/info/brand-studio.html)'s Logos room and the bar sends the whole scan to the studio. None of them re-scans the document - each is built from what the passes already extracted. Limits, stated: 400 pages and 120 MB, beyond which it reads what it can and says the rest is too long, and a single mark over 4 MB is not sent to the studio (download the SVG instead). ## Script audio - `#/script` A writing surface over on-device speech. Write or paste a script - markdown is fine, only the words are read, so code blocks and images drop out and links keep their text - pick a voice, audition it, choose a speed and press **Generate speech** (or Ctrl/Cmd-Enter). Under the sheet sit the two numbers a narrator wants: how many words, and roughly how long they take to listen to, always labelled as an estimate. The first run downloads the voice model once, with its size stated before you commit. After that it runs on the device, and the script itself is never uploaded. A script past about five thousand characters gets a warning, not a wall. **Save to your uploads** writes the clip as a WAV in your own asset library, with the voice, speed and per-word timings kept on the record - which is what a captioning surface later reads. It is marked as AI-generated and carries that badge wherever assets are shown, and the file itself is signed: a Content Credential is embedded in the WAV bytes, so the clip says what made it wherever it travels. See [Generated once, rendered the same](/info/ai-features.html) for why generated audio is declared this way. Where a device or browser has no speech support, the view says so in a sentence rather than showing a form that cannot work, and the Utilities grid does not draw the card at all. --- **Related:** [Using Lolly](/info/using.html) for the tools and the gallery these sit beside. [Your favourites](/info/favourites.html) for starring a utility tile. [Search](/info/search.html) for reaching any of them by name from anywhere. [Exporting & Formats](/info/exporting.html) for what the tools themselves can write. --- Source: https://lolly.tools/info/create/favourites.md Page: https://lolly.tools/info/create/favourites.html # Your favourites # Your favourites Star a tool (or, on Utilities, a utility card) and a big, cinematic tile for it appears in a strip above the grid. It's the fastest way back to the handful of things you actually use - built by you, one star at a time, not pre-loaded with anyone's idea of what you'll want. A fresh install shows none of this. There's no starter set: the strip simply doesn't exist until you star your first thing, and it collapses away again if you ever get back down to zero. ![The Tools gallery with two favourited tools drifting past in the strip above the grid](/t/url-shot?url=%2F%23%2F&width=1440&height=900&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%7Bdisplay%3Anone!important%7D&drive=click%3A%5Bdata-fav%3D%22qr-code%22%5D%3Bclick%3A%5Bdata-fav%3D%22battlecards%22%5D%3Bwait%3A800&waitSelector=.gallery-view%5Bdata-shots-settled%5D&walker=1&format=svg&dark=1&filename=fav-strip-gallery) ## Starring and unstarring The star lives in three places, and all three write to the same list: - **On the card itself** - the ★ in its corner. Click it once to add, again to remove. - **In its right-click menu** - **Add to favourites** / **Remove from favourites**. - **In the selection bar** - tick a card's checkbox (or several: ⌘/Ctrl-click, Shift-click a range or drag a box across empty space) and an action bar appears along the bottom with a **Favourite** button that stars, or unstars, the whole selection in one go. Right-clicking inside a selection offers the same **Favourite** / **Unfavourite** row. ![A single tool card, with the ★ that stars it sitting in the top corner beside the download and info buttons](/t/url-shot?url=%2F%23%2F&width=700&height=420&dpi=192&waitMs=1600&drive=click%3A%5Bdata-fav%3D%22qr-code%22%5D&cropSelector=%5Bdata-tool-id%3D%22qr-code%22%5D&walker=1&format=svg&dark=1&filename=fav-star-toggle) The same star works on a **utility** card in the Utilities view - Verify & Inspect, Colour Lab, Unpack, Spreadsheet and the rest. They aren't tools (no saved sessions, nothing to keep offline), but the strip treats a starred one exactly the same way: a tile of its own, icon-led since there's no preview to show. > Not the strip you're after? The **★ Favourites** pill in **Sort & filter** shows the same starred set as an ordinary filtered list in the grid below, instead of the drifting strip up top - useful if you'd rather scan a plain list. **One star, two lists.** The ★ on a **catalogue asset** ([Using Lolly → The Catalogue](/info/using.html#the-catalogue-your-asset-library)) is a different list entirely: it pins that logo, image or colour to the top of every asset picker. Same verb, same glyph, unrelated sets - starring a tool never touches your pinned assets, and vice versa. ## Gallery or Cover Flow Open **Sort & filter → Featured view** to choose how the strip itself looks: - **Gallery** - a slow, calm filmstrip that drifts sideways on its own and pauses the moment you touch it. - **Cover Flow** - the same tiles fanned out in 3-D, one centred and upright, the rest tucked back at an angle; drag or flick through them, then press **Open** on whichever one is centred. A new install opens on Cover Flow at desktop width and on the plain filmstrip on a phone-width screen - the fan doesn't hold up that narrow yet - but the moment you pick one yourself, that choice sticks and stops guessing. ## This is local, not part of the design system Your starred tools are saved onto your **profile** - the same on-device record that holds your name, your headshot and your other preferences (see [Profiles](/info/profile.html)). That means: - It's saved **on this device**, in the browser's own local database. No account, nothing uploaded. - It has nothing to do with the **design system** - the colours, fonts and tools the app was set up with. Starring a tool is a personal shortcut for you; it changes nothing about what anyone else's install shows. - It **doesn't follow you** anywhere on its own. The one way it travels is the same way your whole profile does - export a portable backup and import it on another device - and that's a deliberate, occasional action you take, never an ambient sync running in the background. Even the choice between **Gallery** and **Cover Flow** is more local still: it lives in a small preference kept only in this one browser's own storage, and it isn't part of that profile export at all. A new device always starts back on the plain default, no matter how many profiles you've moved around. --- **Related:** [Search](/info/search.html) for the other way back to something quickly. [Using Lolly](/info/using.html) for the gallery, hiding tools you never use and the rest of the app. [Profiles](/info/profile.html) for what else rides on that on-device record. --- Source: https://lolly.tools/info/create/exporting.md Page: https://lolly.tools/info/create/exporting.html # Exporting & Formats How to get a finished file out of a tool - picking the right format, setting the output size and what each option does. Like everything else, **export happens on your device**; nothing is uploaded. ## How export works The preview *is* the file. When you export, the host renders that canvas to the format you chose and hands you a download (or puts it on your clipboard). A tool only offers the formats its author declared, and the picker hides any your browser can't produce (see [Video](#video)). Three paths produce a file. Most tools **render the canvas** to the chosen format. Text and data formats (HTML, MD, TXT, JSON, CSV, ICS, VCF) are instead **generated from the tool's content**, not rasterised from the picture. And privacy utilities (e.g. *Strip Hidden Data*) use a third path: the file *you* pick is transformed byte-for-byte on device and handed straight back - no canvas, no watermark and no provenance metadata added, because it's already your own file. The actions in the export controls: - **Download** - save the file (the primary action). - **Copy** - put the image on your clipboard to paste straight into Slack, email, a doc. Where a browser can't copy images, it downloads instead and tells you. - **Save** - keep the current design as a saved tool session in your library. - **Share** - opens the **Share dialog**: a copyable link that reproduces the design, on-visit toggles (fullscreen, export panel, download- or copy-on-open) and an optional **Shortest link** that packs the whole state into a compact token (see [URL Mode](/info/url-mode.html)). (A tool's author picks which of these appear; the default set is Copy, Download and Save.) ![The export panel - format, size and the Copy / Download / Save / Share actions](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26options&width=1440&height=900&dpi=192&waitMs=2000&format=svg&walker=1&dark=1&filename=export-panel) Share opens over the tool, with the link already built and the on-visit toggles under it. ### Rendering many at once A single export is one file, but you can render **many** in one pass - each delivered as one `.zip`: - **Projects → Render folder** exports every saved session in a folder (and its sub-folders) as one nested zip; **Render selection** does the same for any multi-selection; a single saved session renders straight to its own file. No Batch/Pro needed - see [Using Lolly → Projects](/info/using.html). - **Batch (Pro)** renders a grid of input sets - every variant of one template at once. A saved session can also be re-shared as a tool link from Projects (it reconstructs the tool URL from the saved inputs), so a link reopens it with the exact same settings. ## Choosing a format The filename and the format picker sit at the top of the panel as one `name.format` pair, and the picker lists only the formats this tool's author declared. ![The filename field fused to the format picker, so the export reads as one name.format pair](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26options&width=1440&height=900&dpi=192&waitMs=2000&format=svg&cropSelector=.filename-extension&walker=1&dark=1&filename=exp-format-picker) | You want… | Use | Why | |---|---|---| | Crisp logos / artwork that scales | **SVG** | Vector - infinitely scalable, tiny, editable | | Vector for Office / Windows apps | **EMF** | Pastes as editable vector into PowerPoint / Word; text stays live and editable, and Google Drive opens it in Google Drawings for Slides | | Vector for print / design apps | **EPS**, or **EPS (CMYK)** | PostScript vector for Illustrator / press workflows | | Vector for cutting / CAD machines | **DXF** | Laser cutters, vinyl plotters, CNC - outline paths in millimetres | | An editable slide deck | **PowerPoint** (PPTX) | Native editable text + shapes, with images and vectors kept extractable | | A course an LMS can import | **SCORM** (LMS) | A zip with the manifest, a launch page, the slides and the narrated film with captions - from a Design deck | | An editable text document | **Word** (DOCX) or **OpenDocument** (ODT) | Real paragraphs and headings a word processor can keep editing (Doc Studio) | | A photo or general-purpose image | **PNG** (lossless) or **JPG** (smaller) | Universal raster | | Smaller modern images | **WebP** / **AVIF** | Better compression, alpha | | Print | **PDF**, or **Print PDF** (CMYK) | True page size; CMYK for press | | Print raster for a press | **Print TIFF** (CMYK) | DeviceCMYK pixels for a RIP | | Animated for the web | **GIF** | Works everywhere, larger files | | Animated with full colour + real alpha | **APNG** | Animated PNG - no palette limit, true transparency | | Animated, smallest file | **Animated WebP** | Full colour + alpha, better-compressed than GIF or APNG | | Animated vector that scales | **Animated SVG** | Self-contained; loops in a browser or ``, no codec, any size | | Video for social / sharing | **MP4** or **WebM** | Best quality-per-byte (see below) | | Rich text / email signature | **HTML** | Pastes formatted into mail clients | | Plain content | **MD** / **TXT** | Text only | | A calendar event | **ICS** | Imports into any calendar app | | A contact card | **VCF** | Imports into Contacts / address books | | Structured data to re-import | **JSON** / **CSV** | Round-trips the tool's content | | A favicon | **ICO** | Multi-size site icon (**ZIP** bundles several formats) | The first row is the common case. A wordmark set in your brand face exports as SVG, where every letter is an outlined path rather than a pixel, so it stays crisp at a business-card size and at a building-wrap size from the same file. ![A hairline wide-tracked wordmark reading Aurora, the kind of pure vector artwork the SVG row of the table is about](/t/url-shot?url=%2F%23%2Ftool%2Fwordmark%3Ftext%3DAurora%26weight%3D200%26tracking%3D16%26size%3D200%26full&width=1440&height=900&dpi=192&waitMs=2200&cropSelector=%23tool-canvas&walker=1&format=svg&dark=1&filename=vt-wordmark-vector) ## Size & print units By default exports use the tool's native pixel size. Where a tool exposes **dimensions**, you can set width × height and a **unit**: - **px** (default) - exact pixels. - **mm · cm · in · pt · pc** - physical/print sizes. With a physical unit you also set **DPI** (default **300** for print); the engine converts correctly per format - **PDF** becomes a true page at that size, **raster** renders at the right pixel count for the DPI (and embeds the resolution), **SVG** keeps the physical unit with a px viewBox. To get a higher-resolution raster, enter a larger width/height, or choose a physical unit and raise the DPI (pixels = size × DPI). There's no one-click scale toggle. Example: width `210`, height `297`, unit `mm` → an A4 page. ![The dimensions row set to 210 by 297 mm, with the DPI field revealed because the unit is physical](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26w%3D210%26h%3D297%26unit%3Dmm%26format%3Dpdf%26options&width=1440&height=900&dpi=192&waitMs=2000&format=svg&cropSelector=.export-dims&walker=1&dark=1&filename=exp-export-dims) ## Stills from a timed composition A **timed composition** - a [Sequence](/info/using.html#timeline-sequence) timeline in Design, or any timeline-driven artboard - is a moving thing, so a still export has to answer "which moment?". The rule is what you'd expect: **the frame at the playhead**. Park the playhead where you want the picture and export; what you see is what comes out. When you want more than one moment, the **Frames** field appears beside the output size (only for a timed composition, and only for a still format - PNG, JPG, WebP, SVG or PDF). Leave it at `1` for the playhead frame. Raise it and you get that many stills sampled at equal intervals across the whole sequence: - **Raster and SVG** come back as one **zip** - `-01.png`, `-02.png` and so on. - **PDF** comes back as a **single document of that many pages**. Useful for a storyboard, a thumbnail sheet, a contact sheet for review or a social carousel cut straight from a video edit. Sampling is taken at the **midpoint** of each interval rather than at the edges, because the first instant of a sequence is often an enter transition that hasn't faded in yet and the last is the state after every clip has ended - endpoint sampling would spend two of your frames on near-blank ones. The count is capped at **64** (a contact sheet is for a human to read), and anything nonsensical typed into the field falls back to `1` rather than failing the export. Each frame is an ordinary still, so Content Credentials, the imprint, physical units and DPI all behave exactly as they do for a single export. The **Frames** field is the way to get a sheet today. The engine reserves a matching `cuts` URL param, but no shell reads it from a link yet, so a shared link always reopens on the playhead frame - see [URL Mode](/info/url-mode.html#contact-sheets-cuts). ## Multi-page PDF Some tools build a **multi-page PDF document** instead of a single artwork - a cover, content that flows onto as many pages as it needs and a back page, all in one file (see the *Booklet* tool). Each page is a **true PDF page** sized to that page's box, so readers and printers get real pages, not one tall image. - **Pages from content.** Add blocks of text and images; new pages are created automatically as the blocks fill, and you can force any block to start a new page. - **Real page sizes.** Choose A4, US Letter or A5 (portrait - the two-column layout is built for it) - every page, and the exported PDF, renders at exactly that size. Multi-page PDFs are RGB documents and don't carry crop/bleed marks - those belong to the single-page **Print PDF** path above. They do carry the same **PDF/X-4 metadata** as every PDF export (page boxes, XMP, document ID, an sRGB output intent with embedded profile), and they offer **Content Credentials** (below) - on the *Booklet* tool the option comes pre-selected. ## Making many things at once Lolly has three distinct ways to work at volume, and they solve different jobs - batch editing is a first-class capability of the platform, not something each tool reinvents: - **One design × a table of rows → one multi-page document.** Tools with a `table` input (like *Battlecards*) turn every row into a page automatically - paste a table from your spreadsheet, get a deck-sized PDF. Your real batch editor stays the spreadsheet: fix ten rows there, paste again. The tool itself never manages pages. - **One design × a data file → many separate files.** The `/pro` batch grid takes a CSV and renders one export *per row* - name badges, certificates, one file each. - **Many different assets, edited side by side.** *Multi-edit* opens several saved sessions in one view for coordinated touch-ups across distinct designs. Rule of thumb: rows of the same design that belong in **one document** → a table-driven tool; rows that must ship as **separate files** → `/pro`; **different designs** that need the same tweak → multi-edit. (A planned "combine media" render option will bridge the first two - concatenating same-format exports into one PDF, one video or a proofing contact sheet.) ## PowerPoint (PPTX) Design, Markdown Slides, Booklet, Doc Studio, Chart and Diagrams can export a **PowerPoint deck** - one slide per page. The point isn't a pixel-perfect screenshot; it's to hand a colleague a deck they can actually **edit and take assets out of**. So each page is decomposed into native objects: - **Text** becomes real, **editable PowerPoint text boxes** - with the font size, colour, weight, italics and alignment from the layout - so you can fix a typo or restyle in PowerPoint. - **Vectors** (logos, icons, the SUSE mark) are embedded as **real SVG pictures** - they stay crisp at any size, and PowerPoint can even *Convert to Shape* on them. - **Images** come through at their native resolution as their own extractable pictures (a `cover`-cropped hero keeps the full image behind the crop, so you can re-frame it), with any on-image treatment (filters, blends) baked in faithfully. - **Backgrounds, borders and rules** become real rectangle/line shapes. ![The export panel with PowerPoint chosen: one slide per page, text and shapes kept editable](/t/url-shot?url=%2F%23%2Ftool%2Fdeck-studio%3Foptions&width=1440&height=900&dpi=192&waitMs=2500&drive=click%3A%5Bdata-fmt-trigger%5D%3Bwait%3A400%3Bclick%3A%5Bdata-fmt%3D%22pptx%22%5D%3Bwait%3A800&cropSelector=.export-popup&walker=1&format=svg&dark=1&filename=exp-pptx) Layout is approximate by design - the goal is faithful, reusable **content**, not a locked screenshot. Anything the walker can't express natively (a complex filtered or masked region) is embedded as a picture so nothing is lost. A deck has a single slide size, taken from the first page. PowerPoint is also a way **in**. Drop a `.pptx` on any upload surface and pick the slides you want - each becomes an asset to place, grade or build on. **Markdown Slides** writes a fresh deck from Markdown (and hands out a branded `.pptx` template), and the **Rebrand** utility re-themes an existing deck in place - theme palette, hardcoded colours and fonts - without touching its charts, SmartArt or animations, handing back a `.pptx`. See [Import a design → Decks and documents](/info/design-import.html#decks-and-documents). ## Penpot Any tool that can export SVG can also export a **Penpot file** (`.penpot`) - the document, ready to keep editing in Penpot rather than just to look at. - From **Design**, each frame becomes a Penpot **board**, every box on it carries over as its own editable shape - rectangle, ellipse, text, image or path - and the result arrives as a reusable **component** in the Assets panel, ready to drag out again. - From a tool whose render is **plain geometry** - the vector tools such as QR Code, Street Map, Wordmark and Palette Lab - every path, rectangle, text run and gradient carries over the same way, as its own editable shape. - From a tool that lays its render out in **HTML**, the **text stays text** - one editable Penpot text object per line, not an outline and not a flat picture. A piece of artwork Penpot has no construct for - a complex illustration, a live canvas - rides along on its own as an image, so it never flattens the text and boxes around it. A whole layout Penpot cannot separate faithfully can still come across as one picture at full fidelity, so nothing is ever dropped. - A colour or font a shape **inherits from a brand token** stays **linked** to that token in the file: change the token in Penpot's Tokens panel and every shape that inherits it re-paints together, while a one-off literal colour stays exactly where it is. The brand's colours and typographies also land in the **Assets** panel and its full design-token set in the **Tokens** panel - the same DTCG token set Lolly's own palette uses. Penpot keeps its own copy from there: an edit made in its Tokens panel comes back into the brand through [Brand Studio's import](/info/design-import.html), not on its own. | What you made | In Penpot | |---|---| | Text | Editable text objects - fix a typo or restyle | | Shapes, paths, gradients, images | Their own editable vector shapes | | A colour or font from a brand token | A live token link - edit the token, every shape that inherits it follows | | The whole design | A reusable component in the Assets panel | | A complex illustration or live canvas | Embedded on its own as an image, the editable content around it untouched | ![The export panel with Penpot chosen: the .penpot file, and Send to Penpot beside the download](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26options&width=1440&height=900&dpi=192&waitMs=2000&drive=click%3A%5Bdata-fmt-trigger%5D%3Bwait%3A400%3Bclick%3A%5Bdata-fmt%3D%22penpot%22%5D%3Bwait%3A800&cropSelector=.export-popup&walker=1&format=svg&dark=1&filename=exp-penpot) To open it, start Penpot, pick a project and choose **Import**. Signed in to Penpot from the export panel, **Send to Penpot** does that step for you - pick a project there and the file goes straight into it. ## SCORM (course packages) A Design deck can leave as a **SCORM package** - the zip a learning management system imports, whether that is Moodle, Canvas, Blackboard or a corporate LMS. Pick **SCORM (LMS)** in the format picker and the export writes: - **The slides**, one image per artboard, as vector where the artboard allows it and as pixels only where it does not. - **The narrated film** with its caption track - the speaker notes read by an on-device voice, exactly as [Presenting](/info/create/using.html#presenting) plays them. - **A launch page** that steps through the slides, plays the film and reports completion back to the LMS through the SCORM 1.2 runtime, the version every LMS accepts. - The fonts the deck uses, so the launch page renders the same offline. ![The export panel on a Design deck with SCORM (LMS) chosen](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Ftemplate%3Dfeature-tour%26format%3Dscorm%26options&width=1440&height=900&dpi=192&waitMs=3500&css=.fc-insp%7Bdisplay%3Anone!important%7D.edge-dock-slot--fill%7Bflex%3A1%201%20auto!important%3Bheight%3Aauto!important%3Bmax-height%3Anone!important%3Boverflow%3Avisible!important%7D.export-popup.is-floating%7Bheight%3Aauto!important%7D.export-popup-body%7Bmax-height%3Anone!important%3Boverflow%3Avisible!important%7D&cropSelector=.export-popup&walker=1&format=svg&dark=1&filename=exp-scorm) Nothing in the package calls home: it is files in a zip, and it runs inside the LMS with no bundler, no CDN and no account. The web and desktop apps build it; the CLI does not, because photographing the artboards and encoding the film need the browser. ## DXF (cut files) Vector tools (Brand Lockup, QR Code, Street Map, Wayfinding Signage, Pose Geeko, the logo lockups, Diagram Builder) can export **DXF** - the AutoCAD R12 interchange format that laser cutters, vinyl plotters and CNC/CAD software read. Geometry is written as outline **paths in millimetres** (curves flattened to a fine tolerance), text is outlined to paths and colour maps to the nearest AutoCAD Color Index (which typically drives the tool/operation on a cutter). DXF is line-art only - a photographic or filtered region has no cut-path form and is dropped (Lolly warns), so use SVG/PDF when you need to keep raster content. Street Map is the clearest case: the whole design is already strokes, so every road and canal becomes a cut path with nothing to drop. ::: showcase ![A Street Map render of Paris in ink on cream - pure line art, so every stroke survives the trip to a cutter](/t/url-shot?url=%2F%23%2Ftool%2Fstreet-map%3Fcity%3Dparis%26roadColor%3D1a1a2e%26waterColor%3D93c5fd%26background%3Dfaf7f2%26full&width=1440&height=900&dpi=96&waitMs=3200&cropSelector=%23tool-canvas&walker=1&format=svg&dark=1&filename=vt-street-map-cut) Scroll, and the camera pulls back through the actual geometry: seven paths, no pixels anywhere, every stroke hairline-crisp at any zoom. That is the same file a cutter reads. ::: ## Animated SVG Motion tools (Animated Ad, Lottie Ad) can export **Animated SVG** - a self-contained, *vector* animation. Unlike GIF/APNG/WebP (which sample each frame to pixels), an animated SVG stacks vector snapshots with embedded CSS keyframes, so it **scales to any size with no codec and no external runtime** - it loops in a browser tab or an ``. Text stays outlined so it renders anywhere. It shares the animated formats' **Duration** / frame-rate controls, and (being heavier per frame than a bitmap) uses a lower default frame rate. ## Transparency Tools that support it offer a **transparent background** toggle (e.g. *No BG*). Transparency is preserved by PNG, WebP, AVIF, SVG (still and animated), APNG and Animated WebP. JPG and PDF are always opaque, and TIFF flattens onto white (onto black on the HDR path - see below). ## Colour spaces Two different questions, worth keeping apart: which colour spaces Lolly can **read and think in**, and which ones it **writes**. **Reading.** Wherever a colour is written - a tool's stylesheet, an imported SVG's paint, a design token's value, a shadow or gradient inside a CSS shorthand - Lolly reads the full **CSS Color 4** vocabulary: `#hex`, `rgb()`, `hsl()`, `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()`, the CSS named colours and `color()` in the predefined spaces - `srgb`, `srgb-linear`, `display-p3`, `a98-rgb`, `prophoto-rgb`, `rec2020`, `xyz-d50`, `xyz-d65` - including components written as the `none` keyword. One parser does this for the whole platform, so the browser and every export walker agree on what a colour string means. That matters more than it sounds, because a browser resolves modern CSS into modern CSS. Write `color-mix(in oklab, …)` and Chrome computes `oklab(…)`; use a brand token stored as `oklch()` and that's the literal value the export walker sees. Colours in those forms are read correctly rather than dropped - which is what a walker that only understood `rgb()` did, exporting brand-coloured text as black, losing tinted panels and table rules and reading `oklch(0.7 0.1 200) 0px 2px 4px` as a shadow offset of 0.7 by 0.1. **Thinking.** Colour maths happens perceptually rather than in raw channels. Palette derivation, ramps, harmonies and contrast run in **OKLCH/OKLab**, and an out-of-gamut colour is brought into range by CSS Color 4's own gamut-mapping algorithm - chroma reduction with a perceptual-distance check - rather than by clipping channels, so a vivid colour settles on the nearest colour you'd actually accept instead of a flattened one. Gradients interpolate in a space you pick (OKLab by default, or `oklch`, `lab`, `lch`, `srgb`, `srgb-linear`, `hsl`, with a hue-travel direction for the polar ones), and mixing is **premultiplied**, so a fade to transparent stays the right colour instead of darkening toward black on the way. One interpolator serves both the preview and the export walkers - which is what stopped a conic gradient from being blended one way on screen and another in the exported file. **Writing.** The output is deliberately narrower than the input, because a file has to be readable by whatever opens it, and a space is only ever *declared* on output when the numbers were really converted into it. Screen and web formats are written as **sRGB** and tagged as such; the print formats are written as **CMYK** against a named press condition (below); and the HDR path is **Rec.2100 PQ** (above). A wide-gamut colour that reaches an export is mapped into sRGB rather than mislabelled - carrying `color(display-p3 …)` through into a vector file is a planned extension, not something today's exports claim to do. A gradient authored in OKLab is *baked* to plain sRGB stops on the way out, with extra stops inserted only where sRGB would visibly diverge from the perceptual curve, because an SVG `` and a PDF axial shading have no interpolation-space setting to carry the intent. One authored value, three renderers, no drift. ## Colour profiles So colours reproduce faithfully in colour-managed apps (print shops, Photoshop, browsers), exports are **tagged with a colour profile**: - **PNG / JPG** carry an embedded **sRGB** ICC profile - the colour space the preview is actually rendered in - so nothing is left to guess. (Tagging only; the pixels aren't re-encoded.) - **Print PDF (CMYK)** declares a target **press condition** in its *OutputIntent* (default *Coated FOGRA39*), telling a RIP/print shop how its CMYK inks are meant to be read. Brand swatches with measured ink values are converted exactly; other colours use a standard device conversion. That declaration is a *name*: no CMYK profile ships with Lolly, and PDF/X-4 wants the profile embedded, so a named condition writes the output intent without claiming PDF/X-4 conformance. Load a CMYK profile of your own and choose its **Embed** row in the Colour profile control and it is embedded as the file's *DestOutputProfile* - at which point the PDF can genuinely be PDF/X-4, and claims it whenever the rest of the file allows. Three things withhold the claim while keeping the output intent (a RIP still wants that): RGB artwork the CMYK pass couldn't convert, the `prov` proof-margin credit text (drawn in a standard font that isn't embedded, and X-4 makes no exception for those) and a **Strong** password, since X-4 forbids encryption. The condition it declares is then read off that profile: a registered name where the profile proves one, `Custom` under the profile's own name where it does not, so the file can never name one press condition while carrying another's measurements. - **Print TIFF (CMYK)** writes untagged **DeviceCMYK** pixels and records the same press condition as provenance in its TIFF metadata (*ImageDescription*) rather than embedding a profile. The same Colour-profile control drives both CMYK formats - a TIFF cannot embed a press profile at all, so an **Embed** row records that profile's own name there and nothing more. - **TIFF (RGB)** is the plain, uncompressed sRGB sibling - a lossless raster at the chosen DPI for archival or an editor round-trip, with provenance recorded in the same TIFF metadata. Any transparency is flattened onto white (this profile carries no alpha). Like the CMYK TIFF it's desktop-only, since browsers can't preview a TIFF and mobile downloads dead-end. - **SVG**, **EMF**, **EPS** and **DXF** are resolution- and profile-independent vectors with no embedded profile - SVG's colours are plain sRGB, EMF's and EPS's are device RGB (and **EPS (CMYK)** writes naive DeviceCMYK) and **DXF** carries the nearest AutoCAD Color Index. (SVG, EPS and DXF, like PDF, outline any text to vector paths, so the result renders even where the font isn't installed. EMF instead keeps text LIVE by default - real metafile text records that stay selectable and editable in Office and Google Slides, falling back to outlines only for runs the format cannot express; the export panel's "Outline fonts" option forces paths everywhere.) **SVG** also reproduces CSS `box-shadow` from the HTML - each outer shadow is painted behind the box, offset/spread and Gaussian-blurred to match the browser, and inset shadows are painted inside it the same way. This is automatic - no setting to fiddle with. Thumbnails and previews skip the tag to stay small. One profile *is* a choice, because it changes the pixels rather than just labelling them - see **HDR** below. ## HDR (bright colours) Ordinary exports are sRGB: white is white, and a saturated brand colour is as bright as the screen's normal white. On an HDR-capable display there's a lot of headroom above that, and the **HDR** card in the export panel uses it - your brand colours and white text are boosted toward peak brightness so they genuinely *glow*, while the dark areas stay dark and give the glow its contrast. ![The HDR card in the export panel, switched on, with the White / Reach / Dark lift / Focus dials revealed under it](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26format%3Dpng%26hdr%3D1%26options&width=1440&height=900&dpi=192&waitMs=2000&format=svg&cropSelector=.export-hdr&walker=1&dark=1&filename=exp-hdr-card) - **Formats.** The raster formats with a place to carry the signal: **PNG**, **JPG**, **AVIF** and **TIFF**. (Not WebP - it's 8-bit with no working HDR decode path, so a PQ WebP would simply look dark. Vectors and PDF have no HDR model at all.) - **Off by default**, unlike colour tagging - it changes the pixels, so it's opt-in. Tick the card, or pass `hdr=1` in a share link. - **What's actually written.** The pixels are re-encoded to **Rec.2100 PQ** - BT.2020 primaries with the SMPTE ST 2084 (PQ) transfer curve - and the container carries the matching signal so a colour-managed app knows to read them that way: a generated **ICC v4 profile with a `cicp` tag** (JPG, TIFF), a **`cICP` chunk** (PNG) or a rewritten `colr` box (AVIF). The boost is gated on **perceptual (OKLab) lightness**, so mid-and-above colours punch to peak and dark ones are calmed rather than blown out, and it's hue-preserving - a brand green gets brighter, not minty. - **The dials.** Four, revealed when the card is on: **White** (the peak-brightness ceiling, 400–2000 nits), **Reach** (how far down the tones the glow spreads), **Dark lift** (how much the darks brighten - `0` keeps them dark) and **Focus** (how much colour richness the boost keeps). They ride in the same param as a compact tuned value - `hdr=1600-60-0-50` is White 1600, Reach 60, Dark lift 0, Focus 50 - so a tuned look is reproducible from the link. - **Where you'll see it.** Colour-managed viewers on an HDR display: Preview / Quick Look / Safari on Apple devices, Chrome on an HDR monitor. On an ordinary SDR screen the file still shows as a normal image. - **Know before you ship it.** Many platforms **re-encode** what you upload and strip the HDR signal - social networks, messaging apps, some CMSes - which can leave the image looking dark or washed out. Use HDR where you control the destination (a site you build, a video wall, a deck on a bright panel), not as a default for everything. - **Transparency.** PNG and AVIF keep their alpha; JPG is opaque as always. The **TIFF** path flattens onto **black**, not the SDR path's white - in PQ, white is the 10,000-nit code, so flattening onto it would ring every edge with a blinding halo. - **The same bytes from the terminal.** `--hdr=1` with `--export=png` or `--export=jpg` writes the 16-bit Rec.2100 PQ PNG and the ISO 21496-1 gain-map JPEG on the [CLI](/info/cli.html) too, out of the same engine encoders rather than a browser - so an ``-native tool produces an HDR file with no Chromium anywhere in the path, and the PNG is byte-identical to the web shell's. AVIF and TIFF stay web-shell exports for now. ## Video Animated tools export motion as **MP4**, **WebM** or **GIF** - and, where offered, **APNG**, **Animated WebP** or the vector **Animated SVG** (above). Which video container you see depends on your browser - the picker only shows what it can actually record: | Browser | Shows | |---|---| | Safari / iOS | **MP4** | | Firefox | **WebM** | | Chrome / Edge 126+ / Android | **MP4 and WebM** | | Older Chrome | **WebM** | GIF works everywhere (great for chat/email; larger and lower-colour than video). Animated tools also expose **Wait** (seconds to let the animation settle before recording) and **Duration** (clip length). > A shared `?format=…` link that requests a container your browser can't record gracefully falls back to the other and names the file accordingly. **Sound.** Video exports aren't silent. A tool can lay a **music bed** under the clip - an audio asset from the catalogue, looped or trimmed to the clip length, with fade-in/out, volume and automatic ducking under the footage's own sound - and the recording tools carry their footage's live audio straight through to the file. **MP4** and **WebM** keep the mixed track; GIF and the animated image formats (APNG, Animated WebP, Animated SVG) are silent by nature. ## Audio Some tools export **audio on its own**, not just as a video track. The **Voice Recorder** captures a mic take with a live level meter and gentle coaching, then saves it as **MP3** (the default, transcoded in your browser) or in its native container - **M4A** (AAC), **OGG** or **WebM** (Opus), whichever your browser recorded. As with everything else, the encode happens on your device - nothing is uploaded. Audio you *bring in* is just as broad. The asset picker accepts **MP3**, **WAV**, **OGG**/**Opus**, **M4A**/**AAC** and **FLAC** (kept byte-for-byte and decoded on-device), **MIDI** (`.mid` - converted on import to a tiny on-device synth track) and **tracker modules** - **MOD**, **XM**, **IT**, **S3M**, **STM**, **MTM** (decoded on-device by a bundled player, a few kilobytes of song data). Any of these can become the **music bed** under a video export, or play in Neurospicy Mode's ambient player. Audio *is* part of the `format=` / `--export=` pipeline below: `wav`, `mp3`, `m4a` and `opus` are ordinary format ids, so an audio-only export is as shareable and as scriptable as a PNG. What comes out is the sound alone, no picture. ## Provenance & watermark Where the format supports it, exports carry **provenance metadata** - software, source, the tool's name and your profile credit line - embedded natively (PNG iTXt, JPEG EXIF, PDF info, SVG ``, GIF comment). It's authorship only; nothing is uploaded. **Experimental** tools additionally stamp a visible watermark, applied by the host so it can't be removed by editing the tool. **The Lolly Imprint.** Raster exports also carry Lolly's own **invisible pixel watermark** - the *Lolly Imprint* - **on by default**, just like Content Credentials. Where the credential and the provenance metadata travel *alongside* the pixels and are lost to a re-save, a screenshot or a metadata strip, the Imprint lives *in* the pixels and survives recompression - so a copy of the image can still be recognised as Lolly-made later. It's a durable hint, not a cryptographic guarantee, and it's presence-only (it carries no personal data). It rides in **PNG, JPG, WebP, AVIF, TIFF and BMP**, and in the Lolly-rendered rasters composited into a **PDF or PPTX** - never in an image *you* embedded, only in what Lolly itself renders. Uncheck the **Lolly Imprint** card in the export panel to skip it, or pass `imprint=0` in a share link. (AVIF survival through re-encoding isn't calibrated yet; PDF/PPTX detection covers the embedded Lolly rasters.) [/verify](/verify) detects it on-device - see [Content Credentials Identity](/info/content-credentials-identity.html#the-lolly-imprint). **The durable credential.** A second, heavier mark sits beside the Imprint: **Durable credential**, which uses an on-device neural model (TrustMark format) to write Lolly's id *into* the pixels so the "made with Lolly" link survives a metadata strip, a re-encode and re-reading by TrustMark-aware tools as well as Lolly's own. It's **off by default** - unlike the pure-JavaScript Imprint it costs a neural pass per export plus a one-time model download, so it's a deliberate opt-in rather than a quiet tax. Raster only (**PNG, JPG, WebP, AVIF, TIFF**), ticked in the export panel or passed as `durable=1` in a share link. On the desktop and mobile apps the card is hidden outright rather than shown as a no-op, because there's no origin to fetch the model from offline. **Content protection.** In the export panel, *Password protect*, **C2PA Credentials**, the **Lolly Imprint** and the **Durable credential** fold into one collapsed, format-aware **Content protection** group, so a file's provenance and protection options live in one place - the group shows only the cards that apply to the chosen format, and hides itself entirely when none of them do. Print marks are deliberately *not* in it: they're print production geometry rather than protection, so **Print marks & bleed** - the bleed measurement in millimetres plus Crop, Registration, Bleed, Colour bars and Stamp details - keeps its own top-level card on the print formats. ![The Content protection group opened on a PNG export, showing only the cards that apply to it](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26format%3Dpng%26imprint%3D1%26options&width=1440&height=900&dpi=192&waitMs=2000&walker=1&format=svg&cropSelector=.export-protection&dark=1&filename=exp-content-protection) **Before you export (print preflight).** Turn on **Print preflight** (`export-preflight`) in your Profile's feature flags - it is **off by default**, so an individual exporting a PNG for a chat message is never ambushed by prepress findings, and a deployment's control plane ([lolly.work](https://lolly.work)) can default it on for its members - and a **Before you export** card appears at the foot of the panel, immediately above the buttons, whenever the print rules have something true to say about the job: format, size and bleed, then trim and bleed areas, ink coverage, plate counts and page count, with a verdict beside its heading. It sits under every setting because it's a statement *about* those settings rather than another one of them - and it never blocks an export. It tells you what a print shop is about to see. **Cost, worked out from your rate card.** Below preflight - last of all, still above the buttons - sits a card that turns those same counts into money, and only ever from prices somebody gave it. It reads whatever the preflight pass counted, whether or not the preflight card itself is switched on, and it needs two things to be true: the job has something a price list can price at all (plates, sheets, area, pages, variant rows or output files - so a plain logo PNG never shows it), **and** a **rate card** is present. A rate card is a JSON price list from your printer. A default build carries none and has no in-app way to load one: it arrives either as a catalogue asset a deployment ships, or through the optional rate-card extension a self-hoster or control plane switches on. With no rate card, nothing is shown - not a prompt, not an empty table. The rule the whole thing is built around is that **it never invents money**. Every figure is a rate you supplied times a quantity Lolly counted - `4 plate × €35.00` - and the total names its own source in the same sentence as the figure: the issuer the card names, and the date the card says its rates are from. There is no default currency, no placeholder and no zero standing in for a missing price. What the file says about itself stays reported speech: *"The file says: … Lolly has not verified this."* And when it can't compute honestly, the working table **disappears** rather than degrading into a greyed-out or filled-in figure: - Lines the card doesn't price mean **no total at all** - just a headline saying how many of them are unpriced. A partial sum isn't a smaller answer, it's a wrong one. - A quantity that's a ceiling rather than an exact count carries **"up to"** through into its subtotal, so a bound is never laundered into a flat figure. - Rates past their valid-until date show **counts only**, until you press *Use these rates anyway* - and then the expiry date rides with the figure, so a lapsed total can't be read as a current one. - Opened through a **link**, money stays hidden until you ask for it on this device. Neither the card nor that reveal ever travels in a URL - the same reason the CLI takes `--rate-card=` as a local file flag and never as a link param. The card is chrome, never content: it's stripped from every export stage, so it cannot move a pixel of the file you download. And it's arithmetic, not a quote - only your printer can give you one. **Composed renders.** When a tool embeds another tool's output (e.g. an *Event Name Badge* embedding a *QR Code*), the nested render is inlined into the parent's export - it stays a **true vector** in SVG and PDF and rasterises crisply in PNG/JPG/WebP. The embedded child is an intermediate: it gets *no* watermark and *no* provenance of its own; only the finished parent asset does. (Composition covers SVG and the raster formats; HTML/MD/TXT can't be composed.) ## Password protection Two independent kinds of lock, both entirely on-device. **PDF open-password** - the export panel's *Password protect* card offers two tiers: ![The Password protect card expanded on a PDF export, with the password field and the two lock tiers](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26format%3Dpdf%26password%3Ddemo%26options&width=1440&height=900&dpi=192&waitMs=2000&walker=1&format=svg&cropSelector=.export-pdfpass&dark=1&filename=exp-pdf-password) - **Standard** - a basic 40-bit lock (RC4). It opens in *any* PDF app, and - being a light deterrent, not real protection - it can travel in a share link (clear-text, by design). RGB `pdf` only. - **Strong** - AES-256 (PDF 2.0). Its password is typed at export and is **never** put in a link; it opens only in newer PDF apps (Acrobat / Preview ~2018 on), and older apps may report the file as damaged. Strong also applies to **Print / CMYK PDFs** and to **each PDF inside a batch zip** (the batch confirm dialog collects the password). Because PDF/X-4 forbids encryption, a Strong-locked Print PDF keeps its CMYK, marks and output-intent but drops the PDF/X-4 conformance claim. Either tier is mutually exclusive with Content Credentials (an encrypted PDF can't take the credential). **Locked downloads (whole-zip + defense-in-depth)** - a **ZIP** export (the export panel's *ZIP* format, which bundles several of a tool's formats), a **folder** download (Projects → Download) or the **batch grid** can lock the entire zip with one password, at two tiers: - **Standard** - traditional **ZipCrypto**: opens in *any* unzip tool including Windows Explorer's built-in extract, but weak (a deterrent). Its password can travel in a `?password=` share link. - **Strong** - **AES-256** (WinZip AE-2): strong, but does **not** open in Windows Explorer's built-in extract - the recipient needs 7-Zip / WinZip / Keka / macOS. Typed at export, never put in a link. The same *Password protect* card in the export panel drives both the PDF and ZIP locks, rewording itself for the chosen format. The one password protects **every** member - images, SVG, everything, PDFs included (only the zip container can protect non-PDF files, which have no lock of their own). And it's **defense-in-depth**: any PDF inside is *also* individually AES-256-locked with the same password, so a PDF stays locked even after the zip is unpacked. The prompt appears when you start the download; a blank password means no lock. **Password-gated share links** - any share link can be encrypted so that opening it asks the recipient for a password. The whole link state is AES-256-encrypted under a key derived from the password (PBKDF2); only ciphertext travels, so the **password is never in the link** and decryption happens **in the recipient's browser** - the server that serves the link sees only the ciphertext in the URL, never the password and never the decrypted design. Turn it on in the **Share** dialog. An encrypted link can only be *opened* in Lolly (it can't be embedded as an image, since that path can't prompt). See [URL Mode → Encrypted links](/info/url-mode.html). ## Content Credentials (C2PA) Exports can carry **Content Credentials** - a signed [C2PA](https://c2pa.org) manifest embedded in the file that records, in a tamper-evident way, that the file was made with Lolly and hasn't been altered since. It's the standards-track version of the provenance metadata above: a cryptographic claim (what made the file, when, by whom and where) bound to a hash of the file's bytes, so any later edit is detectable by a C2PA-aware viewer. The standard is stewarded by the [Content Authenticity Initiative](https://contentauthenticity.org) (Adobe, BBC, Microsoft, Nikon and others), so the same credentials Lolly writes are the ones cameras, newsrooms and creative suites are adopting. ![The C2PA Credentials card, pre-ticked, with the credential lifetime beside it](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26format%3Dpng%26c2pa%3D30%26options&width=1440&height=900&dpi=192&waitMs=2000&format=svg&cropSelector=.export-c2pa%5Bdata-c2pa-only%5D&walker=1&dark=1&filename=exp-c2pa-card) - **Formats.** Every container with a C2PA embedding: **PDF** (both RGB and Print), **PNG / Animated PNG**, **JPG**, **GIF**, **SVG**, **TIFF** (RGB and Print), **WebP** (still and animated), **AVIF**, **MP4**, **WebM** and the audio containers **MP3**, **WAV**, **M4A** and **OGG/Opus** - so a recorded or synthesised voice clip ships with the same credential a picture does. A **ZIP** bundle stamps each supported member individually, which is also where an **Animated SVG** picks one up (it's an ordinary SVG document underneath; a direct Animated SVG export offers no card of its own). MP4, AVIF and M4A use the spec's BMFF binding and MP3 its ID3v2 mapping, so `c2patool` and other C2PA-aware viewers verify them; **WebM** and **OGG/Opus** have no standardised C2PA mapping yet, so Lolly carries the manifest as a Matroska attachment and an OpusTags field respectively, which Lolly's own verifier (and CLI) checks. (`ico`, `eps`, `emf`, `dxf`, `bmp`, `pptx`, the Office formats and the text/data formats have no C2PA container.) - **On by default.** The **C2PA Credentials** card in the export panel comes pre-selected for nearly every tool - untick it to skip the credential on a single export (or pass `c2pa=off` in a share link). A tool can opt out entirely in its manifest. - **What it records.** The tool and app that made the file, the signing time, the export surface (browser engine family + OS family - coarse on purpose, never a fingerprint) and - only when *Profile → Use my details* is on - your name and email as the work's author. - **What recipients see.** Inspect-content-credentials tools (Adobe apps, `c2patool`, contentcredentials.org/verify) will read the manifest and show the claim. Because Lolly signs with a key generated **on your device** - not a certificate from a trust list - viewers report it as an *unverified* credential. The structure and the tamper-evidence are real; the signer identity is simply not vouched for by an authority. To upgrade that, you can enrol a **verified identity** (Profile → Content Credentials): a short-lived certificate from the Lolly CA ties your email to your exports while the signing key still never leaves your device - see [Content Credentials Identity](/info/content-credentials-identity.html). - **Checking a file.** Lolly verifies its own credentials too: drop any file on [/verify](/verify) (or run `lolly validate ` in the CLI) for an on-device report - headlined by whether the file was genuinely made with Lolly and unchanged since. The web Verify view reads well beyond the credential: it flags **AI-generated content**, detects the **Lolly Imprint**, checks **SEAL** signatures and (opt-in) third-party pixel watermarks and surfaces **hidden data** - all on-device, nothing uploaded. See [Content Credentials Identity → Beyond the credential](/info/content-credentials-identity.html#beyond-the-credential-what-else-verify-shows). - **Privacy.** Everything happens on your device: the signing key is created for the export and never leaves the browser, nothing is uploaded and the claim contains only what the provenance metadata already carries. Privacy utilities (on-device transforms of *your own* files) never add credentials, and *Strip Hidden Data* will remove a C2PA manifest like any other embedded metadata. - **Interactions.** For PDFs, Content Credentials and **password protection** (either tier - see above) are mutually exclusive (an encrypted PDF can't take the credential attachment). The credential is added as the final step over the finished bytes - after DPI/EXIF/colour-profile stamping, PDF/X metadata and print marks. ## On a phone The export controls live behind the floating **Render** button, which opens the **Export** sheet - same formats, size, copy, download and share, sized for touch. ## Format reference Every id the host can render, grouped. These are also the values for the URL `format=` parameter and the CLI `--export=` flag - see [URL Mode](/info/url-mode.html) and [CLI](/info/cli.html). A tool offers only the subset its author declared, so the picker is always shorter than this list. | Kind | Ids | |---|---| | Raster | `png` · `jpg`/`jpeg` · `webp` · `avif` · `tiff` (RGB TIFF) · `cmyk-tiff` (Print TIFF) · `bmp` · `ico` | | Vector | `svg` · `svgz` (gzipped SVG) · `emf` · `wmf` · `eps` · `eps-cmyk` (EPS CMYK) · `dxf` (cut file) | | Page & document | `pdf` · `pdf-cmyk` (Print PDF) · `pptx` (PowerPoint) · `scorm` (SCORM course package, a zip) · `penpot` (Penpot design file) · `docx` (Word) · `odt` (OpenDocument Text) | | Motion | `gif` · `apng` (Animated PNG) · `webp-anim` (Animated WebP) · `svg-anim` (Animated SVG) · `webm` · `mp4` | | Audio | `wav` · `mp3` · `m4a` · `opus` | | Text & data | `html` · `md` · `txt` · `json` · `csv` · `ics` · `vcf` · `css` · `scss` · `gpl` (GIMP palette) | | Bundle | `zip` | A few more ids come from a **tool's own export hook** rather than the shared render path: `ase` (Adobe Swatch Exchange, from Palette Lab), `exr` and `hdr` (Darkroom's high-dynamic-range rasters) and `ttf` / `otf` / `woff` (Font Convert). They pick a format the same way - the picker, `format=`, `--export=` - the bytes are just built by the tool. Font Convert is the one exception: it transforms a font file *you* supply, so there's nothing for a bare URL to render. --- Source: https://lolly.tools/info/create/positioning.md Page: https://lolly.tools/info/create/positioning.html # How Lolly compares What Lolly does that today's creative tools do not, and what it deliberately leaves to them. For the tool-by-tool version, one page each for Canva, Adobe, Figma, rendering APIs and online converters, see [Lolly compared, tool by tool](/info/compare.html). Each page states what the other tool does better and what Lolly does instead. > **Pilot status:** Lolly is a closed-pilot prototype, not a finished product, and its security is currently undergoing SUSE's strict infrastructure hardening, preparing for enterprise scale. The [Adoption & Governance](/info/adoption-governance.html#status) page covers the current state. ## Today's tools Each ring below scores how completely a product class delivers a capability **as shipped today** - not as marketed - with every class scored on its best representative. Lolly is scored with the same knife: it takes the only red ring on the board, for maturity. Open a row name for the reasoning behind its scores. Columns are sorted by the Overall completeness row at the top - the mean of the scored rows, with the spend row excluded. Capability completeness across today's creative tools, researched August 2026. Scoring: 0 absent, 25 workaround-grade, 50 real but gated or partial, 75 strong with caveats, 100 core competency. **Scoring notes.** Lolly's scores assume its published claims hold, which is why maturity is its one red ring: closed pilot, security hardening in progress, nothing audited yet. Research moved several cells. Canva is scored on its best family member per row, since it owns Affinity and Cavalry (both given away October 2025). Offline and on-device rendering score 75 through Affinity - a desktop suite that still needs a verified account and carries telemetry, the deduction Adobe also takes - while Canva's own offline mode edits only pre-synced designs, one device, limited window. Autofill scores 50: real but Enterprise-gated, async, text and image only. Figma's mass generation rose 25 to 50 when Buzz shipped spreadsheet fill (free beta, August 2026). One rule governs the board: Full (100), on rows that touch your content or identity, needs a capability you can use with no account and no cloud precondition; rows describing the product itself (maturity, ease of use) are exempt. It costs Adobe on provenance: the broadest shipped C2PA (Photoshop, Lightroom, Premiere, Firefly) signs locally and in the cloud, but never without an Adobe account and identity, so 75. It caps the render APIs on mass generation and automation for the same reason. Lolly's provenance 75 reflects on-device offline signing: architecturally stronger but unaudited, and a device key reads as unverified in stock validators until an identity or an organization's own CA vouches for it. Penpot's 50 arrives through the official Lolly Export plugin: the same engine signing, opt-in, disclosed as Lolly's own. Penpot also takes the board's only off-scale ring, 90 on on-device rendering - browser canvas, save to your own sovereign cloud (even a laptop), private export; only the server hop separates it from Lolly. Cloudinary gets its own column: a media pipeline (DAM, transform API, CDN), and the only cloud column shipping C2PA (50, because fl_c2pa signs on delivery, attesting delivered-by-Cloudinary, not made-by-you). Live collaboration runs the other way: Figma sets the scale benchmark (200 editors) and Lolly's pairwise, air-gapped P2P scores Partial. Price is a guess, labelled as such: list-price arithmetic on realistic seat mixes, wide on purpose, for scale not procurement. Render APIs take 75 on constraints: templates locked, no brand-governance layer. The gap: nothing shipping today is constraints-first and offline with no account and no server in the render path, and no one has copied the account clause. Lolly now ships its own open canvas - **Design**, a direct-manipulation free canvas - but colours, type and assets on it conform to the brand globals, so even free arrangement stays constraints-first. What Lolly still is **not** is an unconstrained design suite; designers will continue to use Illustrator and Figma for bespoke work - and when that work needs to become a governed, reproducible asset, the Design tool's [Import a design](/info/design-import.html) brings the finished Figma, Penpot, Illustrator, InDesign or PDF file onto the canvas as editable, brand-conformed boxes. ![Design's free canvas, where the colours, faces and assets on offer are the brand's own](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Ftemplate%3D__blank__&width=1440&height=900&dpi=192&waitMs=2400&walker=1&format=svg&dark=1&filename=aud-open-canvas) ## Use it for - Rapid generation of operationalised creative assets (event tiles, badges, signatures, alerts) - Free-form arrangement on the open canvas (Design) when the pieces - colours, type, icons, images - must stay conformed to the brand globals - Landing a finished Figma, Penpot, Illustrator, InDesign or PDF design (the Design tool's Import a design) so it can be edited, governed and re-rendered deterministically in every Lolly format - One-to-many "fill in three fields, get the finished asset" flows - including bulk runs from a spreadsheet/CSV in the `/pro` batch grid (paste or import rows, one finished asset per row, download as a zip) - Always-on, recurring branded outputs - Things where central control of brand expression matters more than expressive flexibility Deck Studio is a good measure of the ceiling here: a whole slide deck declared as data, laid out live on the canvas and exported as a native editable PowerPoint. ![Deck Studio in the split view - the deck's slides listed as blocks on the left, the laid-out deck rendering on the right](/t/url-shot?url=%2F%23%2Ftool%2Fdeck-studio&width=1440&height=900&dpi=192&waitMs=2600&walker=1&format=svg&dark=1&filename=ov2-deck-studio-output) ## Do not use it for - Bespoke or flagship hero content (billboards, major videos) - Unique campaign work that genuinely needs a designer - Ideation that needs to escape the brand system entirely - Lolly's open canvas still conforms colours, type and assets to the brand globals, and that's the point ## Innovate probabilistically, scale deterministically Most "AI creative" pitches put the model on the wrong side of an old line. Scribes and illuminators already settled where it falls: you work loose on the sketch, where anything can be tried and nothing is committed, and then you go to the printing press, which is intimidating exactly because it commits. The sketches were where the art was. The press was how it travelled. Two instruments, two jobs, each inventive in its own way, and the printed work could be trusted because the press kept its promise on every pull. Lolly is the press, not the sketch. Bring whatever you like to the ideation - a model, a designer, a napkin - but the moment an idea has to become ten thousand assets it goes through something that renders the same way every time, from inputs anyone can read back. That is what the comparison above is really about: not who has the better generator, but who makes the committed step reproducible. > Trust the creative process, scale with rigour. ## Approve the tool, not the file Every other tool on the board produces a *file* that then has to be checked - a brand manager in a Slack thread, legal on the disclaimer, a round of changes, another review. Lolly moves the approval **one step upstream**. The brand rules - exact hex codes, licensed font files, bleed margins, spacing - are hard-coded into the tool's HTML and CSS, so the template *cannot* emit an off-brand asset. The layout itself does the enforcing. So you stop approving outputs and start approving the **tool** that makes them. Approve it once, and every asset it ever produces is pre-approved by construction - no human in the loop, no review cycle, at any volume. This is the change the deterministic engine actually delivers: it isn't a faster version of the old approval process, it removes the process. For the creative team it's a guard-rail, not a replacement - you still throw the ball (the data, the copy, the image) and the code is the bumper lane that keeps every throw out of the gutter. ![The producer's whole job: type the words. Type, colour and spacing were settled when the tool was approved](/t/url-shot?url=%2F%23%2Ftool%2Fwordmark%3Ftext%3DApproved&width=1440&height=900&dpi=192&waitMs=2000&walker=1&format=svg&dark=1&filename=aud-approve-the-tool) | Approving assets the old way | Approving the tool, the Lolly way | |---|---| | Every finished file is checked, one at a time | The tool is checked once | | Request → designer builds → brand review → legal check → changes → re-review | One parameter change → finished asset | | Designer, brand manager, legal and requester all in the loop | The producer, on their own | | Days per asset | Seconds per asset | | 10,000 assets = 10,000 review cycles | 10,000 assets = zero (the template was already approved) | ## What this uniquely provides - **Wild design potential delivered safely in context.** Tools can express adventurous design ideas inside hard coded guard-rails. - **Software-defined content automation that returns the final asset.** Input → final file. No "now save it from your design tool and post-process it." - **Tools compose tools.** One tool can embed another tool's render and return it as part of a single finished asset, with no tool-to-tool code coupling - a primitive no open-canvas or DAM-templating product on the board offers. - **Vendor neutrality.** Full feature and cost control. Open-source engine. Tools and assets are git-tracked content, not locked in a SaaS database. The first of those is the one people underestimate. A poster-grade city map, drawn as true vector road and water paths, from a dropdown and two colour fields that cannot be pointed outside the brand: ![Amsterdam's canal rings and road network drawn edge to edge in the brand's own ink, every stroke placed by the template rather than by hand](/t/url-shot?url=%2F%23%2Ftool%2Fstreet-map%3Fcity%3Damsterdam%26theme%3Dlight%26full&width=1440&height=900&dpi=96&waitMs=3200&walker=1&format=svg&cropSelector=%23tool-canvas&dark=1&filename=ov2-street-map-poster) ## Content sovereignty There is a name for what the previous section adds up to: sovereignty. Your media pipeline runs on hardware you own. Your brand - the tokens, the fonts, the logos, the tools that enforce them - lives in files you hold, in version control you control, not in a vendor's database with an export button. Rendering happens on the device in front of you, so an asset never transits a third party to exist, and the whole path from input to finished file is open source and inspectable. If every SaaS design vendor disappeared tomorrow, a Lolly deployment would not notice. This matters to anyone whose work should outlive a subscription: the parent whose photo book lives on that laptop as much as the public body whose brand library sits under procurement rules. For organisations - public bodies, regulated industries, anyone whose brand is a strategic asset rather than a decoration - "where does our content live and who can turn it off" is a governance question, not a preference. Sovereignty here is a property of the architecture rather than a hosting feature added for compliance, and the [Privacy Policy](/info/privacy.html) and [Verify It Yourself](/info/verify-yourself.html) pages exist so you can check that claim rather than take it. Underneath it all is one promise, stated as a commitment rather than a feature: **if it renders on your device, it is free forever.** The engine, the shells, the tools, the formats - the entire on-device creative path is open source and stays that way. That promise has a mechanism: a version that has been released is licensed so it cannot be taken back, and no contributor agreement exists that could relicense the work later. The whole boundary fits in one sentence: everything that renders on your device is free and open source, forever; coordinating people and machines across a network is the job of a separate control plane, [lolly.work](https://lolly.work). --- Source: https://lolly.tools/info/create/compare.md Page: https://lolly.tools/info/create/compare.html # Lolly compared, tool by tool Lolly overlaps with several kinds of software and replaces none of them wholesale. Each page below states plainly what the other tool does better and what Lolly does instead. Nothing here is a superlative about Lolly, and nothing here names a villain: the reader does the arithmetic. For the capability-by-capability picture across the whole field, see [How Lolly compares](/info/positioning.html), which carries the comparison figure. _Last checked: August 2026._ - [Lolly and Canva](/info/compare-canva.html) - making an on-brand graphic without a subscription. - [Lolly and Adobe](/info/compare-adobe.html) - converting, exporting and signing files without a Creative Cloud account. - [Lolly and Figma](/info/compare-figma.html) - laying out a design and turning it into a reusable output. - [Lolly and Penpot](/info/compare-penpot.html) - open-source design tooling, shared brand tokens and where the render happens. - [Lolly and brand portals](/info/compare-brand-portals.html) - locked templates for people who are not designers, without a per-seat licence (Bynder, Frontify, Marq, Brandfolder). - [Lolly and rendering APIs](/info/compare-render-apis.html) - generating many on-brand images from data (Bannerbear, Placid). - [Lolly and online file converters](/info/compare-converters.html) - turning one file format into another, on your own device. Every page is dated, because a comparison goes stale. If a claim here no longer matches what the other tool does, it is a bug in this page. --- Source: https://lolly.tools/info/create/compare-canva.md Page: https://lolly.tools/info/create/compare-canva.html # Lolly and Canva _Last checked: August 2026._ Lolly is not a replacement for Canva, though it does what most people open Canva for: making an on-brand graphic, a social post or a simple layout, in a browser, without design software or a subscription. ## Where they overlap Both let someone who is not a designer produce a finished graphic from a template. Lolly runs the render on your own device, needs no account, and the file never leaves your machine. Canva runs in the cloud and keeps your designs on its servers; its AI-training preference is an opt-out setting (August 2026). ## What Canva does better today A much larger template and stock library, real-time team editing across the product, an integrated content marketplace, and a mature mobile app. The price line has moved too: the Affinity design suite Canva owns went free in October 2025, Cavalry with it, and Canva's offline mode is free on every tier - an open design keeps rendering with the Wi-Fi off, held per device for a 14-day window. If you want a large library to browse, or a team editing one document live, Canva is the more complete product. ## What Lolly does instead Every asset comes from a tool you can inspect and reuse, with Content Credentials sealed into the file (a credential signed on the device reads as unverified in a stranger's validator until [an identity vouches for it](/info/content-credentials-identity.html)). Approve the tool once and every output is on-brand by construction, at any volume. The line that holds is no longer the subscription: it is that Lolly needs no account and makes no connection you did not ask for. Canva's offline render works without Wi-Fi; activating the product still requires a verified account, and the apps make connections that cannot be declined from inside them. Lolly opens from a plain URL, and the only traffic is what you asked for. Canva is a trademark of its owner. This page describes where the two tools overlap and is not affiliated with Canva. --- Source: https://lolly.tools/info/create/compare-adobe.md Page: https://lolly.tools/info/create/compare-adobe.html # Lolly and Adobe _Last checked: August 2026._ Lolly is not a replacement for Adobe's Creative Cloud, though it does what many people need Photoshop or Express for: resizing and converting images, stripping metadata, adding a watermark or Content Credentials, and exporting a print-ready PDF. ## Where they overlap Both export professional formats (PDF/X, CMYK, high-bit-depth raster) and both can write C2PA Content Credentials. Adobe signs Content Credentials locally in its desktop apps. ## What Adobe does better today Deep pixel and vector editing, the widest professional toolset, and the largest ecosystem of plugins and integrations. For freeform creative editing, Adobe leads. ## What Lolly does instead It signs Content Credentials on the device with no Adobe account and no Adobe-provisioned certificate, and it runs the same render path in the browser, on the desktop and in the terminal. Adobe's own web route, the Content Authenticity app, signs in the cloud: uploads are capped at 20 MB of JPG or PNG, a thumbnail of your image is retained and it has stayed in beta sixteen months after launch (August 2026). With Lolly every file stays on your machine. The on-device transform utilities add nothing to your file; rendered raster output carries Lolly's own invisible Imprint mark by default (presence-only, no personal data), and it is one switch to turn off. One concession rides with the signing claim: a credential signed with an on-device key reads as unverified in a stranger's stock validator today. The structure and the tamper evidence are real; the signer identity is simply not vouched for by an authority. That is the stranger case. An organization can anchor trust in its own certificate authority and pin its own root, so its files verify inside its own trust domain with no external authority in the loop, Adobe included. [lolly.work](https://lolly.work) coordinates that at scale, and [Content Credentials Identity](/info/content-credentials-identity.html) documents the mechanism. Adobe, Photoshop and Creative Cloud are trademarks of Adobe. This page describes where the tools overlap and is not affiliated with Adobe. --- Source: https://lolly.tools/info/create/compare-figma.md Page: https://lolly.tools/info/create/compare-figma.html # Lolly and Figma _Last checked: August 2026._ Lolly is not a replacement for Figma, though it does what many people need Figma for: laying out a design, importing one that already exists, and producing a reusable, parameterised output from it. ## Where they overlap Both are design surfaces you can import a file into, and Lolly reads Figma files. Both let you place type, shapes and images on a canvas. ## What Figma does better today Real-time multiplayer at scale (hundreds of simultaneous editors on one file), a mature plugin ecosystem, and interactive prototyping. Figma Buzz now covers bulk asset generation too: it fills templates from a CSV or XLSX, holds guidelines an editor locks and is free in beta (August 2026). For live team design at scale, Figma is the benchmark, and it set that benchmark. ## What Lolly does instead It turns a design into a re-renderable tool that runs offline, needs no account and produces on-brand assets at volume with Content Credentials sealed in (unverified in a stranger's validator until [an identity anchors them](/info/content-credentials-identity.html)). The render path has no server in it at all: the same output from a browser tab, the terminal or CI, with the state carried in the URL. Its collaboration is private and peer-to-peer, over an invite you accept, and it works with no internet at all when both devices share a network. Figma is a trademark of its owner. This page describes where the tools overlap and is not affiliated with Figma. --- Source: https://lolly.tools/info/create/compare-render-apis.md Page: https://lolly.tools/info/create/compare-render-apis.html # Lolly and rendering APIs _Last checked: August 2026._ Lolly is not a replacement for a rendering API like Bannerbear or Placid, though it does what most people need one for: generating many on-brand images from data, automatically. ## Where they overlap Both produce an image from a template and a set of inputs, driven by a URL or a script. Lolly's [URL mode](/info/url-mode.html) and [CLI](/info/cli.html) do this with no server and no account. ## What those APIs do better today Hosted infrastructure that scales for you, a managed render queue, and turnkey integrations with the services teams already use. The billing can be honest too: Bannerbear stops rendering at the quota rather than billing past it. If you want someone else to run and scale the renderer, a hosted API is the ready-made answer. ## What Lolly does instead The same template runs on your own device or in your own CI, free, offline, with the state carried in the URL. None of the six hosted render APIs checked for this page offers a self-hosted version (August 2026); Lolly's renderer is the download. The cost does not disappear - it moves to your own devices and CI minutes - but there is no per-render charge, no quota and nothing leaves your infrastructure. Every output carries Content Credentials (unverified in a stranger's validator until [an identity anchors them](/info/content-credentials-identity.html)). And when a team wants this same engine run as a managed server render instead of running it themselves, that is [lolly.work](https://lolly.work)'s job. Bannerbear and Placid are trademarks of their owners. This page describes where the tools overlap and is not affiliated with either. --- Source: https://lolly.tools/info/create/compare-converters.md Page: https://lolly.tools/info/create/compare-converters.html # Lolly and online file converters _Last checked: August 2026._ Lolly is not a replacement for every online file converter, though it does what most people need one for: turning one file format into another - HEIC to JPG, PNG to SVG, PDF to images - without installing anything. ## Where they overlap Both convert files in a browser. The difference is where the work happens. ## What the ad-funded sites offer A very wide list of format pairs, and no software to install. Many of them are quick to reach from a search. ## What Lolly does instead The conversion runs on your own device, so the file is never uploaded to a stranger's server. There are no ads and no trackers, no sign-up, and no size cap set by someone else's bandwidth. It works offline, and the output carries no watermark or metadata of ours. See [every format Lolly can open and make](/info/formats.html). This page describes a category of site rather than one product, and names none, because the point is the same for all of them: your file does not need to leave your device to change shape. --- Source: https://lolly.tools/info/create/compare-penpot.md Page: https://lolly.tools/info/create/compare-penpot.html # Lolly and Penpot _Last checked: August 2026._ Lolly is not a replacement for Penpot, though it does what many people need Penpot for: opening a design file, holding a brand's colours and type in one place and getting a finished asset out the other end. ## Where they overlap Both are open source and both read the same brand primitives. Penpot shipped design tokens natively in 2.6.0 (April 2025), following the W3C DTCG draft, with 17 token types and multidimensional themes; Lolly's brand is a DTCG document too, so one palette serves both. Lolly's [Import a design](/info/design-import.html) opens a `.penpot` export as an editable layout. The Lolly Export plugin for Penpot is Lolly's own work, and it signs C2PA Content Credentials with Lolly's engine, on the device and opt-in - and, like every on-device credential, it reads as unverified in a stranger's validator until [an identity vouches for it](/info/content-credentials-identity.html). ## What Penpot does better today A full freeform design tool, real-time multiplayer with cursors and presence, plus a community Lolly does not have: 58,680 GitHub stars, 1.5 million users reported by Penpot in March 2026 and monthly releases that reached 2.17.0 in July 2026 with a WASM prototype renderer built on Skia. It is MPL-2.0 and self-hosts free through Docker Compose, an official Helm chart, Kubernetes, OpenShift or Rancher. Penpot states that "the experience stays the same, whether you use Penpot in the cloud or self-hosted", and cloud Professional is free for up to 8 team members, with $7 per user per month capped at $175 above that and $25 per user per month for Enterprise. For drawing a design with other people, Penpot is the more complete answer. ## What Lolly does instead Cloud and self-hosted Penpot both run against a server; Lolly renders and signs in the page itself, with no server and no account. A Lolly tool is a file you keep in version control rather than a document in a database, so approving the tool once approves every asset it makes, at any volume. What comes out is the production file, in every format Lolly writes, from the browser, the desktop or the terminal. Penpot is a trademark of its owner. The plugin named above is Lolly's, not Penpot's. See [How Lolly compares](/info/positioning.html) for the capability-by-capability picture and [Lolly compared, tool by tool](/info/compare.html) for the rest of this set. --- Source: https://lolly.tools/info/create/compare-brand-portals.md Page: https://lolly.tools/info/create/compare-brand-portals.html # Lolly and brand portals _Last checked: August 2026._ Lolly is not a replacement for a brand portal like Bynder, Frontify, Marq or Brandfolder, though it does what most people need one for: handing someone who is not a designer a locked template and getting an on-brand file back. ## Where they overlap Both give a non-designer a template that bends only where it is allowed to bend, and both keep the approved brand assets somewhere a whole team can reach them. What differs is where the template lives and who meters it. ## What brand portals do better today Governed org-wide asset storage, approval workflows, rights management, versioning and usage analytics, with the administration around them: Marq publishes a Team tier at $12 per user per month, gates advanced template locking to Team and API, then puts SSO and SCIM on Enterprise. Lolly has none of that. An organisation that needs a reviewed, rights-managed library with reporting on who used what should buy one. ## What Lolly does instead The templating and the locking are the engine itself, free, with no per-seat meter and no procurement round. Most portals are quote-priced: 2026 procurement data puts Bynder's enterprise average near $124,700 a year with typical yearly escalation of 22 to 23 percent, Frontify's median contract near $32,400 on active-user pricing and Brandfolder's near $24,700. Lolly's tools and brand assets are files you hold in version control rather than records in a vendor database. The render happens on your own device. The two sit together well: the portal holds the library and the approvals, Lolly makes the files. This page describes a category rather than one product, and the vendors named are examples quoted from their published facts. See [How Lolly compares](/info/positioning.html) for the capability-by-capability picture and [Lolly compared, tool by tool](/info/compare.html) for the rest of this set. --- Source: https://lolly.tools/info/create/extension.md Page: https://lolly.tools/info/create/extension.html # Browser Extension The **Lolly URL Screenshot** extension lets the web app screenshot any web page from inside your browser. Without it, capturing a URL needs the desktop app - a browser page can't read pixels from another site on its own. The extension can, using the same capture the desktop app uses. It does one other job the same way: reading a single page you name so Brand Studio can pull a brand out of a live website. Both are covered below. It runs on Chromium-based browsers: **Chrome, Edge, Brave, Arc, Opera** - Chrome 111 or newer. Until it is installed, **URL Screenshot** still opens so you can compose a shot, and a note at the top of the tool's controls says what is missing. ![The URL Screenshot tool's note offering the extension, shown when capture to file has no host to run on](/t/url-shot?url=%2F%23%2Ftool%2Furl-shot&width=1440&height=900&dpi=192&waitMs=2400&walker=1&format=svg&cropSelector=%23capture-hint-notice&dark=1&filename=exp-url-shot-notice) Every control is live while you wait: the target URL, the scroll depth, the settle delay, the crop insets and the recolour. Only the capture itself needs a host. ![The URL Screenshot controls with a target URL, scroll depth, settle delay and crop insets, all usable before the extension exists](/t/url-shot?url=%2F%23%2Ftool%2Furl-shot%3Furl%3Dhttps%3A%2F%2Flolly.tools%26scrollDepth%3D0.3%26waitMs%3D1200&width=1440&height=2200&dpi=192&waitMs=2400&format=svg&css=%23tool-canvas%7Bdisplay%3Anone%7D&cropSelector=%23tool-inputs&walker=1&dark=1&filename=fq-url-shot-controls) ## Install ### From the Chrome Web Store *Coming soon.* Once it's published you'll install it in one click, then reload Lolly. ### Load it yourself (developers) The extension lives in the repo at `shells/chrome-extension/`. 1. Open `chrome://extensions`. 2. Turn on **Developer mode** (top-right). 3. Click **Load unpacked** and choose the `shells/chrome-extension/` folder. 4. Reload Lolly - **URL Screenshot** now works in the browser. ## How it works - A small script tells Lolly the extension is present, so the **URL Screenshot** tool switches on automatically - no setup. - When you render, the extension opens the target page in a background tab, captures it via the DevTools Protocol (the same `Page.captureScreenshot` the desktop app uses), then closes the tab and hands the image back. - It runs entirely in your browser, on your network - so capturing `localhost` or an internal site works. The capture itself is never uploaded anywhere; the only network traffic is your own browser loading the page you asked to shoot. While a capture runs you may briefly see a *"…started debugging this browser"* banner on the temporary tab. That's the DevTools Protocol at work; it clears itself when the shot is done. ## Reading a site for Brand Studio The **Website** source in Brand Studio starts a brand from a site you already have. On Chromium the extension is what reads it; on the desktop app a native fetch does the same job and on a plain browser with no extension the tile isn't offered at all. What happens when you press it: - One address, one page. The extension opens it in the same kind of background tab, reads the rendered markup, the stylesheet text and a handful of icon and logo images, then closes the tab. It does not follow links and it does not crawl. - Stylesheets and fonts hosted elsewhere (a CDN, a font service) are fetched too, because the page's colours and type live in them. Cross-origin requests go without your cookies; same-origin ones use them, exactly as the page itself would. - Everything is capped - a bounded number of sheets, images and bytes - so a hostile or half-broken page returns partial material rather than hanging. - The bytes go straight back to the Lolly tab that asked. The parsing into colours, type and logos happens on your device; nothing is uploaded. Nothing is read until you press. Pasting an address only fills the field in. ## After installing Reload the Lolly tab. The "Get the extension" prompt disappears and **URL Screenshot** becomes available in the gallery and in Batch mode. ## Permissions Its `manifest.json` declares four permissions plus host access: - `debugger` - drive the background tab through the DevTools Protocol. This is what takes the screenshot. - `tabs` - open the temporary background tab and close it again afterwards. - `scripting` - run the one-page reader inside the site you named, for the Brand Studio Website source. - `storage` - note the id of a tab it opened, in session storage only, so the tab still gets closed if the browser suspends the extension mid-read. Cleared on the next start; nothing about you is stored. - `host_permissions: [""]` - host access to *all* sites, because you can point it at any URL you choose. Chrome surfaces this at install time as a broad "read and change all your data on all websites" warning. Despite that warning, it only reads the single page you ask it to capture or import, and it does not read or transmit your browsing data - nothing is uploaded anywhere. The manifest also sets `minimum_chrome_version: 111`. The current version is 0.2.1. ## Troubleshooting - **Still seeing "Get the extension"?** Reload the Lolly tab - detection happens on page load. - **Nothing happens on this site?** The extension only activates on Lolly's own origins. Running a custom build on another domain? Add it to `content_scripts.matches` in the extension's `manifest.json`. - **A capture fails?** Check the URL is reachable and starts with `http://` or `https://`. Some pages actively block automated capture. --- Source: https://lolly.tools/info/build/builders.md Page: https://lolly.tools/info/build/builders.html # Lolly for Builders Build a tool once and the request stops coming back to you: the job becomes a template other people fill in themselves, correctly, without you in the loop. Your work is plain HTML, CSS and JavaScript - version-controlled, diffable, reviewable and running on an open engine, so it stays yours with nothing to unwind later. Lolly is a platform-agnostic engine that runs the same render path across several shells - the web PWA, the desktop and mobile apps, the CLI and the TUI - and tools are data rather than bundled code: a manifest, a template and optional hooks, so a new tool ships without an app update. Pick the lane below that matches what you are here to build. You are the multiplier in the relay: a creative authors the rules and you scale them into a pipeline, a CLI batch or an MCP endpoint, so [The lifecycle of a campaign](/info/overview.html#the-lifecycle-of-a-campaign) follows one asset through all three pairs of hands. New here? The **[Quickstart](/info/quickstart.html)** gets a brand and your first render in place before you go deep. [About](/info/about.html) names the licence boundary and the repositories each piece lives in, and [Lolly for Operators](/info/operators.html) is the door for whoever will ask you about rollout and security. ## Designers Bring finished work in, set the rules once and keep the tools you already use. What you decide here is what every tool in the gallery obeys, so a colour or a typeface is fixed in one place rather than argued about per asset. - **[Import a design](/info/design-import.html)** - bring a `.fig`, `.penpot`, `.ai`, `.idml` or SVG file in as an editable, re-renderable tool rather than a flat picture. - **[The Brand Studio](/info/brand-studio.html)** - the on-device editor for logos, colours, type and the rest of the design system (`#/start`). - **[Design Tokens](/info/design-tokens.html)** - the DTCG token model a brand is expressed in and how a tool reads it at render time. - **[Authoring Assets](/info/authoring-assets.html)** - catalog assets, tiers, locales, palettes, themable icons and fonts. - **[The Dashboard](/info/dashboard.html)** - your design system shown read-only, plus the capability map for the device in front of you. ## Developers One render path behind every surface: the GUI, the CLI and an agent all produce the same file from the same inputs. A tool is a manifest, a template and optional hooks, and every input is expressible as a URL parameter, which is why a link and a command line are the same thing here. - **[Architecture](/info/overview.html)** - why Lolly exists, the engine/shell/tool separation and the commitments that hold it together. - **[Authoring Tools](/info/authoring-tools.html)** - the full guide: manifest, template, styles, hooks, composition and publishing. - **[Host API](/info/host-api.html)** - the `HostV1` capability bridge every tool is written against, plus the note that hooks are not a sandbox. - **[URL Mode](/info/url-mode.html)** - every input as a URL parameter: reserved params, compact encoding and packed links. - **[Constraints](/info/constraints.html)** - why output comes out right by construction, with the tests that enforce each rule. - **[Determinism](/info/determinism.html)** - same inputs, same file: what is byte-reproducible and what is not. - **[Reproducibility](/info/reproducibility.html)** - the URL as the artifact, and how far a bare link can carry. - **[CLI](/info/cli.html)** - headless rendering over the same path as the GUI, driven by `--foo=bar` argv pairs. - **[Signing from the terminal](/info/cli-signing.html)** - give CLI output a real signing identity instead of an anonymous on-device key. - **[TUI](/info/tui.html)** - the interactive terminal shell, for when a pipeline needs a human in it. - **[MCP Server](/info/mcp.html)** - the native endpoint an AI agent uses to discover and run tools. - **[AI Agents](/info/ai-agents.html)** - driving Lolly from a model, where a URL is the API. - **[Browser Extension](/info/extension.html)** - capture a live URL as a reusable asset for a tool to compose. - **[Content Credentials Identity](/info/content-credentials-identity.html)** - CA-issued signing for on-device C2PA, with the engine contracts behind what an export carries. ## Infrastructure A static host is the easy path and an air-gapped install is the same install. Nothing in the render path needs a server, so what you are deploying is files, not a service to keep alive. - **[Deployment](/info/deployment.html)** - the web app, the packaged apps and the two optional services: where each piece runs. - **[Configuration](/info/configuration.html)** - profiles, brand packs, capability gating, feature flags and catalog validation. - **[Build Guide](/info/build-guide.html)** - build every target: CLI, TUI, desktop, mobile and the web PWA. - **[Building for iOS](/info/ios-build.html)** - the extra gates Apple adds, and what a device build needs on top of the desktop one. - **[Contributing Setup](/info/contributing-setup.html)** - a development checkout sized to what you are here to do, from a slim clone upward. - **[Data Transfer](/info/data-transfer.html)** - the `lolly-backup` bundle: envelope, integrity and the cross-shell guarantees. - **[Threat Model & Trust Boundaries](/info/threat-model.html)** - what Lolly defends against, what it does not and where each boundary falls, before a review asks. --- Source: https://lolly.tools/info/build/overview.md Page: https://lolly.tools/info/build/overview.html # Overview ![Lolly Icon - Large green and white lollipop candy](/info/icon.svg) This document captures the purpose, structure and architectural decisions for the Lolly platform. It reflects both the product vision and the current state of the codebase. > **Status:** Lolly is an internal prototype in a **closed pilot that hasn't completed**. The engine is deterministic and internally consistent, but the product is early - SUSE is customer number one - and its cryptography and file-parsing engines are currently undergoing SUSE's strict infrastructure hardening, preparing for enterprise scale (we're really good at this). Read the architecture below as design intent under test, not a finished, certified product. See [Adoption & Governance](/info/adoption-governance.html#status) for how the pilot is run and measured. > **How to read this page.** It carries two kinds of material, in order. The first half is > **why this exists**: the problem, the positioning and the lifecycle a single asset travels > through. From [The big picture](#the-big-picture-how-the-layers-fit) onward it is > **how the layers fit**: the architecture document for contributors, covering the engine/shell/pack > separation, the repository layout, the delivery targets and the commitments that constrain every > change to the platform. If you are here to change the codebase rather than to understand the > product, start at the big picture. > > Two companions go deeper than this page does. [`engine/README.md`](../engine/README.md) in the > repository is the module-by-module map of the engine, with a generated table of every module and > what it parses or writes. [Threat Model & Trust Boundaries](/info/threat-model.html) > is the same architecture read as trust boundaries, and it is the right page for any question about > what the engine treats as untrusted. --- ## Why this exists Teams face a recurring problem: repeatable creative and content work that is too predictable to justify skilled hands every time, but too quality-sensitive to hand off without guardrails. The result is either slow throughput (specialist bottleneck), inconsistency (people using whatever tool they have) or vendor lock-in (a SaaS DAM that controls your templates). This platform is the direct answer: > **Programmatic creative and content at scale** - zero-labor asset generation, with the rules under central control, for employees, vendors and partners. Lolly isn't where a design system gets invented - it's where it gets produced. Think of it like a vending machine for design: make a selection, get a result. Every time. The engine works for the highest quality each format can produce on the hardware in front of you, and the same engine makes the same file on every surface it ships to. The outcome is **abundance**: every event has correct signage, every CVE alert matches the house style, every label prints clean, every email signature is current - all without a design ticket. The platform handles recurring operationalised creative. It is deliberately not a bespoke creative tool - designers still own flagship work. ### Innovate probabilistically, scale deterministically Every argument about AI in a creative pipeline stalls on the same question: which part of this is the machine's job? It is an old question with a settled answer. Scribes and illuminators already worked between two instruments - the loose sketch, where nothing was fixed and everything could be tried, and the printing press, intimidating precisely because it committed. The sketches were where the art happened. The press was how it reached anyone. Nobody confused the two, and both kept advancing - new inks, new faces, new presses - each improving in harmony with the craft and the intention it served. Lolly draws the same line. Explore probabilistically: a model, a designer, a rough idea, a prompt that goes somewhere nobody planned. Then scale deterministically - the thing that reaches ten thousand outputs is a *tool*, and a tool renders the same way every time from inputs you can read. The exploration stays free because nothing downstream depends on it landing the same way twice. The output earns trust because it is not a guess. Getting AI experimentation into predictable, reproducible outcomes is not a new discipline; it is the same division of labour that made printed work worth trusting in the first place. > Trust the creative process, scale with rigour. ### Against the alternatives Capability completeness across today's creative tools, researched August 2026. Scoring: 0 absent, 25 workaround-grade, 50 real but gated or partial, 75 strong with caveats, 100 core competency. The gap is plain: nothing shipping today gives us constraints-first, offline-capable, low-skill, internally accessible output. Lolly even includes an open canvas - **Design** - where colours, type and assets conform to the brand globals, so free arrangement stays constraints-first. What it is **not** is an unconstrained design suite: designers continue to use Illustrator and Figma for bespoke flagship work. Permutations can be assembled with this tool. ![Every tool in the library as a card, grouped by category, so a producer picks one and starts](/t/url-shot?url=%2F%23%2F&width=1440&height=900&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%2C.brand-tips%7Bdisplay%3Anone!important%7D&tolerance=0.03&waitSelector=.gallery-view%5Bdata-shots-settled%5D&walker=1&format=svg&dark=1&filename=aud-gallery-landscape) **Use it for:** Rapid generation of operationalised creative assets - event tiles, name badges, signatures, CVE alerts, QR codes, social cards, consignment labels, structured reports. **Do not use it for:** Bespoke hero content. --- ## The lifecycle of a campaign The clearest way to see what Lolly is isn't a feature list - it's to follow a single asset as it passes from hand to hand. Watch one localized campaign card move through the organisation: 1. **The creative sets the rules.** A designer authors the base template in the Design tool, hard-coding the brand's typography and colour variables. They're not making one card - they're doing the foundational work *once* so they never have to hand-localize it again. 2. **The developer scales it.** That same template is wired into a nightly pipeline through the CLI, so a fresh chart or a new language variant is generated automatically - no designer re-opens the file. 3. **The producer just uses it.** A sales rep, offline on a plane, opens the same tool and generates a perfectly on-brand deck for a client meeting. No design skill, no network, no wait. The "fresh chart" in step two is a render like this one, produced from a data string and a handful of parameters with nobody opening a design file: ![A titled stacked area chart, its three series banded in a cool palette with axes, legend and title all placed by the template rather than by hand](/t/url-shot?url=%2F%23%2Ftool%2Fchart%3FchartType%3Darea%26stackMode%3Dstacked%26palette%3Dcool%26heading%3DProduct%2520mix%2520by%2520quarter%26full&width=1440&height=900&dpi=192&waitMs=2600&walker=1&format=svg&cropSelector=%23tool-canvas&dark=1&filename=ov2-lifecycle-chart) The point isn't that Lolly is good for designers *and* good for developers *and* good for sales, each in a vacuum. It's a **relay race**: the creative's initial work is scaled by the developer, which in turn empowers the producer. The effortless experience for the non-technical rep on the plane is only *possible* because of the rigour the designer set and the developer deployed. That's the force multiplier. Lolly isn't a drawer of separate tools for separate roles - it's one deterministic asset lifecycle that every role touches, and each hand it passes through multiplies the value of the last. --- ## One approval, ten thousand assets Because approval lives in the tool and not the file (see [How Lolly compares](/info/positioning.html)), scale stops being a review problem. Approve a localized social-card tool once, then generate **10,000 assets across 12 languages** from a spreadsheet - and not one of them needs a fresh compliance check from legal or brand, because the template they all come from was already approved. The same deterministic tool reaches that scale three ways, all producing identical, pre-approved output: - **A person, in the app.** The `/pro` batch grid: paste or import the rows, get one finished asset per row, download the zip. No design skill, no ticket, no wait. - **A developer, from the command line.** The CLI runs the *same* engine and the *same* render path headless, so the tool can be sequenced over all 10,000 rows in a script or a nightly pipeline. A `lolly --field=…` call in a loop is the whole integration. - **A system or an AI agent, over MCP.** The same tool operated programmatically, at the same fidelity and even greater scale - because a machine won't get bored while thousands of files roll in. ![Batch mode on a fresh install: one empty row waiting for a tool, with the whole spreadsheet surface and its Render button in place before any data arrives](/t/url-shot?url=%2F%23%2Fbatch&width=1440&height=900&dpi=192&waitMs=3500&walker=1&format=svg&dark=1&filename=ov2-batch-grid) One set of brand constraints, fixed once by a designer; three routes to the identical pre-approved output - and the machine route scales furthest of all, because it never tires while the files roll in. --- ## The big picture: how the layers fit Everything from here down is architecture. The diagram is the whole system in one view: tools are data at the top, the engine in the middle knows nothing of any platform, the shells below it implement one contract, and the catalogs supply the content. ``` ┌─────────────────────────────────────────────┐ │ Tools (data, not code) │ │ tool.json + template.html + hooks.js? │ └─────────────────────────────────────────────┘ ▲ │ talks to via Capability Bridge v1 ▼ ┌─────────────────────────────────────────────┐ │ Engine │ │ loader · validator · runtime · template │ │ inputs · url-mode │ │ PLATFORM AGNOSTIC. Knows nothing of DOM, │ │ filesystem, or You. │ └─────────────────────────────────────────────┘ ▲ │ implements HostV1 ▼ ┌──────────────┬──────────────┬──────────────┬──────────────┐ │ Web Shell │ Tauri Desktop│ Tauri Mobile │ CLI Shell │ │ (PWA) │ │ │ │ └──────────────┴──────────────┴──────────────┴──────────────┘ ▲ │ fetches from ▼ ┌─────────────────────────────────────────────┐ │ Catalogs │ │ catalog/tools/index.json + tool dirs │ │ catalog/assets/index.json + asset files │ └─────────────────────────────────────────────┘ ``` ### Repository layout Content is mounted as packs: `community/`, `docs/`, every `shells/*`, both `services/*` and `brands/suse` are each their own repository, checked out as git submodules of this one. The parent owns `engine/`, `schemas/`, `scripts/`, `tests/`, `api/`, `brands/lolly-start/` and `profiles.json`. See [Build Guide » Getting the source](/info/build-guide.html) for the checkout command and the cross-repo workflow. ``` lolly/ ├── engine/ # Platform-agnostic core. Open source (MPL-2.0). │ └── src/ │ ├── index.ts # public surface - loader, runtime, template, inputs, url-mode │ ├── loader.ts # fetches and validates tool files │ ├── runtime.ts # orchestrates the 5-step lifecycle │ ├── template.ts # Handlebars hydration + annotateTemplate │ ├── inputs.ts # manifest → runtime input model │ ├── url-mode.ts # URL ↔ input state round-trip │ ├── validate.ts # JSON Schema validation of manifests │ ├── compose.ts # resolve nested tool renders (composes) │ ├── embed.ts # parse portable lolly.tools embed URLs │ └── bridge/ │ └── host-v1.ts # type re-export of the @lolly-tools/core contract │ ├── shells/ │ ├── web/ # PWA - hosted online; primary distribution │ │ └── src/ │ │ ├── main.ts # boot, routing │ │ ├── theme.ts # theme apply/persist (FOUC prevention) │ │ ├── bridge/ # web implementations of HostV1 APIs │ │ │ ├── index.ts # compose all bridge pieces │ │ │ ├── db.ts # IndexedDB setup │ │ │ ├── state.ts # host.state - saved edits │ │ │ ├── profile.ts # host.profile - user details │ │ │ ├── assets.ts # host.assets - catalog + user uploads │ │ │ ├── clipboard.ts # host.clipboard │ │ │ ├── export.ts # host.export - rasterise/serialize │ │ │ ├── net.ts # host.net - allowlisted fetch │ │ │ └── media.ts # host.media - live camera frames (onFrame) │ │ ├── catalog/ │ │ │ └── sync.ts # boot-time catalog sync + offline cache │ │ ├── styles/ # app-wide CSS (app.css, picker.css, tokens.css) │ │ └── views/ │ │ ├── gallery.ts # tool library listing + saved-state cards │ │ ├── tool.ts # mounts one tool (inputs + canvas + actions) │ │ ├── picker.ts # asset picker UI (invoked by host.assets) │ │ ├── profile.ts # user details editor │ │ ├── projects.ts # /p - folders of saved sessions (nested; folder/selection export) │ │ └── free-canvas.ts # free-canvas editor overlay for render.layout:"editor" tools │ │ │ ├── cli/ # Node.js CLI - same engine, headless jsdom │ │ ├── bin/lolly.ts │ │ └── src/ │ │ ├── run.ts # loadTool → createRuntime → export → write file │ │ └── bridge.ts # CLI implementation of HostV1 │ │ │ ├── tui/ # Interactive terminal shell (Ink) - reuses the CLI bridge │ │ └── src/ │ │ ├── main.tsx # full-screen app: Gallery / Projects / Profile / ToolView │ │ └── bridge.ts # CLI bridge + on-disk state under ~/.lolly │ │ │ ├── tauri-desktop/ # downloadable desktop app │ └── tauri-mobile/ # iOS/Android app │ ├── tools/ # profile VIEW (gitignored) - data, not code. Merged from packs: │ # community/ (public, brand-agnostic, MPL) + brands//tools (brand-owned). │ # A SELECTION follows - the mounted set depends on the profile. │ ├── qr-code/ │ ├── quotes/ │ ├── email-signature/ │ ├── snippet/ │ ├── countdown-timer/ │ ├── color-palette/ │ ├── color-block/ # typed/heterogeneous blocks (addMenu discriminator) │ ├── dynamic-layout/ │ ├── tool-logo/ # "Logo" - auto-switching brand logo │ ├── street-map/ # offline vector city-block maps │ ├── url-shot/ # "URL Screenshot" (capture capability) │ ├── strip-data/ # on-device metadata strip - JPEG/PNG/SVG/PDF (file in → clean file out) │ ├── compress-pdf/ # on-device PDF compressor - recompresses images (file in → smaller file out) │ ├── brand-lockup/ # "Brand Lockup" - SUSE logo lockups; HarfBuzz text-to-path (wasm) │ ├── chart-creator/ # SVG charts from structured data │ ├── filter/ # photo effects in one tool - halftone/scanline/posterize/voronoi (vector), duotone/pixel-stretch/imperfections (raster) │ ├── meeting-planner/ # global timezone meeting scheduler │ ├── calendar-ics/ # event → .ics calendar file plus a card │ ├── digi-ad/ # "Animated Ad" - looping banner from scenes │ ├── event-name-badge/ # conference badges - composes qr-code as an SVG │ ├── wayfinding-signage/ # event signage; directions blocks auto-fit label text │ ├── text-helper/ # on-device text workbench (format/decode/hash/de-identify) │ ├── design/ # "Design" - freeform WYSIWYG editor canvas (render.layout: editor) │ ├── multi-page-pdf/ # multi-page PDF document - cover, flowing content blocks, back page │ ├── diagram-builder/ # org / layercake / process / cycle / pyramid diagrams │ ├── logo-wall/ # many logos → auto-packed grid │ ├── logo-lockup-partner/ # SUSE + partner co-brand lockup │ ├── icon/ # favicon .ico / png / svg from text + colours │ ├── lottie-digi-ad/ # animated Lottie ad banners │ └── pose-geeko/ # pose the SUSE Geeko mascot - print-ready stills │ ├── catalog/ │ ├── tools/index.json # tool registry │ └── assets/ │ ├── index.json # asset registry │ └── suse/... # logo, palette, etc. │ ├── schemas/ # JSON Schema for tool.json, asset entries, AssetRef ├── scripts/ # build-catalog-index.ts, checksum-assets.ts, validate-catalog.ts ├── tests/ # engine tests └── docs/ # this file + authoring guides + positioning ``` --- ## Platform delivery model The platform runs across several surfaces - web PWA, Tauri desktop/mobile, the scriptable CLI and the interactive TUI. All of them use the same engine and the same tool files. ### Web (PWA) - primary distribution Hosted at a SUSE-controlled URL. Works offline once the service worker has cached tools and assets. This is where most employees, vendors and partners will use the platform. No account required - state is stored in IndexedDB per device. The web shell is responsive from one layout. On desktop a tool is a resizable controls sidebar beside a preview stage with trackpad-native canvas navigation (Cmd/Ctrl-wheel or pinch to zoom about the cursor, Space- or middle-drag to pan, `0`/`1`/`+`/`−` keys and a Fit/% HUD). On mobile (≤640px) the controls become a top-anchored sheet with a drag grip that snaps peek/half/full (tap toggles) over a static full-screen preview, and a floating **Render** button opens the **Export** controls in a bottom-sheet popup. Touch gets pinch-zoom and drag-pan on the preview. The render path and the export controls are identical across both - only the chrome reflows. ![The desktop split view - controls generated from the manifest on the left, the live canvas on the right](/t/url-shot?url=%2F%23%2Ftool%2Fchart-creator&width=1440&height=900&dpi=192&waitMs=2200&walker=1&format=svg&dark=1&filename=aud-web-split) The same tool at phone width, with no second layout to maintain: the controls become a sheet at the top, the preview holds the whole screen and the render pill floats over it. ![An audiogram on a 430px-wide screen - the controls sheet above, the finished square artwork below and the floating render pill](/t/url-shot?url=%2F%23%2Ftool%2Faudiogram%3Faudio%3Dlolly%2Floops%2Ffireplace-loop%26title%3DField%2520notes%26subtitle%3DEpisode%252012%26style%3Dwave&width=430&height=900&dpi=192&waitMs=3200&css=%23ag-wave%7Bdisplay%3Anone!important%7D.ag-ph%7Bdisplay%3Ablock!important%7D&walker=1&format=svg&rasterDpi=110&dark=1&filename=ov2-phone-audiogram) **Batch mode (`/pro`).** The web shell also ships a spreadsheet-style batch grid (`shells/web/src/pro/`) that renders many rows at once across one or many tools. It does CSV/TSV round-trip plus spreadsheet paste, per-row template/format/size/unit/dpi, a blocks-editor side panel with a live preview, collapsible export columns, a per-row "relevance" tag bar, left drag-handle row reorder, two-step delete confirm, saved batch sessions and a `.zip` download. This is the one-to-many surface behind the "mass content generation" positioning. ### Tauri desktop / mobile Packaged native app (small footprint via Tauri). Provides full offline availability, filesystem access for CLI-dependent tools (PDF Smasher, Font Outliner) and camera access. Scheduled for mid-2026 tooling enhancement. ### CLI `lolly [--input=value ...] --output=file.png` Desktop users can invoke many tools from the terminal. The CLI shell loads the same engine, creates a jsdom DOM, runs the same render path and writes the file. URL mode is the transport - CLI is not a separate implementation. This guarantees CLI and GUI outputs are identical. ```bash lolly qr-code --url=https://suse.com --output=qr.svg lolly quotes --quote="Ship it." --output=quote.png lolly # lists available tools lolly qr-code # lists inputs for that tool ``` ### TUI `npm run tui` The interactive counterpart to the CLI: a full-screen, keyboard-first terminal app (built on Ink) for browsing tools, filling in inputs, saving projects and exporting - all without a GUI. Its host bridge **reuses the CLI's implementation** for the DOM-free formats (SVG/EMF/EPS/HTML + text/data), and adds on-disk state under `~/.lolly` plus an opt-in inline preview. Beyond that it has a **browser render tier**: a scoped headless Chromium (the same one the MCP server installs) that produces raster/PDF/video and live-URL capture on demand - driving a built copy of the web shell so output is identical, and launching only when you first export such a format. So `url-shot` (with crop + recolor + vector PDF/SVG) and every raster/pdf tool run in the terminal too. See the [TUI guide](/info/tui.html). Whichever surface you are on, the dashboard's Capabilities tab is the full map of what the platform declares it can do, grouped and readable without opening a single tool. --- ## Tool categories Tools are tagged with a `category` in their manifest for gallery grouping. Rows are listed in gallery section order. The `utility` section always renders **last** in the gallery (after every other category, including future ones) - it's the on-device "Offline Utilities" drawer. | Category | Examples | Planned | |---|---|---| | `everyone` | QR Code Generator, Quote Card, Email Signature, Logo, Wordmark, Audiogram, Battlecards, Sequence, Record | Employee Image Stationery | | `designer` | Brand Lockup, Design, Chart, Darkroom, Filter, Pose Geeko, Booklet | Font Outliner | | `event` | Meeting Planner, Event Name Badge, Wayfinding Signage, Calendar ICS, Booth Studio | Event Stationery, Bulk Name Badges, Room Agenda Cards | | `product` | - | CVE Alert, Product Release Announcement, Blog OG Image | | `utility` | Strip Hidden Data, Text Helper, Compress PDF, Convert Image, Convert Font, Redact, Run Web Code, Screen Capture, URL Screenshot | Unit/format converters, more on-device privacy utilities | Those cells are **examples, not inventories**. Which tools exist is a property of the profile you mounted, not of this page: a brand pack adds its own, and can exclude a community tool it would rather not ship. `catalog/tools/index.json` - generated from the manifests, and the registry the gallery actually reads - is the authoritative list; to count what a profile mounts, count the manifests (`ls community/*/tool.json brands/*/tools/*/tool.json`) rather than trusting a number written down here. (A tool id present in two packs mounts once, from the winning pack.) Tools are also classified by status: `official` (brand approved, no watermark), `community` (external contribution), `experimental` (watermarked exports). Most of the library is `official`; the newer studios and the capture tools tend to sit at `community` or `experimental` while they settle. Every surface shows the badge, so a reader knows what they are picking up before they open it - and, like the category cells above, the per-status membership moves too fast to enumerate here. Read it off the gallery or the generated index. **Design** is the first tool built on the `render.layout: "editor"` free-canvas mode - a chromeless, direct-manipulation surface where you drag, resize, rotate and snap boxes of text, shapes and images, then export through the same render path as every other tool. **Strip Hidden Data** is the first **on-device utility** (`privacy: "on-device"`): a content-transform tool that takes a file *you* supply, processes it entirely in the browser and hands back a clean copy - never uploaded, never watermarked, no provenance stamped. **Text Helper** is the second - an on-device workbench for everyday paste-into-a-website jobs (JSON format, JWT decode, Base64, URL encode/decode, SHA hashing). **Compress PDF** is the third - it shrinks a PDF by recompressing its images, again entirely on-device. The marker and its badge text "Runs on your device - nothing is uploaded" now cover the whole transform set: Strip Hidden Data, Text Helper, Compress PDF, **Convert Image** (HEIC/TIFF/AVIF → WebP/JPG/PNG), **Convert Font**, **Redact** (destroy regions of an image, SVG or PDF), **Prompt Card** and **Rebrand** (re-theme a `.pptx` in place) where the profile mounts it. This is a privacy-utility category that replaces handing confidential files to single-purpose websites. ![The Utilities drawer, where every card is a tool that transforms a file you already have](/t/url-shot?url=%2F%23%2Fu&width=1440&height=900&dpi=192&waitMs=1600&css=.welcome-dialog%2C.personalize-nudge%2C.brand-tips%7Bdisplay%3Anone!important%7D&tolerance=0.03&format=svg&walker=1&dark=1&filename=aud-utilities) > Note: `category` and `status` are denormalised into `catalog/tools/index.json` (the registry the gallery reads) from each `tool.json`. The manifest is the source of truth - the index is **generated** by `npm run build:catalog` and `npm run validate:catalog` fails CI if the committed index drifts from the manifests. --- ## Architectural commitments These decisions are settled. Changing any of them is a major undertaking - they shape every other decision in the codebase. ### 1. Declarative tools, with an imperative escape hatch A tool is a manifest (`tool.json`) + a template (`template.html`) + optional `hooks.js`. **The manifest declares inputs.** Not the template. Inputs are not inferred from Handlebars tokens. The manifest is the contract; the template consumes named variables by `{{id}}`. ![Street Map's control stack - a city dropdown, a theme select, weight sliders and colour triggers, every one of them drawn from a manifest line](/t/url-shot?url=%2F%23%2Ftool%2Fstreet-map%3Fcity%3Damsterdam&width=1440&height=900&dpi=192&waitMs=2400&walker=1&format=svg&css=%23tool-canvas%7Bdisplay%3Anone%7D&cropSelector=%23tool-inputs&dark=1&filename=ov2-street-map-controls) **Hooks are optional.** Most tools are pure declarative - manifest + template is enough. Tools needing computed values (QR encoding, chart data shaping) provide `hooks.js` exposing named lifecycle functions (`onInit`, `onInput`, `onFrame` - the per-frame live-camera hook for motion-reactive tools - `onLevel`, `beforeExport`, `afterExport`, `exportFile` - the file-in/file-out transform path used by on-device utilities like Strip Hidden Data - and `exportStill`, for a tool that owns its own deep raster). The host loads hooks via `new Function('host', …)` with the capability bridge injected as closure scope. This is a **portability contract, not a security sandbox**: hooks still run in the page realm and *can* reach `window`/`fetch`/`document` in a browser shell - `host.*` is the supported, portable surface, not an enforced boundary. Async hook results are time-boxed (`onInit` 5s, `onInput` 2s, `beforeExport`/`afterExport` 5s, `exportFile`/`exportStill` 10s) and late results discarded; a runaway *synchronous* hook cannot be preempted. Untrusted third-party hook code is therefore not safe to run until Worker isolation ships. This matters because: declarative tools can be authored by non-developers. If every tool were a web app, the risk note "limited skills to create/maintain workhorse templates" becomes a permanent bottleneck. ### 2. Tools and assets are data, not bundled code The web and Tauri apps fetch tool and asset catalogs from a known URL at boot, cache them locally and operate on whatever is there. **Adding a new event tile or seasonal asset does not require an app release.** Asset bytes are SHA-256 checksummed to prevent CDN poisoning. Asset `id` + `version` drives cache invalidation. ### 3. The Capability Bridge is the only API tools see Tools never touch the DOM outside their template area, never call `fetch` directly, never read the filesystem. They call versioned `host.*` methods. The contract's canonical definition is `packages/core/src/host-v1.ts` - the tool-author SDK `@lolly-tools/core`, so a third party can build against it without depending on the engine; `engine/src/bridge/host-v1.ts` is a type re-export of it, and engine/shell code keeps importing from that path unchanged: | Bridge API | What it does | |---|---| | `host.profile` | User's firstname, email, headshot, city, etc. Pre-fills inputs via `bindToProfile`. | | `host.assets` | Catalog queries, asset resolution, host-provided picker UI. | | `host.state` | Save / load input slots. IndexedDB on web, filesystem on Tauri, memory on CLI. | | `host.clipboard` | Write text or image to clipboard (with platform fallbacks). | | `host.export` | Rasterise or serialise the render target. Applies watermark for experimental tools. | | `host.net` | Allowlisted fetch - only available if the tool declared `"network"` capability. (No shipping tool currently uses it.) | Optional, additive surfaces appear only when a shell provides them. Some are **capability-gated** - exposed only when the tool declares the matching flag: `host.compose` (embed another tool's render - `compose`), `host.capture` (page capture for URL Screenshot - `capture`) and `host.recorder` (mic/camera/display capture for the recording tools - `microphone` / `camera` / `screen`). The rest are **feature-detected** - present whenever the shell can provide them, with the tool keeping a fallback for shells that can't. A handful of headline surfaces, to show what it covers - [Host API](/info/host-api.html) documents every one, and `packages/core/src/host-v1.ts` is the contract itself: | Surface | Since | What it adds | |---|---|---| | `host.tokens` | 1.0 | DTCG design tokens - the brand's own primitives | | `host.text` | 1.0 | Text-to-path via HarfBuzz WASM (the `wasm` capability flags tools that rely on it) | | `host.media` | 1.4 | Live camera frames driving the `onFrame` hook. Progressive enhancement, deliberately *not* gated by the `camera` flag - such a tool still works as an ordinary still-image tool | | `host.color` | 1.40 | Perceptual colour maths: ΔEOK, WCAG + APCA contrast, OKLab ramps, class-breaks, categorical palettes, harmony schemes (1.60), CSS Color 4 mixing and gradient baking (1.68). Pure and synchronous - shells attach the engine's `makeColorApi()` rather than implementing anything, so it cannot drift | | `host.images` | 1.60 | Decode / resize / re-encode bytes on device - the convert path (HEIC → JPEG, compress to WebP, downscale). Shipped in the web shell as a lazy facade, so the HEIC decoder never enters the boot chunk | | `host.geom` | 1.64 | Exact vector geometry: path booleans, offsetting, stroke-to-fill, spline lowering, simplification, hit testing. Also pure, synchronous and attached from the engine (`makeGeomApi()`); failures are *returned*, never thrown | The rest follow the same rules and are documented alongside them: `pdf` (1.8) and `pptx` (1.58) for on-device document surgery, `audio` (1.71) and `speech` (1.96) for clip analysis and on-device TTS/transcription, `viz` (1.72) for the MilkDrop placeholder contract, `codec` (1.100) and `layers` (1.102) for deep-bit and layered-bitmap output, `upscale` (1.101) and `matte` (1.103) for the on-device models, `raster` (1.105) for hooks doing their own pixel work, `connectors` (1.106) for export-safe arrows and `c2pa` (1.85) for signing finished bytes. The count grows; the rules don't. The declarable capabilities are: `network`, `filesystem`, `clipboard`, `camera`, `microphone`, `screen`, `ffmpeg`, `wasm`, `capture`, `compose`. (`screen`, added in 1.54, is display capture via `host.recorder` - the user picks a screen/window/tab in browser-native UI; distinct from `capture`, which rasterises a URL the tool itself names.) The same tool runs in browser, Tauri and headless CLI because each shell implements this interface - the tool never knows which it's in. The bridge is versioned. Adding methods is a minor version. Removing or changing signatures is a major version bump. When v2 ships, v1 must continue to work. ### 4. Asset IDs are forever `suse/logo/primary` is a contract. Once published: - The ID never changes, never gets reused. - Byte changes → bump `version` in the manifest. - Replaced by a new asset → set `deprecated: true` and optionally `replacedBy`. - Existing references always resolve. This makes saved tool states and URL-shared links durable across years. ### 5. URL mode is first-class Every input must be expressible as a URL parameter: ``` lolly.tools/#/tool/qr-code?url=https://suse.com&ecl=H ``` ![That link on its own, with nothing else in it, is the finished asset](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Fsuse.com%26ecl%3DH%26full&width=760&height=760&dpi=192&waitMs=2000&walker=1&format=svg&dark=1&filename=aud-url-mode-qr) CLI mode is URL mode under a different transport - the CLI shell builds a URL-state object from argv and runs the **same** engine pipeline. There is one render path. CLI cannot drift from GUI because it isn't a separate implementation. `url-mode.ts` handles the round-trip (parse and serialize). A set of **reserved params** is never forwarded to the tool as inputs: the output controls (`format`, `export`, `copy`, `filename`, `width`/`w`, `height`/`h`, `unit`, `dpi`), the print and provenance dials (`bleed`, `marks`, `profile`, `password`, `c2pa`, `imprint`, `durable`, `meta`, `hdr`, `depth`, `cuts`) and the state carriers (`template`, `z` - the "Shortest link" packed token - and `zx`, the same encrypted under a password). The `RESERVED` set in `engine/src/url-mode.ts` is the authority and is pinned by a test; [URL Mode](/info/url-mode.html) documents every one of them, including the handful not listed here. Asset inputs in URL mode are serialised by their `id`; the runtime resolves them via `host.assets.get()` before hydration. `width`/`height` are values in `unit` (default `px`, also `mm`/`cm`/`in`/`pt`/`pc`); with a physical unit `dpi` sets raster resolution. They set the canvas document size and pre-fill the export dimensions panel. Because every input travels in the link, a parameter change is a different finished asset. This whole palette is one seed colour, a harmony and a step count: ![Nine steps across four hues, all grown from the single seed colour carried in the link](/t/url-shot?url=%2F%23%2Ftool%2Fcolor-palette%3Fseed%3De0521a%26harmony%3Dtetrad-4%26steps%3D9%26full&width=1440&height=900&dpi=192&waitMs=2000&walker=1&format=svg&cropSelector=%23tool-canvas&dark=1&filename=ov2-url-palette) ### 6. Storage goes through the bridge, not direct Web shell: IndexedDB. Tauri: filesystem. CLI: in-memory. Tools see only `host.state.save(slot, data)` and `host.state.load(slot)`. `localStorage` is not used - it's too small and can't hold blobs. Users can save multiple named edit slots per tool and return to each session later. No account creation is required; state is per-device. Because the bridge is the only seam, that per-device state is also *portable*: `shells/web/src/data-transfer.ts` reads everything back out through `host.profile`/`host.state`/`host.assets` into a single `lolly-backup` zip that imports on any other install - the offline answer to "move to a new device" that doesn't need a server (full spec: `docs/data-transfer.md`). SUSE ID integration (multi-device sync) is a future milestone on top of this. ### 7. Maturity tags answer the "brand approved" risk by design Every tool declares `status: official | community | experimental` in its manifest. The gallery sorts by status. Experimental tools watermark their exports automatically - the watermark is applied by `host.export.render`, not by the tool, so it cannot be opted out of by a non-official tool author. This is a structural answer to the perception risk that using any tool implies brand approval. Process answers (a review queue, SUSE ID gating) layer on top. ### 8. Tool inputs are typed via the manifest, including assets Inputs declare a `type`: `text`, `longtext`, `number`, `boolean`, `color`, `select`, `asset`, `date`, `time`, `datetime-local`, `url`, `blocks`, `vector`, `table` and `file`. The host renders a generic control per type from the manifest - tools write zero control code. (Pre-filling from the user's profile is not a type - any input can carry `bindToProfile`.) Three carry more weight than the rest: - **`asset`** (with `filter` and `allowUpload`) is the bridge to the global asset system; `allowUpload: false` is the brand-enforceability lever for things like sponsorship-tile logos where only library assets are permitted. User uploads use the same `AssetRef` shape as library assets, so tools handle them identically. - **`blocks`** is a repeating field-group - a mini-table inside one input, edited in a side panel, with a typed/discriminated add menu and per-block asset fields. Clicking a rendered block on the canvas focuses that block's row. Used by `meeting-planner`, `chart-creator`, `event-name-badge`, `wayfinding-signage`, `color-block` and `digi-ad`. - **`vector`** groups a fixed set of numbers (e.g. a transform) into one compound control; **`file`** holds the user's own file as bytes in memory for on-device transform utilities (e.g. `strip-data` and `compress-pdf`). ### 9. Templates are logic-less (Handlebars, not EJS) Handlebars was chosen over EJS deliberately: - Logic-less. Templates can be authored by non-developers. - Safe by default. `{{x}}` HTML-escapes; `{{{x}}}` is opt-in raw. - No arbitrary JS in templates means no per-template XSS audit surface. Logic lives in `hooks.js` where it is explicit and reviewable. Available Handlebars helpers: `{{default}}`, `{{upper}}`, `{{lower}}`, `{{eq}}`, `{{markdown}}`, `{{asset ref}}`, `{{asset ref "property"}}` (plus data-format helpers `icsStamp`/`rfcText`/`csvCell` used by sibling `.ics`/`.vcf`/`.csv` templates). ### 10. Tools compose tools A tool can embed **another** tool's render with no tool-to-tool imports - composition is resolved by the engine, never by tool code. There are two surfaces: - **Declarative manifest** - `composes: [{ id, tool, inputs, format?, width?, height? }]`. The engine renders the named child and places the result in the logic-less template as `{{asset }}`. `event-name-badge` composes `qr-code` as an SVG today. - **Portable embed URL** - ``. The shell renders that child **locally** (a placeholder pixel shows until the local render resolves); nothing is ever fetched from `lolly.tools`. Compose any tool's render: an **SVG** child stays a true vector when the parent exports to SVG or PDF and rasterises crisply for PNG; **PNG/JPG/WEBP** children embed as images. Requires the `compose` capability. Composed children are intermediates - never watermarked or provenance-stamped - and composition degrades gracefully: a shell that can't render a child just omits the slot and the parent still renders. --- ## What we explicitly chose not to do - **No EJS / no arbitrary JS in templates.** XSS surface is zero. Logic lives in `hooks.js`. - **No mandatory asset CMS.** Individuals ingest their own creative files straight into their catalogue in-app (the [Catalogue](/info/using.html) view and the Brand Studio) - no server, no admin console. Work is handed on as a **session**: a share link carries the whole state, and the same session travels in a backup or over a collab session. Whoever controls the deployment can then lock a shared session in as a **template** - open the link, record its values as a template entry in that tool's directory in the brand pack and commit - after which it appears in the tool's "New from template" chooser and is deep-linkable as `?template=`. Git is the deployment owner's locking step, never the creator's. For a *shared, governed* catalog, an organisation **can** manage the asset directory the same way and gate updates through PR review - an available governance model, not a requirement of the app. - **No forced RBAC.** The open app is public-access by default; brand risk is managed by maturity tags + watermarks. An org that wants tighter control layers on its own auth and the git-reviewed catalog above. - **No central database.** All user state is per-device. SUSE ID integration is on the roadmap but not a launch blocker. - **No shared tools/engine code path.** The engine is open source and so are the brand-agnostic tools in `community/`; a brand pack such as the private `brands/suse/` carries its own tools and catalog under its own terms. Either way the separation is enforced (no cross-imports from `engine/` into tool content) so the split stays clean. --- ## Lifecycle, end to end A user opens `lolly.tools/#/tool/qr-code?url=https://suse.com&ecl=H`: 1. **Boot.** Web shell opens IndexedDB, constructs the capability bridge, syncs the tool and asset catalogs (or loads from cache when offline). 2. **Route.** URL hash → `tool` view, with `qr-code` and URL params extracted. 3. **Load.** `loadTool('qr-code', fetchFile)` fetches `tool.json`, validates against the JSON Schema, fetches `template.html`, `styles.css` and `hooks.js` source. 4. **Parse URL state.** `parseUrlState` translates URL params into initial input values. Asset refs (`?logo=suse/logo/primary`) are parsed as lightweight `{ id, _unresolved: true }` objects. 5. **Runtime.** `createRuntime(tool, host, initialValues)` builds the input model (merging profile data, defaults and initial values), resolves asset refs via `host.assets.get()`, loads hooks (closure-scoped `host`, not sandboxed), calls `hooks.onInit`. 6. **Render.** Shell subscribes to runtime; on every state change it receives `{ model, hydrated }`. It renders input controls from the model and writes the hydrated template HTML into `#tool-canvas`. 7. **Interact.** User types in an input → `runtime.setInput(id, value)` → constraints applied → `hooks.onInput` called → re-hydrate → re-render. The canvas updates live. 8. **Export.** User clicks Download(PNG) → `runtime.export(canvasNode, 'png')` → `host.export.render` (rasterises via dom-to-image-more; SVG/PDF go through dedicated DOM-walking vectorisers) → blob → `host.export.download`. The format range a tool can opt into is broad, and the `render.formats` enum in `schemas/tool.schema.json` is the authority on it - rasters and float rasters, vectors and cut files, print/CMYK, motion, editable documents (`pptx`, `docx`, `odt`), palette and data/text outputs, audio and font files. [URL Mode](/info/url-mode.html) names every id and what it produces. Audio is in that enum like anything else (`wav`, `mp3`, `m4a`, `opus`, declared by the audiogram and the recording tools); separately, a recording tool's `render.capture` mode drives `host.recorder`, whose take arrives as a finished Blob in whatever container the browser recorded. (Tools that set `render.export: false` - e.g. Color Palette, Countdown Timer, Strip Hidden Data, Text Helper, Compress PDF - hide the download/format/dimension controls.) Physical units are converted per format here (PDF → true page points, raster → pixels at DPI with a `pHYs` chunk). Authorship/provenance metadata (author, tool, source - built by `engine/src/metadata.ts`) is embedded per format: PNG iTXt, JPEG EXIF, PDF info dict, SVG ``, GIF comment. Experimental tools get a watermark inserted by the host, not the tool. ![The export panel that `?options` opens: the filename and format pair, the output size and the controls that write the file](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools%26options&width=1440&height=900&dpi=192&waitMs=2200&cropSelector=.export-popup&walker=1&format=svg&dark=1&filename=aud-export-popup) Same lifecycle in Tauri. Same lifecycle in CLI - jsdom provides the headless DOM; output goes to a file or stdout. --- ## Open-source status **Code is MPL-2.0.** `engine/`, `shells/*`, `services/*`, `schemas/` and `docs/` are open source under **MPL-2.0** - a vendor-neutral scaffolding platform for brand tooling, with each shippable unit in its own repository under [github.com/lolly-tools](https://github.com/lolly-tools). **Tool content ships as brand packs**, each with its own terms (see the pack's `NOTICE.md`). `community/` is the public [`lolly-tools`](https://github.com/lolly-tools/lolly-tools) repository and its brand-agnostic tools are MPL-2.0 too. `brands/suse/` is the private `suse-lolly` pack: the SUSE tools and the SUSE catalog, **proprietary to SUSE**, including its licensed PremiumBeat music. `brands/lolly-start/` is the blank starter brand this repository owns. Fonts ship inside a pack under the **SIL Open Font License 1.1** - the SUSE pack carries the SUSE and SUSE Mono typefaces. The repo-root `tools/` and `catalog/` are gitignored *views*: a profile assembles them from `community/` plus the active brand pack, which is why every script and shell reads those two paths and never a pack directly. The split is enforced - there are no cross-imports from `engine/` into tool content - so the platform/content boundary stays clean. --- ## Where the engine ends and the host begins If you can describe it in pure data + Handlebars → **engine**. If it touches the DOM, filesystem, network or any browser/OS API → **host**. The line is sharp on purpose. The engine is the open-source part. Everything that knows about SUSE, specific platforms or runtime environments stays out of it. For the next level of detail, [`engine/README.md`](../engine/README.md) enumerates every engine module and what it is responsible for, and [Threat Model & Trust Boundaries](/info/threat-model.html) records where that same line doubles as a trust boundary. --- Source: https://lolly.tools/info/build/design-tokens.md Page: https://lolly.tools/info/build/design-tokens.html # Design Tokens Design tokens are Lolly's **single source of truth for brand primitives** - colours first, then dimensions, type and the rest. One canonical token document feeds three things at once: the values baked into tools, the swatches the colour picker offers and an import/export bridge to other design tools. The format is the [W3C Design Tokens (DTCG)](https://www.designtokens.org/TR/drafts/format/) standard - the same format [Penpot imports and exports](https://help.penpot.app/user-guide/design-systems/design-tokens/) - so a brand defined in Lolly round-trips with Penpot and Tokens Studio. (Tokens carry the brand *variables* both ways; finished Figma/Penpot *files* come in through the Design tool's [Import a design](/info/design-import.html).) This page is the spec. The engine model is [`engine/src/tokens.ts`](../engine/src/tokens.ts); the format contract is pinned by [`tests/tokens.test.ts`](../tests/tokens.test.ts). > **Status.** The **colour** slice is shipped: brand colours are canonical tokens, the picker's swatches come from them and a chosen brand colour stays *linked* to its token. **Dimension tokens** (radius, spacing, sizing, stroke, opacity, rotation, shadows) and **typography** (brand fonts) are shipped too, editable in the [Brand Studio](/info/brand-studio.html)'s Tokens and Type tabs. **User import/export** is shipped - import W3C DTCG / Tokens Studio / Penpot in the Studio (or via [`ingest:brand`](/info/configuration.html#brand-packs)), and export a `LollyBrand` pack or a design-tokens palette. Token-aware tool *injection into templates* remains on the [roadmap](/info/overview.html#roadmap). ![The Tokens panel - pick a kind, name it and the token joins the document your brand travels in](/t/url-shot?url=%2F%23%2Fstart%3Ftab%3Dtokens&width=1440&height=900&dpi=192&waitMs=1600&css=.start-head%7Bdisplay%3Anone%7D&cropSelector=.be-tokens&format=svg&walker=1&dark=1&filename=bs-token-editor) ## Why tokens Before tokens there were three disagreeing colour "truths": a hard-coded swatch array in the web shell, a `palette` catalog asset that tools could reference and the engine's colour-management maths. Tokens collapse the first two into one canonical, versioned, git-reviewed source - and make it portable. The goal the project states plainly: *keep as many things as possible canonical via tokens, and be able to import/export them with tools like Penpot.* ## The format (a DTCG profile) A token document is DTCG JSON. A **token** is an object with `$value` (required) and `$type`; **groups** are objects without `$value` and pass their `$type` down to descendants. References between tokens use the curly-brace alias `{dotted.path}`. ```json { "color": { "$type": "color", "brand": { "jungle": { "$value": "#30ba78", "$description": "Jungle", "$extensions": { "com.suse.lolly": { "cmyk": [70, 0, 65, 0] } } } }, "semantic": { "primary": { "$value": "{color.brand.jungle}" } } } } ``` Two deliberate choices keep us interoperable with Penpot while serving Lolly's print needs: - **Colours: read every form, store perceptually, resolve to hex.** DTCG's newer colour *object* (`{colorSpace, components, alpha, hex}`) is parsed, and so is every CSS Color 4 string - `#hex`, `rgb()/rgba()`, `hsl()/hsla()`, `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()` and `color( …)` across 14 colour spaces (`engine/src/css-color.ts`), which covers everything Penpot emits and a good deal more. A value *resolves* to a hex string (`#rrggbb` / `#rrggbbaa` / `transparent`) for rendering, except a bare colour keyword, which is handed back as-is. What Lolly **stores** is a different question: a derived brand writes `oklch()` into `$value`, and a swatch's *Stored as* control can pin `lch` (the default), hex, `rgb()` or `hsl()` instead. All of those are forms Penpot ingests cleanly. - **CMYK rides in `$extensions`.** Print anchors live under the vendor key `com.suse.lolly`. DTCG reserves `$extensions` for exactly this, and Penpot round-trips it untouched - so a colour stays print-correct without breaking compatibility. **Sets and themes.** Top-level keys can be token *sets*; a `$themes` array selects and orders sets per theme (later sets override earlier - Tokens-Studio layering), with `$metadata.tokenSetOrder` fixing the order. A document with no `$themes` is one implicit set, and token paths keep their `color.brand.x` shape. ## The four layers **1. Engine - [`engine/src/tokens.ts`](../engine/src/tokens.ts).** Platform-agnostic, like `units.ts`. `createTokenSet(doc, { theme })` parses a document, inherits group `$type`, resolves `{}` aliases (chains, cycle-safe), applies the theme and returns a flat lookup with `get` / `resolve` / `query({type})` / `colors()` / `themes()`. It knows DTCG and nothing else - no DOM, no storage, no SUSE - so it ships as part of the open-source engine. **2. Catalog - a `tokens` asset.** Each brand pack ships one core-tier `tokens` asset and the active profile decides which is canonical - `suse/tokens/brand` in the SUSE pack, `lolly/tokens/brand` in the blank brand, `/tokens/brand` for a pack built by `ingest:brand` (a `tokens`-type catalog asset, `core` tier so it's always available offline). It is generated from the shell palette by [`scripts/build-brand-tokens.ts`](../scripts/build-brand-tokens.ts) and validated against [`schemas/tokens.schema.json`](../schemas/tokens.schema.json) by `npm run validate:catalog`. Versioned and checksummed like any asset. **3. User tokens - *shipped*.** A device-local store for the tokens a user creates or imports (from Penpot, DTCG or a `LollyBrand` pack), travelling as the `tokens.json` part of a `LollyBrand` pack, and inside the full data backup as a user asset in `assets.json` (see [Data Transfer](/info/data-transfer.html)). The [Brand Studio](/info/brand-studio.html) handles import, editing, the active theme and export back to DTCG. **4. Bridge - `host.tokens`.** An additive, optional v1 capability (like `net`/`text`): `get` / `colors` / `resolve` / `themes`. Each shell implements it over the engine model and its sources; a shell that doesn't is simply not token-driven. Loading is offline-safe (prefers the core-prefetched blob, falls back to a direct fetch, then to the built-in palette). ## Lolly UI tokens Your brand describes identity. Lolly also carries a small **application UI** system so menus, fields, panels and editor chrome remain coherent even when a brand has no complete component library. It travels under a reserved namespace: ```text lolly.foundation.* # Lolly’s compatible base scale lolly.ui.* # stable application roles a design system may override brand.* # your identity tokens ``` The Tokens room shows the Lolly roles as **placeholders**. They affect the app and the Penpot UI-library export, but they do not bloat a brand document merely because it was opened. Changing one value saves only that owned `lolly.ui.*` leaf; Reset removes it and restores Lolly’s stock role. The supported UI contract is deliberately narrow: semantic text/surface/action, selection, border and focus colours; control/panel/page spacing; role-based radius; elevation/edge/focus effects; UI fonts/type sizes; feedback/overlay/ navigation motion; and chrome/overlay/toast layers. Lolly validates each value before it reaches CSS. Unknown paths, unsafe CSS, incompatible types and unresolved aliases stay out of the live app rather than partially applying. Two foundation values are especially useful when starting with a token library: - `shape.radius` drives the derived radius scale. Set it to `0px` for square UI; set it high for rounded controls. The pill/round roles derive from it too. - `space.base` drives Lolly’s spacing rhythm (`.25×`, `.5×`, `.75×`, `1×`, `1.25×`, `1.5×`, `2×`, `3×`), so a system’s base unit reaches panels and controls without copying individual gaps. Choose **Export Lolly UI to Penpot** in [Components](/#/components) to get the stock library as editable frames plus both token layers and the active brand. The archive names every declared app component/pattern and uses sample data by default. Token records are present in `tokens.json`; a property that Penpot cannot bind natively remains an editable resolved value rather than a misleading claim of a live binding. Lolly does not export personal sessions or current content as an app surface unless that surface explicitly records consent. ## How tools consume tokens **Platform hydration (the common case).** Most tools never mention tokens. The platform feeds them: - **The picker's swatches are tokens.** The colour field sources its swatches from `host.tokens.colors()` for the active theme, labelled and grouped by token. This is what the `palette` input field always promised. - **Brand-bound values stay linked.** Choosing a token swatch stores a *reference*, not a frozen hex (see below), so a later token edit propagates everywhere. **Direct injection - *(planned)*.** A token-aware tool (a brand sheet, a style guide, a chart that maps series to brand colours) can receive the resolved token tree in its template context via the existing `extras` mechanism, opted into from the manifest. ## The value model: reference + cached value When a user picks a token-backed colour, the stored value is **a reference plus the hex it last resolved to**: ```json { "ref": "{color.brand.jungle}", "value": "#30ba78" } ``` A token-backed swatch reads as its token in the sidebar: the trigger carries the swatch's *name*, not a frozen hex, which is the visible difference between a linked value and one typed in by hand. ![A tool's colour row holding a token-backed value, the trigger showing the swatch circle and its name instead of a hex code](/t/url-shot?url=%2F%23%2Ftool%2Fwordmark%3Ftext%3DLinked%26color%3D%257Bcolor.spectrum.rose%257D&width=1440&height=900&dpi=192&waitMs=2000&css=%23tool-canvas%7Bdisplay%3Anone%7D&walker=1&format=svg&cropSelector=.input-row%3Ahas%28%5Bdata-color-field%3D%22color%22%5D%29&dark=1&filename=at2-token-linked-swatch) The `ref` keeps the value canonical - editing the token, or switching themes, re-resolves it everywhere. The cached `value` is the graceful fallback for a device where that token is absent. The path is fully backward-compatible: a plain colour string (a custom pick, or any existing tool) flows through untouched. How it moves through the engine: - **Hydration** ([`runtime.ts`](../engine/src/runtime.ts) → `resolveTokenRefs`, mirroring asset-ref resolution): on load, each token-backed colour re-resolves against the live token set and its cached hex is refreshed. `modelToValues` then hands the template a plain hex string - templates and data/JSON exports never see an object. - **Hooks** see the flattened hex too (`modelForHooks` in [`inputs.ts`](../engine/src/inputs.ts)): the `{ ref, value }` shape is an engine detail and never reaches a tool's `onInit`/`onInput`, so the common `(inputs.x || '').trim()` pattern keeps working. - **URL mode** ([`url-mode.ts`](../engine/src/url-mode.ts)): a token colour serialises to its reference (`?bg={color.brand.jungle}`), so a shared link re-resolves against the *recipient's* tokens. Parsing a `{path}` yields an unresolved token value the runtime then resolves. - **Editing past a token** (typing a hex, dragging alpha) emits a plain string - deliberately de-linking from the token, because the user just overrode it. Nothing in the link below names a colour. Its three colour params are token paths (`{color.spectrum.violet}`, `{color.spectrum.teal}`, `{color.spectrum.amber}`), resolved at render time against whatever brand the device is carrying. ![A mesh gradient whose violet, teal and amber all arrived as token references in the link rather than as hex values](/t/url-shot?url=%2F%23%2Ftool%2Fgradient%3Fcount%3D3%26color1%3D%257Bcolor.spectrum.violet%257D%26color2%3D%257Bcolor.spectrum.teal%257D%26color3%3D%257Bcolor.spectrum.amber%257D%26full&width=880&height=560&dpi=96&waitMs=2400&walker=1&format=svg&dark=1&filename=at2-token-refs-in-url) ## Penpot interop - **Author/generate:** the brand tokens are a DTCG document - already the shape Penpot reads. - **Import/export (shipped):** import a Penpot single-file, ZIP or multifile export (`$themes.json` + `$metadata.json` + one file per set) in the [Brand Studio](/info/brand-studio.html) or via [`ingest:brand`](/info/configuration.html#brand-packs), and export Lolly's tokens back as DTCG. CMYK survives Lolly→Lolly round-trips via `$extensions` and is ignored by Penpot, as the standard intends. ![The download pill parked at the palette's bottom edge, with the format menu that carries the same colours back out as DTCG JSON](/t/url-shot?url=%2F%23%2Fstart%3Ftab%3Dcolor&width=1440&height=900&dpi=192&waitMs=1800&cropSelector=.be-pal-dock&format=svg&walker=1&dark=1&filename=bs-palette-download) ## Take your tokens to Penpot The round trip above, step by step - no code involved. **Export from Lolly.** Open the [Brand Studio](/info/brand-studio.html) and go to the Colours room. The download pill parked at the bottom of the palette pane offers several formats; choose **Design tokens (JSON)**. The file you get is a W3C design-tokens document - the format Penpot and Tokens Studio read. If you have chosen faces in the Type room (Primary, Headings, Code, Italic), those travel in the same file as font tokens. Roles you left on the platform default stay out of the file: the export only carries choices you actually made. **Import into Penpot.** Open a Penpot file and switch to the Tokens panel in the left sidebar. Import the JSON you just downloaded. Your sets appear in the panel, and the colours and fonts can be applied to shapes and text from there. Two more paths exist beyond the token file. Once Penpot is connected under Profile → Connected services, a finished render can be sent from Lolly into a Penpot project as an image. And the Lolly bridge plugin for Penpot - installed by URL from a Lolly instance - can place a working Lolly session on the Penpot canvas as editable shapes. ## Migration & status The brand colours moved into tokens without changing what anyone sees: `scripts/build-brand-tokens.ts` derives the token document from the existing palette, so the picker shows the same colours - now sourced canonically. The shell palette remains the fallback (and, for now, the source of CMYK anchors on export). With dimension/typography tokens and user import/export now shipped, most of the app resolves from tokens and the standalone palette recedes; template-level token *injection* is the main piece still to land. The corner radius is the plainest of the shipped dimension tokens: one slider writing one `shape.radius` value that the app chrome, the panels and every opted-in tool then follow. ![The Rounded corners control in the Tokens tab - a live preview square, a slider and the value it writes, all standing for a single dimension token](/t/url-shot?url=%2F%23%2Fstart%3Ftab%3Dtokens&width=1440&height=900&dpi=192&waitMs=1800&css=.start-head%7Bdisplay%3Anone%7D&walker=1&format=svg&cropSelector=.be-radius-panel&dark=1&filename=at2-token-radius-dimension) ## Reference - Engine model: [`engine/src/tokens.ts`](../engine/src/tokens.ts) - `createTokenSet`, `resolveColorValue`, `colorToHex`, `isAlias`, `isTokenValue`, `TOKEN_EXT`. - Tests: [`tests/tokens.test.ts`](../tests/tokens.test.ts) (model) and [`tests/tokens-value-path.test.ts`](../tests/tokens-value-path.test.ts) (URL/hydration round-trip). - Schema: [`schemas/tokens.schema.json`](../schemas/tokens.schema.json). Generator: [`scripts/build-brand-tokens.ts`](../scripts/build-brand-tokens.ts). - Bridge contract: `TokensAPI` in [`packages/core/src/host-v1.ts`](../packages/core/src/host-v1.ts) (re-exported as `engine/src/bridge/host-v1.ts`). Web impl: [`shells/web/src/bridge/tokens.ts`](../shells/web/src/bridge/tokens.ts). --- Source: https://lolly.tools/info/build/constraints.md Page: https://lolly.tools/info/build/constraints.html # Constraints A constraint in Lolly is a rule the software enforces at load time and render time, rather than guidance a style guide offers an author: a tool exposes a fixed set of declared inputs, and the values its template can see are exactly those inputs plus whatever the tool's own code computed. Nothing else is reachable. That is what the landing page means by "[it comes out right](/info/index.html)" - a property of the mechanism, not a promise about care. This page is the mechanism, the tests that hold it and the places it stops. ## The manifest declares the inputs Every tool is a directory with a `tool.json` manifest, and that manifest is validated against `schemas/tool.schema.json` at catalog build time, at shell load time and while authoring. Two lines of that schema carry most of the weight: - The root object requires `id`, `name`, `version`, `engineVersion`, `status`, `render` and `inputs`, and sets `"additionalProperties": false`. A manifest cannot carry a key the schema does not know about. - Each entry in `inputs` requires an `id` and a `type`, and `type` is a closed enum of fifteen: `text`, `longtext`, `number`, `boolean`, `color`, `select`, `asset`, `date`, `time`, `datetime-local`, `url`, `blocks`, `vector`, `file`, `table`. Inputs are declared, never inferred from the template. Reading a tool's manifest tells you the complete surface a person using it can change, before you open the template at all. The declaration also carries the bounds. A `number` input's `min`/`max` clamp on update, a `text` input's `maxLength` truncates, a `select` closes its option list and a `color` input can name a `palette` asset to restrict choices to brand swatches - with `swatchesOnly` removing the hex field and the native picker entirely (`schemas/tool.schema.json`, the `color` conditional). ## The template cannot compute Templates are Handlebars and logic-less on purpose (`engine/src/template.ts`). The helper set is registered once at module load and deliberately small: `default`, `upper`, `lower`, `eq`, `icsStamp`, `rfcText`, `csvCell`, `arrow`, `markdown`, `asset` and `media`. There is no way to write a loop with a side effect, call out to the page or invent a value. `{{x}}` HTML-escapes; `{{{x}}}` is the opt-in raw form. The context a template is hydrated with is one line, `engine/src/runtime.ts:517`: ```js ctxCache = { ...modelToValues(model), ...extras }; ``` Declared input values, then hook-computed extras. A template that references a name in neither renders empty, because there is no outer scope for it to reach into. This is also why no template needs its own security audit: with no arbitrary code in a template, there is no code in a template to audit. ## Brand values resolve from tokens Colour, type and spacing come from the brand's design tokens rather than from numbers typed into a template. `engine/src/tokens.ts` is the engine's single source of truth for token semantics: it parses a W3C DTCG document (the format Penpot and Tokens Studio exchange), resolves `{dotted.path}` aliases including chains, applies `$themes` set layering and normalises every colour form to a plain hex string for the rest of the app. A colour input can hold a token reference rather than a literal, as `{ ref, value }`. The reference is what travels in a share link; the template only ever sees the resolved string. So re-pointing a brand token updates every tool that referenced it, and no template has to be edited. ## One place input semantics live `engine/src/inputs.ts` builds the runtime input model from the manifest: defaults resolved, profile bindings applied, control chosen. Its header states the rule the architecture depends on - this is the only place input semantics live, and shells render the model rather than interpreting manifest declarations themselves. That is why the same `number` input with a `min`, a `max` and a `step` becomes a slider in the browser, the same clamped number in the CLI and the same value in an MCP call. A shell cannot quietly widen a constraint, because a shell never reads the constraint. ## The same closed set in a URL A tool's URL is not a wider door than its sidebar. `engine/src/url-mode.ts` parses a query string against the tool's own declared inputs, and anything it does not recognise is ignored rather than guessed at. The one set of names that mean something without being inputs is closed and explicit - the `RESERVED` set at `engine/src/url-mode.ts:327`, covering output concerns such as `format`, `width`, `height`, `unit`, `dpi`, `profile`, `bleed`, `marks` and the provenance switches. So there is no undocumented parameter that changes what a tool will do. You can read the manifest, read `RESERVED` and know the complete vocabulary a link can speak. The CLI speaks the same one, because `--foo=bar` is converted by that module too. ## A tool reaches only the hosts its manifest names Tools never touch the network directly. `host.net` is the only path, it is gated by the `network` capability plus an explicit `network.allowlist` in the manifest, and it is fail-closed: no capability or no allowlist means every fetch rejects before any I/O happens. `tests/net-allowlist-conformance.test.ts` proves that against the real shared module across shells, which is the drift it exists to catch - a new shell writing its own `host.net` and omitting the check. ## The receipts | Claim | Enforced by | |---|---| | A malformed manifest is refused | `tests/engine.test.ts` - `validate: rejects manifest missing required fields`, `rejects invalid id format`, `rejects unknown status` | | A URL param that is not a declared input is dropped | `tests/engine.test.ts` - `url-mode: ignores unknown params (forward-compat)` | | Declared bounds actually bind | `tests/engine.test.ts` - `inputs: number constraints clamp to min/max on update`, `inputs: text maxLength truncates on update` | | A template escapes by default and cannot invent values | `tests/engine.test.ts` - `template: escapes HTML by default (XSS guard)`, `template: missing values render empty in if-blocks` | | A token reference resolves before the template sees it | `tests/tokens-value-path.test.ts` | | A tool's network access is fail-closed | `tests/net-allowlist-conformance.test.ts` | | The shipped catalog matches its manifests | `scripts/validate-catalog.ts`, run as a CI job in `.github/workflows/ci.yml` - duplicate ids, index drift, asset checksums, `bindToProfile` fields, palette references and `replacedBy` chains | | Every tool still renders at its declared defaults | the catalog-wide render gate in `.github/workflows/ci.yml`, which renders every tool in the active profile and exits non-zero on any failure | ## Check it yourself The whole claim is readable in a few minutes: ```bash git clone --recurse-submodules https://github.com/lolly-tools/lolly.git cd lolly cat community/qr-code/tool.json # the complete input surface of one tool npm install # the contract tests import ajv + handlebars node --test tests/engine.test.ts # the validate / inputs / template contract ``` The manifest you just read is the same file the browser fetches, the CLI loads and the catalog validator checks. There is no second, richer configuration behind it. ## Limits - **Constraints bound the filler, not the author.** A tool author can write a badly proportioned layout, pick a poor default or expose an input that should have been locked. The engine has no opinion about whether a design is good, only about whether the person using the tool can leave its rules. Authoring quality is a review question, and [Authoring Tools](/info/authoring-tools.html) is where that review starts. - **Hooks are the escape hatch, and they are trusted code.** A tool declaring `hooks` gets its `hooks.js` loaded through `new Function` with the host bridge injected. `engine/src/runtime.ts` says so at the site: closure-scope injection, not isolation. In a browser shell a hook can reach `window`, `document` and `fetch`, and some shipping tools rely on it. Async hook results are time-boxed by `HOOK_BUDGET_MS`, a synchronous runaway hook cannot be preempted in-realm, and a manifest may opt into a Worker with `isolate: true` where its hooks touch no DOM globals. Run tools you have reviewed. The [Threat Model](/info/threat-model.html) states this boundary in full. - **Raw output is available on purpose.** `{{{x}}}` and the `markdown` helper emit unescaped HTML by design. An author who pipes an input through triple-stache has opted out of escaping, and tool review is the control that catches it. - **A constraint holds inside the tool.** Once a file is exported it is an ordinary PNG, SVG or PDF, and anyone can open it in another program and change it. Constraints govern how the file was made, which is also why the export carries a [Content Credential](/info/security.html) recording that. ## Related - [Determinism](/info/determinism.html) - the same declared inputs producing the same render on every shell. - [Reproducibility](/info/reproducibility.html) - those inputs travelling in a link, so the render can be rebuilt later. - [Sovereign creative production](/info/sovereign-production.html) - the same tools and rules as files an organisation holds. - [Authoring Tools](/info/authoring-tools.html) - writing a manifest, a template and hooks against these rules. --- Source: https://lolly.tools/info/build/determinism.md Page: https://lolly.tools/info/build/determinism.html # Determinism Determinism in Lolly means the same inputs, put through the same tool, produce the same render on every shell - in the browser, on the desktop, in the terminal and through the MCP endpoint - and, for the formats that carry no clock, the same bytes. That is the precise version of the landing page's "[identical where you want it](/info/index.html)". This page shows where the property comes from, exactly how far it extends into the bytes and the handful of things that legitimately break it. ## One render path, two transports There is a single renderer. `createRuntime(tool, host, initialState)` in `engine/src/runtime.ts` orchestrates one mounted tool through the same lifecycle everywhere: load, build the input model, resolve asset references, run the hooks, hydrate the template, export. Shells supply a capability bridge; they do not supply a renderer. The command line is the same path under a different transport. `engine/src/url-mode.ts` says it at the top of the file: the CLI uses the same conversion, so `--url=https://suse.com` on the terminal and `?url=https://suse.com` in the address bar become the same value in the same input model. There is no second parser to drift and no CLI-only code path to fall behind. ``` # the same render, two ways in lolly qr-code --url=https://suse.com --export=svg --output=./qr.svg https://lolly.tools/#/tool/qr-code?url=https://suse.com&format=svg&export ``` Because tools sync to clients as data, ahead of the binary that runs them, the pairing is checked rather than hoped for. Every manifest declares an `engineVersion` range, and `loadTool` refuses a tool whose range excludes the running engine before it even fetches the template. A tool never half-loads against an engine that lacks the capability it was written for. The same property holds inside one shell over time. Hook results are time-boxed rather than awaited indefinitely (`HOOK_BUDGET_MS` in `engine/src/runtime.ts`), and a late async result is discarded rather than allowed to patch inputs after the fact - so a slow network or a slow machine changes how long a render takes, never what it contains. ## Semantic determinism, and what a seed gives you instead A generative model with a fixed seed replays an accident. The seed reproduces one image, and only on one model at one version, on one stack, with the same sampler - and if you want the same picture with a different headline, there is nothing to change but the prompt, which changes everything else too. A template embodies a decision. The layout, the type ramp, the safe area and the brand colours are fixed by the tool; the inputs are the axes the author chose to leave free. Feeding it new values yields a parameterized family of outputs, every member of which was decided once. That is the property automation actually needs: not "I can get this frame back", but "I can get five hundred frames, each correct, none of them a surprise". This is why the [MCP endpoint](/info/mcp.html) exposes renders from parameters rather than image generation. An agent choosing input values inside a tool's declared bounds cannot produce an off-brand file, and the same call next month returns the same thing. ## The same render, byte for byte: how far it goes Reproducible renders and reproducible bytes are separate promises, and only some formats keep the second one. The measured table lives in [the CLI guide](/info/cli.html) and this page does not restate it loosely - the summary that matters here is its first line. **A default render is deliberately not byte-reproducible.** Content Credentials and the Lolly Imprint are on by default, in the app and in the CLI alike, and a credential is signed with a fresh key and a fresh timestamp on every run. `--no-provenance` is the switch that turns a run bare, and the machine paths (`smoke`, `batch`) apply it themselves because a machine path wants reproducibility by default. With `--no-provenance`, measured back to back on one machine: - **Byte-identical:** SVG, EMF, EPS and DXF; the data formats JSON, CSV, VCF and MD; PNG rendered through the resvg tier from an ``-based tool. - **Not byte-identical:** ICS (RFC 5545 requires a `DTSTAMP`, which is the clock), PDF (every PDF carries `/CreationDate` and `/ModDate`), and the headless-Chromium tier - JPG, WebP and HTML-layout PNG, where the browser's paint and encode move between runs. Video is the same tier plus a frame-timed capture: assume no. Two caveats travel with that list. Format-level reproducibility is not tool-level reproducibility - a tool that renders the current time produces different bytes in every format, correctly. And the measurements are one machine back to back: a different OS, a different font set or a different engine version moves the bytes of anything that shapes text. ## The receipts | Claim | Enforced by | |---|---| | The engine contract itself: URL round-trips, the reserved-param set, input model behaviour, template hydration | `tests/engine.test.ts` | | CLI and URL mode resolve the same values, including asset sub-fields inside repeating blocks | `tests/engine.test.ts` - `runtime: resolves asset sub-fields inside blocks (CLI/URL parity)` | | The vector and data emitters produce exact, pinned bytes on every clone - no browser, no brand pack, no network | `tests/export-emitter-golden.test.ts` (every input pinned, no now-defaults, no randomness) | | The real CLI mechanism, end to end into svg, emf, eps, eps-cmyk, dxf and csv | `tests/cli-export-golden.test.ts`, with the bytes pinned in `tests/fixtures/cli-export.golden.json` | | Collaborative convergence never reads the wall clock or unseeded randomness, so two devices order edits identically | `tests/canvas-op-no-wallclock.test.ts` (a byte-level static guard over the merge path) | | A tool is refused rather than half-loaded when the running engine is outside its declared range | `tests/loader-engine-version.test.ts` | | The web export bridge does not silently change what it draws | `scripts/characterize-export.ts` | `scripts/characterize-export.ts` deserves its own note, because its honesty is the point. It drives the built web shell's real export path, renders every tool-and-format pair twice and classifies the result automatically: identical twice means it is hashed, differing or container-shaped means it is held to a size band instead. Nondeterminism is discovered rather than assumed. It is also explicitly a same-session local net rather than a committed CI golden, because raster bytes depend on the local Chromium build and the OS font stack, so its hashes are not stable across machines. ## Check it yourself ```bash lolly qr-code --url=https://suse.com --export=svg --no-provenance --output=a.svg lolly qr-code --url=https://suse.com --export=svg --no-provenance --output=b.svg shasum -a 256 a.svg b.svg # identical lolly qr-code --url=https://suse.com --export=svg --output=c.svg lolly qr-code --url=https://suse.com --export=svg --output=d.svg shasum -a 256 c.svg d.svg # different: each carries its own fresh credential ``` The second pair differs because every default export is signed afresh, with its own key and timestamp. Drop the provenance and the two runs converge on the same bytes. ## What it buys you in a pipeline Determinism is only useful if a machine can act on it, so the terminal surface is built to be branched on rather than parsed. Every command returns one code per outcome, frozen at general availability: `0` produced it, `1` ran and failed, `2` wrong invocation, `3` impossible in this installation (worth retrying on another runner), `4` a protective check refused, `5` a legitimate negative answer, `6` wrong password, `70` an internal bug. `--json` puts one document on stdout and moves every human line to stderr. The full table is in [the CLI guide](/info/cli.html). Combined with the byte table above, that gives a build step three honest options: render a vector format bare and compare hashes, render a raster format and compare it visually, or verify the credential and compare provenance. What it never requires is trusting that a hosted service returned the same thing it returned last week. ## Limits - **Time-of-render inputs.** A countdown, a date stamp or a calendar file reads the clock because that is its job. ICS is the clearest case: RFC 5545 requires a `DTSTAMP`, so two runs a second apart differ in exactly that line. - **Live-data lookups.** One shipping tool crosses the network for a value. The Meeting Planner (in the SUSE brand pack) asks `geocoding-api.open-meteo.com` to turn a city name into coordinates and a time zone. It is disclosed on the input itself - the `notice` field in its `tool.json` - it sends only the name, and the answer is cached on the device by the `geocode()` function in that tool's `template.html`, keyed per city. So the first render of a new city depends on an outside service; every later render of that city is offline and identical. The [privacy policy's network table](/info/privacy.html) lists this crossing with every other one. - **Fonts.** Vector export converts text runs to real outlines, so the bytes depend on which font file was resolved. `shells/web/src/bridge/font-registry.ts` pins that by returning an ordered chain - brand catalog faces first, then your own uploaded fonts, then the shell-served platform faces - rather than trusting whatever the operating system happens to have. The CLI resolves sfnt faces under the repo root and refuses a browser-only woff2 with a clear error rather than shaping blanks. A run on a machine with a different font set is a different render, and that is the failure mode the chain exists to make visible. - **Provenance marks are on by default.** A credential carries a fresh signature and timestamp, and the Imprint moves pixels. Both are wanted; both make a default export differ run to run. `--no-provenance` (or `c2pa=off&imprint=0` in a URL) is the deliberate opt-out. - **The characterization harness is local.** It proves that a refactor did not change what a build draws on your machine. It is not a cross-machine byte guarantee, and nothing in the repo claims one for the browser-tier formats. Practical consequence, stated plainly: commit the hash of an SVG rendered with `--no-provenance` if you want a lockfile entry, and compare a PDF, an ICS, a JPEG or anything signed by rendering and inspecting rather than by digest. ## Related - [Constraints](/info/constraints.html) - why the inputs a render depends on are a closed, declared set. - [Reproducibility](/info/reproducibility.html) - those inputs travelling in a link, so you still have them next year. - [Sovereign creative production](/info/sovereign-production.html) - running the whole path on hardware you control. - [CLI](/info/cli.html) - the measured byte-reproducibility table and the flags behind it. --- Source: https://lolly.tools/info/build/reproducibility.md Page: https://lolly.tools/info/build/reproducibility.html # Reproducibility Reproducibility in Lolly means the inputs to a render are portable and durable: the entire state of a design lives in its URL, so the link is the artifact and the picture is a thing the link makes. Open it next year, on another device, and it renders again. This is the neighbour of [determinism](/info/determinism.html) and a different claim. Determinism says the same inputs give the same file. Reproducibility says you still have the inputs - they are a short piece of text you can paste in a ticket, commit to a repository or bookmark, rather than a document trapped in an account. It is what the landing page means by "[same inputs, same file, every time](/info/index.html)" being worth anything at all a year later. ## Every input is expressible as a URL param `engine/src/url-mode.ts` owns the round-trip in both directions: `parseUrlState` turns a query string into input values, `serializeUrlState` turns the current state back into a query string. Being expressible as a URL param is a hard requirement on every input type rather than a convenience some tools happen to implement, which is also why the CLI works: it is that same conversion under a different transport. The names that mean something without being inputs are a closed set, `RESERVED` at `engine/src/url-mode.ts:327`: `format`, `export`, `copy`, `full`, `options`, `slot`, `output`, `filename`, `_v`, `width`/`w`, `height`/`h`, `unit`, `dpi`, `bleed`, `marks`, `cuts`, `c2pa`, `imprint`, `durable`, `hdr`, `depth`, `password`, `profile`, `lang`, `z`, `zx` and a handful more. Everything else in a link is the design. Tools can opt into compact encoding - `urlKey` aliases, `#`-less colours, tilde-delimited block arrays - so a long design stays a manageable link. Where that is still not enough, `engine/src/url-pack.ts` packs the whole query into one token, and `packEncrypted` does the same under a password that is never in the link. ## Physical intent travels too A link carries what the render is meant to be, not just what it looked like on the screen that made it. `width` and `height` are values in `unit` (`px`, `mm`, `cm`, `in` or `pt`), `dpi` sets raster resolution for physical units, and `bleed`, `marks` and `profile` carry the print intent. The conversion is the engine's single source of truth in `engine/src/units.ts`, applied per format at export time - PDF to real points, SVG to unit plus a pixel viewBox, raster to pixels at the requested DPI with a `pHYs` chunk. So `?unit=mm&width=210&height=297&dpi=300&bleed=3mm&marks=crop,reg` is a reproducible A4 print job, not a screenshot someone hopes is big enough. ## Brand references stay references A colour input can hold a token reference rather than a baked hex value, and the reference is what travels. `tests/tokens-value-path.test.ts` pins exactly that: a token-backed colour survives serialization into a shared link and comes back as a reference, while the template only ever sees the resolved string. The consequence is the useful one. A link made against `color.brand.primary` re-renders under whatever that token means when it is opened, so a rebrand updates old links instead of breaking them. A link made with a literal colour keeps that colour. You choose which one you wanted at the moment you picked the value. ## A tool-sourced image is a recipe Paste a Lolly link into an asset picker and the picture it makes becomes an input to another tool. `engine/src/tool-url.ts` recognises every shape the app can hand you - the embed form, the hash share route and the pretty path - and `buildEmbedUrl` canonicalises it into one strict embed URL that becomes the asset's persistent identity. That identity round-trips through URL mode and saved sessions, and the runtime re-renders it through `host.compose.renderUrl` on every load rather than storing a bitmap. A nested chart inside a poster stays live: change the chart's link and the poster changes with it. ## Sessions, projects and templates carry the same state The same state serialization backs the app's own storage. A saved session holds the values; **Share link** on any session in Projects produces the URL form of it; a `.lolly` bundle carries one for handing to a colleague, and an operator who wants a starting point to be permanent records those values as a `templates[]` entry on the tool's manifest in the brand pack, deep-linkable as `?template=` ([Deployment](/info/deployment.html)). Each is the same inputs in a different envelope. ## What has to survive for a link to render in ten years Worth being concrete about the dependency list, because it is short and every item is something you can hold: 1. **The link.** Plain text. Paste it in a ticket, a spreadsheet cell, a commit message or a `README`. 2. **The tool.** A directory: `tool.json`, `template.html`, optional `styles.css`, optional `hooks.js`. Data, not a binary. Keep it in version control if the render matters. 3. **An engine that satisfies the tool's `engineVersion` range.** The manifest declares it and the loader enforces it, so an incompatible pairing is refused rather than rendered wrongly. 4. **The assets the link names.** Catalog assets have permanent ids by design - `suse/logo/primary` is never renamed or reused - so a reference stays valid as long as the pack does. Nothing on that list is an account, a subscription or a running service. Every item is a file you can copy and keep. ## Check it yourself ``` # a link and the CLI carry the same state; both write the same SVG lolly qr-code --url=https://suse.com --export=svg --no-provenance --output=./a.svg # the same inputs, expressed as a URL https://lolly.tools/#/tool/qr-code?url=https://suse.com&format=svg&export ``` Then change one input in the app, copy the link out of the Share dialog and read it. Every value you set is visible in it, which is the whole claim in one glance. ## Prove it yourself Reading a link is one kind of proof. Rebuilding the file is the other. Give the CLI a finished artifact and the `.lolly` of the session that made it, and it renders the session again on your machine and compares: ``` lolly validate ./poster.svg --rebuild ./poster.lolly ``` `IDENTICAL` (exit 0) means the session you kept still produces those exact bytes. `DIFFERENT` (exit 1) names every reason it could check, and only the ones it could check: | Reason | What it means | |---|---| | `engine-version` | The artifact's credential names an engine version, and this machine runs a different one. | | `tool-version` | The tool version recorded in the artifact, in the `.lolly` and in this catalog do not all agree. | | `font` | A face the `.lolly` names is missing here, or resolves to a file with a different sha256. The `.lolly` records each face the render used as family, weight, style and the digest of the whole source font file. No font bytes travel in the file, so this is a name check against your own copy. | | `content` | The bytes differ and none of the above explains it, reported with the offset where they first diverge. | The scope is narrow on purpose. Only `svg`, `emf`, `eps`, `dxf` and `csv` can be compared, because those are the formats whose output comes from the engine's own emitters and is byte-stable across a double render. Asking to rebuild a `png`, `jpg`, `webp` or `pdf` is refused with exit 2 rather than answered: raster and PDF output is laid out and rasterised by a browser engine whose version is part of the result, which is a [determinism](/info/determinism.html) question with a format-by-format answer. Content Credentials are switched off for the rebuild and stripped from the delivered SVG before comparing, since a credential is signed with a fresh key at a fresh timestamp and can never be byte-equal to itself. One more honest limit: the rebuild renders through the CLI. A file exported from the browser was serialised by the web shell, whose SVG writer is not byte-identical to the terminal's, so comparing one of those reports `content`. Compare a CLI export with the CLI, and use the reason list on a web export to see whether the engine, the tool or a font moved under it. ## The receipts | Claim | Enforced by | |---|---| | A query string round-trips through parse and serialize without loss | `tests/engine.test.ts` - `url-mode: round-trips` | | The reserved-param set is exactly the documented one, so an input name can never silently collide with an output setting | `tests/engine.test.ts` - `url-mode: RESERVED set matches the documented reserved-param list` | | An unknown param is ignored rather than misread, so an old link survives a newer tool | `tests/engine.test.ts` - `url-mode: ignores unknown params (forward-compat)` | | Packing is lossless for any query string, including the compact block encoding | `tests/url-pack.test.ts` - `decode(encode(x)) === x` | | A pasted tool link canonicalises to a stable identity that re-parses | `tests/tool-url.test.ts` | | A tool-sourced asset is re-rendered on every load rather than cached as pixels | `tests/tool-url-asset.test.ts` | | A token reference survives a shared link and resolves before the template | `tests/tokens-value-path.test.ts` | | Compact block encoding round-trips a value containing its own delimiter | `tests/engine.test.ts` - `url-mode: a block value containing a ~ round-trips losslessly via the JSON form` | | An artifact and its session rebuild to the same bytes, and a changed input reports why | `tests/reproducibility-rebuild.test.ts` | | A raster or PDF rebuild is refused rather than answered with a coin flip | `tests/reproducibility-rebuild.test.ts` - `a raster or PDF artifact is refused, not guessed at` | | A `.lolly` whose parts do not match its integrity map is refused before anything is believed | `tests/reproducibility-rebuild.test.ts` - `the node reader refuses a .lolly whose parts do not match its integrity map` | ## Limits - **Reproducibility is of the declared render.** `format`, `unit`, `dpi`, `bleed`, `marks`, `profile` and `depth` are parameters like any other. A link reproduces the render it declares. Ask the same link for a different format and you have asked for a different render, and whether that one comes back byte-identical is a [determinism](/info/determinism.html) question with a format-by-format answer. - **Files you uploaded do not travel in a bare link.** Images from your device exist on your device, and a shared URL does not contain them - [Using Lolly](/info/using.html) says so at the Share dialog, which is where you meet the limit. When the bytes have to move, the file is the vehicle: the backup archive carries your profile, every saved session, your uploaded images and your preferences as one zip ([Data Transfer](/info/data-transfer.html) is its format spec), and a `.lolly` file hands a single saved starting point to a colleague. A link whose asset is missing reports the dropped reference rather than rendering something wrong. - **A link renders against the tool the deployment holds.** The Share dialog can stamp `_v`, the tool version the link was made against, and `_v` is reserved for exactly that. What guarantees the old version is still there is the tool being data: a manifest, a template and optional hooks in a directory you can keep in version control and serve yourself. If a render has to survive a decade, keep the tool alongside the link. - **A password-protected link is only as durable as the password.** `packEncrypted` keeps the password out of the URL by design, which means nothing can recover the state without it. - **A link is a snapshot.** Two people opening the same link get the same starting point and then diverge. Editing the same session at the same time is a different feature, described in [Working together](/info/collaborate.html). ## Related - [Constraints](/info/constraints.html) - why what a link can say is a closed, declared vocabulary. - [Determinism](/info/determinism.html) - what happens when the same inputs are rendered twice. - [Sovereign creative production](/info/sovereign-production.html) - holding the tools, the brand and the links yourself. - [URL Mode](/info/url-mode.html) - the full encoding reference, param by param. --- Source: https://lolly.tools/info/build/authoring-tools.md Page: https://lolly.tools/info/build/authoring-tools.html # Authoring Tools A tool is a folder. Drop it in `tools/`, add a `tool.json` + `template.html`, run `npm run build:catalog` to register it, done. (`catalog/tools/index.json` is **generated** from the manifests - never hand-edited; see Publishing.) ## Authoring with AI Agents If you have the lolly.tools repo in front of your agents, you can simply ask them to make tools for you using whatever challenge you think will resolve the design solution. Sounds hard? not if you have the tokens and any source material. Lolly developers tested 600+ human-created logo lock-up combinations as separate svg files with only paths. They then directed agents to create a tool that could reproduce the source material. One lunch-break later and the tool became real, and behaved to our satisfaction. Even if you rely mostly on this method, it's good to understand how tools operate. ## Start from a design you already have You don't always start from a blank manifest. If the layout already exists in **Figma, Penpot, Illustrator or InDesign**, bring it in with the **Design** tool's **[Import a design](/info/design-import.html)** button and skip straight to a working artboard. A finished file - a native Figma `.fig`, a Penpot export or *any SVG* (InDesign and Illustrator export it, and nearly every design app can) - is parsed on your device and opens on the free canvas as editable boxes: text stays retypable, shapes stay shapes, images join your library and type and colours conform to the brand globals. From there it's an ordinary session, so it already behaves like a tool: - **Save it and it's a reusable template** at a URL - anyone with Lolly can open, refill and render it - locked parts still locked - with no design app. - **Mix in other tools.** Drop a QR code, a live chart or another render into any box through the asset picker; embedded tools stay live and re-render on load. - **Render and scale** through the same deterministic export path - SVG/PDF/PNG/video, the batch grid, Projects folders. Import gets you the visual 90% without writing a line of `tool.json`. Reach for a hand-authored, fully declarative tool (sidebar inputs, hard-coded constraints, hooks) when you need those knobs - the anatomy below is that path. ## Anatomy ``` tools/your-tool-id/ ├── tool.json # required - declares inputs, outputs, identity ├── template.html # required - Handlebars-flavoured markup ├── styles.css # optional - auto-scoped to #tool-canvas ├── hooks.js # optional - imperative escape hatch ├── thumb.png # optional - gallery thumbnail (recommended) ├── templates/ # optional - curated starting points (see Templates & presets) ├── i18n/ # optional - .json string overlays (see Localizing a tool) └── assets/ # optional - tool-local images, fonts, etc. ``` ## The manifest (`tool.json`) Validated against `schemas/tool.schema.json`. Required fields: - `id` - lowercase, hyphen-separated, **never changes** once published - `name` - `version` - SemVer; bump on every change - `engineVersion` - SemVer range, e.g. `"^1.0.0"` - `status` - `official` | `community` | `experimental`. Experimental tools **watermark every export** (the host applies it - your tool does nothing). This is the positive counterpart to the `privacy: "on-device"` "no watermark" rule below. - `render` - see [The `render` block](#the-render-block) below. At minimum `{ width, height, formats }`. - `inputs` - array of input declarations (see below) Strongly recommended but not schema-required: `description` (the gallery's About card reads it), `category` and `tags`. Optional: - `requires` - the optional `host.*` APIs your hooks call **without** feature-detecting them: `["text", "tokens"]`, say. The runtime refuses to mount the tool on a shell that lacks one, before any hook runs, and the gallery greys it out there. An API you guard (`if (host.text)`, `host.text?.toPath(...)`) is progressive enhancement and must not be listed. You do not have to maintain this by hand: `node scripts/tool-requires.ts --write` reads your `hooks.js`, writes the list, and raises `engineVersion` to the minor that introduced the newest API you depend on; `validate:catalog` warns when the manifest and the hooks disagree. - `capabilities` - `["network", "filesystem", "clipboard", "camera", "microphone", "ffmpeg", "wasm", "capture", "compose", "screen"]`. Device abilities rather than bridge APIs (that is `requires`, above). Required for the host to expose those APIs to your tool, and what shells use to gate/label the tool where a capability can't be fulfilled. Tools without `"network"` cannot call `host.net.fetch` - and a `"network"` tool must also declare *which* URLs (see `network`, next); tools that use `composes` (below) declare `"compose"`; a tool that records audio through `host.recorder` declares `"microphone"`. - `network` - `{ "allowlist": [...] }`. The https URLs `host.net.fetch` may reach, for tools with the `"network"` capability. Fail-closed: no allowlist, no fetch. See [Network access](#network-access-host-net). - `examples` - example input value-sets that demonstrate the tool's range, rendered live as the gallery tile's preview strip. See [Example looks](#example-looks-examples). - `listed` - boolean, defaults `true`. Whether the tool appears in the gallery listing: the grid, search, favourites and the featured/utility strips. Set `false` to **unlist** a tool that is a *mechanism* invoked from context rather than a destination someone browses to - `asset-export`, reached from the catalog's per-asset **Download**, is the only unlisted tool in the SUSE pack. An unlisted tool still loads normally via `#/tool/`, URL mode and the CLI; unlisting only removes it from the listing. - `new` - boolean. Forces the gallery's **New** badge on this tool regardless of catalog position. Newness is otherwise inferred from catalog order - the most-recently-appended tools wear the badge and it self-expires as later tools ship - so set this to keep a tool flagged after it drops out of that trailing window. - `privacy` - `"on-device"`. Marks a content-transform utility that processes the user's own file entirely on the device. Shows the "Runs on your device - nothing is uploaded" badge; enforces (validated) that the tool is never `experimental` and (at runtime) that exports carry no provenance metadata and no watermark. See the `file` input + `exportFile` hook below. - `hooks` - `{ onInit?, onInput?, onFrame?, onLevel?, beforeExport?, afterExport?, exportFile?, exportStill? }` boolean flags. If any are true, you must ship `hooks.js` with the matching functions. (`exportFile` is the transform path - file bytes in → transformed bytes out; `exportStill` lets a tool own a raster still at a bit depth the 8-bit DOM raster cannot originate (16-bit/HDR PNG, OpenEXR, Radiance); `onFrame` makes the tool react to a live camera; `onLevel` makes it react to live audio levels while recording - all covered below.) The list is exhaustive and the schema sets `additionalProperties: false`, so a hook name that is not here is rejected at validation rather than accepted and silently ignored. - `composes` - embed another tool's render as an image (tool composition; see below). Requires the `"compose"` capability. - `a11yLabel` - accessible description of the rendered output. The preview canvas is exposed to screen readers as a single `role="img"`; this is its label. It's a Handlebars string hydrated with the current input values (same context as the template), so it stays accurate as the user edits - e.g. `"QR code linking to {{url}}"` or `"Meeting plan for {{default count \"a\"}} people"`. Use `{{default x \"fallback\"}}` for empty inputs. Omit it and the label falls back to `" preview"`. Keep it short and factual - it replaces, not supplements, the canvas contents for SR users. None of those fields stay private to the repo. The gallery's About card is the manifest read back to whoever is deciding whether to open the tool: name, category and status from identity, the export chips and canvas size from `render`, the version and a `capabilities` line whenever the tool declared any. ![The About card for the Filter tool, listing its exports grouped as vector, raster and video chips, its 1080 by 1080 canvas and its version, all read straight from the manifest](/t/url-shot?url=%2F%23%2F%3Ftool%3Dfilter&width=1440&height=1200&dpi=192&waitMs=2200&css=.welcome-dialog%2C.personalize-nudge%2C.brand-tips%7Bdisplay%3Anone!important%7D&format=svg&walker=1&cropSelector=.meta-dialog-body&dark=1&filename=at2-manifest-about-card) ### The `render` block Most of what `render` declares surfaces in one place the user sees: the export popup. Formats, page size and unit, the Convert paths outlining toggle and the Content Credentials card are all keys below. ![The export popup - format and size fields, a Convert paths toggle and a pre-ticked Content Credentials card](/t/url-shot?url=%2F%23%2Ftool%2Fwordmark%3Foptions&width=1440&height=900&dpi=192&waitMs=2200&css=.export-popup%7Bwidth%3A360px!important%7D&walker=1&format=svg&cropSelector=.export-popup&dark=1&filename=auth-export-popup) `render` carries `width`, `height`, `formats` (`svg`, `png`, `pdf` and the rest of the ids in the `render.formats` enum in `schemas/tool.schema.json` - vector, raster, print, document, motion, audio, data and font outputs. The enum is the authority on the whole set; [URL Mode](/info/url-mode.html) says what each id produces), plus these optional keys: - `actions` - which action buttons to show. One or more of `copy`, `download`, `save`, `share`. **Defaults to `['copy','download','save']`** if omitted. - `export` - set `false` for utility/interactive tools with no export (hides the download/copy/format/dimension bar; shows **Save** only when the tool has inputs). - `layout` - `sidebar` (default), `canvas`, `editor`, `document` or `deck`. `canvas` hides the sidebar and presents the tool as a full-bleed working area; a single declared `file` input becomes a drag-and-drop / click-to-pick zone on the canvas itself (used by `strip-data` - drop a file → get a file back). `editor` is a chromeless **free-canvas WYSIWYG** surface: the sidebar is hidden but the render canvas and export controls stay, and the shell mounts a select / drag / resize / rotate / snap overlay driven by one `blocks` input whose rows carry a `canvas` geometry flag (x/y/w/h/rotation). The shell also provides pasteboard behaviour for free: boxes dragged past the artboard stay visible and selectable (gently faded outside the frame, which keeps its shadow as the export boundary), while exports remain bounded by the canvas in every format - the tool template does nothing to opt in. The data stays flat and URL-expressible, so CLI and URL renders are identical. `design` is the reference tool. `document` is a chromeless **multi-page rich-text document** surface for paged tools: like `editor` it keeps the render canvas and export controls, but the shell mounts a word-processor editor (an on-canvas format ribbon for paragraph style H1–H4 / bold / italic / lists / colour, per-block width/align/move/delete, click-to-edit contentEditable and rich-HTML paste that becomes headings/lists/tables). It drives one `blocks` input (`content`) whose rows are content blocks (heading/text/lolly/table); **pagination lives in the tool's hook** (blocks flow into fixed-size `[data-pdf-page]` boxes, the same paged-export mechanism `multi-page-pdf` uses), so CLI and URL render the same document with no editor. `doc-studio` is the reference tool. `deck` is a **slide-deck editor** and, unlike the other three, it **keeps the input sidebar**: the shell mounts a live on-canvas overlay over one `blocks` input whose rows are slides, so a slide can be clicked and its text, colour and images edited in place, with a thumbnail rail for navigation. The DOM stays bounded because edit mode renders one active slide plus tiny thumbnails; the overlay restores a full-deck render before export. - `units` - defaults `true`. Set `false` to offer **pixels only**: it hides the physical-unit (`mm`/`cm`/`in`/`pt`) selector and the DPI field from the download bar, so width and height stay in px and an on-screen pixel is an exported pixel. Use it for output that is inherently pixel-measured (screen assets, favicons) where a print size would make the width/height inputs disagree with the true raster resolution. - `convertPaths` - defaults `true`. When the tool exports a vector format, the engine **auto-injects a "Convert paths" toggle** that outlines text to vector paths (in SVG/PDF/PDF-CMYK) so the output renders identically without the fonts installed. Set `false` to suppress it and never outline - e.g. a capture tool whose output is raster (`url-shot`), or a tool that draws its text as raster/canvas before export (`event-name-badge`, `wayfinding-signage`). - `transparentBg` - defaults `false`. Adds a **"No BG"** (transparent background) toggle to the export bar; the engine injects it into the input model so hooks can react via `onInit`/`onInput` (`chart-creator`). - `preview` - `{ format?, auto? }`. Marks a tool whose live canvas is a placeholder until an explicit, expensive render runs (e.g. a capture tool that screenshots a page in `beforeExport`); the shell wires a `[data-preview]` control. `auto: true` renders one frame on load. Used by `url-shot`. - `video` - `{ wait?, duration? }` (seconds; defaults `1` / `5`). Capture timing used when `webm`/`mp4`/`gif`/`apng` is in `formats` (`digi-ad`). - `liveMaxEdge` - integer px. For `onFrame` (live camera) tools only: the requested longest edge of the working camera frame handed to the hook. The shell downscales the source camera to a small default that suits a vector trace, so a raster-output effect (the `filter` tool's pixel-stretch) raises this for sharper output. The shell clamps it to the native camera frame - it never upscales - and to its own ceiling, and ignores it for tools without `onFrame`. A companion `liveMaxEdgeInput` names a number input whose value overrides it, so the resolution can be a user-facing slider (re-applied to the live stream on change). - `c2pa` - defaults **`true`** (Content Credentials are **opt-out**). The **Content Credentials** card in the export popup is pre-checked for every stampable format (`pdf`, `png`/`apng`, `jpg`, `gif`, `svg`, `tiff`/`cmyk-tiff`, `webp`, `mp4`/`webm`, zip members), so the finished file gets a signed C2PA manifest (on-device key, so viewers report it as an unverified credential). Set `false` to opt a tool out. Forced **off** for `privacy: "on-device"` tools, which must never embed provenance into a user's own file. A `?c2pa=` link/save value overrides this per export. - `dims` - set `false` to hide the export dimension inputs in the download bar. - `aspectWarning` - `{ min?, max?, message }`. An **editor-only** amber caution shown in the Export popup when the chosen page aspect (`width ÷ height`) falls outside `[min, max]` (either bound optional). It's purely a guard against picking a size that breaks the layout - it never appears in the exported output. `multi-page-pdf` declares `{ "max": 1, "message": "…" }` (portrait-only). **Physical units & print.** `width`/`height` are values in the export's `unit` (`px` default, or `mm`/`cm`/`in`/`pt`), and `dpi` sets raster resolution for physical units. PDF exports a true page size; the CMYK formats (`pdf-cmyk`, `cmyk-tiff`) pair with the `convertPaths` outlining toggle to produce print-ready, fonts-not-installed output. A `select` option can also carry `width`/`height`/`unit` to drive the export page size from a dropdown - e.g. `wayfinding-signage`'s **Sign size** select (A4/A3/A2… in mm) sets the printed page proportions when chosen. - `printMarks` - unset by default: the print-finishing card is offered for the print-capable formats, but its master toggle starts **off** for RGB vector output (`pdf`/`svg`/`eps`) and on only for the separating press formats (`pdf-cmyk`/`cmyk-tiff`/`eps-cmyk`). Set `true` to **declare print intent** - the card then defaults on for every print-capable format. Set `false` to opt a tool out of the card entirely (crop/registration/bleed marks). Multi-page PDF tools set `false` because their output is a paginated RGB document, not a single marked plate. Physical units (`mm`/`cm`/`in` + `dpi`) alone never enable marks or bleed - an explicit `bleed=`/`marks=` link, a user toggle or declared print intent does. - `paged` - defaults `false`. Marks a multi-page document tool (one that lays out several `[data-pdf-page]` boxes, like `multi-page-pdf`); the gallery renders each page as its own horizontally-scrollable preview slide rather than input-variant examples. - `paginate` - `{ "source": "" }`. **Engine-driven pagination**: the runtime hydrates your template once per row of the named `table` input and wraps each hydration in its own `[data-pdf-page]` box - you author ONE page and never manage pagination, page counts or loops. Each hydration's context gains a `page` object: `page.index`/`page.number`/`page.count`, `page.first` (the row's first cell - the natural page title), `page.cells` (`[{ column, value, col }]` for every column - `col` is the cell's original column index, stable even when the template renders only a subset), `page.fields` (cells minus the first - the labelled body fields, whose labels are the user's own column headings) and `page.byColumn` (trimmed lower-cased column name → the row's cell, for by-name lookup: `{{lookup page.byColumn "icon"}}`). A template can opt any rendered cell into the web shell's on-canvas editing by stamping it `data-cell="{{page.index}}:{{col}}"` (add `data-cell-md` when the cell holds markdown rendered with `{{{markdown value}}}` - edits round-trip back to markdown in the table input; without it the cell edits as plain text), and can offer a click-to-pick image slot with `data-cell-pick="{{page.index}}"` plus `data-pick-column="Icon"` (the column written to, created if absent) and optional `data-pick-tag="icon"` (catalog tag filter). Pair with `paged: true` for the scrolling all-pages canvas and filmstrip. `battlecards` is the reference tool: a hook-free one-card template that turns any pasted table into a multi-page PDF, one card per row. - `filmstrip` - `"left"` (default) or `"bottom"`. Which edge a `paged` tool's slide-sorter thumbnail rail runs along. `left` is a vertical rail beside the canvas, right for tall documents; `bottom` is the deck-strip shape, for tools whose pages are wide and few (cards, slides), where a left rail eats the width the page needs. `battlecards` uses `bottom`. - `pages` - `{ count, width, height, gap?, min?, max? }`. Turns an `editor`-layout tool into a **multi-page canvas** (the social-carousel pattern): the shell sizes the canvas to a horizontal strip of N same-size page frames and the free-canvas overlay places boxes across all of them. Box coordinates stay one flat, global, URL-expressible array; the tool's hook derives which page each box belongs to and emits one `[data-pdf-page]` frame per page, so headless CLI/URL renders match and export fans out (multi-page PDF, or one still per page). Requires `layout: "editor"` and `paged: true`. Each property names the input id the geometry is read from (`count`/`width`/`height` are number inputs), so the shell stays generic. **Multi-page PDF.** A tool builds a paginated PDF by marking page boxes in its template with `data-pdf-page` - each flagged element becomes one true PDF page sized to its own CSS box, so a cover, content that flows across pages and a back page render as real pages rather than one tall image. Pages are drawn as vectors (text outlined to paths) and the document can carry an open-`password`. The path falls back to the normal single-page renderer when no `[data-pdf-page]` boxes are present, and it bypasses the crop/bleed print-finishing path (pair it with `printMarks: false`). See the `multi-page-pdf` tool for the reference layout (cover + flowing `blocks` content + back page). ### Example looks (`examples`) A tool ships one committed thumbnail, but `examples` lets its gallery tile demonstrate *range*: an array of example input value-sets, each rendered live on the client (the same off-screen engine path an export takes) as a horizontally-scrollable preview strip - and, when the tool is `featured`, as the hero row's cross-fade. Each look is memoised, so later visits are instant. Omit it for a tool whose single committed preview says enough. ```jsonc "examples": [ { "label": "Launch teal", "values": { "heading": "Ship it", "background": "#0c322c" } }, { "label": "Reverse mark", "theme": "dark", "values": { "ink": "mono" } } ] ``` - **Key `values` by input `id` - never by `urlKey`.** Example values seed the runtime the way batch-row values do: resolved by input id only. A `urlKey` is URL-mode transport, so a urlKey-keyed value would silently render the tool's *default* look - and the validator errors on it. When copying a compact share link into an example, translate each short key back to its input id first. - `label` documents the look's intent (it isn't shown to end users). `theme` (`light` / `dark`) is only for looks that render ink on a **transparent** background (e.g. a reverse/white logo) - the clashing theme filters that look out of the strip; omit it when the look bakes its own background. - `width` / `height` in `values` are honoured as per-example preview dimensions even when the tool declares no such inputs. - An `asset` value must be a **ref object**, never a bare string: `{ "source": "library", "id": "your/asset/id", "_unresolved": true }` (optionally with a `?theme=` suffix on a themable icon id). A `blocks` value is an array of row objects keyed by the block's declared field ids. `npm run validate:catalog` checks every look: `values` keys must be declared input ids (a urlKey gets a pointed error naming the right id), catalog asset refs must exist (and any `?theme=` suffix must name a real icon theme), blocks-row keys must be declared fields. It also warns when a tool declares looks but no gallery-displayable format (svg/png/jpg/jpeg/webp), and when a strip exceeds 8 looks - each look is a live render, so keep it to a handful of genuinely different ones. The pre-`examples` alias `featured.variants` still renders but is deprecated - author `examples`. ### A short walkthrough (`guide`) Some tools aren't finished when the render is. An email signature is finished the moment it's pasted into Gmail's settings, and nothing on the canvas says so. `guide` is a handful of steps for that last mile, shown by the shell as a dialog behind a help button beside the tool's name - and opened once automatically the first time a device opens the tool. ```jsonc "guide": { "title": "Put it in Gmail", "tracks": [ { "id": "desktop", "label": "On a computer", "steps": [ "Open **Export**, set the format to **HTML**, and press **Copy**.", "In Gmail, open **Settings** and choose **See all settings**.", "Paste into **General → Signature**, then press **Save Changes**." ], "note": "Outlook and Apple Mail take the same paste." }, { "id": "mobile", "label": "On a phone", "steps": ["…"] } ] } ``` - **One track per route the user might take** (on a computer vs on a phone). A single track renders as a plain numbered list; two or more render as tabs, so the alternative is visible rather than buried. Up to four tracks, eight steps each. - **Steps are plain text.** `**bold**` is the only markup, for naming the control a step points at - everything else is escaped. Link to the docs from a step when the long version is what's wanted: this is a nudge, not documentation. - **`id` is a contract like an input id** - the i18n sidecar path is built from it (`guide.tracks..label` / `.note` / `.steps.`, plus `guide.title`), so renaming one orphans its translations. - Point at controls the shell actually has. If a step says "set the format to HTML", `render.formats` had better still include `html`. ### Input types Each declaration becomes a real control, built by the shell from the input model - you never write the UI. Six lines of `inputs` in `qr-code`'s manifest produce this entire sidebar. ![One declared input, one generated control: a url, a colour, a select, a number, a boolean](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code&width=1440&height=900&dpi=192&waitMs=2000&cropSelector=.tool-inputs&walker=1&format=svg&dark=1&filename=aud-manifest-controls) ![The QR tool's sidebar - a URL field, two colour swatches, an error-correction dropdown, a quiet-zone slider and a joined-modules toggle, all generated from the manifest](/t/url-shot?url=%2F%23%2Ftool%2Fqr-code%3Furl%3Dhttps%3A%2F%2Flolly.tools&width=1440&height=900&dpi=192&waitMs=2200&walker=1&format=svg&cropSelector=%23tool-inputs&dark=1&filename=auth-input-controls) | Type | What it produces | UI control | |------------------|-----------------------------------------------------------|---------------------| | `text` | string | text input | | `longtext` | string | textarea | | `number` | number | input or slider | | `boolean` | boolean | checkbox | | `color` | string (hex) | color picker, or constrained to a palette asset via `palette: "asset/id"` | | `select` | string (one of `options[].value`); an option may carry `width`/`height`/`unit` to set the export page size | dropdown | | `asset` | `AssetRef` object (id, url, type, etc.) | host-provided asset picker | | `date` | ISO date string | text input in the sidebar; native date field in the `/pro` grid | | `time` | `HH:MM` string | time input | | `datetime-local` | ISO datetime string | flatpickr datetime picker | | `url` | string | text input | | `blocks` | array of objects (repeating field groups) | add/remove/reorder row editor | | `vector` | object `{ fieldId: number }` (a fixed set of numbers) | one row of zoom x/y controls | | `file` | a `FileRef` (the user's own file: `name`/`mime`/`size`/`bytes`) | file picker (on-device utilities) | | `table` | `{ columns: string[], rows: string[][] }` - a user-defined grid where the column headings AND rows are data (unlike `blocks`, whose fields you declare) | minimal grid editor with spreadsheet paste (TSV / Markdown / CSV), copy-out and a pop-out floating window | A `table` input is the batch-creation primitive: paste a table copied from Excel / Google Sheets / Notion / Slack / Markdown and it replaces the whole grid; the Copy button writes TSV *and* a real HTML `` back to the clipboard so the round trip into collaboration tools is lossless. Cells can hold whole paragraphs. Pair it with `render.paginate` (below) and each row becomes a page. In URL mode the entire table is ONE compact param; in the CLI, `---data=table.csv` fills it from a CSV/TSV/Markdown file. Four declarations of four different types are four different controls. `color-palette` declares exactly that and nothing else: a `color`, a `select`, a `number` and a `boolean`. ![Colour Palette's whole sidebar - a swatch trigger, a harmony dropdown, a shades slider and a neutrals switch, one control per declared type](/t/url-shot?url=%2F%23%2Ftool%2Fcolor-palette%3Fseed%3D%25232563eb%26harmony%3Dtetrad-4%26steps%3D9&width=1440&height=900&dpi=192&waitMs=2000&format=svg&cropSelector=%23tool-inputs&walker=1&dark=1&filename=at2-input-types-palette) `text` and `longtext` differ only in the declaration, and the shell picks the control: a single-line field for one, a sized textarea for the other. `prompt-card`'s prompt is a `longtext`. ![The prompt field in Prompt to Image - a tall textarea holding many lines, produced by nothing more than type longtext](/t/url-shot?url=%2F%23%2Ftool%2Fprompt-card&width=1440&height=900&dpi=192&waitMs=2000&css=%23tool-canvas%7Bdisplay%3Anone%7D&walker=1&format=svg&cropSelector=.input-row%3Ahas%28%5Bdata-input-id%3D%22text%22%5D%29&dark=1&filename=at2-input-longtext) The three moment types (`date`, `time`, `datetime-local`) are real input types with real controls, but no tool in the open community set declares one, so there is no screenshot of them here. #### `blocks` - repeating groups A `blocks` input is a list of repeating sub-records (e.g. team members, each with a name and city). Declare the per-row fields under `fields`: ```json { "id": "people", "type": "blocks", "label": "Team members", "fields": [ { "id": "name", "type": "text", "label": "Name" }, { "id": "city", "type": "text", "label": "City" } ] } ``` In the template, iterate with `{{#each people}}…{{/each}}`. The value round-trips to the URL as a JSON array (see `docs/url-mode.md`); very large lists outgrow a pasteable link - the shell auto-compresses long queries (the packed `z` form) and warns past ~2,000 chars, so share those states via a saved-state `slot` instead. Blocks are edited in a side panel, and clicking a rendered block on the canvas focuses that block's field. `meeting-planner` is the reference implementation for the simple (homogeneous) case. ![Deck Studio's blocks input - each row is its own card of fields, carrying the row type as its label and an Add slide button below the stack](/t/url-shot?url=%2F%23%2Ftool%2Fdeck-studio&width=1440&height=900&dpi=192&waitMs=2200&walker=1&format=svg&cropSelector=.blocks-input%5Bdata-input-id%3D%22deck%22%5D&dark=1&filename=auth-blocks-rows) **Advanced blocks (typed / heterogeneous rows).** Sub-fields aren't limited to `text` - a field may be `text`, `color`, `select`, `asset`, `number` or `boolean`. And the row set can be **discriminated** by a `select` sub-field: - `addMenu: { field, label }` turns the **"+ Add"** button into a typed menu - each option of the named discriminator sub-field becomes a menu entry. The discriminator is fixed at creation and shown as the block's label rather than an editable control. An entry already used is disabled unless its option sets `repeatable: true`. - `showFor: ["kind"]` on a sub-field limits it to blocks whose discriminator value is listed. - `multilineFor: ["kind"]` (with optional `rows`) renders a text sub-field as a textarea for those discriminator values. `color-block` is the reference for typed/heterogeneous blocks (`addMenu` keyed on a `kind` select, `showFor`, `multilineFor` and the full sub-field type set). **Drop files to add rows.** A `blocks` input may declare `dropToAdd: { field, accept }` - dropping one or more files onto the blocks list appends one row per file, uploading each into the named `asset` sub-`field` (the row's other fields start at their defaults). `accept` is a MIME filter (default `image/*`). `logo-wall` is the reference: drop many logos → one block each. (It ships with the **SUSE brand pack**, so it is only on disk on a profile that mounts that pack.) **Paste a Markdown document (`mdPaste`).** A `blocks` input may set `mdPaste: true` to add a **Paste Markdown** button to the blocks toolbar: it reads the clipboard, splits the Markdown into one block per heading (heading line → the block's `heading` field, the section beneath → its `body` field, kept as Markdown for a `{{markdown}}` render) and appends the blocks - so a whole document arrives as editable, page-flowing blocks. Used by the paged/document tools. **Import rows from a spreadsheet (`importData`).** A `blocks` input may declare `importData: { formats?, mode?, columns? }` to offer an **Import data** button that fills the whole list from a **CSV or JSON** file - the ingest counterpart to CSV/JSON *export*. The engine (`parseDataRows`) maps columns onto the block's sub-fields: an explicit `columns` map (`{ fieldId: "Column Name" }`) wins, otherwise each column header/key is matched case-insensitively to a field's `id` then its `label`. `formats` limits the accepted types (default both); `mode` is `replace` (default) or `append`. JSON may be an array of objects, an array of arrays (positional, in field order) or `{ "data": [ … ] }`. The imported rows are ordinary blocks - they serialise to the URL and save like any hand-entered data. `chart-creator` is the reference: import a two-column `Label,Value` sheet to chart it. **Reference pickers (`optionsFrom`).** A sub-field can be a dropdown whose choices are the *rows of another blocks input* - so a row references another row by a friendly name instead of a hand-typed id. Declare `optionsFrom` on the field: ```json { "id": "parent", "label": "Reports to", "optionsFrom": { "input": "nodes", "value": "nodeId", "label": "label", "excludeSelf": true, "excludeDescendants": true, "emptyLabel": "- Top level -" } } ``` The value **stored** is the target row's *derived id* - `slug(value field)`, else `slug(label)`, else an ordinal, de-duplicated - i.e. exactly the id a hook resolves with (your hook should slug both a row's id and the back-reference, so the two agree). A stored value matching no current row is shown as a selected **"(unknown)"** option rather than vanishing, so a stale reference is visible. Options: `value`/`label`/`prefix` (the source sub-fields + ordinal prefix), `sources: [{input,value,label}]` to merge several inputs (e.g. cards **and** layers, de-duped by value), `freeText: true` for a combobox (datalist) that also accepts a typed-in value (e.g. a new kanban column), `excludeSelf`, `excludeDescendants` (needs `nesting`, below) and `emptyLabel`. **Tree blocks (`nesting`).** A `blocks` input can be edited as a tree: the sidebar renders the flat array as an **indented outline** (pre-order) and the header drag drops a card **above / below** (sibling) or **inside** (child) another, updating its parent reference - the whole subtree travels with it. The data stays a flat reference-by-id array, so it serialises and renders exactly as before (the renderer still walks the parent pointers). Declare `nesting` on the input: ```json { "id": "nodes", "type": "blocks", "nesting": { "parentField": "parent", "keyField": "nodeId", "labelField": "label", "activeWhen": { "diagramType": ["org", "mindmap"] } } } ``` `activeWhen` gates tree mode by top-level input values (an array value matches by membership); omit it to always nest. `diagram-builder` is the reference for both `optionsFrom` and `nesting` (org / mind map nest; process / kanban / layercake stay flat and reference by picker). #### Editor canvas: connectors, grid & fixed size (`canvas.connect` / `grid` / `fixedCanvas`) A `blocks` input carrying a `canvas` object is the free-form WYSIWYG artboard behind `render.layout: "editor"` (see [The `render` block](#the-render-block)): its `*Field` keys map each row's geometry (`xField`/`yField`/`wField`/`hField`/`rotationField`, plus fill/text/image sub-fields) so the shell can mount its select / drag / resize / rotate overlay while the data stays a flat, URL-expressible array. The shell mounts the whole editor rail for you - add, arrange, undo and the primary export actions - so the manifest declares geometry fields and nothing else. ![The free-canvas editor rail the shell mounts for an editor layout - add, arrange, undo and export, none of it declared by the manifest](/t/url-shot?url=%2F%23%2Ftool%2Fdesign%3Ftemplate%3D__blank__&width=1440&height=900&dpi=192&waitMs=2400&css=.fc-toolbar%7Bopacity%3A1!important%7D&walker=1&format=svg&cropSelector=.fc-toolbar&dark=1&filename=auth-editor-rail) Three of the `canvas` keys turn a plain box canvas into a **diagram editor**: - **`grid`** - `{ size, default }`. Opt into snap-to-grid: the overlay rounds drag/resize to a lattice of `size` canvas px, starting on when `default` is true (per-axis alignment guides still win; holding Alt disables the snap). - **`fixedCanvas`** - `true` locks the canvas to `render.width`/`render.height`: the shell withholds `setCanvasSize` and ignores reserved `?width`/`?height`, so box coordinates stay 1:1 with the render size. **Required whenever a hook draws into a fixed-viewBox overlay** - e.g. connector arrows in an `` sized to the artboard. - **`connect`** - opts into connector authoring: a Connect-mode rail button (click a source box, then targets), a live connector preview and an Auto-arrange (tidy-tree) button. **Edges are stored as rows of a _second_ `blocks` input** named by `input`; the tool's `hooks.js` reads that array and renders the arrows (as an SVG of filled paths - one artboard-sized `` per the `org-chart` pattern). The `*Field` keys name sub-fields of that connectors block: ```json "connect": { "input": "connectors", "fromField": "from", "toField": "to", "styleField": "style", "arrowField": "arrow", "headField": "head", "colorField": "color", "dashField": "dash", "widthField": "width", "layerClass": "oc-connectors", "defaultStyle": "elbow", "defaultArrow": "end", "defaultHead": "triangle", "defaultColor": "#94a3b8", "defaultWidth": 2.5 } ``` - `fromField` / `toField` (default `from` / `to`) hold the source and target **box ids**. - `styleField` - route flavour select (`straight` / `elbow` / `elbow-v` / `elbow-h` / `elbow-src` / `elbow-tgt` / `curved`). - `arrowField` - which ends carry an arrow (`none` / `end` / `both`); `headField` - arrowhead **shape** (`triangle` / `open` / `circle` / `diamond` / `bar`). - `colorField` (color) / `dashField` (`solid` / `dashed` / `dotted`) / `widthField` (number, px) - the edge's line styling. - `layerClass` - the CSS class on the tool's rendered connector ``, which the shell hides mid-drag while it paints its own live preview. - `default*` (`defaultStyle` / `defaultArrow` / `defaultHead` / `defaultColor` / `defaultWidth`) - the values a newly-drawn edge starts at. `org-chart` is the reference implementation: an `editor`-layout box canvas with `grid`, `fixedCanvas: true` and a `connect` writing to a `connectors` blocks input whose rows its hook turns into one artboard `` of arrows. It ships with the **SUSE brand pack**, so it is only on disk on a profile that mounts that pack. A canvas that maps the **ten time sub-fields** (`startField`, `durField`, `clipInField`, `speedField`, `enterField`, `exitField`, `enterMsField`, `exitMsField`, `muteField`, `laneField`) becomes a timeline editor: the shell mounts the timeline panel, the clock and the sequence export path. All ten or none - a partial mapping gives the panel somewhere to read from and nowhere to write, so it is treated as absent. Three further keys are **optional** on top of that, each additive on its own: - **`zField`** - names the `number` sub-field holding a box's depth (px above the surface). Consumed by the projection and depth-ordering math; the depth shadow (below) reads the same field. - **`kfField`** - names the `text` sub-field holding a box's keyframe track (see `docs/url-mode.md`'s Keyframe tracks section for the wire grammar). A track can key a box's **size** as well as its pose (`w`/`h`, absolute px), and that is the one channel pair whose effect is a real re-layout: a size tween reflows, so text rewraps and a border stays one pixel wide. Hooks must treat this field as **strict emission only**: parse it and re-serialise the result, never pass the raw stored value through to a rendered attribute - a hand-edited share URL can put anything in a text field, so the re-serialise step is what keeps only charset-clean tokens on the page. - **`linkField`** - names the `text` sub-field holding the **A/V detach back-reference**: the id of the box this one was detached from (or onto), written on **both** sides when a clip's audio is split onto its own lane, so re-attaching works from either end. Machine-written by the timeline panel and never typed, so it wants `showFor: []`. It is not an instancing or geometry-sharing mechanism; a tool that omits it is still fully time-capable, it just never offers "Detach audio". Two more depth affordances are values inside existing declarations rather than `canvas` keys. A box `kind` of **`camera`** is a non-visual marker that aims and dollies the view: it has **no canvas footprint** - excluded from hit-testing, marquee, align/distribute and z-order, selected from its timeline bar or chip and contributing no pixels to the export. And the `shadow` select can gain a **`depth`** option alongside the existing choices - a drop-shadow derived from the box's `zField` value (falling off with depth) rather than a manually authored offset/blur/colour. #### `vector` - a group of numbers as one control Use `vector` when a few related numbers belong together - zoom + pan, an x/y offset, padding, margins. Instead of separate `number` inputs (one column each in `/pro` bulk mode), a `vector` is **one input, one control, one column**: a row of compact number fields where each label can be dragged to scrub the value (Figma-style) or typed into. Declare the numeric sub-fields under `fields`: ```json { "id": "imageFraming", "type": "vector", "label": "Zoom & Position", "fields": [ { "id": "zoom", "label": "Zoom", "min": 100, "max": 400, "step": 1, "default": 100 }, { "id": "x", "label": "X", "min": 0, "max": 100, "step": 1, "default": 50 }, { "id": "y", "label": "Y", "min": 0, "max": 100, "step": 1, "default": 50 } ] } ``` ![A vector control in Mesh Gradient - one labelled row of compact number fields you can drag to scrub or type into](/t/url-shot?url=%2F%23%2Ftool%2Fgradient&width=1440&height=900&dpi=192&waitMs=2000&css=%23tool-canvas%7Bdisplay%3Anone%7D&walker=1&format=svg&cropSelector=.input-row%3Ahas%28.vector-input%5Bdata-input-id%3D%22pos1%22%5D%29&dark=1&filename=auth-vector-control) The value is an object keyed by field id, so the template reads each part with dot access: `{{imageFraming.zoom}}`, `{{imageFraming.x}}`, `{{imageFraming.y}}`. Each field clamps to its own `min`/`max` and falls back to its `default`. In URL mode (and `/pro` CSV) each field is its **own flat param/column**, namespaced `"."` - e.g. `?imageFraming.zoom=200&imageFraming.x=30&imageFraming.y=70`, or CSV columns `imageFraming.zoom`, `imageFraming.x`, `imageFraming.y`. There is no `urlKey` on a vector. `quotes` (`imageFraming`) is the reference implementation; the `filter` tool carries the same control per raster effect (namespaced, e.g. `du_imageFraming`). `imageFraming` is a **canonical input** (see below) - reuse that id and field set verbatim for any zoom/pan-an-image control rather than inventing a synonym. #### Framing an image: one pattern, every tool Placing, cropping, straightening and perspective-correcting an image is ONE control everywhere (plans/148). Do not write an `object-position` string by hand, and do not invent a second set of ids for the same job: a divergent id or range forfeits the shared `/pro` column, and a hand-written recipe drifts from what the export paths actually do. Three declarations make an image slot framable: ```json { "id": "image", "type": "asset", "assetType": "image", "label": "Image" }, { "id": "imageFraming", "type": "vector", "label": "Zoom & Position", "framingFor": "image", "fields": [ { "id": "zoom", "label": "Zoom", "min": 100, "max": 400, "step": 1, "default": 100 }, { "id": "x", "label": "X", "min": 0, "max": 100, "step": 1, "default": 50 }, { "id": "y", "label": "Y", "min": 0, "max": 100, "step": 1, "default": 50 }, { "id": "rotate", "label": "Rotate", "min": -180, "max": 180, "step": 0.5, "default": 0 }, { "id": "pitch", "label": "Vertical", "min": -45, "max": 45, "step": 0.5, "default": 0 }, { "id": "yaw", "label": "Horizontal", "min": -45, "max": 45, "step": 0.5, "default": 0 } ] }, { "id": "imageFit", "type": "select", "label": "Fit", "attachTo": "image", "display": "icon-toggle", "default": "cover", "options": [ { "value": "cover", "label": "Fill", "icon": "fitCover" }, { "value": "contain", "label": "Fit", "icon": "fitContain" } ] } ``` Every field is opt-in: declare `x`/`y` alone for a pan-only slot, add `rotate` to straighten, add `pitch`/`yaw` for perspective correction (the Geometry panel in Lightroom, "Adjust" in Instagram). `framingFor` names the asset input the vector frames, and it is what turns the sidebar numbers into a real on-canvas control. Render it with the **`{{framing}}` helper**, which emits the placement CSS *and* the marker the shell binds to, in one call: ```html ``` The argument is the input's **id**, not its value - the helper needs the id for the marker, and reads the value (and the companion `imageFit`) off the render context. A `style=` option appends your own declarations after the geometry, and `persp=` overrides the viewing distance the pitch/yaw envelope projects through. What the author gets for those three declarations, with no shell code and no per-tool branch: - **on the canvas** - tap the image to arm it, then drag to move, scroll or pinch to zoom about the pointer, drag the top handle to straighten (Shift snaps to 15 degrees), hold Alt and drag to correct converging verticals, double-click to reset, arrow keys to nudge, Escape to release. One drag is one undo step; - **"Use as a new image"** - bakes the framing into new pixels saved to the user's library as a child of the original, with the source carried as a Content Credential ingredient, then points the input at the child and resets the framing. Only ever on an explicit click; the default outcome is always that the original is untouched and the framing lives in the URL; - **URL and CLI parity** - `?imageFraming.zoom=180&imageFraming.yaw=-6`, or `--imageFraming.yaw=-6`, with no extra work; - **export parity** - the same numbers place the image in the SVG walker, the PDF vector path, the raster path and PPTX. One caveat before you offer `pitch`/`yaw`: a tilted plane is a projective homography, which SVG and PDF have no transform for. Pan, zoom and roll stay fully vector; a *tilted* image exports through the walker's posed-raster path for that element instead. Leave the two fields out of a tool whose output must stay vector at all costs. **Canvas-drawing tools** (a hook compositing into a ``) call `drawFramed(ctx, source, iw, ih, W, H, framing, fit)` from `community/_shared/framing.js` instead of the helper, and mark the rendered element with `data-framing="imageFraming"` by hand. It is the same maths - a fixture table pins the two implementations equal - so a canvas tool and a DOM tool place the same photo the same way. **Inside a `blocks` row** a sub-field cannot be a `vector`, so the same values live as sibling numbers `Zoom` / `X` / `Y` / `Rotate` / `Pitch` / `Yaw`. Put `framingFor: ""` on the row's asset sub-field and render with the helper's block mode: ```html {{#each blocks}}{{/each}} ``` Whole marks are the documented exception: `logo-wall`, `logo-lockup-partner` and `snippet`'s title icon offer scale only, because a logo is not cropped. #### `asset` - library or device upload An `asset` input opens the host's asset picker and stores the chosen `AssetRef` - uniform whether it came from the catalog or the user's device: ```json { "id": "logo", "type": "asset", "label": "Logo", "assetType": "image", // vector | raster | image | video | audio | lottie | any - constrains the picker "allowUpload": true // also let the user add an image from their device } ``` `assetType` constrains what the picker offers: `raster` (bitmaps only), `vector` (SVG only - for inline-recolourable logos), `image` (**any still image - raster _or_ vector**, the right choice for a generic picture slot), `video`, `audio` (`audiogram` uses this), `lottie` or `any` (everything, including non-image assets). Prefer `image` over `raster` for photo/illustration slots so users can also pick or upload SVGs. ![The Image row in the Filter tool - a thumbnail slot and a Choose asset button that opens the host's picker, with nothing about pickers in the manifest](/t/url-shot?url=%2F%23%2Ftool%2Ffilter&width=1440&height=900&dpi=192&waitMs=2000&css=%23tool-canvas%7Bdisplay%3Anone%7D&walker=1&format=svg&cropSelector=.input-row%3Ahas%28.asset-picker-trigger%5Bdata-input-id%3D%22image%22%5D%29&dark=1&filename=at2-input-asset-picker) When `allowUpload` is `true`, the picker offers the user's **personal image library** alongside the catalog. Users add images from their device; the host stores the bytes **verbatim** (a silent re-encode would break a Content Credential's hard binding) and only offers to downscale when a file is genuinely huge. Metadata stripping is a separate, opt-in user preference (*Strip metadata from uploads*, default off). The library is **not capped by count** - the only limit is the device's own storage, checked before each write - and it is reusable across tools and managed in **Profile → Storage → My images**. SVG uploads are sanitised on ingest (script/handler stripping) and pass through without rasterising. These images are **device-local**: their `AssetRef.source` is `"user"` and their `user/…` id is meaningful only on the device that holds the bytes, so they are **omitted from shareable URLs** (see `docs/url-mode.md`). Tools treat `user` and `library` assets identically - no tool code is involved in the upload. **Use any tool as an image (paste a Lolly link).** Every `asset` input also accepts a **Lolly tool link** pasted into the picker's search box - a share link copied from another tool (`…/#/tool/qr-code?url=…`) or an embed URL (`…/tool/qr-code.svg?…`). The host renders that tool (via `host.compose`) and drops the result into the slot; the user can pick the render format and size before committing. This is the **end-user** counterpart to authored `composes` (below) - no manifest declaration needed, and it works in every tool's image inputs by default. The picker offers SVG **and** bitmap render formats for any image slot (SVG is the default - it stays crisp and inlines as true vector in SVG/PDF export, and rasterises cleanly for PNG); a `vector`-typed slot is restricted to SVG. The chosen asset's identity is the canonical embed URL, so it **persists in saved sessions and shareable links** and re-renders on load - exactly like a library id. (The picker offers this whenever the shell can compose; the `compose` *capability* gates only authored `composes`, not this end-user path.) #### `file` - the user's own file (on-device utilities) A `file` input takes a file the user picks **into memory** and hands its raw bytes to the tool. It's the input shape for **content-transform utilities** - the "boring file jobs you'd otherwise hand to a stranger's website": strip EXIF, crop, compress, convert. Unlike `asset` (which is for *brand* imagery and goes through the catalog/upload library), a `file` is the user's own content that's processed and handed straight back, never stored or uploaded. With `layout: "canvas"` a single `file` input stops being a sidebar row and becomes the working area itself - the drop zone `strip-data` opens with. ![Strip Hidden Data's canvas - a drag-and-drop file zone with a Choose a file button and the note that nothing is uploaded](/t/url-shot?url=%2F%23%2Ftool%2Fstrip-data&width=1440&height=900&dpi=192&waitMs=1800&walker=1&format=svg&cropSelector=%23tool-content&dark=1&filename=auth-file-input) ```json { "id": "photo", "type": "file", "label": "Photo", "accept": ["image/jpeg", "image/png", ".jpg", ".png"], "maxSize": 52428800 } ``` - `accept` - allowlist of MIME types and/or extensions for the picker (a UX hint; still validate bytes in the hook). Omit to accept anything. - `maxSize` - max bytes; the host rejects larger files at pick time. The value is a **`FileRef`**: `{ __file: true, name, mime, size, bytes, url }`. The `bytes` are a `Uint8Array` the hook reads directly (no `host.*` call - the bytes ride in the value by design, because the portable `host.*` surface has no file-read API). A `file` value is **never serialised into a URL** (binary has no shareable form) and **never persisted** - it lives only in memory on the device, which is the whole privacy point. In CLI transport a file param is a path the runner loads: `--photo=./pic.jpg`. #### Producing output: the `exportFile` hook + `privacy: "on-device"` A content-transform utility doesn't rasterise the canvas - it produces a *transformed file*. Declare the `exportFile` hook and mark the tool as an on-device utility: ```json { "status": "official", "privacy": "on-device", "render": { "width": 760, "height": 620, "formats": ["jpg"], "export": false, "actions": [] }, "hooks": { "onInput": true, "exportFile": true } } ``` - `privacy: "on-device"` shows the **"Runs on your device - nothing is uploaded"** badge and enforces (validated) that the tool is never `experimental`, and (at runtime) that exports carry **no provenance metadata and no watermark** - you must not stamp anything into a user's own file. - `render.export: false` hides the standard format/size/download bar; `"actions": []` opts out of the default Save/Share buttons (saving would persist the user's bytes - never do that). - The `exportFile` hook reads the picked file and returns the transformed bytes as a plain record: ```js function exportFile({ model }) { const inputs = Object.fromEntries(model.map(i => [i.id, i.value])); const f = inputs.photo; // the FileRef const cleaned = stripMetadata(f.bytes); // your transform (pure bytes → bytes) return { bytes: cleaned, mime: f.mime, filename: f.name.replace(/(\.\w+)?$/, '-clean$1') }; } ``` In the template, a `` triggers the hook; the shell wraps the bytes in a Blob and delivers them via `host.export.file` (download on web, `--output` on the CLI). Use `onInput`/`onInit` to return *extras* the template displays (e.g. what metadata was found). `strip-data` is the reference implementation. #### `bindToProfile` Any input can declare `bindToProfile: "firstname"` (or `email`, `headshot`, etc). When the tool mounts, it pre-fills from the user's profile. They can override per-session. ## Canonical inputs (reuse shared ids) `/pro` (the web shell's batch mode) is a **spreadsheet grid** that renders many rows at once across one or many tools - CSV/TSV round-trip and spreadsheet paste in, a `.zip` of per-row outputs out, with collapsible export columns and saved batch sessions. Because it lays every selected tool's inputs out as a grid, the `id`/constraint choices you make below directly shape that grid. `/pro` batch mode lays every selected tool's inputs out as a grid. **It keys each column by input `id`** - so two tools that call the same concept by the same id collapse into *one* column, and if they also agree on type + constraints (number `min`/`max`/`step`, select options, color palette), that column becomes **bulk-writable**: the user types one value and it fills every row. Diverge on the id (or the constraints) and you get a separate, cell-by-cell column instead. So picking a shared id is a real UX decision, not a style preference. To make this the default path, the blessed ids and their constraints live in **`schemas/canonical-inputs.json`**. When your tool needs one of these concepts, copy the id (and constraints) verbatim: | Concept | Canonical id | Type | |---|---|---| | Headline | `heading` | `text` | | Sub-headline | `subheading` | `text` | | Body copy | `body` | `longtext` | | Call to action | `cta` | `text` | | Ink / foreground colour | `color` | `color` | | Background colour | `background` | `color` | | Primary image · portrait · backdrop | `image` · `headshot` · `bgImage` | `asset` | | Background image dimming | `bgOpacity` | `number` (0–1, step 0.01) | | Zoom + pan an image | `imageFraming` | `vector` `{ zoom, x, y }` (zoom optional) | Conventions: per-element typography numbers are `FontSize` / `FontWeight` (weight `100`–`900` step `100`), e.g. `headingFontSize`, `bodyFontWeight`. Labels are *advisory* - show whatever label fits your tool; the `/pro` header just uses the first non-empty one, and bulk-write only cares about id + type + constraints. Adding a genuinely new shared input? Add it to `schemas/canonical-inputs.json` first, then adopt it - `npm run validate:catalog` emits a **warning** (never an error) when a tool uses a canonical id with a divergent type or constraints, so drift stays visible. ## The template (`template.html`) Handlebars-flavoured. **Logic-less by design.** ```html
{{#if heading}}

{{heading}}

{{else}}

(enter a heading)

{{/if}} {{#if logo}} {{/if}}
``` - `{{value}}` - HTML-escapes by default. Always use this for user input. - `{{{value}}}` - raw, no escape. Only for trusted, system-generated HTML. - Block helpers: `{{#if}}`, `{{#each}}`, `{{#unless}}`. No arbitrary JS. `wordmark` is about as small as a template gets: one string, one face, one weight. Everything below came from the link's params flowing into `{{ }}` slots, with no code in between. ![The Wordmark canvas rendering the word Handlebars at weight 800, the whole output of a template whose only moving part is one text value](/t/url-shot?url=%2F%23%2Ftool%2Fwordmark%3Ftext%3DHandlebars%26weight%3D800%26size%3D150%26full&width=1440&height=900&dpi=192&waitMs=2000&walker=1&format=svg&dark=1&filename=at2-template-wordmark) **Custom helpers.** The engine registers these in `engine/src/template.ts` (the source of truth - this table should list exactly what it registers, no more, no fewer): | Helper | What it does | |---|---| | `{{default x "fallback"}}` | `x` unless it's null/undefined, then the fallback. | | `{{upper s}}` / `{{lower s}}` | Upper/lower-case a string. | | `{{eq a b}}` | Strict equality - use inside a condition, e.g. `{{#if (eq kind "note")}}`. | | `{{markdown body}}` | Render a **small Markdown subset** to safe HTML: `#`…`######` headings, `**bold**`, `*italic*`, `~~strike~~`, bullet and numbered lists, `[label](url)` links and `![alt](url)` images. Author text is HTML-escaped **before** any tag is introduced, and link/image URLs are scheme-allowlisted (links: http/https/mailto/tel; images add data:/blob:) - anything else renders as plain text. Images carry `class="md-image"` so a tool can size them. Use `{{{markdown body}}}` (triple braces). Used for `blocks` bodies, `table` cells and pasted Markdown. | | `{{arrow text}}` | A leading `>` `<` `^` `v` becomes `→ ← ↑ ↓` (for directional labels). | | `{{asset ref}}` | The resolved URL of an asset input. Use in `src`/`href`. | | `{{asset ref "width"}}` | A specific field of the asset (`width`, `height`, …). | | `{{media ref}}` | Emits the right element for **any** asset kind - ``, `