Compare commits

..
Author SHA1 Message Date
Tanish Manku 802dbd5637 pagescene: release detached contexts and stop the viewport observer on unmount
Measured in the running webapp (live WebGL objects counted per context
over add/delete, New Scene and navigate-away/back cycles), the scene
itself is now freed, but every visit to /edit-3d still added two live
WebGL contexts with the post-processing render targets and programs
inside them. renderer.dispose() does not delete those GL objects, and
leaving the page detaches the canvases, so the contexts lingered until
garbage collection. Unmount now forces the context loss for a renderer
whose canvas has left the DOM. A scene change keeps the canvases
mounted and the next Editor reuses their contexts, so those are kept.

ViewportController's ResizeObserver was never disconnected: after
unmount it kept calling renderScene on the dead editor whenever the
container resized, keeping the whole Editor reachable and, once the
context was released, throwing. It is now disconnected on unmount.
2026-10-06 16:02:21 -07:00
Tanish Manku 63b2a71c28 upload-modal: free 3D and splat previews instead of only their renderers
Switching between files in the 3D or splat upload modal, or closing it,
disposed the renderer but none of the preview scene: every previewed
GLB's buffers and textures and every splat's buffers stayed on the GPU,
and each preview's blob URL was never revoked. The splat modal also
never cancelled in-flight loads.

loadPreviewOnCanvas now returns dispose() in place of cancel(): it
cancels in-flight loads, stops the animation loop, frees the scene
(including a splat still decoding when superseded), revokes the blob
URL and disposes the renderer. The canvas is reused for the next file,
so the context loss is forced only once the canvas has left the DOM.
Both upload components tear down through it.
2026-10-06 15:52:33 -07:00
Tanish Manku a8c86f85f2 pagedraw: free 3D model renders and the model overlay's GPU state
render3DModelToDataUrl builds a throwaway renderer for every 2D-canvas
snapshot of a 3D model. It freed geometries and materials but not
textures, and never released the context, so each snapshot held a WebGL
context until its canvas was collected. It now frees the scene with
disposeObject3D and forces the context loss.

Model3DOverlay had the same texture leak, and a GLB that finished
loading after the overlay closed or switched models was still added to
the disposed scene. Late loads are now freed instead. The overlay
renders into a React-owned canvas that a model change reuses, so the
context loss is forced only once that canvas has left the DOM.
2026-10-06 15:51:09 -07:00
Tanish Manku 97173bee39 viewer-3d: free models on swap and unmount, and drop late loads
Switching models or closing the viewer only removed the model from the
scene, so every GLB's buffers and textures and every splat's buffers
stayed on the GPU. A model that finished loading after the URL changed
or the viewer unmounted was still added to the scene.

Model teardown now frees the model, and loads that finish after their
effect was cleaned up are freed instead of shown. Unmount frees the
whole scene (only the placeholder cube was freed before) and forces the
context loss, which is safe here because the viewer owns its canvas and
has already detached it.

The existing render spec failed because jsdom has no ResizeObserver;
stub it and add an unmount-before-init case.
2026-10-06 15:50:13 -07:00
Tanish Manku 0e899246fa 3d: move disposeObject3D into viewer-3d so every 3D surface can share it
pagedraw's model overlay, upload-modal's previews and viewer-3d itself
leak the same way pagescene did. viewer-3d is the shared 3D leaf lib
(upload-modal already imports NodeHierarchyHelper from it), so the
helper moves there and pagescene imports it from
@storyteller/ui-viewer-3d.
2026-10-06 15:49:31 -07:00
Tanish Manku 5914741479 pagescene: prettier on the disposal helper and specs 2026-10-06 15:34:44 -07:00
Tanish Manku cbae8ce468 pagescene: discard objects that finish loading after a load is cancelled
Scene loads add each asset to the scene as soon as it finishes. When a
load was cancelled (superseded by a newer load, or the editor unmounted)
the loader returned after its tasks settled, leaving whatever had
finished in the scene. On unmount that leaked the objects; on a
superseded load they appeared in the newer load's scene.

A cancelled load now removes and frees the objects it created. Lights
are left alone because Scene tracks its base lights in fields a newer
load may have re-pointed at them.
2026-10-06 15:34:28 -07:00
Tanish Manku 6d31959e8a pagescene: free the scene when the editor unmounts
unmountEngine disposed the renderers but none of the scene. Every mount
builds a new Editor, and on a scene change it builds it on the same
canvases, which hands it the same WebGL context, so the old scene's
buffers and textures stayed allocated in a live context. In the webapp
this happened on every scene-to-scene navigation.

Unmount now finishes running entrances, disposes the gizmo (which also
removes its pointer listeners from the reused canvas) and frees the
scene's contents and background. This is safe because EngineProvider
serializes the scene before calling unmountEngine and nothing reuses an
Editor after it. The old pause/resume use of unmount/remount, which kept
the scene alive across tab switches, went away in #654.

forceContextLoss is deliberately not called: the next Editor may be
created on these canvases and would start on a lost context.
2026-10-06 15:33:29 -07:00
Tanish Manku b0e2ed9216 pagescene: free scene loads and per-object registries along with GPU memory
Loading a scene over the current one removed every child without freeing
anything. It now goes through Scene.disposeContents, the same path New
Scene uses.

Deleting or clearing objects also left them in Scene's side registries:
deleted Water stayed in shader_objects and was ticked every frame, video
planes kept their <video> element playing and buffered in video_planes,
and MMD meshes stayed registered with the animation helper.
Scene.disposeObject now prunes those entries and stops the video
(pause alone keeps the decoder alive, so the source is dropped too)
before freeing the tree. Object delete routes through it.
2026-10-06 15:32:51 -07:00
Tanish Manku df65e52eae pagescene: free shader-uniform textures and SparkRenderer buffers on dispose
disposeObject3D only looked at material properties, so textures held in
ShaderMaterial uniforms leaked: Water's normal map and mirror texture,
and Spark's splat textures. It now collects textures from both places,
deduplicated so a texture referenced twice (ChromaKeyMaterial keeps its
VideoTexture as a property and a uniform) is disposed once.

The SparkRenderer that Spark adds to the scene on the first splat render
owns a viewpoint with render targets and a pool of splat accumulators,
none of which its material references. Free them when it is disposed.

Water keeps its mirror render target in a closure, so its framebuffer and
depth buffer remain out of reach; only the color texture is freed.
2026-10-06 15:30:46 -07:00
heojunfoandClaude Opus 5.5 0b613b0cb3 Free GPU resources on New Scene, object delete and skybox change
Three paths in the 3D stage dropped objects without releasing what they
held on the GPU:

- Scene.clear() (New Scene) assigned `scene.children = []`, which
  disposed nothing and left each child parented to the scene. Every New
  Scene leaked all geometries, materials, textures and splat buffers of
  the previous one.
- SceneUtils.deleteObject disposed geometries and materials but not the
  textures they reference (Material.dispose() does not free its maps),
  so deleting a textured GLB leaked every map.
- _create_skybox replaced scene.background without disposing the
  previous cube texture.

Add disposeObject3D(), which frees geometries, materials and their
textures (deduplicated), skeleton bone textures and Spark splat buffers,
and use it in clear() and deleteObject. clear() now uses Object3D.clear()
so children are actually unparented. The old deleteObject also checked a
custom `.texture` field that nothing in the codebase sets; that branch
is dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 15:29:02 -07:00
295 changed files with 28960 additions and 138 deletions

No files matched your search

