## Summary
Adds FigCraft (interface design) as the 13th Crafting App, after
DesignCraft.
- **Catalog**: new `figcraft` entry in `crafting-apps.ts` (copy adapted
from the repo README, no third-party product names), release `0.4.0`,
accent colour from the app icon.
- **Assets**: icon, share card and the seven README screenshots from
`storytold/figcraft` main, plus a poster frame for the video.
- **Video section**: an optional `demo` on an app entry renders a
section directly under the hero. FigCraft uses it for the live-sessions
collaboration clip (muted, looping, plays in view via `LazyVideo`, which
gains a `poster` prop). Later sections renumber when a demo is present.
- **/apps hub**: coverage line mentions interface design, the title
keeps "One craft." on one line, and the icon row is a grid of two even
rows (7 + 6) from `sm` up.
## Testing
Checked `/apps` and `/apps/figcraft` in `next dev` at desktop width,
dark theme: video plays, section indices run 01–06, no broken images.
`tsc` reports no errors in the touched files. Not checked: production
build, light theme, mobile layout beyond the icon row.
## Not included
The launcher's release-polling list
(`poll_craft_app_releases_thread/config.rs`) does not list FigCraft yet.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Points every launcher download on /apps and the Crafting App pages at
[v0.2.1](https://github.com/storytold/craft-launcher/releases/tag/v0.2.1).
That release fixes macOS installs and updates failing with "couldn't
open the disk image".
This is merged before the release is published. It only goes live when
the site is published on Netlify.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## What
- **App pages lead with ArtCraft Launcher.** The first download button
at the top of every `/apps/<slug>` page now downloads ArtCraft Launcher
for the visitor's system. It's the same OS-aware button as the `/apps`
hub, with the platform and build underneath. With no detected system it
jumps to `#get-it`. If the launcher ever has no release, the hero falls
back to the app's own download.
- **Shorter labels.**
- The launcher button now reads **Download Launcher** (hub, app heroes
and the Get-it callout).
- The standalone platform buttons read **Standalone for macOS / Windows
/ Linux** instead of "Download PhotoCraft for Windows". The section
heading and each "Other PhotoCraft files" list still name the app.
- **Spacing fix.** The "For Windows · Installer (64-bit)" line under the
launcher button no longer widens its column, which used to push the
buttons beside it away.
The launcher stays at v0.1.0, its latest published release. All 20 links
the site builds for it exist in that release. v0.1.1 is built but still
a draft; bump `CRAFT_LAUNCHER_RELEASE` once it's published.
## Checks
- Ran the site locally (`next dev`) and looked at `/apps/photocraft` and
`/apps` in Edge at desktop and phone widths: one row of buttons, even
gaps, and the launcher link goes to the Windows x64 MSI.
- `npm run typecheck` reports the same 54 errors as `main` without this
change (Playwright and three.js types in `tests/` and
`media-viewer-3d.tsx`); none come from these files.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary
Pushes ArtCraft Launcher
([storytold/craft-launcher](https://github.com/storytold/craft-launcher))
as the recommended way to install the Crafting Apps. One install covers
every app and keeps them up to date. Each app keeps its own installers.
- **`/apps` header:** the main button is now **Download ArtCraft
Launcher**. It detects the visitor's OS and links straight to the right
installer, with a small line naming the file (e.g. "For macOS · Disk
image (Universal)"). Below the buttons, one line says it's the
recommended option and that each app also has its own installers.
- **`/apps/<slug>` Get-it section:** split into two numbered options.
**Option 1 · Recommended** is the launcher block: the pitch on the left,
and on the right a sunken panel with the version, a full-width download
button and an About link. **Option 2 · Standalone** ("Just <App>?
Download it standalone.") comes next, followed by the app's existing
per-platform installers, unchanged.
- **Fallback:** when the OS can't be detected (phones, tablets), the
launcher buttons go to the launcher's GitHub release page.
## Implementation
- `CRAFT_LAUNCHER_RELEASE` in `crafting-app-releases.ts` holds the
launcher's version (v0.1.0). It reuses the shared asset list minus the
macOS CLI zip, and its files are named `artcraft-launcher-*`. Setting it
to `null` hides every launcher button.
- `src/lib/craft-launcher.ts` holds the name, repo, download list and
release-page URL. `src/components/apps/launcher-callout.tsx` holds the
app-page block.
- `craftReleaseDownloads` now delegates to a new repo-based
`releaseDownloads` helper, which the launcher also uses.
- `LauncherDownloadButton` in `app-downloads.tsx` reuses the existing OS
and CPU detection.
- Download clicks are already tracked as `app_download` (`app_name:
craft-launcher`) by the link classifier in `analytics.ts`.
- Small fix: the platform cards get `min-w-0`, so long filenames no
longer push the page wider than the screen on narrow phones.
## Testing
- `npm run typecheck` and `npm run build` pass.
- All 20 launcher release URLs on the page return 200 on GitHub.
- Checked in Chromium with a macOS user agent: `/apps` and app pages in
light and dark mode, and at 1440px, 390px and 360px. Nothing overflows
the page horizontally.
This needs a deploy of `artcraft-website-next` before getartcraft.com
changes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
## What
Moves VectorCraft's `/apps/vectorcraft` downloads from 0.4.0 to
[v0.5.0](https://github.com/storytold/vectorcraft/releases/tag/v0.5.0).
The other Crafting Apps are already on their latest releases:
| App | Site | Latest release |
|---|---|---|
| VectorCraft | 0.4.0 → **0.5.0** | v0.5.0 |
| PhotoCraft | 0.3.0 | v0.3.0 |
| EffectCraft | 0.4.0 | v0.4.0 |
| FilmCraft, LightCraft, PdfCraft, DesignCraft | 0.2.1 | v0.2.1 |
## Verification
- All 19 files the site lists, including the Windows on ARM builds, are
in v0.5.0, and each download URL resolves. The release page returns 200.
- The release also ships a FreeBSD tarball, which the site doesn't list
for any app.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## What
The `storytold/printcraft` repo is now
[`storytold/pdfcraft`](https://github.com/storytold/pdfcraft), and every
Crafting Apps README already links `getartcraft.com/apps/pdfcraft`,
which didn't exist on the site until now. This renames the app on the
site.
- **Slug and name:** `/apps/pdfcraft`, shown as "PdfCraft" in the nav,
footer, `/apps` hub, sitemap, press kit and JSON-LD. Copy, category and
theme colour are unchanged; only the name changes.
- **Legacy links redirect permanently (308):**
- `/apps/printcraft` → `/apps/pdfcraft`
- `/images/apps/printcraft/<file>` → `/images/apps/pdfcraft/<file>`, so
share images already cached by social sites keep loading.
- **Images:** moved to `public/images/apps/pdfcraft/`. The four
screenshots and the share image were rebuilt from the README's renamed
`pdfcraft-*.png` renders, which are slightly newer than the site's (they
show the in-app Discord button). The "Full size" and press-kit links had
been pointing at the old `printcraft-*.png` names, which no longer
exist; they now point at the new files.
- **Downloads:** v0.2.1 was published before the rename, so its files
are still called `printcraft-0.2.1-*`. `CraftRelease` gains an optional
`fileSlug` for this, and PdfCraft sets it to `printcraft`. Once a
release ships with `pdfcraft-*` names, removing that one line switches
it over.
## Verification
Rendered locally:
- `/apps/printcraft` and the old image URLs return 308 to their PdfCraft
equivalents. Following the redirect lands on the PdfCraft page with its
theme.
- `/apps/pdfcraft`: the title, canonical URL and `og:image` use
pdfcraft. All 17 download links resolve directly on
`storytold/pdfcraft`, without going through GitHub's redirect.
- The sitemap lists `/apps/pdfcraft`, and `/`, `/apps` and `/press-kit`
show no "PrintCraft". All press-kit links for PdfCraft return 200.
- `tsc` shows no errors in the changed files. The errors that remain are
already on `main`.
## Note
The screenshots still show "PrintCraft" inside the app (the window tab
and the sample PDF), because the PdfCraft repo renamed the image files
without re-rendering them. They'll update once the README screenshots
are re-captured.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## What
### VectorCraft screenshots
The images were regenerated from the latest renders in VectorCraft's
README
([storytold/vectorcraft@1c3b7b1](https://github.com/storytold/vectorcraft/commit/1c3b7b16)).
The site's previous hero still showed the old DrawCraft UI.
- `hero`, `ribbons`, `bezier` and `sheet` replaced, and a new
`perspective` shot added for the Perspective Grid. They follow the
README's order.
- Captions and alt text updated to match what each new shot shows.
- `og.jpg` share image rebuilt from the new hero, composed the same way
as before: a centred crop with a 144px icon in the bottom-left corner.
- Encoding matches the existing files (1600×1000 WebP q82, JPEG q85), so
the files are about the same size, 50–94 KB each.
### Gallery layout
`AppGallery` used to show the first shot after the hero at full width
and pair the rest. With an even number of shots, that left an empty cell
at the end. Now the first shot only goes full width when the count is
odd. VectorCraft's four shots form a 2×2 grid, like the README's "A look
around" table. All other apps have three shots, so their layout is
unchanged.
### Download versions
| App | Before | After |
|---|---|---|
| PhotoCraft | 0.2.0 | **0.3.0** |
| VectorCraft | 0.3.1 | **0.4.0** |
| EffectCraft | 0.3.1 | **0.4.0** |
| FilmCraft, LightCraft, PrintCraft, DesignCraft | 0.2.1 | 0.2.1 (still
latest) |
These three releases add **Windows on ARM** builds (`windows-arm64.msi`,
`windows-arm64-portable.zip`). They're now listed in the Windows card
for those apps through a `WITH_WINDOWS_ARM64` asset list. The main
Windows button stays on x64, because browsers on Windows on ARM
generally report themselves as x64.
## Verification
- Every expected asset exists in each release.
- Rendered all seven `/apps/<slug>` pages locally and requested every
GitHub download and release link. All of them resolve, and the "Version"
label shows the new versions.
- Checked the VectorCraft gallery in the browser: a 2×2 grid with all
images loading.
- `tsc` shows no errors in the changed files. The errors that remain are
already on `main` (missing local Playwright types and
`media-viewer-3d.tsx`).
## Not in this PR
- **`storytold/printcraft` has been renamed to `storytold/pdfcraft`.**
PrintCraft links still work through GitHub's redirect. Renaming the app
on the site is a separate change.
- Releases also ship FreeBSD tarballs, and PhotoCraft also ships Flatpak
and zsync files. The site doesn't list these.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## What
Updates the `/apps/<slug>` download links to each app's latest GitHub
release:
| App | Before | After |
|---|---|---|
| VectorCraft | 0.2.0 | **0.3.1** |
| EffectCraft | 0.2.0 | **0.3.1** |
| FilmCraft | 0.2.0 | **0.2.1** |
| LightCraft | 0.2.0 | **0.2.1** |
| PrintCraft | 0.2.0 | **0.2.1** |
| DesignCraft | 0.2.0 | **0.2.1** |
| PhotoCraft | 0.2.0 | 0.2.0 (still its latest) |
`CRAFTING_APPS_LATEST_VERSION` becomes `0.2.1`, the most common version.
PhotoCraft, VectorCraft and EffectCraft are pinned with
`craftRelease(...)`, following the file's documented convention.
## Verification
- Compared each release's asset list against `CRAFT_RELEASE_ASSETS`:
every expected file exists.
- Rendered all seven app pages locally and requested every GitHub
download/release link (34 per page): all return 200/302.
- The "Version" label and JSON-LD `softwareVersion` show the new
versions.
## Note
PrintCraft 0.2.1 also ships `printcraft-0.2.1-freebsd-x86_64.tar.gz`,
which the site doesn't list. That's left for a follow-up if we want to
offer it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## What
Adds GA4 to `artcraft-website-next`, covering the whole marketing site.
- `src/components/analytics.tsx` loads gtag.js (`afterInteractive`) and
installs one delegated click listener. It's mounted once in the root
layout.
- `src/lib/analytics.ts` holds the typed event catalogue,
`trackEvent()`, `trackAttrs()` (data attributes that work in server
components) and the link classifier.
- Analytics is **off unless `NEXT_PUBLIC_GA_MEASUREMENT_ID` is set**, so
local dev and deploy previews stay out of the reports.
## Why
The new site had no analytics. This gives us download, signup and
checkout funnels, plus CTA performance by page section.
## Events
GA4 Enhanced Measurement handles page views (App Router history
changes), scrolls, outbound clicks and common file downloads. On top of
that:
| Event | Trigger |
|---|---|
| `app_download` | GitHub release asset clicks (ArtCraft + Crafting
Apps): app, version, platform, file |
| `webapp_click` | Links to app.getartcraft.com: destination path |
| `cta_click` | Other `Button` links (tagged `data-cta`) |
| `app_select` | App cards and icons on `/apps` and sibling cards on app
pages: app name |
| `social_click`, `contact_click` | `SOCIAL_LINKS` hosts, mailto / copy
email |
| `begin_checkout` | Plan selection (`signup`/`subscribe`/`switch`) and
credit packs, with GA4 ecommerce `items` |
| `billing_cadence_change`, `view_credit_packs`, `manage_subscription` |
Pricing actions |
| `prompt_submit`, `video_generation` | H3 promptbox (`logged_in`;
complete/failed/timeout) |
| `sign_up`, `login` | Auth gate success |
| `generate_lead` | Beta form submission (honeypot hits excluded) |
| `video_start`, `share`, `copy_command`, `copy_prompt`,
`media_download`, `press_kit_download` | Video facades, share bars, copy
buttons, media/press downloads |
Every delegated click event also carries `link_location`: nav, footer,
modal, or the enclosing `section[id]`.
## Reviewer notes
- `Button` now renders `data-cta=""` on its anchor variants. There is no
visual change.
- `PricingTable.choosePlan` now branches on the computed `checkoutType`
rather than repeating the `user`/`hasActivePlan` conditions. The
behaviour is unchanged.
- `AuthGateModal` forms now return early on error instead of using
if/else, so the success event goes on the success path.
## Deploy
Set `NEXT_PUBLIC_GA_MEASUREMENT_ID` in Netlify, **Production context
only**, then redeploy. The ID is inlined at build time. This is
documented in `netlify.toml`.
After deploying:
- Mark key events in GA: `app_download`, `begin_checkout`, `sign_up`,
`generate_lead`, `webapp_click`.
- Register custom dimensions (e.g. `link_location`, `app_name`,
`platform`, `destination`, `checkout_type`) so the params show in
reports.
## Testing
Ran the dev server with a dummy measurement ID and checked `dataLayer`
on these pages:
- `/`: nav, footer, CTA, social, mailto and YouTube events.
- `/download`: Windows and macOS installers.
- `/apps/photocraft`: Linux downloads, share, copy command, GitHub link,
and `app_select` on the sibling cards (also checked on the `/apps` icon
row and lineup cards).
- `/pricing`: cadence toggle.
- `/minimax-h3`: logged-out `prompt_submit` and example `video_start`.
Not exercised in the browser: the beta form (submitting would post a
real Google Form entry), signup/login, checkout, the credits modal, the
media page, the press kit, email copy and manage plan.
`tsc` shows no new errors. Existing errors in `media-viewer-3d.tsx` and
the Playwright test files are unchanged. The production build was not
run.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
## Summary
Updates the Craft Apps pages to cover the org's seven Craft repos and
adds each app's icon.
- **VectorCraft replaces DrawCraft.** The repo was renamed, so the page
moves to `/apps/vectorcraft`. `/apps/drawcraft` permanently redirects
there, because every Craft README's app table still links the old URL.
- **New pages:** `/apps/effectcraft` (motion graphics & VFX) and
`/apps/designcraft` (page layout & publishing), each with copy,
screenshots and a share image.
- **App icons:**
- A row in the `/apps` header linking to every app.
- On each lineup card and sibling-app card, overlapping the bottom of
the screenshot.
- Above the wordmark in each app's hero.
- **PhotoCraft installers (`v0.1.1-rc.4`):**
- A download button in the hero.
- macOS, Windows and Linux buttons, plus an "All builds and checksums"
link.
- An "Installers ready" badge on its card.
- `softwareVersion` and `downloadUrl` in its JSON-LD.
- **Share images:** each app's image has its icon in the corner, and the
`/apps` share image is the seven icons in a row.
- **Press kit:** a new "Crafting Apps icons" section, linking to each
repo's 1024px PNG.
## Details
- **Data file:** everything is still driven by
`src/lib/crafting-apps.ts`. An app's `release` field (version plus a
filename suffix per platform) produces its download buttons, so other
apps need one data entry when they ship.
- **Icons:** each repo's 512px render, converted to a 256px WebP with
transparency, about 25 KB each.
- **DesignCraft gallery:** its magazine pages are portrait, so they show
four across instead of in the 16:10 layout.
- **Hub copy:** the app count is worked out from the data ("Seven
apps"). The lineup is four columns wide on large screens, so seven cards
plus the Discord cell fill two even rows.
- **Colors:** EffectCraft and DesignCraft get their icon tile colors as
accents, with AA-contrast text colors in light and dark.
- **Adobe names:** the copy still never names Adobe products.
## Testing
- A clean `npm ci && npm run build` (what Netlify runs) passes with a
clean TypeScript check, and all seven app pages are generated as static
HTML.
- `next start` serves every page and asset with a 200, `/apps/drawcraft`
returns a 308 to `/apps/vectorcraft`, and an unknown slug returns 404.
- Checked visually at 1440px and at a 390px mobile viewport: no broken
images or horizontal overflow, and the mobile icon row wraps four over
three.
- The PhotoCraft download links and every 1024px icon URL in the press
kit resolve.
## Summary
Marketing pages on getartcraft.com for the five Craft Apps: PhotoCraft,
DrawCraft, FilmCraft, LightCraft and PrintCraft. Everything is driven by
one data file, `src/lib/crafting-apps.ts`.
- **`/apps` hub:** lineup cards for all five apps, shared principles,
and Discord calls to action.
- **`/apps/<slug>`:**
- Hero screenshot with Discord, GitHub and share links.
- Six highlights and a screenshot gallery.
- "Get it": a Discord waitlist for installers, next to build-from-source
steps with a copy button.
- Cards linking to the sibling apps.
- **Nav:** a "Craft Apps" link after a divider following Support, and a
Discord button on desktop.
- **Footer:** a "Craft Apps" column.
- **Sitemap:** entries for the hub and all five app pages.
- **Press kit:** all 20 app screenshots, with full-resolution downloads.
## Branding
- **Colors:** each app has its own accent (`.app-*` in `globals.css`),
AA-contrast in light and dark.
- **Wordmarks:** text only, with "Craft" set in the app's color. These
pages don't use the serif accent font.
- **Share previews:** a 1200×630 image per app plus one for the hub, and
`SoftwareApplication` JSON-LD on each app page.
- **Adobe:** the copy never names Adobe products.
## Notes
- No installers exist yet in any of the five repos, so each page sends
visitors to Discord and to building from source. When an app ships a
release, adding `downloads` to its entry in `crafting-apps.ts` replaces
that block with download buttons.
- Screenshots come from each repo's `docs/images`, show only
public-domain artwork, and are converted to WebP at 1600px.
- **Small refactors:**
- New shared `DiscordButton`, now also used by the community strip and
the press kit.
- New `CopyButton` in the UI kit.
- `SITE_URL`/`siteUrl` moved into `lib/links.ts`.
- New `kicker`/`mediaTag` props on `CampaignHero`.
## Testing
- A clean `npm ci && npm run build`, the same build Netlify runs,
passes: TypeScript is clean, `/apps` is static, and all five app pages
prerender as static HTML.
- `next start` serves all six routes plus their images, and an unknown
slug returns 404.
- Each page's Open Graph and Twitter tags render with the per-app image.
- Checked visually at 1440px and 1024px (the nav fits) and at a 390px
mobile viewport.
Ports the model-list display names and brand marks, shows the Created
row as date + time like the webapp lightbox, and uses the still-frame
preview for video references.
* Website: port the media share page to artcraft-website-next
Adds the /media/[token] route with server-side OG metadata, the media
viewer with prompt/reference/info details, and legacy ?media= redirects.
* no ruler on media share page
* Align webapp pricing page with website design
* Clarify pricing headline to avoid implying unlimited usage
* Simplify pricing headline to Pick a plan
Ports /seedance-2, /seedance2-5, /minimax-h3, and /creators/jboogxcreative
from the Vite site into the instrument design language, reusing the
landing's own Features / Ownership / Made-with sections (now taking an
`index` prop so eyebrow numbers stay sequential) plus a small set of
campaign building blocks (components/campaign): centered hero with reel
figure, section heading block, prose + flagged note, numbered tip grid,
native <details> FAQ, five-reasons cell grid with the "every model"
marquee, Discord/GitHub strip, closing CTA, and a shared CTA pair that
routes subscribers into the webapp and everyone else to pricing, with the
OS-detected desktop download and post-download signup modal.
- Seedance 2.5: overview with rumor disclaimer, prompt tips, FAQ (FAQPage
JSON-LD), scroll-scrubbed manifesto word reveal (the Vite page's Three.js
walking character and scrubbed knight video were not carried over), then
the reused feature/ownership/reasons/community sections.
- MiniMax H3: real promptbox in the hero. Enqueues an H3 generation via
/v1/omni_gen/generate/video, polls /v1/jobs/job/{token}, and plays the
result inline; logged-out visitors get an inline email/password signup
or login gate and the typed prompt runs on success. Aspect ratio,
resolution, duration, and sound controls; keyframe uploads are left to
the full app. Google SSO is not offered (no OAuth provider in this app).
- Creator spotlight: reel triptych, credits marquee, feed grid, craft
cards, ProfilePage JSON-LD.
Shared: lib/use-platform.ts (OS detection + download-initiated flag) and
components/download/download-modal.tsx extracted from the download page;
api.ts gains login, signup, generateVideo, getVideoJob; ruler roster
reordered so campaign sections precede the reused landing ids; sitemap
lists the four routes.
Restores the Vite pricing table's live behaviour on the Next site without
pulling the Nx @storyteller/api lib into this standalone app:
- lib/api.ts: minimal browser client for the endpoints pricing needs
(session, active subscriptions, signup checkout, subscription checkout,
switch plan, manage-plan portal, credits pack checkout, web referral
log). Same paths, bodies, and envelopes as the shared lib; cookies plus
the signed-session header fallback. Host via NEXT_PUBLIC_API_HOST.
- lib/referral.ts: landing-context capture (?u=, ?r=, landing URL,
referrer) to apex-domain cookies + localStorage, mounted site-wide by
components/landing-context.tsx, which also logs the referral once per
browser session (the old main.tsx bootstrap).
- lib/use-account.ts: one shared session + ArtCraft plan fetch per page
load for every consumer.
- Plan grid: logged out -> Stripe signup checkout with attribution; logged
in without a plan -> subscription checkout; with a plan -> portal plan
switch. "Your plan" tab and disabled CTA on the current tier, "Switch
to X" labels, Manage plan portal link, inline error line.
- Credits cell: logged-in visitors get the one-time credit pack modal
(Stripe checkout); logged-out visitors are sent to the app to sign in.
Server HTML still renders the logged-out state, so static output, SEO,
and every other page are unchanged.
The page-top section on interior pages (Support, Tutorials, News, …) was
running the ordinary anchor-space flip, whose progress at scroll 0 is
(12% of vh + 200px − ~110px) / 200px — so on shorter viewports the 01
heading sat partially flipped, or hidden, instead of leading the stack.
The first non-hero section is now pinned like the hero: flipP = detachP =
1 from the first frame, no queue home, no rail ride. Every page now opens
with its 01 section stacked at the top and 02+ queued below.
- Ruler: every interior page header now carries a section id (pricing,
download, tutorials, news, faq, support, press-kit, beta) registered in
RULER_SECTIONS ahead of the interior ids, so the heading stack starts
with the page's 01 section at the top instead of first appearing
mid-page. The pricing section wraps the promo strip so it begins at the
very first pixel. Roster order is documented as load-bearing (anchors
must increase with index).
- Ruler: capability gate tightened to hover-capable fine pointers at
1024px+ and never on a mobile/tablet user agent.
- Pricing: dropped the "See plans" jump button; the "20% off · ends soon"
badge moved into the plan toolbar next to the cadence toggle.
Reworks /pricing on top of the ported instrument layout so the plan table
pops the way the original did:
- Per-plan color identity restored (green Basic, purple Pro, orange Max,
blue Enterprise) via `.plan-*` tokens with light/dark ink variants: solid
color index tab, color wash pooling under it, plan-colored checks and
CTAs, and a 2px frame on the Most popular / Best value tiers.
- Conversion elements: full-width accent promo strip above the header,
benefit-led headline ("Every model. One studio. Yours forever.") with a
"See plans" jump CTA, 20% off pill and crossed-out anchor on every card,
per-plan yearly savings in dollars, taglines, "2 months free" nudge on
the cadence toggle, arrow CTAs, and a trust strip (cancel anytime, Stripe
checkout, credit refunds, open source) under the grid.
Ports the interior marketing pages into artcraft-website-next, rebuilt in
the landing's instrument language (SectionShell rails + ticks, mono HUD
eyebrows, hairline gap-px cell grids, Archivo display headings with one
serif-italic contrast word, theme tokens for light/dark) instead of the old
dark-only Truchet/glow treatment.
Pages
- /pricing: plan grid (Basic/Pro/Max + Enterprise) with yearly/monthly
TabSelector, 20% promo anchor pricing, highlighted plans invert their
index row, credit-pack and footnote cells. ?ref=sd2fakeyou keeps the
Seedance early-access header + reel. Checkout hands off to the webapp.
- /faq and /faq/[slug]: guide grid + article, FAQPage/Article JSON-LD.
- /news and /news/[slug]: post list + article, blog images resolved
against the media host.
- /tutorials and /tutorials/[slug]: category filter grid, click-to-load
YouTube via LiteYouTube, frontmatter aliases 308 to the canonical slug.
- /support, /download (client-side OS detection, requirements, quick
start, post-download signup modal), /press-kit (asset cells + video
modal), /beta (Google Form signup with honeypot).
Platform (Vite -> Next)
- Markdown content copied into src/content and read from disk in server
code (lib/content.ts) with generateStaticParams, replacing
import.meta.glob; markdown renderer ported to lib/markdown.ts and styled
by token-based .article-content rules in globals.css.
- react-router Links/useParams/useSearchParams -> next/link, awaited
params/searchParams, generateMetadata; react-device-detect -> a
post-hydration user-agent hook; @storyteller/* lib imports replaced by
local data modules (pricing-data, download-links, press-kit-data).
- Removed the legacy-route redirects to the Vite site from next.config
(only the old Stripe return paths still redirect, now to the webapp);
sitemap lists every page and article; ruler roster gains the interior
section ids; Discord/Windows brand icons added.
Opening a generated image/video and quickly clicking the backdrop to
close left the whole page unclickable, with no visible overlay to
explain why.
**Cause:** `Dialog.Overlay`/`Dialog.Content` are `forceMount`ed so
react-spring owns the leave animation. On close `Dialog.Root`'s `open`
flips false while the layer is still mounted, and Radix derives
`disableOutsidePointerEvents` from that flag, so its cleanup never
restores the `pointer-events: none` it set on `<body>`. Closing slowly
masked it; closing fast interrupts the leave spring so the clearing
render never comes.
**Fix:** `unregisterModal` now schedules `releaseStrandedBodyLock`,
clearing a lock nothing still owns. Bails if a modal is still registered
or a real open Radix dialog exists.
Adds a new creator spotlight landing page to the artcraft-website under
`/creators/`.
### Changes
- New spotlight page: hero with fanned reel triptych, credits marquee,
work grid, craft section, and closing CTA, all in the existing
landing-page design language
- Reel cards use locally hosted thumbnails (`public/images/creators/`)
and link out to the posts on Instagram, no embeds
- Primary "Create Now" CTA routes subscribers to the webapp and everyone
else to `/pricing`
- SEO wired up: `PAGE_META` entry for the prerender step, `_redirects`
rules plus short 301 aliases for the route
Recreate restored reference videos/audios from the prompt record with duration: 0. The cost quote forwards that as a zero input-duration hint, and the backend deliberately prices a zero/unknown input at the worst-case 30-second billing maximum (Seedance 2.5 bills reference-video input seconds), so a recreated generation quoted far above the original
charge, e.g. a 1080p 10s output with ~5s of input quotes 548 credits, but 1096 with the failsafe.
Measure each restored reference's duration from its CDN copy while building the recreate payload, the same way the library "Send to prompt" path probes durations. Unmeasurable files keep 0 (worst-case quote as before; generation always bills from its own server-side measurement).
Add Billing tab to webapp settings
- New "Billing" tab in the settings modal, between Account and API Keys
- Shows your current plan (Free/Basic/Pro/Max)
- Subscribers get a "Manage Plan" button that opens the Stripe portal,
same as the pricing page, plus a "View Plans" link for switching
- Free users get a "View Plans" button to the pricing page
- Handles signed-out and loading states like the other tabs
Drag and drop didn't work while the prompt focus mode was open - the
modal sat on top of the inline promptbox's drop zone, so files just did
nothing (webapp and desktop).
### Changes
- `usePromptBoxDrop` now exposes `fullscreenDropZoneProps` - same
handlers/routing, second ref so the Tauri hit-test follows the
focus-mode modal while it's open
- `PromptFullscreenModal` (shared lib + webapp copy) takes optional
`dropZoneProps` / `dropOverlay` and renders the drop zone + hover
overlay on its panel
- Image / Video / Audio boxes + webapp `PromptBox` pass their existing
drop wiring through - one overlay element now serves both the inline box
and focus mode
- No new drop logic anywhere: same MIME routing, accept/reject toasts,
keyframe handling, and paste behavior as dropping on the inline box
On iOS, adding more than 2 image prompts can blow past the browser's
memory limit and force-reload the page, wiping your prompts. We were
storing every uploaded image as a full-res base64 string just to show a
56px thumbnail.
- Thumbnails are now small ~512px previews instead of full-res data URLs
(memory per image goes from ~100MB+ to under 1MB)
- Tapping a thumbnail still shows the original full-res image
- Nothing changes for the backend — uploads and generation still use the
original file + media token
- Fixed the flicker when an upload finishes: the spinner card now turns
into the image card in place, instead of a new card popping in next to
it
- Small cleanups along the way: leaked object URLs in the old prompt
row, and upload spinners that never actually spun
Applies to webapp (desktop + mobile), website, and the desktop app since
they share the upload code.
## Fixes
- Slow paste in prompt box: `execCommand("insertText")` is quadratic per
line in Chromium - now inserts the clipboard as a single text node
(linear)
- Model selector submenu not scrolling in focus mode: the modal's scroll
lock swallows wheel/touch on body-portaled panels - flyout now stops
propagation (same pattern as mention dropdown)
- Cap the always-expanded deck panel at container width so cards wrap
into rows; scroll past 3 rows instead of squeezing the editor
- Drop shrink-0 on the expanded deck root (min-w-0 max-w-full) so it
shrinks inside the webapp's flex-row wrapper
- Collapsed fan badge shows total ref count instead of +hidden
MentionTextarea's root wrapper lacked min-h-0, so as a flex child of the
fullscreen modal's bounded column it kept full content height and got
clipped by overflow-hidden instead of letting the editor scroll.
Removes Font Awesome entirely
### Changes
- All ~1,000 icon call sites now import Lucide components directly from
`lucide-react` (`<PlayIcon className="..." />`); no FA packages, tokens,
or `fa*` identifiers remain anywhere.
- New `@storyteller/icons` lib holds the pieces Lucide doesn't ship:
`DynamicIcon` for runtime-chosen icons (`icon={loading ?
LoaderCircleIcon : SaveIcon}`), 11 brand logos (Discord, TikTok, ...) as
inlined CC BY 4.0 paths, and HD/SD quality badges drawn in Lucide's
stroke style.
- Global `.lucide { display: inline-block; width/height: 1.2em }` rule
in each app stylesheet: keeps icons font-relative like FA (1.2em
compensates Lucide's 24px-viewBox/20px-grid padding) and undoes Tailwind
preflight's `svg { display: block }`. Explicit `w-*`/`size-*` utilities
still win.
- FA-only props migrated: `spin` → `animate-spin`, FA size strings → em
values.
- Dropped `@fortawesome/*` + `@awesome.me` kit deps, both `.npmrc`
files, and
the FA rule in `shared-vite-config.ts` (now externalizes
`lucide-react`).
- Select multiple items in the library and add tags to all of them at
once
- Tag suggestions pop up as you type, based on your existing tags
- Fixed the selection bar looking squished (buttons no longer wrap onto
two lines)
- Video generation now defaults to Seedance 2.5 (was 2.0)
- Omni Reference is the default video input mode for everyone
- Model pickers on the create pages now group models by family
(Seedance, Veo, Kling, etc.) with a flyout submenu per family, so the
list is no longer one long scroll
- The flyout opens on hover or click, and automatically scrolls the
currently selected model into view
- New setting in **Settings → General** ("Group models by family") to
switch back to the classic flat list. Grouped is the default and works
in both the webapp and the desktop app
- Model rows now use full-color brand icons, while the picker button
keeps its existing look
- Video models show capability tags: **Audio Support**, **Start/End**,
and **Reference**
- Short model lists (under 8 models) stay flat, and mobile keeps the
existing bottom-sheet list
- Fix: videos in the media viewer can now go fullscreen (the expand
button was disabled)
## Summary
New marketing page at /minimax-h3 where visitors can try MiniMax H3 for
free,
right on the page.
- Hero is a working promptbox, locked to MiniMax H3 with a "Free" badge
- Full settings available: aspect ratio, resolution, duration, sound,
start/end frames
- New users sign up in a modal when they hit Generate (Google or email),
then their prompt runs immediately without leaving the page
- Generated videos show up right below the promptbox, with a queue
notice and a CTA to keep creating in the app while they wait
- Example videos from MiniMax's announcement, self-hosted with
thumbnails, only load when played, credited to MiniMax
- Overview, highlights, prompt tips, and FAQ copy making the free offer
clear Website promptbox updated to match the current webapp promptbox
(reference deck, drag & drop, focus mode, same buttons)
- SEO, redirects, and URL aliases (/minimax, /hailuo) wired up
- Characters modal: new eye button on character cards to view the image
full size
- Mention chip menu: tapping the avatar thumbnail also opens the
full-size view
- Card buttons (view/edit/delete) are always visible on touch devices
instead of hover-only
- Mobile: Characters open in a bottom drawer with a 2-column grid,
matching the other settings drawers
- All mobile drawers now have a close (X) button
- All mobile drawers can be pulled down to dismiss - the sheet follows
your thumb and closes from where you let go
- Mobile: mention autocomplete list can now be scrolled - dragging no
longer selects an item, only a tap does
- Fixed taps on autocomplete items sometimes not registering on
Android/iOS
- Deleting the space after a mention now reopens the autocomplete next
to the mention instead of at the top of the screen
- Clicking a mention chip while its menu is open now closes the menu
instead of closing and reopening it (desktop + mobile)
- Added missing display names for Seedance 2.5 Preview and ~25 other
models (Vidu Q3, Veo 3.1 Lite, Seedream 5.0 Pro, new Hunyuan 3D 3.1
variants, Marble 1.x, and more) so the task queue and history show
proper names instead of raw IDs.
- Vidu, Recraft, and SeedEdit models now show their brand icons instead
of the generic one.
## Fixes & improvements
### Model picker
- Removed the "Seedance 2.5 - SOON" placeholder from the video model
picker; it was confusing now that Seedance 2.5 Preview is live.
### "Not all media files could be found" error
- Fixed generations failing when the same media file ended up twice in
one reference list (e.g. added as two references, or the same file
uploaded twice).
- Requests now automatically drop duplicate media references before
sending - applies to image, video, audio, mesh, and splat generation on
both the web app and the desktop app.
### Gallery picker UX
- Media already added to a slot is now greyed out with an "Added" badge
in the library picker, so the same file can't be added twice to the same
field.
- Applies per slot across the create pages (video, image, audio, 3D
object, world) on web and desktop - references, start frame, end frame,
video refs, audio refs, and multi-view slots.
- Reusing the same image across different slots (e.g. start *and* end
frame, or two 3D views) is still allowed.
### What
- Drag files onto the promptbox on the Image, Video, and Audio create
pages to add them as references - same look and behavior as the webapp
- On those pages the promptbox is now the only drop target; dropping
elsewhere does nothing
- Every other page keeps the existing upload-modal behavior
### Notes
- The webapp's drop code doesn't work as-is on desktop - Tauri
intercepts file drags before the page sees them, so desktop needed its
own path
- Both now share one component, so the two apps stay in sync going
forward
- Model limits still apply: each box only accepts the reference types
its model supports, and existing slot/duration caps are unchanged
- **Hand-typed mentions always take the character's exact name** -
typing `@person` for a character named `Person` now converts to
`@Person` as you type, so the tag binds correctly instead of silently
failing at generation time.
- **Prompt input no longer freezes at a character tag** - the caret
could get stuck inside a chip (e.g. after deleting the space behind it),
blocking all typing.
- **Reference add-menu no longer hides behind the sidebar** - it now
renders above everything instead of getting clipped by the promptbox
layers.
- **Character autocomplete is scrollable in focus mode** - the
fullscreen prompt's scroll lock was blocking it; same fix for the chip
menu's Replace list.
- Fix popovers (model selector etc.) closing instantly when the mention
textarea was focused
- Fix mention autocomplete not clickable in prompt focus mode
- Fix chip "Replace" reopening the autocomplete - now confirms
immediately
- Fix autocomplete appearing at top-left of screen when caret is next to
a leading chip
- Don't open the autocomplete when clicking the caret onto an
already-confirmed chip
- **Fixed: picking an audio reference on mobile opened the photo/video
picker.** iPhones don't have an audio library, so the browser fell back
to Photos - the only thing you could "attach as audio" was a video, and
it silently uploaded as an audio ref. Audio pickers now open the Files
app filtered to real audio files (MP3, WAV, M4A, FLAC…).
- **Non-audio files are now rejected with a clear error toast** instead
of being silently attached (also covers drag & drop and Android's "any
file" picker).
- **Fixed: limit warnings never appeared in the web app.** Errors like
"Total audio duration cannot exceed 15s" from shared components were
firing into a toast system the web app doesn't render. They're now
routed to the app's own toasts, so length/limit errors actually show up.
- Applies to the create video, create audio, and desktop reference deck
flows
### New stuff
- Character `@mentions` in the video promptbox now show as **chips with
a thumbnail**, so you can see exactly which character is attached to the
prompt
- Clicking a chip opens a menu to **Replace** (pick another character),
**Preview** (view their image), or **Remove** the mention
- Chips behave like a single unit - one backspace deletes the whole
mention, and removing/replacing can be undone with Ctrl+Z
- Copying prompt text still gives clean plain text (`@name ...`), and
pasting it back restores the chips
- When two characters share a name, the one you actually picked is now
the one that gets sent
### Fixes
- Mobile: the `@` autocomplete no longer goes off-screen - it stays
inside the viewport, flips below the caret when needed, and taps select
reliably
- Focus mode: the autocomplete no longer pushes the prompt sideways /
causes overflow
- Enter-to-generate now works while the mention editor is active on the
webapp
- Create flow stored `inference_job_token` as the `character_token`, so
mentioning
a new character sent a bogus token - backend drops it and name-matches
the
raw `@name` against stale characters.
- Don't add pending characters to the mention store; they aren't
generatable yet.
- Keep the pending poll running after the modal closes and sync real
tokens into
the store once the character is active.
- Remove unused `addCharacter` store action.
- Same fix in all three copies: webapp, website, shared `promptbox` lib.
Ports the Video Frame Extractor from the desktop app to the webapp at
`/frame-extractor`, with a reworked UI.
### What's new
- New Frame Extractor page: upload a video, pick one from your library,
or drag and drop
- Scrub to any moment and capture the exact frame at the playhead with
one click
- Frame stepping buttons and keyboard shortcuts (arrows to step, Space
to play/pause, C or Enter to capture)
- Burst capture: grab a sequence of frames to find the sharpest one,
with progress and cancel
- Each captured frame can be used as an image reference, animated into a
video, saved to the library, or downloaded
- Save all and clear actions for captured frames
### Integrations
- "Extract frames" action on library videos (selection bar and lightbox)
opens the extractor with that video preloaded
- Sends frames straight into the create-image / create-video reference
deck, same flow as "Send to prompt"
- New sidebar entry (purple NEW badge) and home page card
- Page is public; saving and references prompt sign-up when logged out
- Adds a **Send to prompt** action to the library's multi-select bar
with two
targets: **Create image** and **Create video**
- Selected images are attached as reference images on the target page
without
touching the user's typed prompt or settings; selection is cleared and
the
app navigates to the create page
- Selected videos can be sent to the video prompt as reference videos;
models
without video-reference support (only Seedance has it) get an
explanatory
toast instead of a silent drop
- Media travels via `pendingRefImages` / `pendingRefVideos` store slots
(mirroring the existing `pendingRecreate` pattern) and is merged on the
receiving page once its model list is loaded, so the **real per-model
reference caps** apply - fixes silent no-ops when refs were already
attached
- Merging dedupes by media token, clamps to the model cap, and toasts
partial
adds ("Added 3 of 5 …"), full decks, and unsupported models
- Keyframe-only video models take the first sent image as the start
frame;
reference-capable models switch into reference mode so the deck is
visible
- DRY: shared `galleryItemToRefImage` mapper (reused by the create-image
library picker) and `buildVideoRefsToAdd` (reused by the video-ref
picker,
duration probing + total-duration cap)
- Bar UX: option list adapts to the selection (video-only hides Create
image),
icon-only button on phones to avoid overflowing the bar
## Changes
**Batch download**
- Add multi-select mode to the create-video results feed (webapp +
desktop) with a "Select" toggle in the topbar, checkboxes on completed
items, and a floating download bar
- Webapp: "Download" bundles selected videos into a single .zip
client-side (new jszip dependency, concurrent fetches, progress and
partial-failure toasts)
- Desktop: "Download" prompts for a folder and saves each selected video
as its own file via native Tauri save, with progress toasts
- Library: add a Download (zip) button to the existing bulk selection
bar
- Shared `ui-generation-list` lib gains a selection store, select
toggle, action bar, and optional selection props on the grid/list views
(existing hosts unaffected)
**Credits refresh on failed generations**
- Webapp: dispatch a credits refresh when the task queue or generation
feed observes a newly failed job (immediately and again after 2.5s so
server-side refunds settle)
- Desktop: also refresh credits when the 5s task poll discovers a
failure that the push event missed, and listen for `credits-change` in
the TopBar
**UI fixes**
- Selection download bar always sits above the promptbox regardless of
its size
- Mobile Generate/History header no longer overlaps the gallery toggles
(flex layout instead of absolute positioning)
- Reference deck top-aligns in the expanded prompt box instead of
centering
- "Play videos with sound" setting now defaults to on
- Drop images, videos, or audio anywhere on the prompt box - files are
sorted into the right reference type based on what the current model
supports
- Drag-over overlay shows which file kinds are accepted; turns red when
the hovered files aren't usable
- Video keyframe mode: dropped images fill the start frame, then the end
frame
- Unsupported kinds, full slots, and non-media files get a clear toast
instead of failing silently
- Reuses the existing `useDeckMedia` upload pipelines, so limits and
error handling match the "+" pickers
Users reported that after using **Recreate**, pressing the generate
arrow did nothing at all (no error, no card, no spinner). Root cause:
several failure paths gave zero feedback, so a rejected request looked
like a dead button or an outage.
### Changes
- **Show real error messages.** The API layer now keeps the server's
error message (e.g. "Image is required") instead of throwing it away, so
users see why a generation was rejected.
- **Toast on every failed submit.** Image and video pages (webapp +
website) now show an error toast when a generation fails to start.
Previously, most failures were recorded invisibly and never shown.
- **Out of credits feedback on the website.** 402 responses now show a
toast (webapp keeps its upgrade modal).
- **Network errors are announced** instead of failing silently.
- **No more silent submit blocks.** Cases that used to quietly ignore
the click now explain themselves:
- Model list still loading
- Model needs a starting image (website; also un-disabled the button so
clicking gives feedback)
- **Recreate model fallback is announced.** If the original generation's
model is no longer available, recreate now tells the user it switched to
the default model instead of silently swapping.
## Summary
Compacts the legacy reference bands above the prompt box (audio ref row,
multi-view/input-mesh row, reference video row) into the reference deck
design already used on the image and video pages. Mobile forms keep
their existing rows.
## Changes
- **Create Audio** - audio tracks are now deck cards (playable tile,
duration badge) with Upload / From library in the deck's "+" menu; band
removed. Mutual-exclusion and "Audio track required" behavior unchanged.
- **Create 3D World** - guide video is now a deck card; store reworked
to the shared `RefVideo[]` shape.
- **Create 3D Object** - Front/Back/Left/Right are always-visible
labeled slot cards next to the image-ref deck (same design as First/Last
frame on video), plus a "Mesh" slot card for mesh-to-mesh models. Upload
logic deduplicated into `mesh-upload.ts`.
- **Webapp PromptBox** - removed the `mediaReferenceRow` band prop; deck
video/audio groups gate on `videoRefsSupported`/`audioRefsSupported` (no
longer video-page-only); new `referenceSlots` prop; `maxImages: 0`
blocks image uploads on pages whose model takes no image refs.
- **Deck lib** - exported `DeckSlotCard`; new `mesh` card kind;
`sortable: false` for fixed slots; non-finite duration caps hide the
seconds limit; collapsed fan width now tracks card count, so a single
ref sits snug against the prompt (multi-ref unchanged).
- Tags now work in the desktop app - no separate build, it all comes
through the shared gallery/lightbox components
- Open any item in the gallery → edit its tags in the details panel
(same chip editor as the webapp, with autocomplete)
- New "Tags" section in the gallery sidebar: your 5 most-used tags + an
"All tags" entry
- Click a tag to see all media with that tag in the normal grid
- "All tags" opens a word cloud (bigger = used more), click a word to
jump in
- Webapp sidebar now also caps at 5 tags with a "See all tags" link
- The tag editor is now one shared component used by both apps instead
of two copies
- Tags editor in LightboxDetails (lightbox + /media/:id): chip input
with autocomplete over the user's existing tags, comma/paste splitting,
duplicate rejection, owner gating via session, and serialized+coalesced
SetMediaFileTags snapshot saves with optimistic chips and error revert
(components/tags/TagChipInput.tsx, TagsSection.tsx)
- Tag browser: "Tags" tab in the library with name/most-used sort, tag
view at /library/tag_… reusing the media grid, breadcrumb header with
rename (FolderNameDialog) and delete (confirm modal), tag context menu
- Sidebar: tags rows under the Assets group below folders, with
expand/collapse chevrons on both Folders (default open) and Tags
(default closed), animated via framer-motion; tag icons use violet to
distinguish them from folders
- New library-tags-store mirroring the folders store (cursor-paged media
caches, optimistic rename/delete with toast revert); shared item mappers
extracted to library-media-map.ts to avoid a circular import; media
deletion now purges tag caches; bulk-selection bar resolves thumbnails
from tag caches too
- Topbar breadcrumbs map /library/tags and tag views to "Tags"
- Tags show as word cloud
Redesigns how references are attached in the image and video
promptboxes, on both the desktop app and the webapp (desktop layout -
mobile is unchanged).
### Reference deck
- Replaces the image-toggle icon and the upload bands above the prompt
with a fanned stack of reference cards inside the promptbox, left of the
prompt text
- Hovering the deck expands it to show every attached ref - no
scrolling, it widens toward the end of the promptbox and wraps into more
rows as needed (ready for up to 50 refs for Seedance 2.5)
- Cards support drag-to-reorder (grab/grabbing cursors), hover remove,
name tooltips (Image 1, Video 2, …), upload progress spinners on
individual cards, and click-to-preview fullscreen
- A circular **+** overlaps the deck: hovering shows an add menu grouped
by type (Image / Video / Audio) with compact limits (e.g. `2/9`, `1/3 ·
8/15s`); clicking it opens a file picker that accepts any supported
media and sorts files by type automatically
### Video modes
- Keyframe mode: Start/End frame band replaced with compact "First frame
⇄ Last frame" cards with a swap button
- Reference mode renamed to **Omni reference** and is now the default;
images, videos, and audio all stack into one mixed deck
### Generate button
- Replaced the "Generate" pill with a circular arrow-up button, credit
cost shown beside it
### Under the hood
- New shared deck components live in `@storyteller/ui-promptbox` and are
used by both apps (previously the two promptbox implementations were
fully separate)
- New `GenerateIconButton` in `@storyteller/ui-button`; the old
`GenerateButton` stays for mobile
- Webapp mobile, audio/object/world pages, and the 2D/3D/Edit
promptboxes keep their existing UI
- The "+" button on the Video Ref row now shows options on hover:
**Upload** or **Pick from library** (on mobile it opens an "Add video"
sheet)
- Picking from library opens the gallery in select mode, filtered to
videos only
- Library videos respect the same limits as uploads (max count and total
duration, with the same error toasts)
- Reused the existing add-button component from the image row so all
reference rows look and behave the same