+9 -4
View File
@@ -34,6 +34,7 @@ artcraft-services/
├── frontend/
│ ├── apps/artcraft-webapp/ # Browser application at app.getartcraft.com
│ ├── apps/artcraft-website-next/ # Current public website at getartcraft.com (Next.js)
│ ├── apps/artcraft-website/ # Legacy product website (Vite)
│ ├── apps/artcraft-desktop-website/ # Static landing page at desktop.getartcraft.com (Vite)
│ └── libs/ # Shared React components and TypeScript libraries
└── Cargo.toml # Rust workspace
@@ -41,9 +42,9 @@ artcraft-services/
## Updating Desktop Download Links
Desktop release links are configured independently in **two places**. When
Desktop release links are configured independently in **three places**. When
updating the advertised ArtCraft release, update both `WINDOWS_VERSION` and
`MAC_VERSION` in both unless the request explicitly targets one platform or
`MAC_VERSION` in all three unless the request explicitly targets one platform or
app:
1. **Current public website (Next.js):**
@@ -51,7 +52,11 @@ app:
This controls the installer links and visible version at
`https://getartcraft.com/download`. Add the release to `DOWNLOAD_HISTORY`
(newest first), then update both version selectors.
2. **Browser webapp:**
2. **Legacy website (Vite):**
[frontend/apps/artcraft-website/src/config/github_download_links.ts](frontend/apps/artcraft-website/src/config/github_download_links.ts).
Add the release to `DOWNLOAD_HISTORY` and update both version selectors.
Updating only this file does **not** update the current public website.
3. **Browser webapp:**
[frontend/apps/artcraft-webapp/src/config/github_download_links.ts](frontend/apps/artcraft-webapp/src/config/github_download_links.ts).
Update both version constants; these links are used by the welcome and
checkout-success pages at `app.getartcraft.com`.
@@ -60,7 +65,7 @@ Keep previous releases for rollback. The Next.js download page reads its visible
version from `DOWNLOAD_VERSIONS`, which uses the same selectors as the URLs; do
not hardcode a separate version in the page component.
Check that both configs produce the requested release tag and installer
Check that all three configs produce the requested release tag and installer
filenames for Windows (`ArtCraft_<version>_x64-setup.exe`) and macOS
(`ArtCraft_<version>_universal.dmg`). Run `npm run typecheck` from
`frontend/apps/artcraft-website-next` after changing its config. Repository edits
+8 -5
View File
@@ -104,7 +104,7 @@ and signals for state, Three.js for 3D scenes, and shared UI and generation tool
| Path | Purpose |
|----------------------------------|-----------------------------------------------|
| `frontend/apps/artcraft-webapp` | Browser application at `app.getartcraft.com` |
| `frontend/apps/artcraft-website-next` | Product website at `getartcraft.com` (Next.js) |
| `frontend/apps/artcraft-website` | Product website at `getartcraft.com` |
| `frontend/libs/api` | HTTP clients, API host selection, and models |
| `frontend/libs/omni-gen` | Shared generation logic |
| `frontend/libs/components` | Reusable UI, editors, and generation controls |
@@ -190,11 +190,14 @@ The browser app runs at `http://localhost:4201`. `VITE_USE_LOCAL_API=true` selec
the hosted API. API host selection lives in
[`StorytellerApiHostStore`](./frontend/libs/api/src/lib/config/StorytellerApiHostStore.ts).
From `frontend`, build the web app with `npx nx build artcraft-webapp`. The
product website is a standalone Next.js app (not part of the Nx workspace); run it
from the repository root with `./script/website/unix_frontend_website_dev.sh`.
From `frontend`, build the web app or run the product website with:
The website runs at `http://localhost:3000`. Repository-root launchers are in
```bash
npx nx build artcraft-webapp
npx nx dev artcraft-website
```
The website runs at `http://localhost:4200`. Repository-root launchers are in
[`script/website`](./script/website). For desktop development, use the
[ArtCraft desktop repository](https://github.com/storytold/artcraft).
+1
View File
@@ -255,6 +255,7 @@ From the services repository:
SQLX_OFFLINE=true cargo test --offline -p storyteller-web --bin storyteller-web login_challenges
cd frontend
npm exec vitest -- run --config apps/artcraft-webapp/vite.config.ts src/pages/login/desktop-login.spec.tsx src/pages/login/login-bridge-continuation.spec.tsx
npm exec vitest -- run --config apps/artcraft-website/vite.config.ts src/pages/login/desktop-login.spec.tsx
```
`SQLX_OFFLINE` controls compile-time query metadata only. These tests still execute
+2 -2
View File
@@ -35,6 +35,6 @@ VITE_USE_LOCAL_API=true npx nx dev artcraft-webapp
The web app runs at `http://localhost:4201`, using the local backend. To develop
against the hosted API, omit `VITE_USE_LOCAL_API=true`.
For the product website, run `./script/website/unix_frontend_website_dev.sh` from the
repository root; it is a standalone Next.js app on `http://localhost:3000`. Repository-root launchers live under [`script/website`](../script/website).
For the product website, run `npx nx dev artcraft-website` from `frontend`; it uses
`http://localhost:4200`. Repository-root launchers live under [`script/website`](../script/website).
See the [frontend README](../frontend/README.md) for builds and troubleshooting.
@@ -1,11 +1,10 @@
use std::str::FromStr;
use std::time::Duration;
use log::info;
use errors::AnyhowResult;
use shared_env_var_config::mysql::env_get_mysql_connection_string_or_default;
use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions};
use sqlx::mysql::MySqlPoolOptions;
use sqlx::MySqlPool;
pub async fn connect_to_database() -> AnyhowResult<MySqlPool> {
@@ -21,37 +20,20 @@ pub async fn connect_to_database() -> AnyhowResult<MySqlPool> {
// shorter timeout sheds load and surfaces the problem instead of hiding it behind long waits.
let acquire_timeout_seconds = easyenv::get_env_num::<u64>("MYSQL_ACQUIRE_TIMEOUT_SECONDS", 10)?;
// NB: Every cached prepared statement stays open on the server until its connection evicts it
// or closes, and the server caps the total across ALL clients (`max_prepared_stmt_count`,
// 16382 on the managed DB). sqlx's default of 100 per connection let `replicas *
// MYSQL_MAX_CONNECTIONS * 100` blow past that cap, failing new prepares with MySQL error 1461.
// Keep `replicas * MYSQL_MAX_CONNECTIONS * MYSQL_STATEMENT_CACHE_CAPACITY` well under it.
let statement_cache_capacity = easyenv::get_env_num::<usize>("MYSQL_STATEMENT_CACHE_CAPACITY", 25)?;
// NB: Recycling connections releases any server-side statements orphaned by cancelled queries
// (eg. a client disconnect between prepare and close). sqlx's default lifetime is 30 minutes.
let max_lifetime_seconds = easyenv::get_env_num::<u64>("MYSQL_MAX_LIFETIME_SECONDS", 600)?;
// NB: Log the settings before building/connecting so a connect failure has context. We do NOT
// log the connection string (it contains credentials).
info!(
"Building MySQL pool: max_connections={}, min_connections={}, acquire_timeout_seconds={}, \
statement_cache_capacity={}, max_lifetime_seconds={}",
"Building MySQL pool: max_connections={}, min_connections={}, acquire_timeout_seconds={}",
max_connections, min_connections, acquire_timeout_seconds,
statement_cache_capacity, max_lifetime_seconds,
);
let connect_options = MySqlConnectOptions::from_str(&db_connection_string)?
.statement_cache_capacity(statement_cache_capacity);
let pool_options = MySqlPoolOptions::new()
.max_connections(max_connections)
.min_connections(min_connections)
.acquire_timeout(Duration::from_secs(acquire_timeout_seconds))
.max_lifetime(Some(Duration::from_secs(max_lifetime_seconds)));
.acquire_timeout(Duration::from_secs(acquire_timeout_seconds));
let pool = pool_options
.connect_with(connect_options)
.connect(&db_connection_string)
.await?;
Ok(pool)
+3 -1
View File
@@ -31,7 +31,7 @@ From the **repository root**, the launcher scripts (these preflight your
environment, install dependencies, and free the dev port first):
```bash
./script/website/unix_frontend_website_dev.sh # artcraft-website-next (marketing site), port 3000
./script/website/unix_frontend_website_dev.sh # artcraft-website (marketing site), port 4200
./script/website/unix_frontend_webapp_dev.sh # artcraft-webapp (user dashboard), port 4201
```
@@ -46,12 +46,14 @@ by its full or relative path:
Or directly from this directory:
```bash
nx dev artcraft-website
npx nx dev @frontend/artcraft-webapp
```
## Building
```bash
npx nx build artcraft-website
npx nx build artcraft-webapp
```
+18 -1
View File
@@ -4,6 +4,8 @@ import react from '@vitejs/plugin-react';
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { execSync } from 'child_process';
import path from 'path';
// Dev-only CDN proxy. Media assets (splats, GLBs, images) are fetched with
// fetch(), but the CDNs only send CORS headers for the production origin —
@@ -69,6 +71,21 @@ function cdnProxyPlugin() {
};
}
// Custom plugin to generate news.json on dev server start
function generateNewsPlugin() {
return {
name: 'generate-news',
buildStart() {
try {
const scriptPath = path.resolve(__dirname, '../../scripts/generate-news-json.mjs');
execSync(`node "${scriptPath}"`, { stdio: 'inherit' });
} catch (e) {
console.warn('Failed to generate news.json:', e);
}
},
};
}
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/apps/artcraft-webapp',
@@ -93,7 +110,7 @@ export default defineConfig(() => ({
port: 4301,
host: 'localhost',
},
plugins: [cdnProxyPlugin(), nxViteTsPaths(), react(), wasm(), topLevelAwait()],
plugins: [cdnProxyPlugin(), generateNewsPlugin(), nxViteTsPaths(), react(), wasm(), topLevelAwait()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
@@ -32,18 +32,6 @@ const nextConfig: NextConfig = {
destination: "/apps/vectorcraft",
permanent: true,
},
// PrintCraft was renamed PdfCraft. Old page links and share images
// (cached by social sites) follow it.
{
source: "/apps/printcraft",
destination: "/apps/pdfcraft",
permanent: true,
},
{
source: "/images/apps/printcraft/:file",
destination: "/images/apps/pdfcraft/:file",
permanent: true,
},
// Legacy share-link form (`/media?media=<token>`) from the Vite site;
// the canonical route is /media/<token>. Mirrored in netlify.toml so
// the edge answers it before the Next runtime.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 74 KiB

@@ -321,14 +321,14 @@ html[data-ruler="on"]::-webkit-scrollbar {
.app-vectorcraft { --app: #e8573f; --app-ink: #b83a24; }
.app-filmcraft { --app: #8b5cf6; --app-ink: #6a3fd6; }
.app-lightcraft { --app: #f2a516; --app-ink: #8a5800; }
.app-pdfcraft { --app: #12a58a; --app-ink: #0a7563; }
.app-printcraft { --app: #12a58a; --app-ink: #0a7563; }
.app-effectcraft { --app: #e0368f; --app-ink: #b0206c; }
.app-designcraft { --app: #7bb51c; --app-ink: #457008; }
:root[data-theme="dark"] .app-photocraft { --app-ink: #7fb0ff; }
:root[data-theme="dark"] .app-vectorcraft { --app-ink: #ff8a70; }
:root[data-theme="dark"] .app-filmcraft { --app-ink: #b69cff; }
:root[data-theme="dark"] .app-lightcraft { --app-ink: #ffc14d; }
:root[data-theme="dark"] .app-pdfcraft { --app-ink: #3fd6b8; }
:root[data-theme="dark"] .app-printcraft { --app-ink: #3fd6b8; }
:root[data-theme="dark"] .app-effectcraft { --app-ink: #ff7ab8; }
:root[data-theme="dark"] .app-designcraft { --app-ink: #a6e04a; }
@media (prefers-color-scheme: dark) {
@@ -336,7 +336,7 @@ html[data-ruler="on"]::-webkit-scrollbar {
:root:not([data-theme="light"]) .app-vectorcraft { --app-ink: #ff8a70; }
:root:not([data-theme="light"]) .app-filmcraft { --app-ink: #b69cff; }
:root:not([data-theme="light"]) .app-lightcraft { --app-ink: #ffc14d; }
:root:not([data-theme="light"]) .app-pdfcraft { --app-ink: #3fd6b8; }
:root:not([data-theme="light"]) .app-printcraft { --app-ink: #3fd6b8; }
:root:not([data-theme="light"]) .app-effectcraft { --app-ink: #ff7ab8; }
:root:not([data-theme="light"]) .app-designcraft { --app-ink: #a6e04a; }
}
@@ -32,12 +32,11 @@ import { ColorAccent } from "./app-wordmark";
const CELL_HEADING_CLASSES =
"mt-4 font-display text-3xl font-medium leading-[1.05] tracking-[-0.03em] text-ink-strong sm:text-4xl";
// The hero carries shot 01. Window captures pair up, and an odd one out
// leads at full width; portrait pages (layout spreads) sit four across.
// The hero carries shot 01. Window captures lead with a full-width shot,
// then pair; portrait pages (layout spreads) sit four across.
export function AppGallery({ app, index }: { app: CraftApp; index: string }) {
const [, ...shots] = app.shots;
const portrait = shots.every((shot) => shot.portrait);
const leadFullWidth = !portrait && shots.length % 2 === 1;
return (
<SectionShell id="gallery">
<SectionEyebrow
@@ -56,7 +55,7 @@ export function AppGallery({ app, index }: { app: CraftApp; index: string }) {
<figure
key={shot.file}
data-reveal
className={twMerge("bg-bg", leadFullWidth && i === 0 && "md:col-span-2")}
className={twMerge("bg-bg", !portrait && i === 0 && "md:col-span-2")}
>
<a
href={craftShotSourceUrl(app, shot)}
@@ -3,14 +3,17 @@ import type { CraftAppSlug } from "./crafting-apps";
// GitHub releases for the Crafting Apps, which drive every download button on
// the /apps pages.
//
// To bump an app: change its version in CRAFT_APP_RELEASES (each app has its
// own, e.g. `craftRelease("0.4.0")`).
// To bump every app: change CRAFTING_APPS_LATEST_VERSION.
// To pin one app to another version: give it its own release in
// CRAFT_APP_RELEASES, e.g. `craftRelease("0.1.1")`.
// To hide an app's downloads (Discord waitlist instead): set it to null.
// If a release renames, adds or drops files, edit CRAFT_RELEASE_ASSETS, or
// pass a custom list as craftRelease's second argument for a single app.
//
// Release tags are `v<version>` and live at github.com/storytold/<slug>.
export const CRAFTING_APPS_LATEST_VERSION = "0.2.0";
export type CraftDesktopPlatform = "macOS" | "Windows" | "Linux";
/** Order of the per-platform download buttons. */
@@ -19,7 +22,7 @@ export const CRAFT_DESKTOP_PLATFORMS: CraftDesktopPlatform[] = ["macOS", "Window
export type CraftReleaseAsset = {
/** Asset filename; `{slug}` and `{version}` are filled in per app. */
file: string;
/** Group in the full file list. "Other" holds the FreeBSD build, web build and checksums. */
/** Group in the full file list. "Other" holds the CLI, web build and checksums. */
group: CraftDesktopPlatform | "Other";
/** Short name, e.g. "Installer (x64)". */
label: string;
@@ -40,8 +43,6 @@ export type CraftRelease = {
version: string;
/** Git tag. Defaults to `v<version>`. */
tag?: string;
/** Fills `{slug}` in filenames. Defaults to the app's slug; a release from before a rename keeps the old one. */
fileSlug?: string;
assets: readonly CraftReleaseAsset[];
};
@@ -91,20 +92,6 @@ export const CRAFT_RELEASE_ASSETS: readonly CraftReleaseAsset[] = [
description: "Unzip and run on 32-bit Windows.",
arch: "x86",
},
{
file: "{slug}-{version}-windows-arm64.msi",
group: "Windows",
label: "Installer (ARM64)",
description: "Windows 11 on ARM laptops.",
arch: "aarch64",
},
{
file: "{slug}-{version}-windows-arm64-portable.zip",
group: "Windows",
label: "Portable (ARM64)",
description: "Unzip and run on Windows on ARM.",
arch: "aarch64",
},
{
file: "{slug}-{version}-linux-x86_64.AppImage",
group: "Linux",
@@ -113,13 +100,6 @@ export const CRAFT_RELEASE_ASSETS: readonly CraftReleaseAsset[] = [
arch: "x86_64",
recommended: true,
},
{
file: "{slug}-{version}-linux-x86_64.flatpak",
group: "Linux",
label: "Flatpak (x86_64)",
description: "Sandboxed. Install with flatpak install --user.",
arch: "x86_64",
},
{
file: "{slug}-{version}-linux-x86_64.deb",
group: "Linux",
@@ -149,13 +129,6 @@ export const CRAFT_RELEASE_ASSETS: readonly CraftReleaseAsset[] = [
arch: "aarch64",
recommended: true,
},
{
file: "{slug}-{version}-linux-aarch64.flatpak",
group: "Linux",
label: "Flatpak (ARM64)",
description: "Sandboxed, on 64-bit ARM.",
arch: "aarch64",
},
{
file: "{slug}-{version}-linux-aarch64.deb",
group: "Linux",
@@ -177,12 +150,6 @@ export const CRAFT_RELEASE_ASSETS: readonly CraftReleaseAsset[] = [
description: "Plain binaries for 64-bit ARM.",
arch: "aarch64",
},
{
file: "{slug}-{version}-freebsd-x86_64.tar.gz",
group: "Other",
label: "FreeBSD (x86_64)",
description: "Plain binaries for FreeBSD 14.",
},
{
file: "{slug}-web-{version}.zip",
group: "Other",
@@ -204,13 +171,15 @@ export function craftRelease(
return { version, assets };
}
const LATEST = craftRelease(CRAFTING_APPS_LATEST_VERSION);
// Every app must be listed, so a new slug fails the type check until it is.
export const CRAFT_APP_RELEASES: Record<CraftAppSlug, CraftRelease | null> = {
photocraft: craftRelease("0.5.0"),
vectorcraft: craftRelease("0.6.0"),
filmcraft: craftRelease("0.4.0"),
lightcraft: craftRelease("0.4.0"),
pdfcraft: craftRelease("0.4.0"),
effectcraft: craftRelease("0.6.0"),
designcraft: craftRelease("0.4.0"),
photocraft: LATEST,
vectorcraft: LATEST,
filmcraft: LATEST,
lightcraft: LATEST,
printcraft: LATEST,
effectcraft: LATEST,
designcraft: LATEST,
};
@@ -25,7 +25,7 @@ export type CraftAppSlug =
| "vectorcraft"
| "filmcraft"
| "lightcraft"
| "pdfcraft"
| "printcraft"
| "effectcraft"
| "designcraft";
@@ -177,31 +177,25 @@ export const CRAFTING_APPS: CraftApp[] = [
file: "hero.webp",
source: "shot-1-neon.png",
caption: "Neon Drive — made in VectorCraft",
alt: "VectorCraft editing the Neon Drive poster, with the title's live outer glow settings in the Appearance panel and its character settings in the Properties panel",
alt: "VectorCraft editing a synthwave poster titled Neon Drive with an outer glow in the Appearance panel",
},
{
file: "ribbons.webp",
source: "shot-2-ribbons.png",
caption: "Live blends and layers",
alt: "VectorCraft showing three live blend ribbons clipped to the artboard, one selected with its key paths, and the Layers panel listing the clip group and blends",
},
{
file: "bezier.webp",
source: "shot-4-bezier.png",
caption: "Pen and direct selection",
alt: "VectorCraft direct-selecting the anchor points and bezier handles of a crescent moon, with the contextual task bar below it",
},
{
file: "perspective.webp",
source: "shot-5-perspective.png",
caption: "Perspective grid, still editable",
alt: "VectorCraft with two lit building facades drawn on the left and right planes of a two-point perspective grid at sunset",
caption: "Live blends between editable spines",
alt: "VectorCraft showing live blends: 70 smooth-color steps between two ribbon paths",
},
{
file: "sheet.webp",
source: "shot-3-sheet.png",
caption: "Booleans, mesh, repeat and envelope",
alt: "VectorCraft in its light theme with four artboards: shape booleans, a gradient mesh, a live radial repeat and an envelope distort",
alt: "VectorCraft feature sheet with exact booleans, a gradient mesh, a live radial repeat and an envelope distort",
},
{
file: "bezier.webp",
source: "shot-4-bezier.png",
caption: "Direct selection on bezier anchors",
alt: "VectorCraft direct-selecting the anchors and handles of a crescent moon path",
},
],
},
@@ -334,8 +328,8 @@ export const CRAFTING_APPS: CraftApp[] = [
],
},
{
slug: "pdfcraft",
prefix: "Pdf",
slug: "printcraft",
prefix: "Print",
category: "PDF workbench",
status: "Early alpha",
headline: ["The ", "open-source", " PDF workbench."],
@@ -373,27 +367,27 @@ export const CRAFTING_APPS: CraftApp[] = [
shots: [
{
file: "hero.webp",
source: "pdfcraft-viewer.png",
source: "printcraft-viewer.png",
caption: "Viewer with threaded comments",
alt: "PdfCraft showing a showcase PDF cover with the All tools panel and threaded comments",
alt: "PrintCraft showing a showcase PDF cover with the tools sidebar and threaded comments",
},
{
file: "organize.webp",
source: "pdfcraft-organize.png",
source: "printcraft-organize.png",
caption: "Organize pages like cards",
alt: "PdfCraft page organizer with three pages selected and the page tools in the toolbar above",
alt: "PrintCraft page organizer with three pages selected for rotate, delete and extract",
},
{
file: "palette.webp",
source: "pdfcraft-palette.png",
source: "printcraft-palette.png",
caption: "A command palette for every tool",
alt: "PdfCraft command palette searching for page and listing the page tools",
alt: "PrintCraft command palette filtering page tools by the word page",
},
{
file: "twoup.webp",
source: "pdfcraft-twoup.png",
source: "printcraft-twoup.png",
caption: "Two-up Read mode, dark theme",
alt: "PdfCraft two-up Read mode in the dark theme showing typeset specimen pages",
alt: "PrintCraft two-up Read mode in the dark theme showing typeset specimen pages",
},
],
},
@@ -655,7 +649,7 @@ export function craftReleaseDownloads(
const base = `${craftAppRepo(app)}/releases/download/${craftReleaseTag(release)}`;
return release.assets.map(({ file, ...asset }) => {
const fileName = file
.replaceAll("{slug}", release.fileSlug ?? app.slug)
.replaceAll("{slug}", app.slug)
.replaceAll("{version}", release.version);
return { ...asset, fileName, href: `${base}/${fileName}` };
});
@@ -0,0 +1,4 @@
# Google OAuth Configuration
# Get the Client ID from the Google Cloud Console:
# https://console.cloud.google.com/apis/credentials
VITE_GOOGLE_CLIENT_ID=YOUR_CLIENT_ID.apps.googleusercontent.com
@@ -0,0 +1,143 @@
# Website → Webapp Migration
The marketing site (`artcraft-website`) is being split apart from the app
(`artcraft-webapp`). Anything that isn't pure marketing content belongs in
the webapp at `app.getartcraft.com`.
The migration is **gated by a single boolean** so it can be reverted while
we soak the new redirect behavior in production. When we're confident the
webapp is handling everything cleanly, do the **cleanup pass** below to
permanently delete everything that's only kept around for the rollback path.
---
## The switch
`src/config/links.ts`:
```ts
export const USE_WEBAPP_FOR_APP_FEATURES = true;
```
- `true` (default): legacy paths redirect to the webapp; navbar shows
"Launch App"; in-page links go cross-origin to `app.getartcraft.com`.
- `false`: everything runs locally on the marketing site (the original
pre-split behavior).
In dev, `WEBAPP_URL` auto-switches to `http://localhost:4201/` so cross-app
links work against the local webapp Vite server.
---
## What's behind the flag
### Pages that exist locally only as a fallback
When `USE_WEBAPP_FOR_APP_FEATURES = true`, these routes mount
`<WebappRedirect to="…" />` instead of the page component. The page files
are kept on disk so we can roll back by flipping the flag.
- `src/pages/login/`
- `src/pages/signup/`
- `src/pages/forgot-password/`
- `src/pages/create-image/`
- `src/pages/create-video/`
- `src/pages/create-vfx/` (the `/background-change` route)
- `src/pages/library/`
- `src/pages/referrals/`
- `src/pages/welcome/`
- `src/pages/onboarding/`
- `src/pages/checkout/` (success + cancel)
### Components only used by the above
- `src/components/auth/` (SignupForm, GoogleLoginButton)
- `src/components/credits-modal.tsx`
- `src/components/generation-gallery/`
- `src/components/lightbox/lightbox.tsx` (the modal `Lightbox`; the
`LightboxDetails` + `shared.ts` files in the same folder are still used
by the public `/media` page and must be kept)
- `src/components/navbar/task-queue.tsx`
- `src/components/prompt-box/`
- `src/components/settings-modal/`
### Lib files only used by the above
- `src/lib/cost-estimate-api.ts`
- `src/lib/enter-to-generate-store.ts`
- `src/lib/omni-gen-hooks.ts`
- `src/lib/prompts-cache.ts`
- `src/lib/recreate.ts`
### Files with conditional flag branches (need cleanup, not deletion)
- `src/app/app.tsx` — drop the `appOrWebapp()` helper, drop the local route
imports, leave only the `<WebappRedirect />` routes
- `src/components/navbar/navbar.tsx` — drop the `!USE_WEBAPP_FOR_APP_FEATURES`
branches (Login + Sign up buttons in desktop and mobile); keep only Launch
App for logged-in users and the Pricing + Launch App combo for logged-out
users
- `src/components/footer/footer.tsx` — drop the conditional Image/Video
links in the Pages column
- `src/components/download-modal.tsx` — drop the inline `SignupForm` flow
and the `view`/`navigate` state; keep only the webapp `/signup` and
`/login` link buttons
- `src/pages/landing3/landing3.tsx` — `appLink("/create-image")` /
`appLink("/create-video")` can become bare `webappUrl("…")` calls (or
inline `${WEBAPP_URL}create-image`)
- `src/pages/media/media.tsx` — drop the local
`applyRecreateFromMediaToken` branch and the `useNavigate` import; keep
only the `window.location.href = …` webapp redirect
- `src/pages/pricing/pricing.tsx` — the "Buy Credits" section + `CreditsModal`
is currently shown unconditionally; once `credits-modal.tsx` is deleted,
remove the section, the `creditsModalOpen` state, the `isLoggedIn` state,
and the session-check `useEffect`
### The flag itself
`src/config/links.ts` — drop `USE_WEBAPP_FOR_APP_FEATURES`, drop the
`appLink()` helper (use `webappUrl()` directly everywhere). Keep
`WEBAPP_URL`, `webappUrl()`, `SOCIAL_LINKS`, `SUPPORT_EMAIL`, and the dev
override.
---
## Cleanup pass — what to do when we're ready to commit to the webapp split
1. **Confirm the flag has been `true` in production for long enough** that
we're sure no real users hit the local routes.
2. **Delete** every directory and file listed under "Pages that exist
locally only as a fallback", "Components only used by the above", and
"Lib files only used by the above".
3. **Strip the flag branches** from each file under "Files with conditional
flag branches".
4. **Remove `USE_WEBAPP_FOR_APP_FEATURES`** and `appLink()` from
`src/config/links.ts`.
5. **Run** `npx tsc -p tsconfig.app.json --noEmit` from
`frontend/apps/artcraft-website` and fix any references the cleanup
exposed. (Pre-existing errors in `download-button.tsx` `LINUX` and
`pricing-table.tsx` `maybeReferralUsername` are unrelated and can be
ignored.)
6. **Verify** in dev with `nx serve @frontend/artcraft-website` that every
navbar/footer/landing/media flow still works and that legacy URLs still
redirect to the webapp via `WebappRedirect`.
---
## What stays on the marketing site no matter what
These are pure marketing content and are **not** behind the flag:
- `/` → `landing3`
- `/landing3`
- `/seedance-2` → `landing-sd2`
- `/download`
- `/media`, `/media/:id` (public viewer; the lightbox's "Recreate" button
does cross-origin to the webapp, but the viewer itself stays here)
- `/press-kit`
- `/tutorials`, `/tutorials/:slug`
- `/faq`, `/faq/:slug`
- `/news`, `/news/:slug`
- `/support`
- `/pricing` — Stripe checkout flow stays here (PricingTable → Stripe →
`/checkout/success` → WebappRedirect → webapp)
+157
View File
@@ -0,0 +1,157 @@
/background-change /index.html 200
/background-change/ /index.html 200
/background-change/* /index.html 200
/checkout_cancel /index.html 200
/checkout_cancel/ /index.html 200
/checkout_cancel/* /index.html 200
/onboarding /index.html 200
/onboarding/ /index.html 200
/onboarding/* /index.html 200
/portal_closed /index.html 200
/portal_closed/ /index.html 200
/portal_closed/* /index.html 200
/checkout_success /index.html 200
/checkout_success/ /index.html 200
/checkout_success/* /index.html 200
/checkout* /index.html 200
/checkout/* /index.html 200
/create-image /index.html 200
/create-image/ /index.html 200
/create-image/* /index.html 200
/create-video /index.html 200
/create-video/ /index.html 200
/create-video/* /index.html 200
/support /index.html 200
/support/ /index.html 200
/support/* /index.html 200
/library /index.html 200
/library/ /index.html 200
/library/* /index.html 200
/download /index.html 200
/download/ /index.html 200
/tutorials /index.html 200
/tutorials/ /index.html 200
/login /index.html 200
/login/ /index.html 200
/login/* /index.html 200
/media /index.html 200
/media/ /index.html 200
/media/* /index.html 200
# Prerendered: serve the per-route index.html so social crawlers get the
# correct OG/title tags (see scripts/prerender-meta.mjs). The SPA still boots
# from this file and React Router takes over for users.
/pricing /pricing/index.html 200
/pricing/ /pricing/index.html 200
/pricing/* /index.html 200
/beta /beta/index.html 200
/beta/ /beta/index.html 200
/beta/* /index.html 200
/faq /index.html 200
/faq/ /index.html 200
/faq/* /index.html 200
/news /index.html 200
/news/ /index.html 200
/news/* /index.html 200
/press-kit /index.html 200
/press-kit/ /index.html 200
/press-kit/* /index.html 200
/referrals /index.html 200
/referrals/ /index.html 200
/referrals/* /index.html 200
# Redirects for growth measurement
/reddit /?utm_source=reddit 301
/news.json /index.html 200
/signup /index.html 200
/signup/ /index.html 200
/signup/* /index.html 200
/forgot-password /index.html 200
/forgot-password/ /index.html 200
/forgot-password/* /index.html 200
/welcome /index.html 200
/welcome/ /index.html 200
/welcome/* /index.html 200
/password-reset /index.html 200
/password-reset/ /index.html 200
/password-reset/* /index.html 200
# Prerendered: exact paths serve their per-route index.html (correct OG tags
# for social crawlers); deep sub-paths fall back to the SPA shell. See
# scripts/prerender-meta.mjs. NOTE: these exact-match serving rules MUST come
# before the /seedance2* alias below, otherwise that wildcard would 301
# /seedance2-5 away to /seedance-2 (first match wins in _redirects).
/seedance2-5 /seedance2-5/index.html 200
/seedance2-5/ /seedance2-5/index.html 200
/seedance2-5/* /index.html 200
/seedance-2 /seedance-2/index.html 200
/seedance-2/ /seedance-2/index.html 200
/seedance-2/* /index.html 200
/minimax-h3 /minimax-h3/index.html 200
/minimax-h3/ /minimax-h3/index.html 200
/minimax-h3/* /index.html 200
/creators/jboogxcreative /creators/jboogxcreative/index.html 200
/creators/jboogxcreative/ /creators/jboogxcreative/index.html 200
/creators/* /index.html 200
# Similar URLs — creator spotlight aliases
/jboogx /creators/jboogxcreative 301
/jboogx/ /creators/jboogxcreative 301
/jboogxcreative /creators/jboogxcreative 301
/jboogxcreative/ /creators/jboogxcreative 301
# Similar URLs: MiniMax H3 aliases
/minimax /minimax-h3 301
/minimax/ /minimax-h3 301
/minimaxh3 /minimax-h3 301
/minimaxh3* /minimax-h3 301
/minimax-h-3 /minimax-h3 301
/minimax-h-3* /minimax-h3 301
/hailuo /minimax-h3 301
/hailuo/ /minimax-h3 301
# Google ads breakage
/seedance-2{ignore} /seedance-2 301
/seedance-2{ignore}* /seedance-2 301
/seedance-2%7Bignore%7D /seedance-2 301
/seedance-2%7Bignore%7D* /seedance-2 301
# Similar URLs — Seedance 2.5 aliases (kept above the broad /seedance2* rule)
/seedance-2-5 /seedance2-5 301
/seedance-2-5* /seedance2-5 301
/seedance-2.5 /seedance2-5 301
/seedance-2.5* /seedance2-5 301
/seedance25 /seedance2-5 301
/seedance25* /seedance2-5 301
# Similar URLs — Seedance 2.0 aliases
/seedance2 /seedance-2 301
/seedance2* /seedance-2 301
/seedance-2.0 /seedance-2 301
/seedance-2.0* /seedance-2 301
+52
View File
@@ -0,0 +1,52 @@
<!doctype html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
if (window.location.pathname === "/login/desktop") {
const policy = document.createElement("meta");
policy.name = "referrer";
policy.content = "no-referrer";
document.head.appendChild(policy);
} else {
const analytics = document.createElement("script");
analytics.async = true;
analytics.src = "https://www.googletagmanager.com/gtag/js?id=G-VKW89949V0";
document.head.appendChild(analytics);
gtag("js", new Date());
gtag("config", "G-VKW89949V0");
}
</script>
<meta charset="utf-8" />
<title>ArtCraft. AI Video and Images. Fast and Open Desktop App.</title>
<meta
name="description"
content="ArtCraft is an Open Desktop app for generating AI Video and Images. You own ArtCraft!"
/>
<meta
name="keywords"
content="ArtCraft, AI, AI Video, AI Images, Open Source"
/>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="stylesheet" href="/src/styles.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Outfit:wght@100..900&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&family=Instrument+Serif:ital@0;1&family=Geist+Mono:wght@300..800&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
@@ -0,0 +1,29 @@
# Netlify settings used to discover this file:
# Base directory: frontend
# Package directory: apps/artcraft-website (relative to the base directory)
# Manage the build command and publish directory here; leave those UI fields empty.
# Paths below are relative to the frontend workspace.
[build]
command = "bash ./apps/artcraft-website/script/netlify_build.sh"
publish = "apps/artcraft-website/dist/"
[[headers]]
for = "/build/*"
[headers.values]
"Cache-Control" = "public, max-age=31560000, immutable"
# [[redirects]]
# from = "/ingest/static/*"
# to = "https://us-assets.i.posthog.com/static/:splat"
# host = "us-assets.i.posthog.com"
# status = 200
# force = true
#
# [[redirects]]
# from = "/ingest/*"
# to = "https://us.i.posthog.com/:splat"
# host = "us.i.posthog.com"
# status = 200
# force = true
@@ -0,0 +1,8 @@
{
"name": "@frontend/artcraft-website",
"version": "0.0.1",
"private": true,
"dependencies": {
"react-masonry-css": "^1.0.16"
}
}
@@ -0,0 +1,15 @@
const { join } = require('path');
// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
// option from your application's configuration (i.e. project.json).
//
// See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
module.exports = {
plugins: {
tailwindcss: {
config: join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 116.34 97.5">
<defs>
<style>
.cls-1 {
fill: #4e7bfb;
}
</style>
</defs>
<g id="Layer_1-2" data-name="Layer 1">
<path class="cls-1" d="M104.28,49.49L81.55,0h-31.23l-3.17,4.76L14.75,53.63,0,75.85l21.55,21.55,63.79-36.94,16.99,37.04,14.01-21.74-12.06-26.27ZM32.89,65.66l32.42-48.87,10.91,23.77-43.32,25.09Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Qwen</title><path d="M12.604 1.34c.393.69.784 1.382 1.174 2.075a.18.18 0 00.157.091h5.552c.174 0 .322.11.446.327l1.454 2.57c.19.337.24.478.024.837-.26.43-.513.864-.76 1.3l-.367.658c-.106.196-.223.28-.04.512l2.652 4.637c.172.301.111.494-.043.77-.437.785-.882 1.564-1.335 2.34-.159.272-.352.375-.68.37-.777-.016-1.552-.01-2.327.016a.099.099 0 00-.081.05 575.097 575.097 0 01-2.705 4.74c-.169.293-.38.363-.725.364-.997.003-2.002.004-3.017.002a.537.537 0 01-.465-.271l-1.335-2.323a.09.09 0 00-.083-.049H4.982c-.285.03-.553-.001-.805-.092l-1.603-2.77a.543.543 0 01-.002-.54l1.207-2.12a.198.198 0 000-.197 550.951 550.951 0 01-1.875-3.272l-.79-1.395c-.16-.31-.173-.496.095-.965.465-.813.927-1.625 1.387-2.436.132-.234.304-.334.584-.335a338.3 338.3 0 012.589-.001.124.124 0 00.107-.063l2.806-4.895a.488.488 0 01.422-.246c.524-.001 1.053 0 1.583-.006L11.704 1c.341-.003.724.032.9.34zm-3.432.403a.06.06 0 00-.052.03L6.254 6.788a.157.157 0 01-.135.078H3.253c-.056 0-.07.025-.041.074l5.81 10.156c.025.042.013.062-.034.063l-2.795.015a.218.218 0 00-.2.116l-1.32 2.31c-.044.078-.021.118.068.118l5.716.008c.046 0 .08.02.104.061l1.403 2.454c.046.081.092.082.139 0l5.006-8.76.783-1.382a.055.055 0 01.096 0l1.424 2.53a.122.122 0 00.107.062l2.763-.02a.04.04 0 00.035-.02.041.041 0 000-.04l-2.9-5.086a.108.108 0 010-.113l.293-.507 1.12-1.977c.024-.041.012-.062-.035-.062H9.2c-.059 0-.073-.026-.043-.077l1.434-2.505a.107.107 0 000-.114L9.225 1.774a.06.06 0 00-.053-.031zm6.29 8.02c.046 0 .058.02.034.06l-.832 1.465-2.613 4.585a.056.056 0 01-.05.029.058.058 0 01-.05-.029L8.498 9.841c-.02-.034-.01-.052.028-.054l.216-.012 6.722-.012z"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72.22 65.68">
<g id="Layer_1-2" data-name="Layer 1">
<path d="M69.38,45.46c-3.18-6.24-6.58-12.49-9.86-18.53-3.31-6.08-6.72-12.37-9.91-18.63l-.05-.09C46.44,3.07,41.24,0,35.66,0s-10.34,3-13.13,8.23L.97,48.96l-.03.07c-1.51,3.8-1.2,8.03.82,11.31,1.9,3.08,5.01,4.96,8.76,5.28.38.03.75.05,1.1.05,2.94,0,4.98-1.12,7.33-2.41,7.13-3.91,19.45-11.21,27.61-16.04,2.6-1.54,4.95-2.93,6.31-3.73l10.71,20.43c.2.39.54.7.95.86.46.19.98.2,1.43,0,1.39-.66,3.05-2.47,3.07-2.49,5.22-5.9,3.03-11.55.34-16.83ZM34.53,14.8c.31-.46.77-.99,1.32-.99.58,0,1.29.57,1.85,1.49,1.57,2.56,2.99,5.44,4.35,8.22,1.34,2.73,2.72,5.55,4.29,8.15l.02.05-29.6,16.99L34.53,14.8Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 781 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Flux</title><path d="M0 20.683L12.01 2.5 24 20.683h-2.233L12.009 5.878 3.471 18.806h12.122l1.239 1.877H0z"></path><path d="M8.069 16.724l2.073-3.115 2.074 3.115H8.069zM18.24 20.683l-5.668-8.707h2.177l5.686 8.707h-2.196zM19.74 11.676l2.13-3.19 2.13 3.19h-4.26z"></path></svg>

After

Width:  |  Height:  |  Size: 438 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>ByteDance</title><path d="M14.944 18.587l-1.704-.445V10.01l1.824-.462c1-.254 1.84-.461 1.88-.453.032 0 .056 2.235.056 4.972v4.973l-.176-.008c-.104 0-.952-.207-1.88-.446z"></path><path d="M7 16.542c0-2.736.024-4.98.064-4.98.032-.008.872.2 1.88.454l1.816.461-.016 4.05-.024 4.049-1.632.422c-.896.23-1.736.445-1.856.469L7 21.523v-4.98z"></path><path d="M19.24 12.477c0-9.03.008-9.515.144-9.475.072.024.784.207 1.576.406.792.207 1.576.405 1.744.445l.296.08-.016 8.56-.024 8.568-1.624.414c-.888.23-1.728.437-1.856.47l-.24.055v-9.523z"></path><path d="M1 12.509c0-4.678.024-8.505.064-8.505.032 0 .872.207 1.872.454l1.824.461v7.582c0 4.16-.016 7.574-.032 7.574-.024 0-.872.215-1.88.47L1 21.013v-8.505z"></path></svg>

After

Width:  |  Height:  |  Size: 873 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Fal</title><path clip-rule="evenodd" d="M15.477 0c.415 0 .749.338.788.752a7.775 7.775 0 006.985 6.984c.413.04.752.373.752.788v6.952c0 .415-.338.748-.752.788a7.775 7.775 0 00-6.985 6.984c-.04.414-.373.752-.788.752H8.525c-.416 0-.749-.338-.789-.752a7.775 7.775 0 00-6.984-6.984c-.414-.04-.752-.373-.752-.788V8.524c0-.415.338-.748.752-.788A7.775 7.775 0 007.736.752C7.776.338 8.11 0 8.526 0h6.95zM4.819 11.98a7.226 7.226 0 007.223 7.23 7.226 7.226 0 007.223-7.23c0-3.994-3.234-7.23-7.223-7.23a7.227 7.227 0 00-7.223 7.23z"></path></svg>

After

Width:  |  Height:  |  Size: 697 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M64 144C64 117.5 85.5 96 112 96L208 96C234.5 96 256 117.5 256 144L256 160L384 160L384 144C384 117.5 405.5 96 432 96L528 96C554.5 96 576 117.5 576 144L576 240C576 266.5 554.5 288 528 288L432 288C405.5 288 384 266.5 384 240L384 224L256 224L256 240C256 247.3 254.3 254.3 251.4 260.5L320 352L400 352C426.5 352 448 373.5 448 400L448 496C448 522.5 426.5 544 400 544L304 544C277.5 544 256 522.5 256 496L256 400C256 392.7 257.7 385.7 260.6 379.5L192 288L112 288C85.5 288 64 266.5 64 240L64 144z"/></svg>

After

Width:  |  Height:  |  Size: 717 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"></path></svg>

After

Width:  |  Height:  |  Size: 651 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>

After

Width:  |  Height:  |  Size: 756 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
<path d="M.03,2.19c.14.03.29.07.43.09,1.26.16,1.76-1.08,2.78-1.5,1.26-.52,2.77-.09,2.67,1.5s-1.89,2.3-2.88,3.2c-.38.34-.74.91-.36,1.4.5.64,1.25.06,1.65-.36,1.18-1.22,2.38-3.76,4.38-3.57,1.47.14,2.2,1.65,2.32,2.97h.96l.03.03v1.36h-.99c-.09,1.37-.54,3.03-1.78,3.79-.9.55-2.02.51-2.73-.31-.78-.89-.43-1.96.16-2.84.71-1.06,1.85-1.83,3.12-2.04.04-.61-.34-1.2-.94-1.35-1.15-.3-2.03.74-2.65,1.54-.79,1-1.87,2.88-3.33,2.8-1.19-.07-1.99-1.08-1.93-2.24.05-1.04,1.04-1.89,1.83-2.46.52-.38,1.71-1.05,1.81-1.72.06-.41-.18-.75-.61-.65-.59.13-1.03.85-1.49,1.22-.69.56-1.57.85-2.43.48v-1.3ZM9.73,7.28c-.28.07-.56.16-.81.3-.58.31-1.35,1.09-1.45,1.77-.08.58.45.87.95.62.87-.43,1.26-1.79,1.31-2.69Z"/>
</svg>

After

Width:  |  Height:  |  Size: 824 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Kling</title><path clip-rule="evenodd" d="M5.493 21.234c-1.112-1.451-1.109-4.263-.081-7.459l-4.557-2.63a1.683 1.683 0 01-.85-1.304 1.505 1.505 0 01.08-.622 13.18 13.18 0 011.037-2.255c3.476-6.02 10.916-8.23 16.619-4.938.46.266.82.67 1.081 1.184.785 1.545.685 4.096-.234 6.954l4.557 2.631c.339.196.596.492.736.832a1.53 1.53 0 01.034 1.093 13.146 13.146 0 01-1.037 2.255c-3.476 6.02-10.916 8.23-16.619 4.938a2.6 2.6 0 01-.766-.68zm11.096-6.615c-2.073 3.591-5.808 5.316-8.343 3.852-1.267-.731-1.994-2.122-2.145-3.778-.095-1.035.036-2.173.4-3.32.217-.684.517-1.37.902-2.039l.008-.014c2.073-3.59 5.808-5.315 8.343-3.852.633.366 1.13.895 1.49 1.54.986 1.772.922 4.415-.285 6.914-.111.23-.232.457-.362.683l-.008.014z"></path></svg>

After

Width:  |  Height:  |  Size: 888 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
<defs>
<style>
.cls-1 {
fill: #010101;
}
</style>
</defs>
<path d="M9.26.37c.36.29.62.65.72,1.11.01.14.01.27.01.41t0,.06c0,.51-.07.99-.22,1.47t-.02.06c-.24.83-.68,1.56-1.25,2.21-.02.02-.04.05-.06.07-.21.23-.42.43-.66.63-.02.02-.04.03-.06.05-.87.72-1.93,1.12-3.04,1.29-.03,0-.06.01-.09.02-.63.1-1.3.08-1.85-.27-.11-.08-.21-.18-.31-.28-.02-.02-.03-.03-.05-.05-.32-.34-.43-.82-.43-1.27.02-.45.27-.87.59-1.17.36-.31.81-.47,1.29-.48.62-.01,1.16-.23,1.64-.62.02-.02.04-.03.07-.05.39-.33.67-.89.73-1.39,0-.11.02-.22.02-.33.03-.51.2-.93.57-1.29.07-.06.15-.12.23-.18.02-.01.03-.02.05-.04.63-.43,1.49-.36,2.09.06Z"/>
<path d="M8.61,6.51c.06,0,.1.02.14.06t.04.05s.03.04.04.06c.01.02.03.04.04.06.01.02.03.04.04.06.4.57.68,1.19.88,1.85t.02.05c.22.71.32,1.62-.02,2.29-.24.47-.63.72-1.11.91-.48.14-1.02.06-1.46-.17-.47-.28-.74-.67-.89-1.19-.03-.12-.04-.25-.04-.37-.02-.65-.25-1.22-.71-1.68-.06-.05-.12-.11-.19-.16-.03-.03-.05-.05-.08-.08.13-.04.25-.08.38-.12.99-.29,2.17-.79,2.86-1.61l.04-.03Z"/>
<path class="cls-1" d="M4.96.4c.34.27.63.67.69,1.1.02.29.02.58-.08.85t-.03.08c-.16.44-.5.77-.92.97-.29.13-.53.17-.85.18-.68.04-1.21.26-1.72.69-.02.02-.05.04-.07.06,0-.36,0-.71,0-1.07,0-.17,0-.33,0-.5,0-.16,0-.32,0-.48,0-.06,0-.12,0-.18,0-.63.12-1.11.56-1.58.67-.65,1.69-.66,2.43-.13Z"/>
<path d="M1.98,7.67s.07.05.1.07c.02.01.04.03.06.04.04.03.09.07.13.1.41.3.88.5,1.39.51.58.01,1.1.17,1.52.59.35.4.5.86.47,1.39-.05.49-.31.89-.67,1.21-.46.35-1.01.43-1.57.36-.47-.1-.88-.4-1.15-.79-.07-.12-.13-.23-.18-.36,0-.02-.02-.05-.03-.07-.1-.31-.09-.61-.09-.93,0-.06,0-.12,0-.18,0-.16,0-.32,0-.48,0-.16,0-.33,0-.49,0-.32,0-.64,0-.96Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Midjourney</title><path d="M22.369 17.676c-1.387 1.259-3.17 2.378-5.332 3.417.044.03.086.057.13.083l.018.01.019.012c.216.123.42.184.641.184.222 0 .426-.061.642-.184l.018-.011.019-.011c.14-.084.266-.178.492-.366l.178-.148c.279-.232.426-.342.625-.456.304-.174.612-.266.949-.266.337 0 .645.092.949.266l.023.014c.188.109.334.219.602.442l.178.148c.221.184.346.278.483.36l.028.017.018.01c.21.12.407.181.62.185h.022a.31.31 0 110 .618c-.337 0-.645-.092-.95-.266a3.137 3.137 0 01-.09-.054l-.022-.014-.022-.013-.02-.014a5.356 5.356 0 01-.49-.377l-.159-.132a3.836 3.836 0 00-.483-.36l-.027-.017-.019-.01a1.256 1.256 0 00-.641-.185c-.222 0-.426.061-.641.184l-.02.011-.018.011c-.14.084-.266.178-.492.366l-.158.132a5.125 5.125 0 01-.51.39l-.022.014-.022.014-.09.054a1.868 1.868 0 01-.95.266c-.337 0-.644-.092-.949-.266a3.137 3.137 0 01-.09-.054l-.022-.014-.022-.013-.026-.017a4.881 4.881 0 01-.425-.325.308.308 0 01-.12-.1l-.098-.081a3.836 3.836 0 00-.483-.36l-.027-.017-.019-.01a1.256 1.256 0 00-.641-.185c-.222 0-.426.061-.642.184l-.018.011-.019.011c-.14.084-.266.178-.492.366l-.158.132a5.125 5.125 0 01-.51.39l-.023.014-.022.014-.09.054A1.868 1.868 0 0112 22c-.337 0-.645-.092-.949-.266a3.137 3.137 0 01-.09-.054l-.022-.014-.022-.013-.021-.014a5.356 5.356 0 01-.49-.377l-.158-.132a3.836 3.836 0 00-.483-.36l-.028-.017-.018-.01a1.256 1.256 0 00-.642-.185c-.221 0-.425.061-.641.184l-.019.011-.018.011c-.141.084-.266.178-.492.366l-.158.132a5.125 5.125 0 01-.511.39l-.022.014-.022.014-.09.054a1.868 1.868 0 01-.986.264c-.746-.09-1.319-.38-1.89-.866l-.035-.03c-.047-.041-.118-.106-.192-.174l-.196-.181-.107-.1-.011-.01a1.531 1.531 0 00-.336-.253.313.313 0 00-.095-.03h-.005c-.119.022-.238.059-.361.11a.308.308 0 01-.077.061l-.008.005a.309.309 0 01-.126.034 5.66 5.66 0 00-.774.518l-.416.324-.055.043a6.542 6.542 0 01-.324.236c-.305.207-.552.315-.8.315a.31.31 0 01-.01-.618h.01c.09 0 .235-.062.438-.198l.04-.027c.077-.054.163-.117.27-.199l.385-.301.06-.047c.268-.206.506-.373.73-.505l-.633-1.21a.309.309 0 01.254-.451l20.287-1.305a.309.309 0 01.228.537zm-1.118.14L2.369 19.03l.423.809c.128-.045.256-.078.388-.1a.31.31 0 01.052-.005c.132 0 .26.032.386.093.153.073.294.179.483.35l.016.015.092.086.144.134.097.089c.065.06.125.114.16.144.485.418.948.658 1.554.736h.011a1.25 1.25 0 00.6-.172l.021-.011.019-.011.018-.011c.141-.084.266-.178.492-.366l.178-.148c.279-.232.426-.342.625-.456.305-.174.612-.266.95-.266.336 0 .644.092.948.266l.023.014c.188.109.335.219.603.442l.177.148c.222.184.346.278.484.36l.027.017.019.01c.215.124.42.185.641.185.222 0 .426-.061.641-.184l.019-.011.018-.011c.141-.084.267-.178.493-.366l.177-.148c.28-.232.427-.342.626-.456.304-.174.612-.266.949-.266.337 0 .644.092.949.266l.025.015c.187.109.334.22.603.443 1.867-.878 3.448-1.811 4.73-2.832l.02-.016zM3.653 2.026C6.073 3.06 8.69 4.941 10.8 7.258c2.46 2.7 4.109 5.828 4.637 9.149a.31.31 0 01-.421.335c-2.348-.945-4.54-1.258-6.59-1.02-1.739.2-3.337.792-4.816 1.703-.294.182-.62-.182-.405-.454 1.856-2.355 2.581-4.99 2.343-7.794-.195-2.292-1.031-4.61-2.284-6.709a.31.31 0 01.388-.442zM10.04 4.45c1.778.543 3.892 2.102 5.782 4.243 1.984 2.248 3.552 4.934 4.347 7.582a.31.31 0 01-.401.38l-.022-.01-.386-.154a10.594 10.594 0 00-.291-.112l-.016-.006c-.68-.247-1.199-.291-1.944-.101a.31.31 0 01-.375-.218C15.378 11.123 13.073 7.276 9.775 5c-.291-.201-.072-.653.266-.55zM4.273 2.996l.008.015c1.028 1.94 1.708 4.031 1.885 6.113.213 2.513-.31 4.906-1.673 7.092l-.02.031.003-.001c1.198-.581 2.47-.969 3.825-1.132l.055-.006c1.981-.23 4.083.029 6.309.837l.066.025-.007-.039c-.593-2.95-2.108-5.737-4.31-8.179l-.07-.078c-1.785-1.96-3.944-3.6-6.014-4.65l-.057-.028zm7.92 3.238l.048.048c2.237 2.295 3.885 5.431 4.974 9.191l.038.132.022-.004c.71-.133 1.284-.063 1.963.18l.027.01.066.024.046.018-.025-.073c-.811-2.307-2.208-4.62-3.936-6.594l-.058-.065c-1.02-1.155-2.103-2.132-3.15-2.856l-.015-.011z"></path></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>OpenAI</title><path d="M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.553 5.553 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.02.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.002zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
<defs>
<style>
.cls-1 {
fill-rule: evenodd;
}
</style>
</defs>
<path class="cls-1" d="M8.6,2.63c.89,0,1.75.35,2.39.99.63.63.99,1.49,1,2.38,0,.66-.2,1.3-.56,1.84-.36.55-.87.98-1.47,1.24-.6.26-1.27.34-1.91.24-.65-.11-1.25-.4-1.73-.84-.59-.5-1.19-.98-1.79-1.47l.86-.69c.84.67,1.57,1.27,1.85,1.5.39.29.87.45,1.37.45,1.23,0,2.32-1.02,2.3-2.28,0-.42-.13-.84-.35-1.19s-.54-.65-.92-.84c-.38-.19-.8-.27-1.22-.23-.42.04-.82.19-1.17.44-1.82,1.51-3.71,2.97-5.56,4.44-.75.53-1.64,0-1.64-.87C.01,6.58.01,5.42.02,4.26c0-.88.89-1.41,1.65-.88l2.03,1.64-.84.68-1.75-1.38c0,1.12,0,2.24,0,3.36,1.75-1.36,3.49-2.74,5.2-4.15.62-.57,1.44-.89,2.29-.89h0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Recraft</title><path d="M19.667 8.275c0-4.57-4.15-8.275-9.27-8.275-1.774 0-3.213 3.705-3.213 8.275 0 1.143.09 2.233.253 3.224H4.29L1 23h9.4v-6.447c5.117 0 9.266-3.707 9.266-8.275l.001-.002zm-9.27-6.76c.93 0 1.682 3.028 1.682 6.76 0 3.733-.752 6.76-1.681 6.76-.93 0-1.681-3.027-1.681-6.76 0-3.732.752-6.76 1.68-6.76z"></path><path d="M19.848 16.552h-9.44L14.028 23h9.438l-3.618-6.448z"></path></svg>

After

Width:  |  Height:  |  Size: 562 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Replicate</title><path d="M22 10.552v2.26h-7.932V22H11.54V10.552H22zM22 2v2.264H4.528V22H2V2h20zm0 4.276V8.54H9.296V22H6.768V6.276H22z"></path></svg>

After

Width:  |  Height:  |  Size: 313 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Suno</title><path d="M16.5 0C20.642 0 24 5.373 24 12h-9c0 6.627-3.358 12-7.5 12C3.358 24 0 18.627 0 12h9c0-6.627 3.358-12 7.5-12z"></path></svg>

After

Width:  |  Height:  |  Size: 308 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Hunyuan</title><path d="M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0zm1.652 1.123l-.01-.001c.533.097 1.023.233 1.41.404 6.084 2.683 7.396 9.214 1.601 14.338a3.781 3.781 0 01-5.337-.328 3.654 3.654 0 01-.884-3.044c-1.934.6-3.295 2.305-3.524 4.45-.204 1.912.324 4.044 2.056 5.634l.245.067C10.1 22.876 11.036 23 12 23c6.075 0 11-4.925 11-11 0-5.513-4.056-10.08-9.348-10.877zM2.748 6.21c-.178.269-.348.536-.51.803l-.235.394.078-.167A10.957 10.957 0 001 12c0 4.919 3.228 9.083 7.682 10.49l.214.065C3.523 18.528 2.84 14.149 6.47 8.68A2.234 2.234 0 102.748 6.21zm10.157-5.172c4.408 1.33 3.61 5.41 2.447 6.924-.86 1.117-2.922 1.46-3.708 2.238-.666.657-1.077 1.462-1.212 2.291A5.303 5.303 0 0112 12.258a5.672 5.672 0 001.404-11.169 10.51 10.51 0 00-.5-.052z"></path></svg>

After

Width:  |  Height:  |  Size: 954 B

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
<path d="M7.51,3.04q.14,0,.27,0c.1,0,.2,0,.3,0,.1,0,.21,0,.31,0,.1,0,.21,0,.31,0,.26,0,.51,0,.77,0,.29.67.58,1.34.87,2.01.15.34.29.67.44,1.01.15.35.3.7.45,1.05.06.14.12.27.18.41.08.19.17.38.25.57q.04.09.08.17c.11.25.17.42.27.69h-2.36c-.45-1.18-.89-2.37-1.3-3.56-.31.75-.58,1.51-.85,2.27-.04.13-.09.25-.13.38-.11.3-.22.61-.32.92h-2.26c.29-.76.59-1.51.91-2.26.04-.1.09-.2.13-.31.09-.21.18-.43.27-.64.12-.27.23-.55.35-.82.11-.26.22-.53.34-.79.04-.1.08-.2.13-.3q.06-.14.12-.27c.03-.08.07-.16.1-.24.07-.15.12-.28.12-.28,0,0,.14,0,.26,0Z"/>
<path d="M1.65,2.96h3.82v1.65h-1.74v4.43H1.65V2.96Z"/>
<path d="M0,2.96h1.3v1.74H0v-1.74Z"/>
<path d="M8.25,6.78h.17c.23.72.46,1.43.7,2.17h-1.56c.21-.73.45-1.45.7-2.17Z"/>
</svg>

After

Width:  |  Height:  |  Size: 854 B

@@ -0,0 +1 @@
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Vidu</title><path d="M20.607 2.247c-2.917-.966-5.426 1.084-6.011 2.96 0 0-2.105 6.76-3.002 9.58-.428 1.346-1.489 3.548-3.487 3.548-1.627 0-2.463-1.527-2.816-2.437L2.865 9.431c-.281-.681.013-2.04 1.14-2.447 1.204-.432 1.978.575 2.178 1.11l3.022 7.74c.72-.928 1.178-2.438 1.476-3.507l-1.984-5.21C7.756 4.686 5.267 3.58 2.962 4.43 1.095 5.118-.702 7.474.275 10.435l2.504 6.44c.38.976 1.881 4.163 5.275 4.163 4.073 0 5.601-3.473 6.449-6.218.424-1.373 2.749-8.797 2.749-8.797.338-1.109 1.71-1.428 2.568-1.148.605.196 1.698 1.031 1.345 2.325-.066.236-1.92 6.209-2.604 8.026-.357.948-1.262 3.006-3.324 2.72-.628 1.39-1.15 2.199-1.94 2.925 2.572 1.218 6.32-.009 7.898-4.776.586-1.773 2.644-8.166 2.644-8.166.598-1.963-.469-4.768-3.232-5.682z"></path></svg>

After

Width:  |  Height:  |  Size: 912 B

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 249.21 224.64">
<g id="Layer_1-2" data-name="Layer 1">
<path d="M6.64.07c2.98-.4,5.65.92,7.26,3.41l66.32,109.22c4.55,5.38,10.84,10.03,18,11.02,8.98,1.24,17.64-3.5,25.87-6.45,38.94-13.94,78-27.61,117.04-41.24,6.08-.57,9.93,4.49,7.2,10.08l-76.03,135.25c-2.61,3.2-7.54,4.48-11.03,1.92-3.25-2.39-7.45-7.65-10.43-10.68-9.93-10.07-19.84-20.11-29.75-30.2-11.48-11.69-20.14-24.31-39.09-20.64-14.85,2.87-21.88,16.72-32.48,25.55-3.85,3.21-9.25,3.5-12.46-.75-1.69-2.24-2.1-6.25-2.7-9.05C22.12,121.2,12.09,64.37,0,8.01-.16,4.05,2.67.61,6.64.07Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Loaded 100 of 295 files, more files were not shown because too many files have changed in this diff. Show more