Refactor: replace fontawesome icons with lucide icons (#1873)

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`).
This commit is contained in:
BFlat
2026-08-10 02:04:45 +07:00
committed by GitHub
parent f767fc9638
commit b36e4fd27b
385 changed files with 3552 additions and 4706 deletions
-3
View File
@@ -1,3 +0,0 @@
@awesome.me:registry=https://npm.fontawesome.com/
@fortawesome:registry=https://npm.fontawesome.com/
//npm.fontawesome.com/:_authToken=104779DB-FD41-4BEA-BDB4-B1845D758A79
@@ -7,8 +7,7 @@ import {
useLocation,
useNavigationType,
} from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon } from "lucide-react";
import Home from "../pages/home";
import Media from "../pages/media";
import { ToastContainer } from "../components/toast/toast";
@@ -63,10 +62,9 @@ function ScrollToTop() {
function AuthCheckSpinner() {
return (
<div className="flex h-screen items-center justify-center bg-[#101014]">
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-4xl text-primary/80"
/>
<LoaderCircleIcon
className="animate-spin text-4xl text-primary/80" />
</div>
);
}
@@ -6,8 +6,7 @@ import {
getReferralUsername,
getReferrer,
} from "@storyteller/common";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LoaderCircleIcon } from "lucide-react";
import { useState } from "react";
interface GoogleLoginButtonProps {
@@ -63,10 +62,9 @@ export function GoogleLoginButton({
{/* Custom visual button - shown behind */}
<div className="absolute inset-0 w-full h-full flex items-center justify-center gap-3 bg-white text-black rounded-full font-medium border border-gray-200 group-hover:border-white/80 group-hover:bg-white/80 transition-colors">
{isLoading ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-lg text-gray-600"
/>
<LoaderCircleIcon
className="animate-spin text-lg text-gray-600" />
) : (
<>
<GoogleIcon />
@@ -1,7 +1,6 @@
import { ReactNode } from "react";
import { Outlet, useNavigate, Navigate } from "react-router-dom";
import { faArrowLeft, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ArrowLeftIcon, LoaderCircleIcon } from "lucide-react";
import { TruchetPattern } from "@storyteller/ui-vfx";
import { AuthShowcase } from "./auth-showcase";
import { useMediaQuery } from "../ui/use-media-query";
@@ -69,7 +68,7 @@ export const AuthLayout = () => {
aria-label="Go back"
className="absolute left-5 top-5 z-20 flex h-9 w-9 items-center justify-center rounded-full text-white/60 transition-colors hover:bg-white/10 hover:text-white"
>
<FontAwesomeIcon icon={faArrowLeft} />
<ArrowLeftIcon />
</button>
<div className="flex flex-1 flex-col justify-center px-8 py-10 sm:px-10">
@@ -78,10 +77,9 @@ export const AuthLayout = () => {
<Outlet />
) : (
<div className="flex justify-center py-12">
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-2xl text-white/40"
/>
<LoaderCircleIcon
className="animate-spin text-2xl text-white/40" />
</div>
)}
</div>
@@ -1,9 +1,5 @@
import {
faEye,
faEyeSlash,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { useState } from "react";
@@ -160,7 +156,7 @@ export const SignupForm = ({
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
tabIndex={-1}
>
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
</button>
</div>
</Reveal>
@@ -172,7 +168,7 @@ export const SignupForm = ({
disabled={isLoading}
>
{isLoading ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
"Create account"
)}
@@ -1,12 +1,7 @@
import { useState } from "react";
import { twMerge } from "tailwind-merge";
import { Modal } from "@storyteller/ui-modal";
import {
faCoins,
faSpinnerThird,
faArrowRight,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ArrowRightIcon, CoinsIcon, LoaderCircleIcon } from "lucide-react";
import { BillingApi } from "@storyteller/api";
import { toast } from "./toast/toast";
@@ -108,10 +103,9 @@ export function CreditsModal({ isOpen, onClose }: CreditsModalProps) {
<div className="flex items-center gap-3">
<span className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-primary/15">
<FontAwesomeIcon
icon={faCoins}
className="text-primary text-lg"
/>
<CoinsIcon
className="text-primary text-lg" />
</span>
<div className="min-w-0">
<div className="text-4xl font-bold leading-none tracking-tight text-white">
@@ -127,17 +121,15 @@ export function CreditsModal({ isOpen, onClose }: CreditsModalProps) {
</span>
<span className="flex items-center gap-1.5 text-base font-semibold text-primary-400">
{isLoading ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin"
/>
<LoaderCircleIcon
className="animate-spin" />
) : (
<>
Buy
<FontAwesomeIcon
icon={faArrowRight}
className="text-xs transition-transform group-hover:translate-x-0.5"
/>
<ArrowRightIcon
className="text-xs transition-transform group-hover:translate-x-0.5" />
</>
)}
</span>
@@ -1,6 +1,5 @@
import { type ReactNode, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon } from "lucide-react";
import { isMobile } from "react-device-detect";
import { type PopoverItem } from "@storyteller/ui-popover";
import { TabSelector } from "@storyteller/ui-tab-selector";
@@ -82,10 +81,9 @@ export function CreateMediaPageShell({
if (!authChecked) {
return (
<div className="flex h-full items-center justify-center bg-[#101014]">
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-4xl text-primary/80"
/>
<LoaderCircleIcon
className="animate-spin text-4xl text-primary/80" />
</div>
);
}
@@ -1,9 +1,6 @@
import { useMemo, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowDownToLine,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowDownToLineIcon, LoaderCircleIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import {
SelectionActionBar,
useGallerySelectionStore,
@@ -88,8 +85,8 @@ export function GallerySelectionDownloadBar({
disabled={isDownloading || selectedItems.length === 0}
className="flex items-center gap-2 rounded-full bg-ui-controls/60 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-ui-controls/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:opacity-60"
>
<FontAwesomeIcon
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
<DynamicIcon
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
className={`text-xs ${isDownloading ? "animate-spin" : ""}`}
/>
{isDownloading && progress
@@ -1,12 +1,5 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowDownToLine,
faArrowRotateRight,
faCheck,
faLink,
faSpinnerThird,
faVideo,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowDownToLineIcon, CheckIcon, LinkIcon, LoaderCircleIcon, RotateCwIcon, VideoIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Tooltip } from "@storyteller/ui-tooltip";
import {
GenerationGridView,
@@ -94,8 +87,8 @@ function CardRecreateButton({
disabled={isRecreating}
className="flex items-center gap-1.5 rounded-md bg-white/5 px-3 py-1.5 text-xs text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-60"
>
<FontAwesomeIcon
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
<DynamicIcon
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
className={isRecreating ? "animate-spin" : ""}
/>
Recreate
@@ -112,8 +105,8 @@ function CardRecreateButton({
aria-label="Recreate"
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-white/70 opacity-0 transition hover:bg-white/15 hover:text-white focus-visible:opacity-100 group-hover:opacity-100 disabled:opacity-60"
>
<FontAwesomeIcon
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
<DynamicIcon
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
/>
</button>
@@ -156,8 +149,8 @@ function CardActions({
disabled={isRecreating}
aria-label="Recreate"
>
<FontAwesomeIcon
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
<DynamicIcon
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
/>
</button>
@@ -171,7 +164,7 @@ function CardActions({
onClick={handleMakeVideo}
aria-label="Make Video"
>
<FontAwesomeIcon icon={faVideo} className="text-sm" />
<VideoIcon className="text-sm" />
</button>
</Tooltip>
)}
@@ -182,8 +175,8 @@ function CardActions({
onClick={handleShare}
aria-label="Share"
>
<FontAwesomeIcon
icon={shareCopied ? faCheck : faLink}
<DynamicIcon
icon={shareCopied ? CheckIcon : LinkIcon}
className="text-sm"
/>
</button>
@@ -197,8 +190,8 @@ function CardActions({
className={buttonClass}
aria-label="Download"
>
<FontAwesomeIcon
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
<DynamicIcon
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
className={`text-sm ${isDownloading ? "animate-spin" : ""}`}
/>
</button>
@@ -1,12 +1,5 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowDownToLine,
faArrowRotateRight,
faCheck,
faLink,
faSpinnerThird,
faVideo,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowDownToLineIcon, CheckIcon, LinkIcon, LoaderCircleIcon, RotateCwIcon, VideoIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Tooltip } from "@storyteller/ui-tooltip";
import {
GenerationListView,
@@ -114,8 +107,8 @@ function RowRecreateButton({
: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-white/40 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-60"
}
>
<FontAwesomeIcon
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
<DynamicIcon
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
/>
</button>
@@ -172,8 +165,8 @@ function GalleryRowActions({
aria-label="Recreate"
className={buttonClass}
>
<FontAwesomeIcon
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
<DynamicIcon
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
/>
</button>
@@ -187,7 +180,7 @@ function GalleryRowActions({
aria-label="Make Video"
className={buttonClass}
>
<FontAwesomeIcon icon={faVideo} className="text-sm" />
<VideoIcon className="text-sm" />
</button>
</Tooltip>
)}
@@ -198,8 +191,8 @@ function GalleryRowActions({
aria-label="Share"
className={buttonClass}
>
<FontAwesomeIcon
icon={shareCopied ? faCheck : faLink}
<DynamicIcon
icon={shareCopied ? CheckIcon : LinkIcon}
className="text-sm"
/>
</button>
@@ -213,8 +206,8 @@ function GalleryRowActions({
aria-label="Download"
className={buttonClass}
>
<FontAwesomeIcon
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
<DynamicIcon
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
className={`text-sm ${isDownloading ? "animate-spin" : ""}`}
/>
</button>
@@ -1,13 +1,8 @@
import { useCallback, useState } from "react";
import { is3DMediaClass } from "@storyteller/ui-generation-list";
import { useNavigate } from "react-router-dom";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import {
faCube,
faImage,
faMusic,
faVideo,
} from "@fortawesome/pro-solid-svg-icons";
import { BoxIcon, ImageIcon, MusicIcon, VideoIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { toast } from "../toast/toast";
import { downloadMediaFile } from "../../lib/download-media";
import {
@@ -30,7 +25,7 @@ export interface GalleryItemActions {
isVideo: boolean;
is3D: boolean;
isAudio: boolean;
mediaIcon: IconDefinition;
mediaIcon: LucideIcon;
mediaLabel: string;
modelDisplayName: string | null;
modelIconPath: string | null;
@@ -74,12 +69,12 @@ export function useGalleryItemActions(
const canMakeVideo = enableMakeVideo && !isVideo && !is3D && !isAudio;
const mediaIcon = isVideo
? faVideo
? VideoIcon
: is3D
? faCube
? BoxIcon
: isAudio
? faMusic
: faImage;
? MusicIcon
: ImageIcon;
const mediaLabel = isVideo ? "Video" : is3D ? "3D" : isAudio ? "Audio" : "Image";
const effectiveModelId = modelId ?? item.modelId;
@@ -1,8 +1,7 @@
import { useState, type ReactNode } from "react";
import { Link } from "react-router-dom";
import { create } from "zustand";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck } from "@fortawesome/pro-solid-svg-icons";
import { CheckIcon } from "lucide-react";
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import { BillingApi } from "@storyteller/api";
@@ -163,10 +162,9 @@ export function InsufficientCreditsModal() {
className="flex items-start gap-3 text-[14px] text-white/75"
>
<span className="mt-[2px] flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-primary/15">
<FontAwesomeIcon
icon={faCheck}
className="text-[9px] text-primary"
/>
<CheckIcon
className="text-[9px] text-primary" />
</span>
<span>{perk}</span>
</li>
@@ -4,25 +4,8 @@ import { Button } from "@storyteller/ui-button";
import { toast } from "../toast/toast";
import { Gravatar } from "@storyteller/ui-gravatar";
import type { UserInfo } from "@storyteller/api";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowDownToLine,
faArrowRotateRight,
faCheck,
faCircleInfo,
faCopy,
faImage,
faLink,
faPause,
faPencil,
faPhotoFilm,
faPlay,
faSpinnerThird,
faTrashCan,
faUser,
faVideo,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowDownToLineIcon, CheckIcon, CopyIcon, ImageIcon, ImagesIcon, InfoIcon, LinkIcon, LoaderCircleIcon, PauseIcon, PencilIcon, PlayIcon, RotateCwIcon, Trash2Icon, UserIcon, VideoIcon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import {
getContextImageThumbnail,
THUMBNAIL_SIZES,
@@ -198,7 +181,7 @@ export function LightboxDetails({
className="absolute top-3 right-3 z-20 flex h-8 w-8 items-center justify-center rounded-full bg-black/40 text-white/70 transition-colors hover:bg-black/60 hover:text-white"
aria-label="Close"
>
<FontAwesomeIcon icon={faXmark} className="h-4 w-4" />
<XIcon className="h-4 w-4" />
</button>
)}
@@ -234,7 +217,7 @@ export function LightboxDetails({
/>
) : (
<div className="h-9 w-9 shrink-0 flex items-center justify-center rounded-xl bg-white/10 text-white/50 border border-white/5">
<FontAwesomeIcon icon={faUser} />
<UserIcon />
</div>
)}
<div className="flex flex-col gap-1">
@@ -252,7 +235,7 @@ export function LightboxDetails({
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
<FontAwesomeIcon icon={faPencil} />
<PencilIcon />
<span>Prompt</span>
</div>
{promptData.text && (
@@ -260,8 +243,8 @@ export function LightboxDetails({
onClick={handleCopyPrompt}
className="flex items-center gap-1.5 text-xs text-white/60 hover:text-white transition-colors"
>
<FontAwesomeIcon
icon={promptCopy.copied ? faCheck : faCopy}
<DynamicIcon
icon={promptCopy.copied ? CheckIcon : CopyIcon}
className="h-3 w-3"
/>
<span>{promptCopy.copied ? "Copied" : "Copy"}</span>
@@ -294,7 +277,7 @@ export function LightboxDetails({
{promptData.contextImages && promptData.contextImages.length > 0 && (
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
<FontAwesomeIcon icon={faImage} />
<ImageIcon />
<span>Reference Media</span>
</div>
<div className="grid grid-cols-5 gap-2">
@@ -315,8 +298,8 @@ export function LightboxDetails({
onClick={() => handleAudioToggle(contextImage.media_token, contextImage.media_links.cdn_url)}
className="relative aspect-square overflow-hidden rounded-lg border border-white/10 hover:border-white/40 transition-colors flex items-center justify-center bg-white/5"
>
<FontAwesomeIcon
icon={isPlayingThis ? faPause : faPlay}
<DynamicIcon
icon={isPlayingThis ? PauseIcon : PlayIcon}
className="text-white/70 text-lg"
/>
</button>
@@ -338,10 +321,9 @@ export function LightboxDetails({
/>
{isVideoRef && (
<div className="absolute inset-0 flex items-center justify-center">
<FontAwesomeIcon
icon={faVideo}
className="text-white/80 text-sm drop-shadow-lg"
/>
<VideoIcon
className="text-white/80 text-sm drop-shadow-lg" />
</div>
)}
</a>
@@ -354,7 +336,7 @@ export function LightboxDetails({
{hasInfoSection && (
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
<FontAwesomeIcon icon={faCircleInfo} />
<InfoIcon />
<span>Information</span>
</div>
@@ -443,7 +425,7 @@ export function LightboxDetails({
<div className="grid grid-cols-2 gap-2">
<Button
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
icon={shareCopy.copied ? faCheck : faLink}
icon={shareCopy.copied ? CheckIcon : LinkIcon}
variant="secondary"
onClick={handleCopyShareLink}
>
@@ -470,8 +452,8 @@ export function LightboxDetails({
: "bg-ui-controls/20 text-white/60 cursor-not-allowed"
}`}
>
<FontAwesomeIcon
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
<DynamicIcon
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
className={isDownloading ? "animate-spin" : ""}
/>
{isDownloading ? "Downloading…" : "Download"}
@@ -479,7 +461,7 @@ export function LightboxDetails({
</div>
{onMakeVideo && (
<Button
icon={faVideo}
icon={VideoIcon}
className="w-full shadow-lg shadow-brand-primary/20"
variant="primary"
onClick={onMakeVideo}
@@ -489,7 +471,7 @@ export function LightboxDetails({
)}
{onEditOnCanvas && (
<Button
icon={faPencil}
icon={PencilIcon}
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
variant="secondary"
onClick={onEditOnCanvas}
@@ -499,7 +481,7 @@ export function LightboxDetails({
)}
{onExtractFrames && (
<Button
icon={faPhotoFilm}
icon={ImagesIcon}
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
variant="secondary"
onClick={onExtractFrames}
@@ -509,7 +491,7 @@ export function LightboxDetails({
)}
{onRecreate && promptData.hasToken && (
<Button
icon={faArrowRotateRight}
icon={RotateCwIcon}
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
variant="secondary"
onClick={onRecreate}
@@ -519,7 +501,7 @@ export function LightboxDetails({
)}
{onDelete && mediaToken && (
<Button
icon={faTrashCan}
icon={Trash2Icon}
className="w-full bg-red-500/10 hover:bg-red-500/20 text-red-500 border border-red-500/20"
variant="destructive"
onClick={onDelete}
@@ -529,7 +511,7 @@ export function LightboxDetails({
)}
{showDownloadAppCta && (
<Button
icon={faArrowDownToLine}
icon={ArrowDownToLineIcon}
className="w-full shadow-lg shadow-brand-primary/20"
variant="primary"
onClick={() => {
@@ -5,13 +5,7 @@ import { useEffect, useState, useMemo, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { MediaFilesApi, PromptsApi, type UserInfo } from "@storyteller/api";
import { is3DMediaClass } from "@storyteller/ui-generation-list";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronLeft,
faChevronRight,
faMusic,
faTrashCan,
} from "@fortawesome/pro-solid-svg-icons";
import { ChevronLeftIcon, ChevronRightIcon, MusicIcon, Trash2Icon } from "lucide-react";
import { addCorsParam, PLACEHOLDER_IMAGES } from "@storyteller/common";
import { ActionReminderModal } from "@storyteller/ui-action-reminder-modal";
import { Viewer3D } from "@storyteller/ui-viewer-3d";
@@ -427,10 +421,9 @@ export function Lightbox({
<div className="flex h-full w-full items-center justify-center px-4 sm:px-8">
<div className="w-full max-w-xl rounded-2xl border border-white/10 bg-gradient-to-br from-white/[0.08] via-white/[0.04] to-white/[0.02] px-4 py-10 sm:px-6">
<div className="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-white/10 ring-1 ring-white/15">
<FontAwesomeIcon
icon={faMusic}
className="text-2xl text-white/70"
/>
<MusicIcon
className="text-2xl text-white/70" />
</div>
<WaveformAudioPlayer
key={selectedImageUrl}
@@ -528,7 +521,7 @@ export function Lightbox({
className="absolute left-2 sm:left-3 top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full bg-black/50 text-white/70 sm:opacity-0 transition-opacity duration-200 hover:bg-black/70 hover:text-white sm:group-hover/nav:opacity-100"
aria-label="Previous item"
>
<FontAwesomeIcon icon={faChevronLeft} className="text-lg" />
<ChevronLeftIcon className="text-lg" />
</button>
)}
{onNavigateNext && (
@@ -540,7 +533,7 @@ export function Lightbox({
className="absolute right-2 sm:right-3 top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full bg-black/50 text-white/70 sm:opacity-0 transition-opacity duration-200 hover:bg-black/70 hover:text-white sm:group-hover/nav:opacity-100"
aria-label="Next item"
>
<FontAwesomeIcon icon={faChevronRight} className="text-lg" />
<ChevronRightIcon className="text-lg" />
</button>
)}
</div>
@@ -590,7 +583,7 @@ export function Lightbox({
onPrimaryAction={handleDelete}
primaryActionText="Delete"
secondaryActionText="Cancel"
primaryActionIcon={faTrashCan}
primaryActionIcon={Trash2Icon}
primaryActionBtnClassName="bg-red-500/10 hover:bg-red-500/20 text-red-500"
/>
</>
@@ -1,17 +1,6 @@
import { useState, useRef, useCallback, useEffect } from "react";
import { Modal } from "@storyteller/ui-modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faPlus,
faArrowLeft,
faUpload,
faUserGroup,
faSpinnerThird,
faXmark,
faPen,
faTrashAlt,
faEye,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowLeftIcon, EyeIcon, LoaderCircleIcon, PenIcon, PlusIcon, Trash2Icon, UploadIcon, UsersIcon, XIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import {
CharactersApi,
@@ -370,7 +359,7 @@ const CharacterListView = ({
className="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-lg border-2 border-dashed border-white/10 bg-white/[0.05] text-white/60 transition-colors hover:border-white/25 hover:text-white/80"
>
<div className="flex aspect-square w-full flex-col items-center justify-center gap-2">
<FontAwesomeIcon icon={faPlus} className="text-lg" />
<PlusIcon className="text-lg" />
<span className="text-sm font-medium">Create New</span>
</div>
</button>
@@ -390,14 +379,13 @@ const CharacterListView = ({
/>
) : (
<div className="flex h-full w-full items-center justify-center text-white/20">
<FontAwesomeIcon icon={faUserGroup} className="text-2xl" />
<UsersIcon className="text-2xl" />
</div>
)}
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-black/40">
<FontAwesomeIcon
icon={faSpinnerThird}
className="text-lg text-white/80 animate-spin"
/>
<LoaderCircleIcon
className="text-lg text-white/80 animate-spin" />
<span className="text-xs font-medium text-white/80">
Creating...
</span>
@@ -433,10 +421,9 @@ const CharacterListView = ({
/>
) : (
<div className="flex h-full w-full items-center justify-center text-white/20">
<FontAwesomeIcon
icon={faUserGroup}
className="text-2xl"
/>
<UsersIcon
className="text-2xl" />
</div>
)}
</div>
@@ -460,7 +447,7 @@ const CharacterListView = ({
title="View full size"
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-black/80"
>
<FontAwesomeIcon icon={faEye} className="text-[10px]" />
<EyeIcon className="text-[10px]" />
</button>
)}
{isUserCreated && (
@@ -472,10 +459,9 @@ const CharacterListView = ({
}}
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-black/80"
>
<FontAwesomeIcon
icon={faPen}
className="text-[10px]"
/>
<PenIcon
className="text-[10px]" />
</button>
<button
onClick={(e) => {
@@ -484,10 +470,9 @@ const CharacterListView = ({
}}
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-red-500"
>
<FontAwesomeIcon
icon={faTrashAlt}
className="text-[10px]"
/>
<Trash2Icon
className="text-[10px]" />
</button>
</>
)}
@@ -616,7 +601,7 @@ const EditCharacterView = ({
onClick={onBack}
className="flex items-center justify-center text-white/60 transition-colors hover:text-white"
>
<FontAwesomeIcon icon={faArrowLeft} />
<ArrowLeftIcon />
</button>
<h2 className="text-xl font-bold text-white">Edit Character</h2>
</div>
@@ -865,7 +850,7 @@ const NewCharacterView = ({
onClick={onBack}
className="flex items-center justify-center text-white/60 transition-colors hover:text-white"
>
<FontAwesomeIcon icon={faArrowLeft} />
<ArrowLeftIcon />
</button>
<h2 className="text-xl font-bold text-white">New Character</h2>
</div>
@@ -895,10 +880,9 @@ const NewCharacterView = ({
/>
{!images[0]!.mediaToken && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
<FontAwesomeIcon
icon={faSpinnerThird}
className="text-white animate-spin"
/>
<LoaderCircleIcon
className="text-white animate-spin" />
</div>
)}
<button
@@ -908,15 +892,14 @@ const NewCharacterView = ({
}}
className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500"
>
<FontAwesomeIcon icon={faXmark} className="text-sm" />
<XIcon className="text-sm" />
</button>
</div>
) : (
<div className="flex h-full w-full flex-col items-center justify-center text-white/60">
<FontAwesomeIcon
icon={faUpload}
className="mb-2 text-xl text-white/40"
/>
<UploadIcon
className="mb-2 text-xl text-white/40" />
<p className="text-sm">Upload reference image</p>
<p className="mb-3 text-xs text-white/40">
Click or drag an image here
@@ -6,14 +6,7 @@ import {
type CSSProperties,
type SyntheticEvent,
} from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faImages,
faPlus,
faSpinnerThird,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { faImage } from "@fortawesome/pro-regular-svg-icons";
import { ImageIcon, ImagesIcon, LoaderCircleIcon, PlusIcon, XIcon } from "lucide-react";
import { Button } from "@storyteller/ui-button";
import { Tooltip } from "@storyteller/ui-tooltip";
import { Modal } from "@storyteller/ui-modal";
@@ -252,7 +245,7 @@ export const ImagePromptRow = ({
<div className="flex min-w-0 flex-1 gap-2 px-3 py-2">
<div className="flex grow flex-col gap-1 min-w-32">
<div className="flex items-center gap-2 text-white/90">
<FontAwesomeIcon icon={faImage} className="h-3.5 w-3.5" />
<ImageIcon className="h-3.5 w-3.5" />
<span className="flex items-center gap-1.5 text-sm font-medium">
{sectionLabel}
{showCount && (
@@ -329,7 +322,7 @@ export const ImagePromptRow = ({
<div className="hidden sm:block w-[1px] bg-white/10" />
<div className="flex grow flex-col gap-1 sm:p-2">
<div className="flex items-center gap-2 text-white/90">
<FontAwesomeIcon icon={faImage} className="h-3.5 w-3.5" />
<ImageIcon className="h-3.5 w-3.5" />
<span className="flex items-center gap-1.5 text-sm font-medium">
End Frame{" "}
<span className="text-xs text-white/60">(optional)</span>
@@ -358,16 +351,15 @@ export const ImagePromptRow = ({
}}
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
>
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
</div>
) : uploadingEndFrame ? (
<div className="flex aspect-square w-10 sm:w-14 items-center justify-center overflow-hidden rounded-lg border-2 border-white/30 bg-white/5">
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
spin
className="h-5 w-5 text-white"
/>
className="h-5 w-5 text-white" />
</div>
) : (
<AddButton
@@ -423,7 +415,7 @@ export const AddButton = ({
const [drawerOpen, setDrawerOpen] = useState(false);
const plus = (
<FontAwesomeIcon icon={faPlus} className="text-2xl text-white/80" />
<PlusIcon className="text-2xl text-white/80" />
);
if (!onPickFromLibrary) {
@@ -456,7 +448,7 @@ export const AddButton = ({
<div className="flex flex-col gap-2 pb-2">
<Button
variant="primary"
icon={faPlus}
icon={PlusIcon}
className="w-full"
onClick={() => {
onUpload();
@@ -467,7 +459,7 @@ export const AddButton = ({
</Button>
<Button
variant="action"
icon={faImages}
icon={ImagesIcon}
className="w-full bg-white/15 hover:bg-white/20"
onClick={() => {
setDrawerOpen(false);
@@ -494,7 +486,7 @@ export const AddButton = ({
<Button
variant="primary"
onClick={onUpload}
icon={faPlus}
icon={PlusIcon}
className="w-full"
>
Upload
@@ -502,7 +494,7 @@ export const AddButton = ({
<Button
variant="action"
onClick={onPickFromLibrary}
icon={faImages}
icon={ImagesIcon}
className="w-full bg-white/15 hover:bg-white/20"
>
Pick from library
@@ -542,7 +534,7 @@ const ImageThumbnail = ({
}}
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
>
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
</div>
);
@@ -601,7 +593,7 @@ const SortableImage = ({
onPointerDown={(e) => e.stopPropagation()}
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
>
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
</div>
);
@@ -620,11 +612,10 @@ const UploadingThumbnail = ({ file }: { file: File }) => {
/>
</div>
<div className="absolute inset-0 flex items-center justify-center bg-black/20">
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
spin
className="h-6 w-6 text-white"
/>
className="h-6 w-6 text-white" />
</div>
</div>
);
@@ -1,13 +1,6 @@
import { useCallback, useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faMusic,
faPlay,
faSpinnerThird,
faStop,
faVideo,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon, MusicIcon, PlayIcon, SquareIcon, VideoIcon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { twMerge } from "tailwind-merge";
import { UploaderStates } from "@storyteller/common";
import {
@@ -234,7 +227,7 @@ export const MediaReferenceRow = ({
<div className="flex grow gap-2 px-3 py-2">
<div className="flex grow flex-col gap-1">
<div className="flex items-center gap-2 text-white/90">
<FontAwesomeIcon icon={faVideo} className="h-3.5 w-3.5" />
<VideoIcon className="h-3.5 w-3.5" />
<span className="flex items-center gap-1.5 text-sm font-medium">
Video Ref
<span className="font-semibold text-white/60">
@@ -256,11 +249,10 @@ export const MediaReferenceRow = ({
))}
{uploadingVideo && (
<div className="flex aspect-square w-10 sm:w-14 items-center justify-center overflow-hidden rounded-lg border-2 border-white/30 bg-white/5">
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
spin
className="h-5 w-5 text-white/60"
/>
className="h-5 w-5 text-white/60" />
</div>
)}
{canAddVideo && (
@@ -283,7 +275,7 @@ export const MediaReferenceRow = ({
<div className="flex grow gap-2 px-3 py-2">
<div className="flex grow flex-col gap-1">
<div className="flex items-center gap-2 text-white/90">
<FontAwesomeIcon icon={faMusic} className="h-3.5 w-3.5" />
<MusicIcon className="h-3.5 w-3.5" />
<span className="flex items-center gap-1.5 text-sm font-medium">
Audio Ref
<span className="font-semibold text-white/60">
@@ -306,11 +298,10 @@ export const MediaReferenceRow = ({
))}
{uploadingAudio && (
<div className="flex aspect-square w-10 sm:w-14 items-center justify-center overflow-hidden rounded-lg border-2 border-white/30 bg-white/5">
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
spin
className="h-5 w-5 text-white/60"
/>
className="h-5 w-5 text-white/60" />
</div>
)}
{canAddAudio && (
@@ -354,7 +345,7 @@ const VideoRefTile = ({
}}
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
>
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
</div>
);
@@ -396,8 +387,8 @@ const AudioRefTile = ({
onClick={handleTogglePlay}
className="flex h-full w-full items-center justify-center"
>
<FontAwesomeIcon
icon={isPlaying ? faStop : faPlay}
<DynamicIcon
icon={isPlaying ? SquareIcon : PlayIcon}
className={twMerge(
"h-5 w-5 transition-colors",
isPlaying ? "text-red-400" : "text-white/60 group-hover:text-white",
@@ -414,7 +405,7 @@ const AudioRefTile = ({
}}
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
>
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
</div>
);
@@ -8,14 +8,8 @@ import {
type ReactNode,
} from "react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronDown,
faChevronUp,
faMusic,
faUserGroup,
faVideo,
} from "@fortawesome/pro-solid-svg-icons";
import { ChevronDownIcon, ChevronUpIcon, MusicIcon, UsersIcon, VideoIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { GenerateIconButton } from "@storyteller/ui-button";
import { Tooltip } from "@storyteller/ui-tooltip";
import {
@@ -891,14 +885,13 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
className="h-full w-full object-cover"
/>
) : item.type === "character" ? (
<FontAwesomeIcon
icon={faUserGroup}
className="h-3.5 w-3.5 text-white/60"
/>
<UsersIcon
className="h-3.5 w-3.5 text-white/60" />
) : (
<FontAwesomeIcon
<DynamicIcon
icon={
item.type === "video" ? faVideo : faMusic
item.type === "video" ? VideoIcon : MusicIcon
}
className="h-3.5 w-3.5 text-white/60"
/>
@@ -978,8 +971,8 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
onClick={toggleExpand}
className="px-3 py-0.5 text-white/30 transition-colors hover:text-white/90"
>
<FontAwesomeIcon
icon={isExpanded ? faChevronUp : faChevronDown}
<DynamicIcon
icon={isExpanded ? ChevronUpIcon : ChevronDownIcon}
className="text-xs"
/>
</button>
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faImages, faMusic, faVideo } from "@fortawesome/pro-solid-svg-icons";
import { ImagesIcon, MusicIcon, VideoIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { toast } from "../toast/toast";
// ── Types ───────────────────────────────────────────────────────────────
@@ -235,10 +235,10 @@ export function PromptBoxDropOverlay({
const kinds = [
...(acceptsImages
? [{ icon: faImages, label: keyframeMode ? "Frames" : "Images" }]
? [{ icon: ImagesIcon, label: keyframeMode ? "Frames" : "Images" }]
: []),
...(acceptsVideos ? [{ icon: faVideo, label: "Video" }] : []),
...(acceptsAudio ? [{ icon: faMusic, label: "Audio" }] : []),
...(acceptsVideos ? [{ icon: VideoIcon, label: "Video" }] : []),
...(acceptsAudio ? [{ icon: MusicIcon, label: "Audio" }] : []),
];
const rejected = dragState === "reject";
@@ -256,7 +256,7 @@ export function PromptBoxDropOverlay({
className="promptbox-drop-chip flex h-9 w-9 items-center justify-center rounded-lg bg-white/10 text-base-fg/90"
style={{ animationDelay: `${i * 50}ms` }}
>
<FontAwesomeIcon icon={kind.icon} className="text-sm" />
<DynamicIcon icon={kind.icon} className="text-sm" />
</div>
))}
</div>
@@ -2,8 +2,7 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import { Tooltip } from "@storyteller/ui-tooltip";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faUpRightAndDownLeftFromCenter } from "@fortawesome/pro-solid-svg-icons";
import { Maximize2Icon } from "lucide-react";
// Webapp "focus mode" for the prompt editor. Mirrors the desktop lib's
// (@storyteller/ui-promptbox) fullscreen feature but lives here because the
@@ -35,10 +34,9 @@ export const PromptFullscreenButton = ({
onClick={onClick}
className="flex h-6 w-6 items-center justify-center rounded-full bg-base-fg/5 text-base-fg/50 transition-colors hover:bg-base-fg/10 hover:text-base-fg/90 focus:outline-none"
>
<FontAwesomeIcon
icon={faUpRightAndDownLeftFromCenter}
className="h-3 w-3"
/>
<Maximize2Icon
className="h-3 w-3" />
</button>
</Tooltip>
</div>
@@ -1,6 +1,5 @@
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck } from "@fortawesome/pro-solid-svg-icons";
import { CheckIcon } from "lucide-react";
import { type PopoverItem } from "@storyteller/ui-popover";
interface DrawerOptionListProps {
@@ -42,10 +41,9 @@ export function DrawerOptionList({ items, onSelect }: DrawerOptionListProps) {
</span>
{item.trailing}
{item.selected && (
<FontAwesomeIcon
icon={faCheck}
className="h-3.5 w-3.5 shrink-0 text-primary"
/>
<CheckIcon
className="h-3.5 w-3.5 shrink-0 text-primary" />
)}
</button>
))}
@@ -1,5 +1,4 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMinus, faPlus } from "@fortawesome/pro-solid-svg-icons";
import { MinusIcon, PlusIcon } from "lucide-react";
interface MobileCountStepperProps {
value: number;
@@ -41,7 +40,7 @@ export function MobileCountStepper({
disabled={index <= 0}
className="flex h-9 w-8 items-center justify-center rounded-lg text-base-fg/80 transition-colors hover:bg-white/10 disabled:opacity-30"
>
<FontAwesomeIcon icon={faMinus} className="h-3 w-3" />
<MinusIcon className="h-3 w-3" />
</button>
<span className="min-w-10 text-center text-sm font-semibold tabular-nums text-base-fg">
{value}/{maxValue}
@@ -52,7 +51,7 @@ export function MobileCountStepper({
disabled={index === allowed.length - 1}
className="flex h-9 w-8 items-center justify-center rounded-lg text-base-fg/80 transition-colors hover:bg-white/10 disabled:opacity-30"
>
<FontAwesomeIcon icon={faPlus} className="h-3 w-3" />
<PlusIcon className="h-3 w-3" />
</button>
</div>
);
@@ -1,7 +1,6 @@
import { type ReactNode } from "react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronRight } from "@fortawesome/pro-solid-svg-icons";
import { ChevronRightIcon } from "lucide-react";
interface MobileFieldButtonProps {
label: string;
@@ -44,10 +43,9 @@ export function MobileFieldButton({
<span className="truncate text-sm font-semibold text-base-fg">
{value}
</span>
<FontAwesomeIcon
icon={faChevronRight}
className="h-3 w-3 shrink-0 text-base-fg/40"
/>
<ChevronRightIcon
className="h-3 w-3 shrink-0 text-base-fg/40" />
</span>
</button>
);
@@ -1,6 +1,5 @@
import { useRef, type ReactNode } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faXmark } from "@fortawesome/pro-solid-svg-icons";
import { XIcon } from "lucide-react";
import {
Sheet,
SheetContent,
@@ -73,7 +72,7 @@ export function SettingsDrawer({
onClick={() => onOpenChange(false)}
className="absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white/10 text-white/70 transition-colors hover:bg-white/20 hover:text-white"
>
<FontAwesomeIcon icon={faXmark} className="text-sm" />
<XIcon className="text-sm" />
</button>
{/* Drag zone: the handle pill + header pull the sheet down; past the
threshold, releasing dismisses. `touch-none` keeps the browser
@@ -1,8 +1,7 @@
import { useState } from "react";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon } from "lucide-react";
import { UsersApi, type UserInfo } from "@storyteller/api";
import { refreshSession, updateSessionUser } from "../../lib/session";
import { toast } from "../toast/toast";
@@ -516,7 +515,7 @@ function FormActions({
disabled={submitting || disabled}
>
{submitting ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
"Save"
)}
@@ -2,14 +2,8 @@ import { useEffect, useState } from "react";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { Modal } from "@storyteller/ui-modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faSpinnerThird,
faCopy,
faCheck,
faKey,
faTrash,
} from "@fortawesome/pro-solid-svg-icons";
import { CheckIcon, CopyIcon, KeyIcon, LoaderCircleIcon, TrashIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import {
UserApiKeysApi,
type ApiKeyInfo,
@@ -137,7 +131,7 @@ export function ApiKeySection(_props: ApiKeySectionProps) {
{loading ? (
<div className="py-6 text-center text-xs opacity-60">
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
</div>
) : keys.length === 0 ? (
<div className="py-6 text-center text-xs opacity-60">
@@ -175,7 +169,7 @@ export function ApiKeySection(_props: ApiKeySectionProps) {
onClick={() => load(false)}
>
{loadingMore ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
"Load more"
)}
@@ -287,7 +281,7 @@ function NewKeyReveal({
return (
<div className="flex flex-col gap-2 rounded-lg border border-primary/40 bg-primary/10 p-3">
<div className="flex items-center gap-2 text-sm font-medium">
<FontAwesomeIcon icon={faKey} className="opacity-80" />
<KeyIcon className="opacity-80" />
<span>API key created</span>
</div>
<p className="text-xs opacity-80">
@@ -303,8 +297,8 @@ function NewKeyReveal({
className="h-8 px-3 shrink-0"
onClick={handleCopy}
>
<FontAwesomeIcon
icon={copied ? faCheck : faCopy}
<DynamicIcon
icon={copied ? CheckIcon : CopyIcon}
className="mr-1.5"
/>
{copied ? "Copied" : "Copy"}
@@ -377,7 +371,7 @@ function ApiKeyRow({
className="h-8 px-2.5 text-red-400"
onClick={onRequestDelete}
>
<FontAwesomeIcon icon={faTrash} />
<TrashIcon />
</Button>
</div>
)}
@@ -509,7 +503,7 @@ function DeleteConfirmModal({
disabled={deleting}
>
{deleting ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
"Delete"
)}
@@ -549,7 +543,7 @@ function FormActions({
disabled={submitting || disabled}
>
{submitting ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
submitLabel
)}
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import { Modal } from "@storyteller/ui-modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCog, faUser, faKey } from "@fortawesome/pro-solid-svg-icons";
import { KeyIcon, SettingsIcon, UserIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Switch } from "@storyteller/ui-switch";
import { useModelPickerStyleStore } from "@storyteller/ui-popover";
import { twMerge } from "tailwind-merge";
@@ -18,10 +18,10 @@ interface SettingsModalProps {
onClose: () => void;
}
const TABS: { id: Tab; label: string; icon: typeof faCog }[] = [
{ id: "general", label: "General", icon: faCog },
{ id: "account", label: "Account", icon: faUser },
{ id: "apiKeys", label: "API Keys", icon: faKey },
const TABS: { id: Tab; label: string; icon: typeof SettingsIcon }[] = [
{ id: "general", label: "General", icon: SettingsIcon },
{ id: "account", label: "Account", icon: UserIcon },
{ id: "apiKeys", label: "API Keys", icon: KeyIcon },
];
export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
@@ -58,7 +58,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
)}
>
<div className="flex items-center gap-2.5 whitespace-nowrap text-sm">
<FontAwesomeIcon icon={t.icon} />
<DynamicIcon icon={t.icon} />
{t.label}
</div>
</button>
@@ -1,26 +1,9 @@
import { useMemo } from "react";
import { Link, useLocation } from "react-router-dom";
import { motion, useReducedMotion } from "framer-motion";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faHouse,
faVideo,
faImage,
faMusic,
faCube,
faGlobe,
faFilm,
faObjectGroup,
faWandMagicSparkles,
faGraduationCap,
faNewspaper,
faCircleQuestion,
faDownload,
faGift,
faPencil,
faPhotoFilm,
} from "@fortawesome/pro-solid-svg-icons";
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
import { BoxIcon, CircleHelpIcon, DownloadIcon, FilmIcon, GiftIcon, GlobeIcon, GraduationCapIcon, GroupIcon, HouseIcon, ImageIcon, ImagesIcon, MusicIcon, NewspaperIcon, PencilIcon, VideoIcon, WandSparklesIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { DynamicIcon, DiscordIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { USER_FEATURE_FLAGS } from "@storyteller/api";
import { useSession } from "../../lib/session";
@@ -38,7 +21,6 @@ import {
SidebarTrigger,
useSidebar,
} from "../ui/sidebar";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { MARKETING_SITE, SOCIAL_LINKS } from "../../config/links";
import { useSceneCacheStore } from "../../pages/pagescene/scene-cache-store";
import { LibraryFoldersNav } from "./library-folders-nav";
@@ -46,43 +28,43 @@ import { LibraryFoldersNav } from "./library-folders-nav";
type NavItem = {
label: string;
href: string;
icon: IconDefinition;
icon: LucideIcon;
external?: boolean;
badge?: string;
};
const PRIMARY_ITEMS: NavItem[] = [{ label: "Home", href: "/", icon: faHouse }];
const PRIMARY_ITEMS: NavItem[] = [{ label: "Home", href: "/", icon: HouseIcon }];
// Generation entries — make something from a prompt.
const CREATE_ITEMS_STATIC: NavItem[] = [
{ label: "Image", href: "/create-image", icon: faImage },
{ label: "Video", href: "/create-video", icon: faVideo },
{ label: "Audio", href: "/create-audio", icon: faMusic },
{ label: "3D Object", href: "/create-object", icon: faCube },
{ label: "3D World", href: "/create-world", icon: faGlobe },
{ label: "Image", href: "/create-image", icon: ImageIcon },
{ label: "Video", href: "/create-video", icon: VideoIcon },
{ label: "Audio", href: "/create-audio", icon: MusicIcon },
{ label: "3D Object", href: "/create-object", icon: BoxIcon },
{ label: "3D World", href: "/create-world", icon: GlobeIcon },
];
// "Studio" entries — edit, compose, and refine existing content. "Edit 3D"'s
// href is computed at render time from the scene-cache store (see useStudioItems).
const STUDIO_ITEMS_STATIC: NavItem[] = [
{ label: "Edit Image", href: "/edit-image", icon: faPencil },
{ label: "Edit 3D", href: "/edit-3d", icon: faCube },
{ label: "Edit Video", href: "/video-editor", icon: faFilm, badge: "BETA" },
{ label: "Edit Image", href: "/edit-image", icon: PencilIcon },
{ label: "Edit 3D", href: "/edit-3d", icon: BoxIcon },
{ label: "Edit Video", href: "/video-editor", icon: FilmIcon, badge: "BETA" },
{
label: "BG Change",
href: "/background-change",
icon: faWandMagicSparkles,
icon: WandSparklesIcon,
},
{
label: "Moodboard",
href: "/moodboard",
icon: faObjectGroup,
icon: GroupIcon,
badge: "BETA",
},
{
label: "Frame Extract",
href: "/frame-extractor",
icon: faPhotoFilm,
icon: ImagesIcon,
badge: "NEW",
},
];
@@ -107,26 +89,26 @@ function useStudioItems(): NavItem[] {
const REFERRALS_ITEM: NavItem = {
label: "Referrals",
href: "/referrals",
icon: faGift,
icon: GiftIcon,
};
const RESOURCES_ITEMS: NavItem[] = [
{
label: "Tutorials",
href: `${MARKETING_SITE}/tutorials`,
icon: faGraduationCap,
icon: GraduationCapIcon,
external: true,
},
{
label: "News",
href: `${MARKETING_SITE}/news`,
icon: faNewspaper,
icon: NewspaperIcon,
external: true,
},
{
label: "FAQ",
href: `${MARKETING_SITE}/faq`,
icon: faCircleQuestion,
icon: CircleHelpIcon,
external: true,
},
];
@@ -135,7 +117,7 @@ const SUPPORT_ITEMS: NavItem[] = [
{
label: "Join Discord",
href: SOCIAL_LINKS.DISCORD,
icon: faDiscord,
icon: DiscordIcon,
external: true,
},
];
@@ -166,7 +148,7 @@ function NavMenuItem({
<>
{/* Icon nudges up in scale on hover — a small tactile cue that the row is
interactive, kept subtle to stay within the "restrained chrome" lane. */}
<FontAwesomeIcon
<DynamicIcon
icon={item.icon}
className="transition-transform duration-200 ease-out group-hover/menu-item:scale-110"
/>
@@ -332,7 +314,7 @@ export function AppSidebar() {
<SidebarFooter className="group-data-[collapsible=icon]:hidden">
<Button
variant="primary"
icon={faDownload}
icon={DownloadIcon}
onClick={() =>
window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer")
}
@@ -1,15 +1,7 @@
import { useMemo, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { AnimatePresence, motion } from "framer-motion";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faBorderAll,
faChevronRight,
faFolder,
faFolderOpen,
faPlus,
faStar,
} from "@fortawesome/pro-solid-svg-icons";
import { ChevronRightIcon, FolderIcon, FolderOpenIcon, Grid3x3Icon, PlusIcon, StarIcon } from "lucide-react";
import { compareFolders } from "@storyteller/ui-gallery-modal";
import { EASE_EMPHASIS } from "../../lib/motion";
import {
@@ -91,7 +83,7 @@ export function LibraryFoldersNav({
aria-label="New folder"
className="mr-1 flex h-5 w-5 items-center justify-center rounded text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors group-data-[collapsible=icon]:hidden"
>
<FontAwesomeIcon icon={faPlus} className="text-xs" />
<PlusIcon className="text-xs" />
</button>
)}
</div>
@@ -100,7 +92,7 @@ export function LibraryFoldersNav({
<SidebarMenuItem>
<SidebarMenuButton asChild isActive={onUnsorted} tooltip="Library">
<Link to="/library" onClick={onNavClick}>
<FontAwesomeIcon icon={faBorderAll} />
<Grid3x3Icon />
<span>Library</span>
</Link>
</SidebarMenuButton>
@@ -112,7 +104,7 @@ export function LibraryFoldersNav({
tooltip="Unfoldered"
>
<Link to="/library/folderless" onClick={onNavClick}>
<FontAwesomeIcon icon={faFolderOpen} />
<FolderOpenIcon />
<span>Unfoldered</span>
</Link>
</SidebarMenuButton>
@@ -124,7 +116,7 @@ export function LibraryFoldersNav({
tooltip="Folders"
>
<Link to="/library/folders" onClick={onNavClick}>
<FontAwesomeIcon icon={faFolder} />
<FolderIcon />
<span>Folders</span>
</Link>
</SidebarMenuButton>
@@ -135,10 +127,9 @@ export function LibraryFoldersNav({
aria-label={expanded ? "Collapse folders" : "Expand folders"}
className="absolute right-1 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors group-data-[collapsible=icon]:hidden"
>
<FontAwesomeIcon
icon={faChevronRight}
className={`text-[10px] transition-transform duration-200 ${expanded ? "rotate-90" : ""}`}
/>
<ChevronRightIcon
className={`text-[10px] transition-transform duration-200 ${expanded ? "rotate-90" : ""}`} />
</button>
)}
</SidebarMenuItem>
@@ -171,21 +162,19 @@ export function LibraryFoldersNav({
}}
className="pl-5 [&.folder-drag-over]:bg-primary/20 [&.folder-drag-over]:text-sidebar-foreground"
>
<FontAwesomeIcon
icon={faFolder}
<FolderIcon
className={folder.colorCode ? "" : "text-primary"}
style={
folder.colorCode
? { color: folder.colorCode }
: undefined
}
/>
} />
<span className="truncate">{folder.name}</span>
{folder.hasStar && (
<FontAwesomeIcon
icon={faStar}
className="ml-auto text-[10px] text-amber-400"
/>
<StarIcon
className="ml-auto text-[10px] text-amber-400" />
)}
</SidebarMenuButton>
</SidebarMenuItem>
@@ -1,13 +1,7 @@
import { useMemo, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { AnimatePresence, motion } from "framer-motion";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronRight,
faEllipsis,
faTag,
faTags,
} from "@fortawesome/pro-solid-svg-icons";
import { ChevronRightIcon, EllipsisIcon, TagIcon, TagsIcon } from "lucide-react";
import { SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar";
import {
compareTagsByUseCount,
@@ -60,7 +54,7 @@ export function LibraryTagsNav({
tooltip="Tags"
>
<Link to="/library/tags" onClick={onNavClick}>
<FontAwesomeIcon icon={faTags} />
<TagsIcon />
<span>Tags</span>
</Link>
</SidebarMenuButton>
@@ -71,10 +65,9 @@ export function LibraryTagsNav({
aria-label={expanded ? "Collapse tags" : "Expand tags"}
className="absolute right-1 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors group-data-[collapsible=icon]:hidden"
>
<FontAwesomeIcon
icon={faChevronRight}
className={`text-[10px] transition-transform duration-200 ${expanded ? "rotate-90" : ""}`}
/>
<ChevronRightIcon
className={`text-[10px] transition-transform duration-200 ${expanded ? "rotate-90" : ""}`} />
</button>
)}
</SidebarMenuItem>
@@ -109,10 +102,9 @@ export function LibraryTagsNav({
}}
className="pl-5"
>
<FontAwesomeIcon
icon={faTag}
className="text-violet-400"
/>
<TagIcon
className="text-violet-400" />
<span className="truncate">{tag.value}</span>
<span className="ml-auto text-[10px] text-sidebar-foreground/40 transition-opacity group-hover/menu-item:opacity-0">
{tag.useCount}
@@ -133,7 +125,7 @@ export function LibraryTagsNav({
aria-label={`Options for tag "${tag.value}"`}
className="absolute right-1 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded text-sidebar-foreground/50 opacity-0 transition-opacity group-hover/menu-item:opacity-100 hover:bg-sidebar-accent hover:text-sidebar-foreground"
>
<FontAwesomeIcon icon={faEllipsis} className="text-xs" />
<EllipsisIcon className="text-xs" />
</button>
</SidebarMenuItem>
))}
@@ -145,7 +137,7 @@ export function LibraryTagsNav({
onClick={onNavClick}
className="pl-5 text-sidebar-foreground/60"
>
<FontAwesomeIcon icon={faEllipsis} />
<EllipsisIcon />
<span>See all tags</span>
<span className="ml-auto text-[10px] text-sidebar-foreground/40">
{tags.length}
@@ -1,21 +1,16 @@
import { Link, useLocation } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faHouse,
faImage,
faVideo,
faGrid2,
} from "@fortawesome/pro-solid-svg-icons";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { HouseIcon, ImageIcon, LayoutGridIcon, VideoIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { twMerge } from "tailwind-merge";
type NavItem = { label: string; href: string; icon: IconDefinition };
type NavItem = { label: string; href: string; icon: LucideIcon };
const ITEMS: NavItem[] = [
{ label: "Home", href: "/", icon: faHouse },
{ label: "Image", href: "/create-image", icon: faImage },
{ label: "Video", href: "/create-video", icon: faVideo },
{ label: "Library", href: "/library", icon: faGrid2 },
{ label: "Home", href: "/", icon: HouseIcon },
{ label: "Image", href: "/create-image", icon: ImageIcon },
{ label: "Video", href: "/create-video", icon: VideoIcon },
{ label: "Library", href: "/library", icon: LayoutGridIcon },
];
function isActive(pathname: string, href: string): boolean {
@@ -43,7 +38,7 @@ export function MobileBottomNav() {
: "text-base-fg/55 hover:text-base-fg/80",
)}
>
<FontAwesomeIcon icon={item.icon} className="h-5 w-5" />
<DynamicIcon icon={item.icon} className="h-5 w-5" />
<span>{item.label}</span>
</Link>
);
@@ -1,8 +1,7 @@
import { Link, useLocation, useNavigate } from "react-router-dom";
import { useState } from "react";
import { create } from "zustand";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck } from "@fortawesome/pro-solid-svg-icons";
import { CheckIcon } from "lucide-react";
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import { GoogleLoginButton } from "./auth";
@@ -107,10 +106,9 @@ export function SignupCtaModal() {
className="flex items-start gap-3 text-[14px] text-white/75"
>
<span className="mt-[2px] flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-primary/15">
<FontAwesomeIcon
icon={faCheck}
className="text-[9px] text-primary"
/>
<CheckIcon
className="text-[9px] text-primary" />
</span>
<span>{perk}</span>
</li>
@@ -1,10 +1,6 @@
import { useCallback, useEffect, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheck,
faExclamation,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { CheckIcon, CircleAlertIcon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
// ── Types ──────────────────────────────────────────────────────────────────
@@ -99,8 +95,8 @@ export const ToastContainer = () => {
toast.type === "success" ? "bg-green-500" : "bg-red-500"
}`}
>
<FontAwesomeIcon
icon={toast.type === "success" ? faCheck : faExclamation}
<DynamicIcon
icon={toast.type === "success" ? CheckIcon : CircleAlertIcon}
className="h-3 w-3 text-white"
/>
</div>
@@ -111,7 +107,7 @@ export const ToastContainer = () => {
onClick={() => dismiss(toast.id)}
className="ml-1 text-white/40 transition-colors hover:text-white/80"
>
<FontAwesomeIcon icon={faXmark} className="h-3 w-3" />
<XIcon className="h-3 w-3" />
</button>
</div>
))}
@@ -17,8 +17,7 @@
import { useEffect, useRef, useState } from "react";
import { useLocation } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPencil, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon, PencilIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { MediaFilesApi } from "@storyteller/api";
import { usePageSceneStore } from "@storyteller/ui-pagescene";
@@ -125,10 +124,9 @@ export function ActiveSceneTitle() {
className="flex min-w-0 max-w-[280px] items-center rounded-lg border border-transparent px-3 py-1.5 text-sm font-semibold text-white transition-all hover:cursor-text hover:bg-white/[0.08]"
>
<span className="truncate">{title}</span>
<FontAwesomeIcon
icon={faPencil}
className="ml-2 shrink-0 text-sm opacity-50"
/>
<PencilIcon
className="ml-2 shrink-0 text-sm opacity-50" />
</button>
) : (
<div className="max-w-[280px] truncate rounded-lg border border-transparent px-3 py-1.5 text-sm font-semibold text-white/80">
@@ -167,11 +165,10 @@ export function ActiveSceneTitle() {
)}
{isSubmitting && (
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
spin
className="shrink-0 text-sm opacity-70"
/>
className="shrink-0 text-sm opacity-70" />
)}
</div>
);
@@ -14,16 +14,8 @@ import {
Transition,
} from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCoins,
faGem,
faCog,
faLifeRing,
faGift,
faImages,
} from "@fortawesome/pro-solid-svg-icons";
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
import { CoinsIcon, GemIcon, GiftIcon, ImagesIcon, LifeBuoyIcon, SettingsIcon } from "lucide-react";
import { DiscordIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { PopoverMenu } from "@storyteller/ui-popover";
import {
@@ -109,7 +101,7 @@ export function TopBarActions({ className }: { className?: string }) {
to="/pricing"
className="hidden lg:flex h-8 items-center gap-1.5 px-3 rounded-lg text-sm font-medium text-white/85 hover:bg-white/[0.04] transition-colors"
>
<FontAwesomeIcon icon={faGem} className="text-[11px]" />
<GemIcon className="text-[11px]" />
Pricing
</Link>
@@ -124,7 +116,7 @@ export function TopBarActions({ className }: { className?: string }) {
{hasPaidPlan === false && (
<Button
variant="primary"
icon={faGem}
icon={GemIcon}
onClick={() => navigate("/pricing")}
className="h-8 px-3 text-sm font-semibold rounded-lg"
>
@@ -138,7 +130,7 @@ export function TopBarActions({ className }: { className?: string }) {
to="/library"
className="hidden sm:flex h-8 items-center gap-1.5 px-3 rounded-lg bg-white/[0.04] hover:bg-white/[0.08] border border-white/[0.08] shadow-none text-sm font-medium text-white/80 transition-colors"
>
<FontAwesomeIcon icon={faImages} className="text-[11px]" />
<ImagesIcon className="text-[11px]" />
My Library
</Link>
@@ -178,10 +170,9 @@ export function TopBarActions({ className }: { className?: string }) {
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
)}
>
<FontAwesomeIcon
icon={faCog}
className="text-[11px] text-white/50"
/>
<SettingsIcon
className="text-[11px] text-white/50" />
Settings
</button>
)}
@@ -195,10 +186,9 @@ export function TopBarActions({ className }: { className?: string }) {
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
)}
>
<FontAwesomeIcon
icon={faLifeRing}
className="text-[11px] text-white/50"
/>
<LifeBuoyIcon
className="text-[11px] text-white/50" />
Support
</button>
)}
@@ -215,10 +205,9 @@ export function TopBarActions({ className }: { className?: string }) {
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
)}
>
<FontAwesomeIcon
icon={faGift}
className="text-[11px] text-white/50"
/>
<GiftIcon
className="text-[11px] text-white/50" />
Referrals
</button>
)}
@@ -235,10 +224,9 @@ export function TopBarActions({ className }: { className?: string }) {
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
)}
>
<FontAwesomeIcon
icon={faDiscord}
className="text-[11px] text-white/50"
/>
<DiscordIcon
className="text-[11px] text-white/50" />
Join Discord
</a>
)}
@@ -266,7 +254,7 @@ export function TopBarActions({ className }: { className?: string }) {
to="/pricing"
className="hidden lg:flex h-8 items-center gap-1.5 px-3 rounded-lg text-sm font-medium text-white/85 hover:bg-white/[0.04] transition-colors"
>
<FontAwesomeIcon icon={faGem} className="text-[11px]" />
<GemIcon className="text-[11px]" />
Pricing
</Link>
<Link
@@ -310,7 +298,7 @@ function CreditsChip({
position="bottom"
align="end"
triggerIcon={
<FontAwesomeIcon icon={faCoins} className="text-primary text-[11px]" />
<CoinsIcon className="text-primary text-[11px]" />
}
triggerLabel={
<span className="whitespace-nowrap text-sm font-medium">
@@ -337,7 +325,7 @@ function CreditsChip({
</button>
</div>
<div className="flex items-center gap-2 text-3xl font-semibold text-white tracking-tight">
<FontAwesomeIcon icon={faCoins} className="text-xl text-primary" />
<CoinsIcon className="text-xl text-primary" />
{credits.toLocaleString()}
</div>
<div className="mt-3 flex gap-2">
@@ -348,7 +336,7 @@ function CreditsChip({
close();
onUpgrade();
}}
icon={faGem}
icon={GemIcon}
>
Upgrade
</Button>
@@ -1,19 +1,7 @@
import { Tooltip } from "@storyteller/ui-tooltip";
import { PopoverMenu } from "@storyteller/ui-popover";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faListCheck,
faSpinnerThird,
faXmark,
faTrashAlt,
faTasks,
faBroom,
faBomb,
faCircleExclamation,
faCopy,
faCheck,
faArrowRotateRight,
} from "@fortawesome/pro-solid-svg-icons";
import { BombIcon, BrushIcon, CheckIcon, CircleAlertIcon, CopyIcon, ListChecksIcon, LoaderCircleIcon, RotateCwIcon, Trash2Icon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Modal } from "@storyteller/ui-modal";
import { useEffect, useMemo, useRef, useState } from "react";
import { JobsApi, JobStatus } from "@storyteller/api";
@@ -201,8 +189,8 @@ const CopyPromptButton = ({ prompt }: { prompt: string }) => {
setTimeout(() => setCopied(false), 3000);
}}
>
<FontAwesomeIcon
icon={copied ? faCheck : faCopy}
<DynamicIcon
icon={copied ? CheckIcon : CopyIcon}
className={copied ? "text-green-400" : ""}
/>
</button>
@@ -237,8 +225,8 @@ const RecreateTaskButton = ({
disabled={isRecreating}
onClick={handleRecreate}
>
<FontAwesomeIcon
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
<DynamicIcon
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
className={isRecreating ? "animate-spin" : ""}
/>
</button>
@@ -270,11 +258,10 @@ const InProgressCard = ({
<div className="rounded-md p-2 transition-colors hover:bg-ui-controls/40">
<div className="flex items-center gap-2.5">
<div className="flex h-[72px] w-[72px] shrink-0 items-center justify-center overflow-hidden rounded bg-ui-controls">
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
className="animate-spin text-base-fg/60"
size="lg"
/>
size="lg" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between text-sm">
@@ -327,7 +314,7 @@ const InProgressCard = ({
onDismiss();
}}
>
<FontAwesomeIcon icon={faXmark} />
<XIcon />
</button>
)}
</div>
@@ -399,7 +386,7 @@ const CompletedCard = ({
onDismiss();
}}
>
<FontAwesomeIcon icon={faXmark} />
<XIcon />
</button>
)}
</div>
@@ -419,11 +406,10 @@ const FailedCard = ({
<div className="rounded-md p-2 transition-colors hover:bg-ui-controls/40">
<div className="flex items-center gap-2.5">
<div className="flex h-[72px] w-[72px] shrink-0 items-center justify-center overflow-hidden rounded bg-red-500/10">
<FontAwesomeIcon
icon={faCircleExclamation}
<CircleAlertIcon
className="text-red-400"
size="lg"
/>
size="lg" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between text-sm">
@@ -492,7 +478,7 @@ const FailedCard = ({
onDismiss();
}}
>
<FontAwesomeIcon icon={faXmark} />
<XIcon />
</button>
)}
</div>
@@ -761,7 +747,7 @@ export const TaskQueue = () => {
title: "",
message: null,
primaryActionText: "",
primaryActionIcon: faTrashAlt,
primaryActionIcon: Trash2Icon,
primaryActionBtnClassName: "",
onConfirm: async () => {},
});
@@ -778,7 +764,7 @@ export const TaskQueue = () => {
</span>
),
primaryActionText: "Clear completed",
primaryActionIcon: faBroom,
primaryActionIcon: BrushIcon,
primaryActionBtnClassName:
"bg-green-500/10 hover:bg-green-500/20 text-green-500",
onConfirm: async () => {
@@ -797,7 +783,7 @@ export const TaskQueue = () => {
</span>
),
primaryActionText: "Clear stale",
primaryActionIcon: faTrashAlt,
primaryActionIcon: Trash2Icon,
primaryActionBtnClassName:
"bg-orange-500/10 hover:bg-orange-500/20 text-orange-500",
onConfirm: async () => {
@@ -816,7 +802,7 @@ export const TaskQueue = () => {
</span>
),
primaryActionText: "Clear failed",
primaryActionIcon: faTrashAlt,
primaryActionIcon: Trash2Icon,
primaryActionBtnClassName:
"bg-red-500/10 hover:bg-red-500/20 text-red-500",
onConfirm: async () => {
@@ -836,7 +822,7 @@ export const TaskQueue = () => {
</span>
),
primaryActionText: "Nuke all",
primaryActionIcon: faBomb,
primaryActionIcon: BombIcon,
primaryActionBtnClassName:
"bg-red-500/10 hover:bg-red-500/20 text-red-500",
onConfirm: async () => {
@@ -1049,7 +1035,7 @@ export const TaskQueue = () => {
{hasNothing ? (
<div className="flex w-full flex-col items-center justify-center p-5 text-base-fg/60">
<div className="flex items-center gap-2.5 text-sm opacity-60">
<FontAwesomeIcon icon={faTasks} /> No tasks yet
<ListChecksIcon /> No tasks yet
</div>
</div>
) : (
@@ -1078,7 +1064,7 @@ export const TaskQueue = () => {
className="text-xs tracking-wide text-red-400/70 transition-colors hover:text-red-300"
onClick={() => handleClearFailed()}
>
<FontAwesomeIcon icon={faXmark} className="mr-1" />
<XIcon className="mr-1" />
Clear failed
</button>
</div>
@@ -1101,7 +1087,7 @@ export const TaskQueue = () => {
className="text-xs tracking-wide text-base-fg/50 transition-colors hover:text-base-fg/100"
onClick={() => handleClearCompleted()}
>
<FontAwesomeIcon icon={faXmark} className="mr-1" />
<XIcon className="mr-1" />
Clear completed
</button>
</div>
@@ -1148,12 +1134,11 @@ export const TaskQueue = () => {
align="end"
triggerIcon={
inProgressCount > 0 ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-[11px]"
/>
<LoaderCircleIcon
className="animate-spin text-[11px]" />
) : (
<FontAwesomeIcon icon={faListCheck} className="text-[11px]" />
<ListChecksIcon className="text-[11px]" />
)
}
onOpenChange={handleOpenChange}
@@ -1198,28 +1183,28 @@ export const TaskQueue = () => {
className="rounded-full flex h-9 items-center justify-center bg-green-500/10 px-3 text-green-500 hover:bg-green-500/20"
onClick={() => handleClearCompleted()}
>
<FontAwesomeIcon icon={faBroom} className="mr-1.5" />
<BrushIcon className="mr-1.5" />
Clear completed
</Button>
<Button
className="rounded-full flex h-9 items-center justify-center bg-orange-500/10 px-3 text-orange-500 hover:bg-orange-500/20"
onClick={() => handleClearStale()}
>
<FontAwesomeIcon icon={faTrashAlt} className="mr-1.5" />
<Trash2Icon className="mr-1.5" />
Clear stale
</Button>
<Button
className="rounded-full flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-400 hover:bg-red-500/20"
onClick={() => handleClearFailed()}
>
<FontAwesomeIcon icon={faTrashAlt} className="mr-1.5" />
<Trash2Icon className="mr-1.5" />
Clear failed
</Button>
<Button
className="rounded-full flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-500 hover:bg-red-500/20"
onClick={() => handleRemoveAll()}
>
<FontAwesomeIcon icon={faBomb} className="mr-1.5" />
<BombIcon className="mr-1.5" />
Remove all
</Button>
<div className="mr-2 h-4 w-[1px] bg-base-fg/10" />
@@ -1,6 +1,5 @@
import { faXmarkCircle } from "@fortawesome/pro-solid-svg-icons";
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CircleXIcon } from "lucide-react";
import { DiscordIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { Link } from "react-router-dom";
import { SOCIAL_LINKS } from "../../config/links";
@@ -26,10 +25,9 @@ const CheckoutCancel = () => {
{/* Cancel Icon */}
<div className="mb-6">
<div className="w-20 h-20 mx-auto rounded-full bg-white/10 flex items-center justify-center">
<FontAwesomeIcon
icon={faXmarkCircle}
className="text-5xl text-white/50"
/>
<CircleXIcon
className="text-5xl text-white/50" />
</div>
</div>
@@ -77,7 +75,7 @@ const CheckoutCancel = () => {
target="_blank"
className="rounded-full bg-white text-black hover:bg-white/80 px-4 py-2 text-sm font-bold rounded-xl justify-center border-transparent"
>
<FontAwesomeIcon icon={faDiscord} />
<DiscordIcon />
Join Discord
</Button>
</div>
@@ -1,16 +1,5 @@
import {
faCheckCircle,
faDownload,
faDesktop,
faRocket,
faImage,
faVideo,
faWandMagicSparkles,
faCube,
faArrowRight,
} from "@fortawesome/pro-solid-svg-icons";
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ArrowRightIcon, BoxIcon, CircleCheckIcon, DownloadIcon, ImageIcon, MonitorIcon, RocketIcon, VideoIcon, WandSparklesIcon } from "lucide-react";
import { DynamicIcon, DiscordIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { Link, useSearchParams } from "react-router-dom";
import { isMobile, isMacOs } from "react-device-detect";
@@ -96,10 +85,9 @@ const CheckoutSuccess = () => {
{/* Success Icon */}
<div className="mb-6">
<div className="w-20 h-20 mx-auto rounded-full bg-green-500/20 flex items-center justify-center">
<FontAwesomeIcon
icon={faCheckCircle}
className="text-5xl text-green-400"
/>
<CircleCheckIcon
className="text-5xl text-green-400" />
</div>
</div>
@@ -117,10 +105,9 @@ const CheckoutSuccess = () => {
<div className="bg-[#252529] rounded-2xl p-6 mb-8 text-left">
<div className="flex items-center gap-3 mb-2">
<div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center">
<FontAwesomeIcon
icon={faWandMagicSparkles}
className="text-primary text-sm"
/>
<WandSparklesIcon
className="text-primary text-sm" />
</div>
<h2 className="text-lg font-medium text-white">
Start creating right here
@@ -131,31 +118,30 @@ const CheckoutSuccess = () => {
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ to: "/create-image", icon: faImage, label: "Image" },
{ to: "/create-video", icon: faVideo, label: "Video" },
{ to: "/create-image", icon: ImageIcon, label: "Image" },
{ to: "/create-video", icon: VideoIcon, label: "Video" },
{
to: "/background-change",
icon: faWandMagicSparkles,
icon: WandSparklesIcon,
label: "BG Change",
},
{ to: "/edit-3d", icon: faCube, label: "Edit 3D" },
{ to: "/edit-3d", icon: BoxIcon, label: "Edit 3D" },
].map((item) => (
<Link
key={item.to}
to={item.to}
className="group flex items-center gap-3 bg-[#1A1A1E] hover:bg-[#222227] border border-white/[0.06] hover:border-white/[0.12] rounded-xl px-4 py-3 transition-all"
>
<FontAwesomeIcon
<DynamicIcon
icon={item.icon}
className="text-primary text-base shrink-0"
/>
<span className="text-white/90 font-medium flex-1">
{item.label}
</span>
<FontAwesomeIcon
icon={faArrowRight}
className="text-white/30 text-[11px] transition-transform group-hover:translate-x-0.5"
/>
<ArrowRightIcon
className="text-white/30 text-[11px] transition-transform group-hover:translate-x-0.5" />
</Link>
))}
</div>
@@ -165,10 +151,9 @@ const CheckoutSuccess = () => {
<div className="bg-[#252529] rounded-2xl p-6 mb-8 text-left">
<div className="flex items-center gap-3 mb-4">
<div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center">
<FontAwesomeIcon
icon={faRocket}
className="text-primary text-sm"
/>
<RocketIcon
className="text-primary text-sm" />
</div>
<h2 className="text-lg font-medium text-white">Next Steps</h2>
</div>
@@ -198,14 +183,14 @@ const CheckoutSuccess = () => {
href={downloadUrl}
className="rounded-full bg-primary hover:bg-primary-600 px-8 py-3 text-sm font-bold rounded-xl justify-center"
>
<FontAwesomeIcon icon={faDownload} className="mr-2" />
<DownloadIcon className="mr-2" />
Download ArtCraft
</Button>
</div>
) : (
<div className="bg-[#431407] border border-orange-900/50 rounded-2xl p-6 text-orange-200 text-sm leading-relaxed">
<div className="flex items-center justify-center mb-3 text-orange-400">
<FontAwesomeIcon icon={faDesktop} className="text-2xl" />
<MonitorIcon className="text-2xl" />
</div>
ArtCraft is a desktop application. <br />
Please head to your computer to download and start creating.
@@ -245,7 +230,7 @@ const CheckoutSuccess = () => {
target="_blank"
className="rounded-full bg-white text-black hover:bg-white/80 px-4 py-2 text-sm font-bold rounded-xl justify-center border-transparent"
>
<FontAwesomeIcon icon={faDiscord} />
<DiscordIcon />
Join Discord
</Button>
</div>
@@ -65,12 +65,7 @@ import {
import { toast } from "../../components/toast/toast";
import { useSignupCta } from "../../components/signup-cta-modal";
import { useInsufficientCredits } from "../../components/insufficient-credits-modal";
import {
faMicrophoneLines,
faMicrophoneSlash,
faRepeat,
faSparkles,
} from "@fortawesome/pro-solid-svg-icons";
import { MicIcon, MicOffIcon, RepeatIcon, SparklesIcon } from "lucide-react";
// ── Constants ────────────────────────────────────────────────────────────
@@ -492,8 +487,8 @@ export default function CreateAudio() {
>
<ToggleButton
isActive={ui.isInstrumental}
icon={faMicrophoneSlash}
activeIcon={faMicrophoneSlash}
icon={MicOffIcon}
activeIcon={MicOffIcon}
label="Instrumental"
onClick={() => setUi({ isInstrumental: !ui.isInstrumental })}
/>
@@ -508,8 +503,8 @@ export default function CreateAudio() {
>
<ToggleButton
isActive={ui.keepLyrics}
icon={faMicrophoneLines}
activeIcon={faMicrophoneLines}
icon={MicIcon}
activeIcon={MicIcon}
label="Keep lyrics"
onClick={() => setUi({ keepLyrics: !ui.keepLyrics })}
/>
@@ -524,8 +519,8 @@ export default function CreateAudio() {
>
<ToggleButton
isActive={ui.isLoopable}
icon={faRepeat}
activeIcon={faRepeat}
icon={RepeatIcon}
activeIcon={RepeatIcon}
label="Loop"
onClick={() => setUi({ isLoopable: !ui.isLoopable })}
/>
@@ -778,7 +773,7 @@ export default function CreateAudio() {
<Button
variant="primary"
onClick={openSignupCta}
icon={faSparkles}
icon={SparklesIcon}
className="h-12 px-6 text-base font-semibold rounded-full"
>
Sign up to create
@@ -1,5 +1,4 @@
import { faCopy } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CopyIcon } from "lucide-react";
import { PopoverMenu, PopoverItem } from "@storyteller/ui-popover";
import { Tooltip } from "@storyteller/ui-tooltip";
import { PROMPT_TOOLBAR_ICON_BUTTON_CLASSES } from "@storyteller/ui-promptbox";
@@ -79,7 +78,7 @@ export const GenerationCountPicker = ({
onSelect={onSelect}
mode="toggle"
panelTitle={panelTitle}
triggerIcon={<FontAwesomeIcon icon={faCopy} className="h-4 w-4" />}
triggerIcon={<CopyIcon className="h-4 w-4" />}
buttonClassName={twMerge(PROMPT_TOOLBAR_ICON_BUTTON_CLASSES, "w-11")}
/>
</Tooltip>
@@ -1,5 +1,4 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGem } from "@fortawesome/pro-solid-svg-icons";
import { GemIcon } from "lucide-react";
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover";
import { Tooltip } from "@storyteller/ui-tooltip";
@@ -63,7 +62,7 @@ export const QualityPicker = ({
mode="toggle"
panelTitle="Quality"
triggerIcon={
<FontAwesomeIcon icon={faGem} className="h-3.5 w-3.5" />
<GemIcon className="h-3.5 w-3.5" />
}
/>
</Tooltip>
@@ -1,10 +1,6 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { faSquare } from "@fortawesome/pro-regular-svg-icons";
import {
faHighDefinition,
faStandardDefinition,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SquareIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { DynamicIcon, HighDefinitionIcon, StandardDefinitionIcon } from "@storyteller/icons";
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover";
import { Tooltip } from "@storyteller/ui-tooltip";
@@ -15,15 +11,15 @@ interface ResolutionPickerProps {
handleResolutionSelect: (selected: string) => void;
}
const RESOLUTION_ICONS: Record<string, IconDefinition> = {
half_k: faStandardDefinition,
four_eighty_p: faStandardDefinition,
seven_twenty_p: faStandardDefinition,
one_k: faStandardDefinition,
ten_eighty_p: faHighDefinition,
two_k: faHighDefinition,
three_k: faHighDefinition,
four_k: faHighDefinition,
const RESOLUTION_ICONS: Record<string, LucideIcon> = {
half_k: StandardDefinitionIcon,
four_eighty_p: StandardDefinitionIcon,
seven_twenty_p: StandardDefinitionIcon,
one_k: StandardDefinitionIcon,
ten_eighty_p: HighDefinitionIcon,
two_k: HighDefinitionIcon,
three_k: HighDefinitionIcon,
four_k: HighDefinitionIcon,
};
const RESOLUTION_LABELS: Record<string, string> = {
@@ -54,8 +50,8 @@ export function buildResolutionItems(
label: RESOLUTION_LABELS[resolution] ?? resolution,
selected: selected === resolution,
icon: (
<FontAwesomeIcon
icon={RESOLUTION_ICONS[resolution] ?? faStandardDefinition}
<DynamicIcon
icon={RESOLUTION_ICONS[resolution] ?? StandardDefinitionIcon}
className="h-4 w-4"
/>
),
@@ -96,11 +92,11 @@ export const ResolutionPicker = ({
panelTitle="Resolution"
showIconsInList
triggerIcon={
<FontAwesomeIcon
<DynamicIcon
icon={
activeResolution
? (RESOLUTION_ICONS[activeResolution] ?? faSquare)
: faSquare
? (RESOLUTION_ICONS[activeResolution] ?? SquareIcon)
: SquareIcon
}
className="h-4 w-4"
/>
@@ -75,7 +75,7 @@ import {
import { toast } from "../../components/toast/toast";
import { useSignupCta } from "../../components/signup-cta-modal";
import { useInsufficientCredits } from "../../components/insufficient-credits-modal";
import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
import { SparklesIcon } from "lucide-react";
// ── Constants ────────────────────────────────────────────────────────────
@@ -679,7 +679,7 @@ export default function CreateImage() {
<Button
variant="primary"
onClick={openSignupCta}
icon={faSparkles}
icon={SparklesIcon}
className="h-12 px-6 text-base font-semibold rounded-full"
>
Sign up to create
@@ -1,11 +1,6 @@
import { useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faSpinnerThird,
faXmark,
faCube,
faImage,
} from "@fortawesome/pro-solid-svg-icons";
import { BoxIcon, ImageIcon, LoaderCircleIcon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { AddButton, type RefImage } from "../../../components/prompt-box";
import { MESH_FILE_ACCEPT, uploadMeshFile, uploadViewImage } from "./mesh-upload";
@@ -53,7 +48,7 @@ export function MeshInputsRow({
const subtitle = showMultiView
? "Reference angles (optional)"
: "Mesh file to process";
const titleIcon = showMultiView ? faImage : faCube;
const titleIcon = showMultiView ? ImageIcon : BoxIcon;
return (
// Title on the left, upload slots right-aligned + top-aligned (matches
@@ -61,7 +56,7 @@ export function MeshInputsRow({
<div className="glass flex items-start gap-3 rounded-2xl px-3 py-2">
<div className="flex grow flex-col gap-1 min-w-32">
<div className="flex items-center gap-2 text-white/90">
<FontAwesomeIcon icon={titleIcon} className="h-3.5 w-3.5" />
<DynamicIcon icon={titleIcon} className="h-3.5 w-3.5" />
<span className="text-sm font-medium">{title}</span>
</div>
<span className="text-[13px] text-white/60">{subtitle}</span>
@@ -186,16 +181,15 @@ function ImageSlot({
onClick={() => onChange(undefined)}
className="absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 text-white backdrop-blur-md transition-colors hover:bg-black"
>
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
</div>
) : uploading ? (
<div className={SLOT_CLASS}>
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
spin
className="h-5 w-5 text-white"
/>
className="h-5 w-5 text-white" />
</div>
) : (
<AddButton
@@ -238,28 +232,27 @@ function MeshFileSlot({
/>
{mesh ? (
<div className="group relative flex aspect-square w-14 items-center justify-center overflow-hidden rounded-lg border-2 border-white/30 bg-white/10">
<FontAwesomeIcon icon={faCube} className="h-5 w-5 text-white/80" />
<BoxIcon className="h-5 w-5 text-white/80" />
<button
onClick={() => onChange(undefined)}
className="absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 text-white backdrop-blur-md transition-colors hover:bg-black"
>
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
</div>
) : uploading ? (
<div className={SLOT_CLASS}>
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
spin
className="h-5 w-5 text-white"
/>
className="h-5 w-5 text-white" />
</div>
) : (
<button
onClick={() => inputRef.current?.click()}
className={SLOT_CLASS}
>
<FontAwesomeIcon icon={faCube} className="text-xl text-white/80" />
<BoxIcon className="text-xl text-white/80" />
</button>
)}
</SlotShell>
@@ -1,12 +1,6 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCube,
faObjectGroup,
faWandMagicSparkles,
faImage,
faGem,
} from "@fortawesome/pro-solid-svg-icons";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { BoxIcon, GemIcon, GroupIcon, ImageIcon, WandSparklesIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover";
import { Tooltip } from "@storyteller/ui-tooltip";
@@ -41,7 +35,7 @@ interface EnumPickerProps {
labels: Record<string, string>;
tooltip: string;
panelTitle: string;
icon: IconDefinition;
icon: LucideIcon;
}
function EnumPicker({
@@ -72,7 +66,7 @@ function EnumPicker({
}}
mode="toggle"
panelTitle={panelTitle}
triggerIcon={<FontAwesomeIcon icon={icon} className="h-3.5 w-3.5" />}
triggerIcon={<DynamicIcon icon={icon} className="h-3.5 w-3.5" />}
/>
</Tooltip>
);
@@ -90,7 +84,7 @@ export const MeshOutputTypePicker = (props: {
labels={MESH_OUTPUT_TYPE_LABELS}
tooltip="Output type"
panelTitle="Output type"
icon={faCube}
icon={BoxIcon}
/>
);
@@ -104,7 +98,7 @@ export const PolygonTypePicker = (props: {
labels={POLYGON_TYPE_LABELS}
tooltip="Polygon type"
panelTitle="Polygon type"
icon={faObjectGroup}
icon={GroupIcon}
/>
);
@@ -118,7 +112,7 @@ export const GeometryQualityPicker = (props: {
labels={MESH_QUALITY_LABELS}
tooltip="Geometry quality"
panelTitle="Geometry quality"
icon={faWandMagicSparkles}
icon={WandSparklesIcon}
/>
);
@@ -132,7 +126,7 @@ export const TextureQualityPicker = (props: {
labels={MESH_QUALITY_LABELS}
tooltip="Texture quality"
panelTitle="Texture quality"
icon={faImage}
icon={ImageIcon}
/>
);
@@ -168,7 +162,7 @@ export const FaceCountPicker = ({
}}
mode="toggle"
panelTitle="Face count"
triggerIcon={<FontAwesomeIcon icon={faGem} className="h-3.5 w-3.5" />}
triggerIcon={<GemIcon className="h-3.5 w-3.5" />}
/>
</Tooltip>
);
@@ -4,7 +4,7 @@ import { PopoverMenu, type PopoverItem } from "@storyteller/ui-popover";
import { Tooltip } from "@storyteller/ui-tooltip";
import { Button, ToggleButton } from "@storyteller/ui-button";
import { GalleryModal, type GalleryItem } from "@storyteller/ui-gallery-modal";
import { faSparkles, faGem, faImage } from "@fortawesome/pro-solid-svg-icons";
import { GemIcon, ImageIcon, SparklesIcon } from "lucide-react";
import {
PromptBox,
ImagePromptRow,
@@ -555,8 +555,8 @@ export default function CreateObject() {
<Tooltip content="PBR materials" position="top" closeOnClick>
<ToggleButton
isActive={enablePbr}
icon={faGem}
activeIcon={faGem}
icon={GemIcon}
activeIcon={GemIcon}
label={enablePbr ? "PBR" : "PBR off"}
onClick={() => setUi({ enablePbr: !enablePbr })}
/>
@@ -566,8 +566,8 @@ export default function CreateObject() {
<Tooltip content="Generate textures" position="top" closeOnClick>
<ToggleButton
isActive={enableTexture}
icon={faImage}
activeIcon={faImage}
icon={ImageIcon}
activeIcon={ImageIcon}
label={enableTexture ? "Texture" : "No texture"}
onClick={() => setUi({ enableTexture: !enableTexture })}
/>
@@ -624,7 +624,7 @@ export default function CreateObject() {
<Button
variant="primary"
onClick={openSignupCta}
icon={faSparkles}
icon={SparklesIcon}
className="h-12 px-6 text-base font-semibold rounded-full"
>
Sign up to create
@@ -1,6 +1,5 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinnerThird, faSparkles } from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon, SparklesIcon } from "lucide-react";
import {
PromptBoxVFX,
VFXResultCard,
@@ -249,10 +248,9 @@ export default function CreateVFX() {
if (!authChecked) {
return (
<div className="flex h-screen items-center justify-center bg-[#101014]">
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-4xl text-primary/80"
/>
<LoaderCircleIcon
className="animate-spin text-4xl text-primary/80" />
</div>
);
}
@@ -341,7 +339,7 @@ interface EmptyStateProps {
const EmptyState = ({ title, subtitle }: EmptyStateProps) => (
<div className="flex max-w-md flex-col items-center gap-4 text-center">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-white/5 ring-1 ring-white/10">
<FontAwesomeIcon icon={faSparkles} className="text-2xl text-white/40" />
<SparklesIcon className="text-2xl text-white/40" />
</div>
<h3 className="text-2xl font-bold text-white">{title}</h3>
<p className="text-sm text-white/60 max-w-xs">{subtitle}</p>
@@ -1,12 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { flushSync } from "react-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCircleInfo,
faClock,
faSparkles,
faWaveformLines,
} from "@fortawesome/pro-solid-svg-icons";
import { AudioLinesIcon, ClockIcon, InfoIcon, SparklesIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { CharactersApi, FilterMediaClasses } from "@storyteller/api";
import type { OmniGenVideoModelInfo } from "@storyteller/api";
import { Button, ToggleButton } from "@storyteller/ui-button";
@@ -1489,10 +1484,9 @@ export default function CreateVideo() {
banner={
requiresImageInput ? (
<div className="flex items-start gap-2.5 rounded-lg border border-amber-500/40 bg-amber-900/80 px-3.5 py-2.5 text-xs text-amber-100">
<FontAwesomeIcon
icon={faCircleInfo}
className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-amber-400"
/>
<InfoIcon
className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-amber-400" />
<span>
This model can&apos;t generate from text alone — add a starting
frame to animate your prompt.
@@ -1570,13 +1564,13 @@ export default function CreateVideo() {
{hasSound && (
<div className="flex items-center justify-between rounded-xl border border-ui-panel-border bg-ui-controls px-3.5 py-2.5">
<span className="flex items-center gap-2 text-sm font-medium text-base-fg/70">
<FontAwesomeIcon icon={faWaveformLines} className="h-4 w-4" />
<AudioLinesIcon className="h-4 w-4" />
Audio
</span>
<ToggleButton
isActive={generateWithSound}
icon={faWaveformLines}
activeIcon={faWaveformLines}
icon={AudioLinesIcon}
activeIcon={AudioLinesIcon}
label={generateWithSound ? "On" : "Off"}
onClick={() => setUi({ generateWithSound: !generateWithSound })}
/>
@@ -1586,7 +1580,7 @@ export default function CreateVideo() {
<>
<MobileFieldButton
label="Output"
icon={<FontAwesomeIcon icon={faClock} className="h-4 w-4" />}
icon={<ClockIcon className="h-4 w-4" />}
value={outputSummary}
onClick={() => setIsOutputDrawerOpen(true)}
/>
@@ -1689,7 +1683,7 @@ export default function CreateVideo() {
<Button
variant="primary"
onClick={openSignupCta}
icon={faSparkles}
icon={SparklesIcon}
className="h-12 px-6 text-base font-semibold rounded-full"
>
Sign up to create
@@ -1729,7 +1723,7 @@ export default function CreateVideo() {
>
{/* {selectedModel?.model === "seedance_2p0" && (
<div className="mb-2 flex items-start gap-2.5 rounded-lg border border-yellow-500/40 px-3.5 py-2.5 text-xs text-yellow-200 shadow-lg backdrop-blur-xl bg-yellow-800/60">
<FontAwesomeIcon icon={faTriangleExclamation} className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-yellow-400" />
<TriangleAlertIcon className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-yellow-400" />
<span>
Seedance 2.0 is in Early Alpha. Generations may be slow and may experience outages.
Seedance may reject safe inputs unexpectedly. Try several short generations before longer ones.
@@ -1738,10 +1732,9 @@ export default function CreateVideo() {
)} */}
{requiresImageInput && (
<div className="mb-2 flex items-start gap-2.5 rounded-lg border border-amber-500/40 bg-amber-900/80 px-3.5 py-2.5 text-xs text-amber-100 shadow-lg backdrop-blur-sm">
<FontAwesomeIcon
icon={faCircleInfo}
className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-amber-400"
/>
<InfoIcon
className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-amber-400" />
<span>
This model can&apos;t generate from text alone — add a starting
frame to animate your prompt.
@@ -1904,10 +1897,9 @@ export default function CreateVideo() {
mode="default"
panelTitle="Duration"
triggerIcon={
<FontAwesomeIcon
icon={faClock}
className="h-3.5 w-3.5"
/>
<ClockIcon
className="h-3.5 w-3.5" />
}
triggerLabel={`${effectiveDuration}s`}
>
@@ -1945,8 +1937,8 @@ export default function CreateVideo() {
>
<ToggleButton
isActive={generateWithSound}
icon={faWaveformLines}
activeIcon={faWaveformLines}
icon={AudioLinesIcon}
activeIcon={AudioLinesIcon}
onClick={() =>
setUi({ generateWithSound: !generateWithSound })
}
@@ -1,11 +1,5 @@
import { useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faPlus,
faSpinnerThird,
faXmark,
faVideo,
} from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon, PlusIcon, VideoIcon, XIcon } from "lucide-react";
import { UploaderStates } from "@storyteller/common";
import {
uploadVideo,
@@ -71,7 +65,7 @@ export function ReferenceVideoSlot({
<div className="glass flex items-start gap-3 rounded-2xl px-3 py-2">
<div className="flex grow flex-col gap-1 min-w-32">
<div className="flex items-center gap-2 text-white/90">
<FontAwesomeIcon icon={faVideo} className="h-3.5 w-3.5" />
<VideoIcon className="h-3.5 w-3.5" />
<span className="text-sm font-medium">Reference video</span>
</div>
<span className="text-[13px] text-white/60">Guide the world (optional)</span>
@@ -95,16 +89,16 @@ export function ReferenceVideoSlot({
onClick={handleRemove}
className="absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 text-white backdrop-blur-md transition-colors hover:bg-black"
>
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
</div>
) : uploading ? (
<div className={SLOT_CLASS}>
<FontAwesomeIcon icon={faSpinnerThird} spin className="h-5 w-5 text-white" />
<LoaderCircleIcon className="h-5 w-5 animate-spin text-white" />
</div>
) : (
<button onClick={() => inputRef.current?.click()} className={SLOT_CLASS}>
<FontAwesomeIcon icon={faPlus} className="text-xl text-white/80" />
<PlusIcon className="text-xl text-white/80" />
</button>
)}
</div>
@@ -4,11 +4,7 @@ import { PopoverMenu, type PopoverItem } from "@storyteller/ui-popover";
import { Tooltip } from "@storyteller/ui-tooltip";
import { Button, ToggleButton } from "@storyteller/ui-button";
import { GalleryModal, type GalleryItem } from "@storyteller/ui-gallery-modal";
import {
faSparkles,
faPanorama,
faWandMagicSparkles,
} from "@fortawesome/pro-solid-svg-icons";
import { RectangleHorizontalIcon, SparklesIcon, WandSparklesIcon } from "lucide-react";
import {
PromptBox,
ImagePromptRow,
@@ -400,8 +396,8 @@ export default function CreateWorld() {
<Tooltip content="360° panorama input" position="top" closeOnClick>
<ToggleButton
isActive={isPanoramic}
icon={faPanorama}
activeIcon={faPanorama}
icon={RectangleHorizontalIcon}
activeIcon={RectangleHorizontalIcon}
label={isPanoramic ? "Panorama" : "Panorama off"}
onClick={() => setUi({ isPanoramic: !isPanoramic })}
/>
@@ -415,8 +411,8 @@ export default function CreateWorld() {
>
<ToggleButton
isActive={disableRecaption}
icon={faWandMagicSparkles}
activeIcon={faWandMagicSparkles}
icon={WandSparklesIcon}
activeIcon={WandSparklesIcon}
label={disableRecaption ? "No recaption" : "Recaption"}
onClick={() => setUi({ disableRecaption: !disableRecaption })}
/>
@@ -481,7 +477,7 @@ export default function CreateWorld() {
<Button
variant="primary"
onClick={openSignupCta}
icon={faSparkles}
icon={SparklesIcon}
className="h-12 px-6 text-base font-semibold rounded-full"
>
Sign up to create
@@ -1,9 +1,4 @@
import {
faArrowLeft,
faEnvelope,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ArrowLeftIcon, LoaderCircleIcon, MailIcon } from "lucide-react";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { useState } from "react";
@@ -112,10 +107,9 @@ const ForgotPassword = () => {
disabled={isLoading}
>
{isLoading ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin"
/>
<LoaderCircleIcon
className="animate-spin" />
) : (
"Send Reset Code"
)}
@@ -126,7 +120,7 @@ const ForgotPassword = () => {
) : (
<div className="text-center py-8">
<div className="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4 text-green-500">
<FontAwesomeIcon icon={faEnvelope} className="text-2xl" />
<MailIcon className="text-2xl" />
</div>
<h3 className="text-xl font-medium mb-2">Check your email</h3>
<p className="text-white/60 text-sm mb-6">
@@ -152,7 +146,7 @@ const ForgotPassword = () => {
to="/login"
className="text-white/40 hover:text-white transition-colors flex items-center justify-center gap-2"
>
<FontAwesomeIcon icon={faArrowLeft} /> Back to Log in
<ArrowLeftIcon /> Back to Log in
</Link>
</div>
</div>
@@ -1,11 +1,5 @@
import {
faArrowLeft,
faEye,
faEyeSlash,
faSpinnerThird,
faCheckCircle,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ArrowLeftIcon, CircleCheckIcon, EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { useState } from "react";
@@ -189,8 +183,8 @@ const VerifyReset = () => {
onClick={() => setShowNewPassword(!showNewPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
>
<FontAwesomeIcon
icon={showNewPassword ? faEyeSlash : faEye}
<DynamicIcon
icon={showNewPassword ? EyeOffIcon : EyeIcon}
/>
</button>
</div>
@@ -223,8 +217,8 @@ const VerifyReset = () => {
}
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
>
<FontAwesomeIcon
icon={showConfirmPassword ? faEyeSlash : faEye}
<DynamicIcon
icon={showConfirmPassword ? EyeOffIcon : EyeIcon}
/>
</button>
</div>
@@ -243,10 +237,9 @@ const VerifyReset = () => {
disabled={isLoading}
>
{isLoading ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin"
/>
<LoaderCircleIcon
className="animate-spin" />
) : (
"Change Password"
)}
@@ -259,7 +252,7 @@ const VerifyReset = () => {
to="/forgot-password"
className="text-white/40 hover:text-white transition-colors flex items-center justify-center gap-2"
>
<FontAwesomeIcon icon={faArrowLeft} /> Request a new code
<ArrowLeftIcon /> Request a new code
</Link>
</div>
</>
@@ -267,7 +260,7 @@ const VerifyReset = () => {
<>
<div className="text-center py-8">
<div className="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4 text-green-500">
<FontAwesomeIcon icon={faCheckCircle} className="text-2xl" />
<CircleCheckIcon className="text-2xl" />
</div>
<h3 className="text-xl font-medium mb-2">
Password Reset Successfully
@@ -1,12 +1,6 @@
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCamera,
faChevronDown,
faLayerGroup,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { CameraIcon, ChevronDownIcon, LayersIcon, XIcon } from "lucide-react";
import { Button } from "@storyteller/ui-button";
import { EASE_EMPHASIS } from "../../lib/motion";
import { formatTime, formatTimePrecise } from "./lib/frame-actions";
@@ -59,7 +53,7 @@ export const ExtractionPanel = ({
</div>
<Button
variant="primary"
icon={faCamera}
icon={CameraIcon}
onClick={onCaptureCurrent}
disabled={disabled || isExtracting}
className="w-full py-2 text-sm font-medium"
@@ -78,16 +72,14 @@ export const ExtractionPanel = ({
className="flex w-full items-center justify-between px-4 py-3 text-sm font-medium text-base-fg/75 transition-colors hover:text-base-fg disabled:cursor-default disabled:opacity-40"
>
<span className="flex items-center gap-2">
<FontAwesomeIcon
icon={faLayerGroup}
className="text-xs text-base-fg/40"
/>
<LayersIcon
className="text-xs text-base-fg/40" />
Burst capture
</span>
<FontAwesomeIcon
icon={faChevronDown}
className={`text-xs text-base-fg/40 transition-transform ${burstOpen ? "rotate-180" : ""}`}
/>
<ChevronDownIcon
className={`text-xs text-base-fg/40 transition-transform ${burstOpen ? "rotate-180" : ""}`} />
</button>
<AnimatePresence initial={false}>
{burstOpen && !disabled && burstAvailable && (
@@ -159,7 +151,7 @@ export const ExtractionPanel = ({
className="flex h-6 w-6 items-center justify-center rounded-md text-base-fg/50 transition-colors hover:bg-white/10 hover:text-base-fg"
aria-label="Cancel extraction"
>
<FontAwesomeIcon icon={faXmark} className="text-xs" />
<XIcon className="text-xs" />
</button>
</div>
) : (
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { faArrowRotateRight } from "@fortawesome/pro-solid-svg-icons";
import { RotateCwIcon } from "lucide-react";
import { Button } from "@storyteller/ui-button";
import { GalleryItem, GalleryModal } from "@storyteller/ui-gallery-modal";
import { MediaFilesApi } from "@storyteller/api";
@@ -450,7 +450,7 @@ export default function FrameExtractor() {
{source && (
<Button
variant="action"
icon={faArrowRotateRight}
icon={RotateCwIcon}
onClick={clearSource}
className="px-3 py-1.5 text-sm"
>
@@ -1,15 +1,7 @@
import { AnimatePresence, motion } from "framer-motion";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowDownToLine,
faCheck,
faImage,
faSave,
faSpinnerThird,
faVideo,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { ArrowDownToLineIcon, CheckIcon, ImageIcon, LoaderCircleIcon, SaveIcon, VideoIcon, XIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Tooltip } from "@storyteller/ui-tooltip";
import { EASE_EMPHASIS } from "../../lib/motion";
import type { ExtractedFrame } from "./lib/extract-frames";
@@ -66,8 +58,8 @@ export const FramesGrid = ({
disabled={isSavingAll}
className="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium text-base-fg/70 transition-colors hover:bg-white/5 hover:text-base-fg disabled:opacity-50"
>
<FontAwesomeIcon
icon={isSavingAll ? faSpinnerThird : faSave}
<DynamicIcon
icon={isSavingAll ? LoaderCircleIcon : SaveIcon}
className={isSavingAll ? "animate-spin" : ""}
/>
{isSavingAll ? "Saving…" : "Save all"}
@@ -107,7 +99,7 @@ export const FramesGrid = ({
className="absolute right-1.5 top-1.5 flex h-6 w-6 items-center justify-center rounded-md bg-black/60 text-white/60 opacity-0 backdrop-blur-sm transition-all hover:text-white group-hover/frame:opacity-100"
aria-label="Remove frame"
>
<FontAwesomeIcon icon={faXmark} className="text-xs" />
<XIcon className="text-xs" />
</button>
<span className="absolute bottom-1.5 left-1.5 rounded bg-black/60 px-1.5 py-0.5 font-mono text-[10px] text-white/85 backdrop-blur-sm">
{formatTimePrecise(frame.timestamp)}
@@ -115,19 +107,19 @@ export const FramesGrid = ({
</div>
<div className="flex items-center justify-around border-t border-ui-panel-border/60 p-1">
<FrameAction
icon={faImage}
icon={ImageIcon}
tooltip="Use as image reference"
onClick={() => onUseAsImageRef(frame)}
loading={state.sending}
/>
<FrameAction
icon={faVideo}
icon={VideoIcon}
tooltip="Animate into a video"
onClick={() => onUseForVideo(frame)}
loading={state.sending}
/>
<FrameAction
icon={state.saved ? faCheck : faSave}
icon={state.saved ? CheckIcon : SaveIcon}
tooltip={state.saved ? "Saved to library" : "Save to library"}
onClick={() => onSave(frame)}
loading={state.saving}
@@ -135,7 +127,7 @@ export const FramesGrid = ({
success={state.saved}
/>
<FrameAction
icon={faArrowDownToLine}
icon={ArrowDownToLineIcon}
tooltip="Download PNG"
onClick={() => onDownload(frame)}
/>
@@ -150,7 +142,7 @@ export const FramesGrid = ({
};
interface FrameActionProps {
icon: IconDefinition;
icon: LucideIcon;
tooltip: string;
onClick: () => void;
loading?: boolean;
@@ -179,8 +171,8 @@ const FrameAction = ({
: "text-base-fg/60 hover:bg-white/5 hover:text-base-fg"
}`}
>
<FontAwesomeIcon
icon={loading ? faSpinnerThird : icon}
<DynamicIcon
icon={loading ? LoaderCircleIcon : icon}
className={loading ? "animate-spin" : ""}
/>
</button>
@@ -1,6 +1,5 @@
import { useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFilm, faImages, faUpload } from "@fortawesome/pro-solid-svg-icons";
import { FilmIcon, ImagesIcon, UploadIcon } from "lucide-react";
import { Button } from "@storyteller/ui-button";
import { twMerge } from "tailwind-merge";
@@ -57,14 +56,14 @@ export const VideoDropZone = ({
accept="video/*"
onChange={(e) => handleFiles(e.target.files)}
/>
<FontAwesomeIcon icon={faFilm} className="text-2xl text-base-fg/25" />
<FilmIcon className="text-2xl text-base-fg/25" />
<div className="text-sm text-base-fg/55">
Drag &amp; drop a video, or
</div>
<div className="flex flex-wrap justify-center gap-2">
<Button
variant="primary"
icon={faUpload}
icon={UploadIcon}
onClick={() => fileInputRef.current?.click()}
className="px-4 py-1.5 text-sm font-medium"
>
@@ -72,7 +71,7 @@ export const VideoDropZone = ({
</Button>
<Button
variant="action"
icon={faImages}
icon={ImagesIcon}
onClick={onPickFromLibrary}
className="px-4 py-1.5 text-sm font-medium"
>
@@ -1,16 +1,7 @@
import { useEffect, useRef, useState } from "react";
import type { RefObject } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faPlay,
faPause,
faVolumeHigh,
faVolumeMute,
faBackwardStep,
faForwardStep,
faChevronLeft,
faChevronRight,
} from "@fortawesome/pro-solid-svg-icons";
import { ChevronLeftIcon, ChevronRightIcon, PauseIcon, PlayIcon, SkipBackIcon, SkipForwardIcon, Volume2Icon, VolumeXIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Tooltip } from "@storyteller/ui-tooltip";
import { formatTime, formatTimePrecise } from "./lib/frame-actions";
@@ -227,13 +218,13 @@ export const VideoScrubber = ({
const stepButtons: {
label: string;
tooltip: string;
icon: typeof faChevronLeft;
icon: typeof ChevronLeftIcon;
delta: number;
}[] = [
{ label: "-1s", tooltip: "Back 1s (Shift+←)", icon: faBackwardStep, delta: -1 },
{ label: "-1f", tooltip: "Back 1 frame (←)", icon: faChevronLeft, delta: -FRAME_STEP_SEC },
{ label: "+1f", tooltip: "Forward 1 frame (→)", icon: faChevronRight, delta: FRAME_STEP_SEC },
{ label: "+1s", tooltip: "Forward 1s (Shift+→)", icon: faForwardStep, delta: 1 },
{ label: "-1s", tooltip: "Back 1s (Shift+←)", icon: SkipBackIcon, delta: -1 },
{ label: "-1f", tooltip: "Back 1 frame (←)", icon: ChevronLeftIcon, delta: -FRAME_STEP_SEC },
{ label: "+1f", tooltip: "Forward 1 frame (→)", icon: ChevronRightIcon, delta: FRAME_STEP_SEC },
{ label: "+1s", tooltip: "Forward 1s (Shift+→)", icon: SkipForwardIcon, delta: 1 },
];
return (
@@ -255,7 +246,7 @@ export const VideoScrubber = ({
className="absolute inset-0 m-auto flex h-12 w-12 items-center justify-center rounded-full bg-black/55 text-white opacity-0 backdrop-blur-sm transition-opacity group-hover/scrubber:opacity-100"
aria-label="Play"
>
<FontAwesomeIcon icon={faPlay} className="ml-0.5" />
<PlayIcon className="ml-0.5" />
</button>
)}
</div>
@@ -301,7 +292,7 @@ export const VideoScrubber = ({
className="flex h-8 w-8 items-center justify-center rounded-md text-white/70 transition-colors hover:bg-white/10 hover:text-white"
aria-label={step.tooltip}
>
<FontAwesomeIcon icon={step.icon} className="text-xs" />
<DynamicIcon icon={step.icon} className="text-xs" />
</button>
</Tooltip>
))}
@@ -311,7 +302,7 @@ export const VideoScrubber = ({
className="mx-0.5 flex h-9 w-9 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20"
aria-label="Play or pause"
>
<FontAwesomeIcon icon={isPlaying ? faPause : faPlay} />
<DynamicIcon icon={isPlaying ? PauseIcon : PlayIcon} />
</button>
</Tooltip>
{stepButtons.slice(2).map((step) => (
@@ -321,7 +312,7 @@ export const VideoScrubber = ({
className="flex h-8 w-8 items-center justify-center rounded-md text-white/70 transition-colors hover:bg-white/10 hover:text-white"
aria-label={step.tooltip}
>
<FontAwesomeIcon icon={step.icon} className="text-xs" />
<DynamicIcon icon={step.icon} className="text-xs" />
</button>
</Tooltip>
))}
@@ -334,8 +325,8 @@ export const VideoScrubber = ({
className="flex h-7 w-7 items-center justify-center rounded-md text-white/60 transition-colors hover:bg-white/10 hover:text-white"
aria-label={isMuted ? "Unmute" : "Mute"}
>
<FontAwesomeIcon
icon={isMuted ? faVolumeMute : faVolumeHigh}
<DynamicIcon
icon={isMuted ? VolumeXIcon : Volume2Icon}
className="text-xs"
/>
</button>
@@ -1,19 +1,8 @@
import { Link } from "react-router-dom";
import { motion, useReducedMotion } from "framer-motion";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faImage,
faVideo,
faMusic,
faWandMagicSparkles,
faCube,
faGlobe,
faFilm,
faObjectGroup,
faPhotoFilm,
faArrowRight,
} from "@fortawesome/pro-solid-svg-icons";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { ArrowRightIcon, BoxIcon, FilmIcon, GlobeIcon, GroupIcon, ImageIcon, ImagesIcon, MusicIcon, VideoIcon, WandSparklesIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import Seo from "../../components/seo";
import { Reveal, RevealGroup } from "../../components/motion/reveal";
import { TRANSITION_SPRING } from "../../lib/motion";
@@ -24,7 +13,7 @@ type AppCard = {
label: string;
description: string;
href: string;
icon: IconDefinition;
icon: LucideIcon;
accent: string;
iconBg: string;
iconColor: string;
@@ -36,7 +25,7 @@ const APPS: AppCard[] = [
label: "Image",
description: "Generate images from prompts and references.",
href: "/create-image",
icon: faImage,
icon: ImageIcon,
accent: "from-blue-500/20 to-blue-500/0",
iconBg: "bg-blue-500/25 border-blue-400/30",
iconColor: "text-blue-300",
@@ -45,7 +34,7 @@ const APPS: AppCard[] = [
label: "Video",
description: "Generate cinematic clips from text or images.",
href: "/create-video",
icon: faVideo,
icon: VideoIcon,
accent: "from-purple-500/20 to-purple-500/0",
iconBg: "bg-purple-500/25 border-purple-400/30",
iconColor: "text-purple-300",
@@ -54,7 +43,7 @@ const APPS: AppCard[] = [
label: "Audio",
description: "Generate songs and sound effects from prompts.",
href: "/create-audio",
icon: faMusic,
icon: MusicIcon,
accent: "from-pink-500/20 to-pink-500/0",
iconBg: "bg-pink-500/25 border-pink-400/30",
iconColor: "text-pink-300",
@@ -63,7 +52,7 @@ const APPS: AppCard[] = [
label: "3D Object",
description: "Turn a prompt or image into a 3D model.",
href: "/create-object",
icon: faCube,
icon: BoxIcon,
accent: "from-cyan-500/20 to-cyan-500/0",
iconBg: "bg-cyan-500/25 border-cyan-400/30",
iconColor: "text-cyan-300",
@@ -72,7 +61,7 @@ const APPS: AppCard[] = [
label: "3D World",
description: "Turn a prompt or image into an explorable world.",
href: "/create-world",
icon: faGlobe,
icon: GlobeIcon,
accent: "from-teal-500/20 to-teal-500/0",
iconBg: "bg-teal-500/25 border-teal-400/30",
iconColor: "text-teal-300",
@@ -81,7 +70,7 @@ const APPS: AppCard[] = [
label: "Edit 3D",
description: "Compose 3D scenes and render with AI cameras.",
href: "/edit-3d",
icon: faCube,
icon: BoxIcon,
accent: "from-amber-500/20 to-amber-500/0",
iconBg: "bg-amber-500/25 border-amber-400/30",
iconColor: "text-amber-300",
@@ -90,7 +79,7 @@ const APPS: AppCard[] = [
label: "Background Change",
description: "Swap or remove backgrounds with AI VFX.",
href: "/background-change",
icon: faWandMagicSparkles,
icon: WandSparklesIcon,
accent: "from-emerald-500/20 to-emerald-500/0",
iconBg: "bg-emerald-500/25 border-emerald-400/30",
iconColor: "text-emerald-300",
@@ -99,7 +88,7 @@ const APPS: AppCard[] = [
label: "Edit Video",
description: "Trim, arrange, and edit clips on a timeline.",
href: "/video-editor",
icon: faFilm,
icon: FilmIcon,
accent: "from-rose-500/20 to-rose-500/0",
iconBg: "bg-rose-500/25 border-rose-400/30",
iconColor: "text-rose-300",
@@ -109,7 +98,7 @@ const APPS: AppCard[] = [
label: "Moodboard",
description: "Collect references and ideas to steer a generation.",
href: "/moodboard",
icon: faObjectGroup,
icon: GroupIcon,
accent: "from-indigo-500/20 to-indigo-500/0",
iconBg: "bg-indigo-500/25 border-indigo-400/30",
iconColor: "text-indigo-300",
@@ -119,7 +108,7 @@ const APPS: AppCard[] = [
label: "Frame Extractor",
description: "Grab a still frame from any video.",
href: "/frame-extractor",
icon: faPhotoFilm,
icon: ImagesIcon,
accent: "from-orange-500/20 to-orange-500/0",
iconBg: "bg-orange-500/25 border-orange-400/30",
iconColor: "text-orange-300",
@@ -166,7 +155,7 @@ export function Home() {
<div
className={`flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border ${app.iconBg} ${app.iconColor} shadow-[inset_0_1px_1px_rgba(255,255,255,0.12)] transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-105`}
>
<FontAwesomeIcon icon={app.icon} className="text-base" />
<DynamicIcon icon={app.icon} className="text-base" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-2">
@@ -181,7 +170,7 @@ export function Home() {
)}
</div>
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-white/[0.04] text-white/40 transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:bg-white/10 group-hover:text-white/80 group-hover:translate-x-0.5">
<FontAwesomeIcon icon={faArrowRight} className="text-xs" />
<ArrowRightIcon className="text-xs" />
</span>
</div>
<p className="mt-1 text-sm text-white/55 leading-snug">
@@ -35,32 +35,8 @@ import {
showActionReminder,
isActionReminderOpen,
} from "@storyteller/ui-action-reminder-modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowDownToLine,
faArrowsRotate,
faBorderAll,
faCube,
faImage,
faMusic,
faVideo,
faPencil,
faTrashCan,
faFolderPlus,
faFolder,
faFolderOpen,
faGlobe,
faPlus,
faXmark,
faStar,
faTag,
faTags,
faEllipsis,
faCloud,
faList,
faSpinnerThird,
faPhotoFilm,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowDownToLineIcon, BoxIcon, CloudIcon, EllipsisIcon, FolderIcon, FolderOpenIcon, FolderPlusIcon, GlobeIcon, Grid3x3Icon, ImageIcon, ImagesIcon, ListIcon, LoaderCircleIcon, MusicIcon, PencilIcon, PlusIcon, RefreshCwIcon, StarIcon, TagIcon, TagsIcon, Trash2Icon, VideoIcon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Lightbox } from "../../components/lightbox/lightbox";
import { toast } from "../../components/toast/toast";
import { downloadItemsAsZip } from "../../lib/download-media-zip";
@@ -86,23 +62,23 @@ import {
const PAGE_SIZE = 60;
const FILTERS = [
{ id: "all", label: "All", icon: faBorderAll, route: "/library" },
{ id: "image", label: "Images", icon: faImage, route: "/library/images" },
{ id: "video", label: "Videos", icon: faVideo, route: "/library/videos" },
{ id: "audio", label: "Audio", icon: faMusic, route: "/library/audio" },
{ id: "meshes", label: "Meshes", icon: faCube, route: "/library/meshes" },
{ id: "splats", label: "Splats", icon: faGlobe, route: "/library/splats" },
{ id: "all", label: "All", icon: Grid3x3Icon, route: "/library" },
{ id: "image", label: "Images", icon: ImageIcon, route: "/library/images" },
{ id: "video", label: "Videos", icon: VideoIcon, route: "/library/videos" },
{ id: "audio", label: "Audio", icon: MusicIcon, route: "/library/audio" },
{ id: "meshes", label: "Meshes", icon: BoxIcon, route: "/library/meshes" },
{ id: "splats", label: "Splats", icon: GlobeIcon, route: "/library/splats" },
];
// Class scopes for the Unfoldered tab. Local state (not routes): the tab is
// one page and the filter maps to the endpoint's `filter_media_class` param.
const FOLDERLESS_CLASS_FILTERS = [
{ id: "all", label: "All", icon: faBorderAll },
{ id: "image", label: "Images", icon: faImage },
{ id: "video", label: "Videos", icon: faVideo },
{ id: "audio", label: "Audio", icon: faMusic },
{ id: "mesh", label: "Meshes", icon: faCube },
{ id: "splat", label: "Splats", icon: faGlobe },
{ id: "all", label: "All", icon: Grid3x3Icon },
{ id: "image", label: "Images", icon: ImageIcon },
{ id: "video", label: "Videos", icon: VideoIcon },
{ id: "audio", label: "Audio", icon: MusicIcon },
{ id: "mesh", label: "Meshes", icon: BoxIcon },
{ id: "splat", label: "Splats", icon: GlobeIcon },
];
const ROUTE_TO_FILTER: Record<string, string> = {
@@ -1140,10 +1116,9 @@ export default function Library() {
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
/>
)}
<FontAwesomeIcon
icon={faBorderAll}
className="relative z-10 text-xs"
/>
<Grid3x3Icon
className="relative z-10 text-xs" />
<span className="relative z-10">All Assets</span>
</Link>
<Link
@@ -1161,10 +1136,9 @@ export default function Library() {
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
/>
)}
<FontAwesomeIcon
icon={faFolder}
className="relative z-10 text-xs"
/>
<FolderIcon
className="relative z-10 text-xs" />
<span className="relative z-10">Folders</span>
</Link>
<Link
@@ -1182,10 +1156,9 @@ export default function Library() {
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
/>
)}
<FontAwesomeIcon
icon={faFolderOpen}
className="relative z-10 text-xs"
/>
<FolderOpenIcon
className="relative z-10 text-xs" />
<span className="relative z-10">Unfoldered</span>
</Link>
<Link
@@ -1203,10 +1176,9 @@ export default function Library() {
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
/>
)}
<FontAwesomeIcon
icon={faTag}
className="relative z-10 text-xs"
/>
<TagIcon
className="relative z-10 text-xs" />
<span className="relative z-10">Tags</span>
</Link>
</div>
@@ -1216,10 +1188,9 @@ export default function Library() {
className="h-8 w-8 flex items-center justify-center rounded-lg text-white/50 hover:text-white hover:bg-ui-controls/40 transition-colors"
title="Refresh library"
>
<FontAwesomeIcon
icon={faArrowsRotate}
className={`text-sm ${initialLoading ? "animate-spin" : ""}`}
/>
<RefreshCwIcon
className={`text-sm ${initialLoading ? "animate-spin" : ""}`} />
</button>
)}
</div>
@@ -1244,7 +1215,7 @@ export default function Library() {
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
/>
)}
<FontAwesomeIcon
<DynamicIcon
icon={filter.icon}
className="relative z-10 text-xs"
/>
@@ -1274,7 +1245,7 @@ export default function Library() {
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
/>
)}
<FontAwesomeIcon
<DynamicIcon
icon={filter.icon}
className="relative z-10 text-xs"
/>
@@ -1288,7 +1259,7 @@ export default function Library() {
{tab === "folders" && !inFolder && (
<Button
variant="primary"
icon={faFolderPlus}
icon={FolderPlusIcon}
onClick={() => openNewFolderModal(null)}
className="rounded-full text-xs sm:text-sm px-3 sm:px-4 py-2"
>
@@ -1332,8 +1303,8 @@ export default function Library() {
<div className="flex items-center gap-1 bg-ui-controls/40 rounded-xl p-1">
{(
[
["cloud", faCloud, "Cloud"],
["list", faList, "List"],
["cloud", CloudIcon, "Cloud"],
["list", ListIcon, "List"],
] as const
).map(([id, icon, label]) => (
<button
@@ -1356,7 +1327,7 @@ export default function Library() {
}}
/>
)}
<FontAwesomeIcon
<DynamicIcon
icon={icon}
className="relative z-10 text-xs"
/>
@@ -1400,14 +1371,14 @@ export default function Library() {
className="h-7 w-7 flex items-center justify-center rounded-lg text-white/50 hover:text-white hover:bg-ui-controls/40 transition-colors"
title="Rename folder"
>
<FontAwesomeIcon icon={faPencil} className="text-xs" />
<PencilIcon className="text-xs" />
</button>
<button
onClick={() => openNewFolderModal(activeFolderId)}
className="h-7 w-7 flex items-center justify-center rounded-lg text-white/50 hover:text-white hover:bg-ui-controls/40 transition-colors"
title="New subfolder"
>
<FontAwesomeIcon icon={faFolderPlus} className="text-xs" />
<FolderPlusIcon className="text-xs" />
</button>
</div>
)}
@@ -1436,14 +1407,14 @@ export default function Library() {
className="h-7 w-7 flex items-center justify-center rounded-lg text-white/50 hover:text-white hover:bg-ui-controls/40 transition-colors"
title="Rename tag"
>
<FontAwesomeIcon icon={faPencil} className="text-xs" />
<PencilIcon className="text-xs" />
</button>
<button
onClick={() => confirmDeleteTag(activeTagToken)}
className="h-7 w-7 flex items-center justify-center rounded-lg text-white/50 hover:text-red hover:bg-ui-controls/40 transition-colors"
title="Delete tag"
>
<FontAwesomeIcon icon={faTrashCan} className="text-xs" />
<Trash2Icon className="text-xs" />
</button>
</div>
)}
@@ -1481,15 +1452,14 @@ export default function Library() {
currentSubfolders.length === 0 && (
<div className="flex flex-col items-center justify-center py-20 gap-3">
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-ui-controls/30">
<FontAwesomeIcon
icon={faFolderOpen}
className="text-2xl text-white/40"
/>
<FolderOpenIcon
className="text-2xl text-white/40" />
</div>
<p className="text-white/40 text-sm">No folders yet.</p>
<Button
variant="primary"
icon={faFolderPlus}
icon={FolderPlusIcon}
onClick={() => openNewFolderModal(null)}
className="rounded-full text-sm px-4 py-2"
>
@@ -1548,10 +1518,9 @@ export default function Library() {
) : tags.length === 0 ? (
<div className="flex flex-col items-center justify-center py-20 gap-3">
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-ui-controls/30">
<FontAwesomeIcon
icon={faTags}
className="text-2xl text-white/40"
/>
<TagsIcon
className="text-2xl text-white/40" />
</div>
<p className="text-white/40 text-sm">No tags yet.</p>
<p className="text-white/30 text-xs max-w-xs text-center">
@@ -1587,10 +1556,9 @@ export default function Library() {
}}
className="flex items-center gap-2 pl-4 pr-1 py-2 text-sm font-medium text-white"
>
<FontAwesomeIcon
icon={faTag}
className="text-xs text-violet-400"
/>
<TagIcon
className="text-xs text-violet-400" />
<span className="max-w-[14rem] truncate">{t.value}</span>
<span className="text-xs text-white/40">
{t.useCount}
@@ -1610,7 +1578,7 @@ export default function Library() {
title="Rename or delete"
className="mr-1.5 flex h-6 w-6 items-center justify-center rounded-full text-white/40 hover:bg-white/10 hover:text-white transition-colors"
>
<FontAwesomeIcon icon={faEllipsis} className="text-xs" />
<EllipsisIcon className="text-xs" />
</button>
</div>
))}
@@ -1824,10 +1792,9 @@ export default function Library() {
setContextMenu(null);
}}
>
<FontAwesomeIcon
icon={faStar}
className={`w-4 ${menuFolder?.hasStar ? "text-amber-400" : "text-base-fg/40"}`}
/>
<StarIcon
className={`w-4 ${menuFolder?.hasStar ? "text-amber-400" : "text-base-fg/40"}`} />
<span>{menuFolder?.hasStar ? "Unstar" : "Star"}</span>
</button>
<FolderColorRow
@@ -1847,7 +1814,7 @@ export default function Library() {
openNewFolderModal(contextMenu.folderId);
}}
>
<FontAwesomeIcon icon={faFolderPlus} className="w-4" />
<FolderPlusIcon className="w-4" />
<span>New subfolder</span>
</button>
<button
@@ -1855,7 +1822,7 @@ export default function Library() {
className="flex w-full items-center gap-2 px-2 py-2 rounded-md hover:bg-ui-controls/60 text-sm text-base-fg"
onClick={() => startRename(contextMenu.folderId)}
>
<FontAwesomeIcon icon={faPencil} className="w-4" />
<PencilIcon className="w-4" />
<span>Rename</span>
</button>
<button
@@ -1867,7 +1834,7 @@ export default function Library() {
confirmDeleteFolder(folderId);
}}
>
<FontAwesomeIcon icon={faTrashCan} className="w-4" />
<Trash2Icon className="w-4" />
<span>Delete folder</span>
</button>
</div>
@@ -1896,7 +1863,7 @@ export default function Library() {
className="flex w-full items-center gap-2 px-2 py-2 rounded-md hover:bg-ui-controls/60 text-sm text-base-fg"
onClick={() => setTagRenameTarget(tagContextMenu.tagToken)}
>
<FontAwesomeIcon icon={faPencil} className="w-4" />
<PencilIcon className="w-4" />
<span>Rename</span>
</button>
<button
@@ -1908,7 +1875,7 @@ export default function Library() {
confirmDeleteTag(token);
}}
>
<FontAwesomeIcon icon={faTrashCan} className="w-4" />
<Trash2Icon className="w-4" />
<span>Delete tag</span>
</button>
</div>
@@ -2075,10 +2042,10 @@ const LibraryTile = memo(function LibraryTile({
const SEND_TO_TARGETS: {
destination: PromptDestination;
label: string;
icon: typeof faImage;
icon: typeof ImageIcon;
}[] = [
{ destination: "image", label: "Create image", icon: faImage },
{ destination: "video", label: "Create video", icon: faVideo },
{ destination: "image", label: "Create image", icon: ImageIcon },
{ destination: "video", label: "Create video", icon: VideoIcon },
];
// Shared styling for the bar's action pills. `whitespace-nowrap` keeps labels
@@ -2245,8 +2212,8 @@ function BulkSelectionBar({
onClick={() => setSendToOpen((v) => !v)}
className={BAR_BUTTON_CLASS}
>
<FontAwesomeIcon
icon={hasPromptImages ? faImage : faVideo}
<DynamicIcon
icon={hasPromptImages ? ImageIcon : VideoIcon}
className="text-xs"
/>
{/* Icon-only on phones — a fourth labeled button overflows the bar. */}
@@ -2275,7 +2242,7 @@ function BulkSelectionBar({
}}
className="flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm text-white hover:bg-ui-controls/50 transition-colors"
>
<FontAwesomeIcon icon={target.icon} className="w-4 text-xs" />
<DynamicIcon icon={target.icon} className="w-4 text-xs" />
<span>{target.label}</span>
</button>
))}
@@ -2298,7 +2265,7 @@ function BulkSelectionBar({
}}
className={BAR_BUTTON_CLASS}
>
<FontAwesomeIcon icon={faPhotoFilm} className="text-xs" />
<ImagesIcon className="text-xs" />
<span className="hidden sm:inline">Extract frames</span>
</button>
)}
@@ -2311,7 +2278,7 @@ function BulkSelectionBar({
onClick={() => setPopoverOpen((v) => !v)}
className={BAR_BUTTON_CLASS}
>
<FontAwesomeIcon icon={faFolderPlus} className="text-xs" />
<FolderPlusIcon className="text-xs" />
{/* Icon-only on phones — five labeled buttons overflow the bar. */}
<span className="hidden sm:inline">Add to folder</span>
</button>
@@ -2341,8 +2308,8 @@ function BulkSelectionBar({
}}
className="flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm text-white hover:bg-ui-controls/50 transition-colors"
>
<FontAwesomeIcon
icon={faFolder}
<FolderIcon
className={
folder.colorCode ? "text-xs" : "text-xs text-primary"
}
@@ -2350,8 +2317,7 @@ function BulkSelectionBar({
folder.colorCode
? { color: folder.colorCode }
: undefined
}
/>
} />
<span className="truncate">{folder.name}</span>
</button>
))
@@ -2365,7 +2331,7 @@ function BulkSelectionBar({
}}
className="flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm text-white/70 hover:bg-ui-controls/50 transition-colors"
>
<FontAwesomeIcon icon={faPlus} className="w-4 text-xs" />
<PlusIcon className="w-4 text-xs" />
<span>Create new folder</span>
</button>
</div>
@@ -2387,7 +2353,7 @@ function BulkSelectionBar({
}}
className={BAR_BUTTON_CLASS}
>
<FontAwesomeIcon icon={faTags} className="text-xs" />
<TagsIcon className="text-xs" />
<span className="hidden sm:inline">Add tags</span>
</button>
{tagsOpen && (
@@ -2429,8 +2395,8 @@ function BulkSelectionBar({
disabled={isDownloading}
className={`${BAR_BUTTON_CLASS} disabled:opacity-60`}
>
<FontAwesomeIcon
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
<DynamicIcon
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
className={`text-xs ${isDownloading ? "animate-spin" : ""}`}
/>
{isDownloading && downloadProgress
@@ -2443,7 +2409,7 @@ function BulkSelectionBar({
onClick={onDeleteSelected}
className="flex items-center gap-2 whitespace-nowrap rounded-full bg-red/90 px-3 py-1.5 text-sm font-medium text-white hover:bg-red transition-colors"
>
<FontAwesomeIcon icon={faTrashCan} className="text-xs" />
<Trash2Icon className="text-xs" />
Delete
</button>
<button
@@ -2452,7 +2418,7 @@ function BulkSelectionBar({
aria-label="Clear selection"
className="flex h-8 w-8 items-center justify-center rounded-full bg-ui-controls/60 text-white hover:bg-ui-controls/90 transition-colors"
>
<FontAwesomeIcon icon={faXmark} />
<XIcon />
</button>
</div>
);
@@ -2464,12 +2430,12 @@ function BulkThumb({ item }: { item: GalleryItem }) {
const [failed, setFailed] = useState(false);
const placeholderIcon =
item.mediaClass === "video"
? faVideo
? VideoIcon
: is3DMediaClass(item.mediaClass)
? faCube
? BoxIcon
: item.mediaClass === "audio"
? faMusic
: faImage;
? MusicIcon
: ImageIcon;
const showImage = !!item.thumbnail && !failed;
return (
<div className="-ml-2 h-8 w-8 flex-shrink-0 overflow-hidden rounded border-2 border-ui-panel bg-black/30 first:ml-0">
@@ -2482,7 +2448,7 @@ function BulkThumb({ item }: { item: GalleryItem }) {
/>
) : (
<div className="flex h-full w-full items-center justify-center bg-black/50">
<FontAwesomeIcon
<DynamicIcon
icon={placeholderIcon}
className="text-xs text-white/50"
/>
@@ -1,9 +1,5 @@
import {
faEye,
faEyeSlash,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { useState } from "react";
@@ -134,7 +130,7 @@ const Login = () => {
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
tabIndex={-1}
>
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
</button>
</div>
</Reveal>
@@ -146,7 +142,7 @@ const Login = () => {
disabled={isLoading}
>
{isLoading ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
"Log in"
)}
@@ -2,12 +2,7 @@ import { useState, useEffect } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheckCircle,
faInfoCircle,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { CircleCheckIcon, InfoIcon, LoaderCircleIcon } from "lucide-react";
import Seo from "../../components/seo";
import { UsersApi, BillingApi } from "@storyteller/api";
@@ -311,10 +306,9 @@ const Onboarding = () => {
<div className="max-w-md w-full text-center">
<div className="mb-6">
<div className="w-20 h-20 mx-auto rounded-full bg-green-500/20 flex items-center justify-center">
<FontAwesomeIcon
icon={faCheckCircle}
className="text-5xl text-green-400"
/>
<CircleCheckIcon
className="text-5xl text-green-400" />
</div>
</div>
<h1 className="text-3xl md:text-4xl font-medium mb-4 text-white">
@@ -342,10 +336,9 @@ const Onboarding = () => {
<div className="max-w-md w-full">
{isNewAccount && (
<div className="bg-blue-500/10 border border-blue-500/30 rounded-xl p-4 mb-6 flex items-start gap-3 animate-fade-in shadow-lg shadow-blue-900/10">
<FontAwesomeIcon
icon={faInfoCircle}
className="text-blue-400 mt-1 flex-shrink-0 text-lg"
/>
<InfoIcon
className="text-blue-400 mt-1 flex-shrink-0 text-lg" />
<div>
<h3 className="text-blue-200 font-medium mb-1">
Account Created
@@ -463,10 +456,9 @@ const Onboarding = () => {
disabled={isSubmitting}
>
{isSubmitting ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin"
/>
<LoaderCircleIcon
className="animate-spin" />
) : (
"Continue"
)}
@@ -8,13 +8,8 @@
// - start from a blank canvas → BlankCanvasModal (from the lib)
import { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faExpand,
faImages,
faPencil,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { ImagesIcon, LoaderCircleIcon, MaximizeIcon, PencilIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { UploaderStates } from "@storyteller/common";
import { MediaFilesApi } from "@storyteller/api";
import {
@@ -160,8 +155,8 @@ export function BaseImageSelector({
disabled={busy}
/>
<div className="flex h-14 w-14 items-center justify-center rounded-2xl border border-primary-400/30 bg-primary/30 text-primary-300">
<FontAwesomeIcon
icon={busy ? faSpinnerThird : faPencil}
<DynamicIcon
icon={busy ? LoaderCircleIcon : PencilIcon}
className={`text-xl ${busy ? "animate-spin" : ""}`}
/>
</div>
@@ -181,7 +176,7 @@ export function BaseImageSelector({
disabled={busy}
className="flex items-center gap-2 rounded-lg border border-ui-controls-border bg-ui-controls px-3 py-2 text-sm font-medium text-base-fg transition-colors hover:bg-ui-controls/80"
>
<FontAwesomeIcon icon={faImages} />
<ImagesIcon />
Pick from Library
</button>
<button
@@ -193,7 +188,7 @@ export function BaseImageSelector({
disabled={busy}
className="flex items-center gap-2 rounded-lg border border-ui-controls-border bg-ui-controls px-3 py-2 text-sm font-medium text-base-fg transition-colors hover:bg-ui-controls/80"
>
<FontAwesomeIcon icon={faExpand} />
<MaximizeIcon />
Blank Canvas
</button>
</div>
@@ -1,13 +1,6 @@
import { useEffect, useMemo, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCompress,
faExpand,
faImage,
faVideo,
faWandMagicSparkles,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { ImageIcon, MaximizeIcon, MinimizeIcon, VideoIcon, WandSparklesIcon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { MediaFilesApi, PromptsApi } from "@storyteller/api";
import { addCorsParam, PLACEHOLDER_IMAGES } from "@storyteller/common";
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
@@ -218,7 +211,7 @@ function ShowOutputPill({ onClick }: { onClick: () => void }) {
aria-label="Show rendered output"
className="pointer-events-auto absolute right-2 top-16 z-30 flex items-center gap-2 rounded-xl border border-ui-controls-border bg-ui-controls px-3 py-1.5 text-sm font-medium text-base-fg shadow-xl transition-colors duration-150 hover:bg-ui-controls/80 animate-in fade-in slide-in-from-right-4"
>
<FontAwesomeIcon icon={faImage} className="h-3 w-3 text-primary" />
<ImageIcon className="h-3 w-3 text-primary" />
Show output
</button>
);
@@ -249,14 +242,14 @@ function ViewToggle({ view, onChange }: ViewToggleProps) {
transform: view === "image" ? "translateX(100%)" : "translateX(0%)",
}}
/>
<ToggleIcon icon={faVideo} active={view === "video"} />
<ToggleIcon icon={faImage} active={view === "image"} />
<ToggleIcon icon={VideoIcon} active={view === "video"} />
<ToggleIcon icon={ImageIcon} active={view === "image"} />
</button>
);
}
interface ToggleIconProps {
icon: typeof faVideo;
icon: typeof VideoIcon;
active: boolean;
}
@@ -268,7 +261,7 @@ function ToggleIcon({ icon, active }: ToggleIconProps) {
(active ? "text-white" : "text-base-fg/60")
}
>
<FontAwesomeIcon icon={icon} className="h-3 w-3" />
<DynamicIcon icon={icon} className="h-3 w-3" />
</span>
);
}
@@ -307,10 +300,9 @@ function Card({
<div className="glass pointer-events-auto overflow-hidden rounded-xl shadow-xl border-2 border-primary">
<div className="flex items-center justify-between gap-3 border-b border-ui-controls-border/60 px-3 py-2">
<div className="flex min-w-0 items-center gap-2">
<FontAwesomeIcon
icon={faWandMagicSparkles}
className="h-3 w-3 shrink-0 text-primary"
/>
<WandSparklesIcon
className="h-3 w-3 shrink-0 text-primary" />
<div className="min-w-0 leading-tight">
<div className="text-xs font-semibold uppercase tracking-wider text-base-fg">
Rendered Output
@@ -332,7 +324,7 @@ function Card({
}
className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-base-fg/60 transition-colors hover:bg-ui-controls hover:text-base-fg"
>
<FontAwesomeIcon icon={faXmark} className="h-4 w-4" />
<XIcon className="h-4 w-4" />
</button>
</div>
</div>
@@ -360,8 +352,8 @@ function Card({
aria-label={isExpanded ? "Collapse output view" : "Expand output view"}
className="absolute bottom-2 right-2 flex h-7 w-7 items-center justify-center rounded-md border border-white/10 bg-black/60 text-white/80 shadow-lg backdrop-blur-md transition-colors duration-150 hover:bg-black/80 hover:text-white"
>
<FontAwesomeIcon
icon={isExpanded ? faCompress : faExpand}
<DynamicIcon
icon={isExpanded ? MinimizeIcon : MaximizeIcon}
className="h-3 w-3"
/>
</button>
@@ -1,11 +1,6 @@
import { useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCubes,
faLayerGroup,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { BoxesIcon, LayersIcon, XIcon } from "lucide-react";
import { SceneSplashCard } from "./splash/SceneSplashCard";
import { EXAMPLE_SCENES } from "./splash/example-scenes";
@@ -70,7 +65,7 @@ function CollapsedPill({ onClick }: { onClick: () => void }) {
onClick={onClick}
className="flex items-center gap-2.5 rounded-full border border-primary/40 bg-gradient-to-r from-primary/90 to-primary px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:from-primary hover:to-primary animate-in fade-in slide-in-from-bottom-2 duration-300"
>
<FontAwesomeIcon icon={faCubes} className="h-3.5 w-3.5" />
<BoxesIcon className="h-3.5 w-3.5" />
<span>See other demo scenes</span>
</button>
);
@@ -87,10 +82,9 @@ function ExpandedPanel({ scenes, onClose, onPick }: ExpandedPanelProps) {
<div className="glass w-[860px] max-w-[90vw] overflow-hidden rounded-xl border border-ui-controls-border shadow-2xl animate-in fade-in slide-in-from-bottom-2 duration-200">
<div className="flex items-center justify-between border-b border-ui-controls-border/60 px-4 py-2.5">
<div className="flex items-center gap-2">
<FontAwesomeIcon
icon={faLayerGroup}
className="h-3 w-3 text-primary"
/>
<LayersIcon
className="h-3 w-3 text-primary" />
<div className="text-xs font-semibold uppercase tracking-wider text-base-fg">
Other demo scenes
</div>
@@ -101,7 +95,7 @@ function ExpandedPanel({ scenes, onClose, onPick }: ExpandedPanelProps) {
aria-label="Close other scenes"
className="flex h-6 w-6 items-center justify-center rounded-full text-base-fg/60 transition-colors hover:bg-ui-controls hover:text-base-fg"
>
<FontAwesomeIcon icon={faXmark} className="h-4 w-4" />
<XIcon className="h-4 w-4" />
</button>
</div>
<div className="grid grid-cols-2 gap-3 p-3 sm:grid-cols-3">
@@ -5,8 +5,7 @@ import {
useParams,
useSearchParams,
} from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faDesktop, faHouse } from "@fortawesome/pro-solid-svg-icons";
import { HouseIcon, MonitorIcon } from "lucide-react";
import { Button } from "@storyteller/ui-button";
import { MediaFilesApi } from "@storyteller/api";
import { Stage3D, usePageSceneStore } from "@storyteller/ui-pagescene";
@@ -388,7 +387,7 @@ function MobileGate() {
<div className="flex h-full w-full items-center justify-center p-6">
<div className="flex max-w-sm flex-col items-center gap-4 text-center">
<div className="flex h-14 w-14 items-center justify-center rounded-2xl border border-amber-400/30 bg-amber-500/25 text-amber-300">
<FontAwesomeIcon icon={faDesktop} className="text-xl" />
<MonitorIcon className="text-xl" />
</div>
<h1 className="text-xl font-semibold text-white">
Edit 3D is desktop-only
@@ -399,7 +398,7 @@ function MobileGate() {
laptop to start editing.
</p>
<Link to="/" className="mt-2">
<Button variant="primary" icon={faHouse}>
<Button variant="primary" icon={HouseIcon}>
Back to home
</Button>
</Link>
@@ -7,8 +7,7 @@
import { useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/pro-solid-svg-icons";
import { PlusIcon } from "lucide-react";
import { MediaFilesApi, PromptsApi } from "@storyteller/api";
interface BlankCardProps {
@@ -55,7 +54,7 @@ function CardThumbnail(props: SceneSplashCardProps) {
return (
<div className="flex aspect-video items-center justify-center bg-white/[0.02]">
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-white/5 text-white/55 transition-colors group-hover:bg-primary/15 group-hover:text-primary">
<FontAwesomeIcon icon={faPlus} className="text-sm" />
<PlusIcon className="text-sm" />
</div>
</div>
);
@@ -1,7 +1,6 @@
import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";
import { faCoins } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CoinsIcon } from "lucide-react";
import { Button } from "@storyteller/ui-button";
import { BillingApi, UsersApi } from "@storyteller/api";
import Seo from "../../components/seo";
@@ -77,7 +76,7 @@ const BuyMoreCreditsCta = ({ onClick }: { onClick: () => void }) => (
className="mt-3 gap-2 rounded-full border border-white/[0.1] bg-white/[0.06] hover:bg-white/[0.1] px-5 py-2 h-11 text-[14px] font-semibold text-white"
onClick={onClick}
>
<FontAwesomeIcon icon={faCoins} className="text-primary text-[13px]" />
<CoinsIcon className="text-primary text-[13px]" />
Buy more credits
</Button>
</div>
@@ -191,10 +190,9 @@ const Pricing = () => {
className="mt-4 gap-2 rounded-full border border-white/[0.1] bg-white/[0.06] hover:bg-white/[0.1] px-5 py-2 h-11 text-[14px] font-semibold text-white"
onClick={() => setCreditsModalOpen(true)}
>
<FontAwesomeIcon
icon={faCoins}
className="text-primary text-[13px]"
/>
<CoinsIcon
className="text-primary text-[13px]" />
Buy Credits
</Button>
</div>
@@ -6,13 +6,8 @@ import {
type ReferralCodeEntry,
} from "@storyteller/api";
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheck,
faCopy,
faPlus,
faTrashAlt,
} from "@fortawesome/pro-solid-svg-icons";
import { CheckIcon, CopyIcon, PlusIcon, Trash2Icon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { useSession } from "../../lib/session";
import { toast } from "../../components/toast/toast";
import Seo from "../../components/seo";
@@ -252,7 +247,7 @@ export default function Referrals() {
disabled={submitDisabled}
className="inline-flex h-11 items-center justify-center gap-2 rounded-full bg-primary hover:bg-primary-600 px-6 text-[14px] font-semibold text-white transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
>
<FontAwesomeIcon icon={faPlus} className="text-[12px]" />
<PlusIcon className="text-[12px]" />
{creating ? "Creating…" : "Create"}
</button>
</div>
@@ -357,7 +352,7 @@ function ReferralCodeRow({
className="h-9 w-9 inline-flex items-center justify-center rounded-full text-white/45 hover:text-white hover:bg-white/[0.06] transition-colors"
title="Delete referral code"
>
<FontAwesomeIcon icon={faTrashAlt} className="text-[13px]" />
<Trash2Icon className="text-[13px]" />
</button>
)}
</div>
@@ -393,8 +388,8 @@ function CopyableLink({ value }: { value: string }) {
onClick={handleCopy}
className="inline-flex h-11 items-center gap-2 rounded-full bg-white/[0.06] hover:bg-white/[0.1] border border-white/[0.1] px-4 text-xs font-semibold text-white/85 transition-all"
>
<FontAwesomeIcon
icon={copied ? faCheck : faCopy}
<DynamicIcon
icon={copied ? CheckIcon : CopyIcon}
className="text-[12px]"
/>
{copied ? "Copied" : "Copy"}
@@ -1,9 +1,5 @@
import {
faEye,
faEyeSlash,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { useState } from "react";
@@ -30,10 +26,9 @@ const SetPassword = () => {
if (!authChecked) {
return (
<div className="flex min-h-screen items-center justify-center bg-[#101014]">
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-4xl text-primary/80"
/>
<LoaderCircleIcon
className="animate-spin text-4xl text-primary/80" />
</div>
);
}
@@ -134,7 +129,7 @@ const SetPassword = () => {
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
tabIndex={-1}
>
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
</button>
</div>
</div>
@@ -157,8 +152,8 @@ const SetPassword = () => {
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
tabIndex={-1}
>
<FontAwesomeIcon
icon={showConfirmPassword ? faEyeSlash : faEye}
<DynamicIcon
icon={showConfirmPassword ? EyeOffIcon : EyeIcon}
/>
</button>
</div>
@@ -171,10 +166,9 @@ const SetPassword = () => {
disabled={isLoading}
>
{isLoading ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin"
/>
<LoaderCircleIcon
className="animate-spin" />
) : (
"Set password"
)}
@@ -1,17 +1,12 @@
import { Link } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCircleQuestion,
faEnvelope,
faChevronRight,
} from "@fortawesome/pro-solid-svg-icons";
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { ChevronRightIcon, CircleHelpIcon, MailIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { DynamicIcon, DiscordIcon } from "@storyteller/icons";
import Seo from "../../components/seo";
import { SOCIAL_LINKS, SUPPORT_EMAIL } from "../../config/links";
type SupportRow = {
icon: IconDefinition;
icon: LucideIcon;
title: string;
desc: string;
to?: string;
@@ -22,7 +17,7 @@ type SupportRow = {
const ROWS: SupportRow[] = [
{
icon: faDiscord,
icon: DiscordIcon,
title: "Join our Discord",
desc: "The fastest way to reach us. Quick replies from the team and credit refunds if something goes wrong.",
href: SOCIAL_LINKS.DISCORD,
@@ -30,13 +25,13 @@ const ROWS: SupportRow[] = [
iconColorClass: "text-white",
},
{
icon: faEnvelope,
icon: MailIcon,
title: "Email us",
desc: SUPPORT_EMAIL,
href: `mailto:${SUPPORT_EMAIL}`,
},
{
icon: faCircleQuestion,
icon: CircleHelpIcon,
title: "Browse the FAQ",
desc: "Answers to common questions about ArtCraft.",
to: "/faq",
@@ -86,16 +81,15 @@ const SupportLink = ({ row }: { row: SupportRow }) => {
row.iconBgClass ?? "bg-white/[0.04] border-white/[0.06]"
} ${row.iconColorClass ?? "text-white/70 group-hover:text-white"}`}
>
<FontAwesomeIcon icon={row.icon} />
<DynamicIcon icon={row.icon} />
</div>
<div className="min-w-0 flex-1">
<div className="text-[15px] font-medium text-white">{row.title}</div>
<div className="text-sm text-white/60">{row.desc}</div>
</div>
<FontAwesomeIcon
icon={faChevronRight}
className="text-xs text-white/30 group-hover:text-white/60 transition-colors"
/>
<ChevronRightIcon
className="text-xs text-white/30 group-hover:text-white/60 transition-colors" />
</>
);
@@ -1,9 +1,5 @@
import { useCallback, useEffect, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faSparkles,
faTriangleExclamation,
} from "@fortawesome/pro-solid-svg-icons";
import { SparklesIcon, TriangleAlertIcon } from "lucide-react";
import { Button } from "@storyteller/ui-button";
import { FolderNameDialog } from "@storyteller/ui-gallery-modal";
import {
@@ -195,7 +191,7 @@ export function VideoEditorProjectsLanding({
</p>
<Button
variant="primary"
icon={faSparkles}
icon={SparklesIcon}
onClick={onNewProject}
className="mt-8 h-12 rounded-full px-6 text-base font-semibold"
>
@@ -206,10 +202,9 @@ export function VideoEditorProjectsLanding({
{status === "error" && (
<div className="flex flex-col items-center justify-center gap-4 py-28 text-center">
<FontAwesomeIcon
icon={faTriangleExclamation}
className="text-3xl text-white/30"
/>
<TriangleAlertIcon
className="text-3xl text-white/30" />
<div>
<div className="text-lg font-medium">
Couldn't load your projects
@@ -1,12 +1,5 @@
import { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faClapperboardPlay,
faEllipsis,
faPen,
faPlus,
faTrashCan,
} from "@fortawesome/pro-solid-svg-icons";
import { ClapperboardIcon, EllipsisIcon, PenIcon, PlusIcon, Trash2Icon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import { PopoverMenu } from "@storyteller/ui-popover";
import type { ProjectMeta } from "@storyteller/ui-video-editor";
@@ -84,10 +77,9 @@ export function VideoProjectCard({
gradientForProject(project.id),
)}
>
<FontAwesomeIcon
icon={faClapperboardPlay}
className="text-3xl text-white/25 transition-colors group-hover:text-white/40"
/>
<ClapperboardIcon
className="text-3xl text-white/25 transition-colors group-hover:text-white/40" />
</div>
)}
<div className="absolute inset-0 flex items-end bg-gradient-to-t from-black/70 to-transparent p-3 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
@@ -115,7 +107,7 @@ export function VideoProjectCard({
align="end"
mode="default"
triggerIcon={
<FontAwesomeIcon icon={faEllipsis} className="text-base-fg" />
<EllipsisIcon className="text-base-fg" />
}
buttonClassName="h-7 w-7 p-0 rounded-full bg-ui-controls/60 hover:bg-ui-controls/90 text-base-fg border border-ui-controls-border"
panelClassName="w-max min-w-44 p-1"
@@ -132,7 +124,7 @@ export function VideoProjectCard({
onRename();
}}
>
<FontAwesomeIcon icon={faPen} className="w-3.5" />
<PenIcon className="w-3.5" />
Rename
</button>
<button
@@ -144,7 +136,7 @@ export function VideoProjectCard({
onDelete();
}}
>
<FontAwesomeIcon icon={faTrashCan} className="w-3.5" />
<Trash2Icon className="w-3.5" />
Delete
</button>
</div>
@@ -175,7 +167,7 @@ export function NewProjectTile({ onClick }: { onClick: () => void }) {
className="group flex h-full w-full cursor-pointer flex-col items-center justify-center gap-2.5 rounded-xl border border-dashed border-white/20 bg-white/[0.02] py-10 transition-colors hover:border-white/40 hover:bg-white/[0.05] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
>
<span className="flex h-9 w-9 items-center justify-center rounded-full bg-white/5 text-white/55 transition-colors group-hover:bg-primary/15 group-hover:text-primary">
<FontAwesomeIcon icon={faPlus} />
<PlusIcon />
</span>
<span className="text-sm font-medium text-white/70">New project</span>
</button>
@@ -1,14 +1,5 @@
import {
faCheckCircle,
faDownload,
faDesktop,
faImage,
faVideo,
faWandMagicSparkles,
faCube,
faArrowRight,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ArrowRightIcon, BoxIcon, CircleCheckIcon, DownloadIcon, ImageIcon, MonitorIcon, VideoIcon, WandSparklesIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { Link } from "react-router-dom";
import { isMobile, isMacOs } from "react-device-detect";
@@ -36,10 +27,9 @@ const Welcome = () => {
{/* Success Header */}
<div className="text-center mb-12">
<div className="inline-flex items-center gap-3 bg-primary/10 border border-primary/30 rounded-full px-6 py-3 mb-6">
<FontAwesomeIcon
icon={faCheckCircle}
className="text-xl text-primary"
/>
<CircleCheckIcon
className="text-xl text-primary" />
<span className="text-white font-medium">
Account created successfully!
</span>
@@ -69,10 +59,9 @@ const Welcome = () => {
<div className="bg-[#1A1A1E] border border-white/10 rounded-3xl p-8 md:p-10">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center">
<FontAwesomeIcon
icon={faWandMagicSparkles}
className="text-primary text-lg"
/>
<WandSparklesIcon
className="text-primary text-lg" />
</div>
<h2 className="text-2xl font-medium text-white">
Start creating right here
@@ -83,31 +72,30 @@ const Welcome = () => {
</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{[
{ to: "/create-image", icon: faImage, label: "Image" },
{ to: "/create-video", icon: faVideo, label: "Video" },
{ to: "/create-image", icon: ImageIcon, label: "Image" },
{ to: "/create-video", icon: VideoIcon, label: "Video" },
{
to: "/background-change",
icon: faWandMagicSparkles,
icon: WandSparklesIcon,
label: "BG Change",
},
{ to: "/edit-3d", icon: faCube, label: "Edit 3D" },
{ to: "/edit-3d", icon: BoxIcon, label: "Edit 3D" },
].map((item) => (
<Link
key={item.to}
to={item.to}
className="group flex items-center gap-3 bg-[#252529] hover:bg-[#2D2D32] border border-white/[0.06] hover:border-white/[0.12] rounded-xl px-4 py-3 transition-all"
>
<FontAwesomeIcon
<DynamicIcon
icon={item.icon}
className="text-primary text-base shrink-0"
/>
<span className="text-white/90 font-medium flex-1">
{item.label}
</span>
<FontAwesomeIcon
icon={faArrowRight}
className="text-white/30 text-[11px] transition-transform group-hover:translate-x-0.5"
/>
<ArrowRightIcon
className="text-white/30 text-[11px] transition-transform group-hover:translate-x-0.5" />
</Link>
))}
</div>
@@ -119,10 +107,9 @@ const Welcome = () => {
<div className="bg-[#1A1A1E] border border-white/10 rounded-3xl p-8 md:p-10">
<div className="flex items-center gap-3 mb-2">
<div className="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center">
<FontAwesomeIcon
icon={faDesktop}
className="text-primary text-lg"
/>
<MonitorIcon
className="text-primary text-lg" />
</div>
<h2 className="text-2xl font-medium text-white">
Want the desktop app too?
@@ -166,7 +153,7 @@ const Welcome = () => {
href={downloadUrl}
className="rounded-full bg-white text-black hover:bg-gray-100 dark:bg-white dark:text-black dark:hover:bg-gray-200 px-6 py-2.5 text-sm font-bold"
>
<FontAwesomeIcon icon={faDownload} className="mr-2" />
<DownloadIcon className="mr-2" />
Download for {isMacOs ? "Mac" : "Windows"}
</Button>
<div className="flex gap-4 text-sm font-medium text-white/30">
@@ -193,7 +180,7 @@ const Welcome = () => {
{isMobile && (
<div className="bg-[#431407] border border-orange-900/50 rounded-2xl p-6 text-orange-200 text-sm leading-relaxed">
<div className="flex items-center justify-center mb-3 text-orange-400">
<FontAwesomeIcon icon={faDesktop} className="text-2xl" />
<MonitorIcon className="text-2xl" />
</div>
ArtCraft is a powerful desktop experience. <br />
Please head to your computer to download and install.
@@ -511,3 +511,18 @@
.ac-sheet-overlay[data-state="closed"] {
animation: ac-sheet-fade 0.2s ease-in reverse forwards;
}
/* Lucide icons ship with a fixed 24px default; size them relative to the
surrounding font so they scale with text like the previous icons did.
1.2em (not 1em) because Lucide draws glyphs on a 20px grid inside a 24px
viewBox — the extra 20% makes the visible mark fill a full 1em, matching
the old solid-glyph sizing. Explicit w-/h-/size- utilities still win
(the utilities layer is emitted later). */
@layer base {
.lucide {
display: inline-block;
width: 1.2em;
height: 1.2em;
vertical-align: -0.2em;
}
}
@@ -132,6 +132,9 @@
},
{
"path": "../../libs/api/tsconfig.lib.json"
},
{
"path": "../../libs/icons/tsconfig.lib.json"
}
]
}
@@ -98,6 +98,9 @@
{
"path": "../../libs/api"
},
{
"path": "../../libs/icons"
},
{
"path": "./tsconfig.app.json"
},
@@ -1,6 +1,5 @@
import { Link } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faDesktop, faXmark } from "@fortawesome/pro-solid-svg-icons";
import { MonitorIcon, XIcon } from "lucide-react";
import { useDismissibleBanner } from "../../lib/useDismissibleBanner";
const DISMISS_KEY = "artcraft-app-download-cta-dismissed-v1";
@@ -13,7 +12,7 @@ export function AppDownloadCta() {
return (
<div className="glass animate-fade-in-up mb-2 hidden sm:flex w-fit mx-auto items-center gap-3 rounded-xl px-4 py-2.5 text-sm text-white">
<div className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-primary/20 text-primary">
<FontAwesomeIcon icon={faDesktop} className="h-3.5 w-3.5" />
<MonitorIcon className="h-3.5 w-3.5" />
</div>
<span className="text-white/90">
Get more out of ArtCraft on desktop - 3D Stage, 2D Canvas &amp; more.
@@ -29,7 +28,7 @@ export function AppDownloadCta() {
className="ml-1 text-white/40 transition-colors hover:text-white/80"
aria-label="Dismiss"
>
<FontAwesomeIcon icon={faXmark} className="h-3 w-3" />
<XIcon className="h-3 w-3" />
</button>
</div>
);
@@ -6,8 +6,7 @@ import {
getReferralUsername,
getReferrer,
} from "@storyteller/common";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { LoaderCircleIcon } from "lucide-react";
import { useState } from "react";
interface GoogleLoginButtonProps {
@@ -61,10 +60,9 @@ export function GoogleLoginButton({
{/* Custom visual button - shown behind */}
<div className="absolute inset-0 w-full h-full flex items-center justify-center gap-3 bg-white text-black rounded-lg font-medium border border-gray-200 hover:bg-gray-50 transition-colors">
{isLoading ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-lg text-gray-600"
/>
<LoaderCircleIcon
className="animate-spin text-lg text-gray-600" />
) : (
<>
<GoogleIcon />
@@ -1,11 +1,6 @@
import { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCheck,
faEye,
faEyeSlash,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { CheckIcon, EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
@@ -79,10 +74,9 @@ export function AuthGateModal({
className="flex items-start gap-3 text-[14px] text-white/75"
>
<span className="mt-[2px] flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-primary/15">
<FontAwesomeIcon
icon={faCheck}
className="text-[9px] text-primary"
/>
<CheckIcon
className="text-[9px] text-primary" />
</span>
<span>{perk}</span>
</li>
@@ -223,7 +217,7 @@ function InlineLoginForm({ onSuccess }: { onSuccess: () => void }) {
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 transition-colors hover:text-white/60"
tabIndex={-1}
>
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
</button>
</div>
</div>
@@ -235,7 +229,7 @@ function InlineLoginForm({ onSuccess }: { onSuccess: () => void }) {
disabled={isLoading}
>
{isLoading ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
"Log in"
)}
@@ -1,9 +1,5 @@
import {
faEye,
faEyeSlash,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { useState } from "react";
@@ -156,7 +152,7 @@ export const SignupForm = ({
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
tabIndex={-1}
>
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
</button>
</div>
</div>
@@ -168,7 +164,7 @@ export const SignupForm = ({
disabled={isLoading}
>
{isLoading ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
"Create Account"
)}
@@ -1,8 +1,7 @@
import { useState } from "react";
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import { faCoins } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CoinsIcon } from "lucide-react";
import { BillingApi } from "@storyteller/api";
import { toast } from "./toast/toast";
@@ -86,10 +85,9 @@ export function CreditsModal({ isOpen, onClose }: CreditsModalProps) {
)}
<div className="flex items-center gap-2.5">
<FontAwesomeIcon
icon={faCoins}
className="text-2xl text-primary"
/>
<CoinsIcon
className="text-2xl text-primary" />
<span className="text-3xl font-bold tracking-tight text-white">
{pack.total.toLocaleString()}
</span>
@@ -1,5 +1,5 @@
import { Button } from "@storyteller/ui-button";
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
import { DiscordIcon } from "@storyteller/icons";
import { SOCIAL_LINKS } from "../../config/links";
interface DiscordButtonProps {
@@ -18,7 +18,7 @@ export const DiscordButton = ({
return (
<Button
className={`rounded-full relative z-10 ${sizeClasses} font-semibold transition-all duration-300 shadow-lg bg-white text-black hover:bg-gray-200 ${className}`}
icon={faDiscord}
icon={DiscordIcon}
as="link"
href={SOCIAL_LINKS.DISCORD}
target="_blank"
@@ -1,9 +1,5 @@
import { Button } from "@storyteller/ui-button";
import {
faApple,
faWindows,
faLinux,
} from "@fortawesome/free-brands-svg-icons";
import { AppleIcon, LinuxIcon, WindowsIcon } from "@storyteller/icons";
import { isMobile, isWindows, isMacOs } from "react-device-detect";
import {
DOWNLOAD_LINKS,
@@ -32,9 +28,9 @@ export const DownloadButton = ({ className = "" }: DownloadButtonProps) => {
const getIcon = () => {
if (isMobile) return undefined;
if (isWindows) return faWindows;
if (isMacOs) return faApple;
if (isLinux) return faLinux;
if (isWindows) return WindowsIcon;
if (isMacOs) return AppleIcon;
if (isLinux) return LinuxIcon;
return undefined;
};
@@ -1,10 +1,4 @@
import {
faTimes,
faEnvelope,
faArrowLeft,
faArrowRight,
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ArrowLeftIcon, ArrowRightIcon, MailIcon, XIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { Button } from "@storyteller/ui-button";
@@ -71,7 +65,7 @@ export const DownloadModal = ({ isOpen, onClose }: DownloadModalProps) => {
onClick={onClose}
className="absolute top-4 right-4 w-8 h-8 flex items-center justify-center rounded-full bg-white/5 hover:bg-white/10 text-white/40 hover:text-white/80 transition-colors z-20"
>
<FontAwesomeIcon icon={faTimes} />
<XIcon />
</button>
{!USE_WEBAPP_FOR_APP_FEATURES && view === "signup" && (
@@ -79,7 +73,7 @@ export const DownloadModal = ({ isOpen, onClose }: DownloadModalProps) => {
onClick={() => setView("menu")}
className="absolute top-4 left-4 w-8 h-8 flex items-center justify-center rounded-full bg-white/5 hover:bg-white/10 text-white/40 hover:text-white/80 transition-colors z-20"
>
<FontAwesomeIcon icon={faArrowLeft} />
<ArrowLeftIcon />
</button>
)}
@@ -117,9 +111,9 @@ export const DownloadModal = ({ isOpen, onClose }: DownloadModalProps) => {
href={webappUrl("/signup")}
className="rounded-full w-full bg-white/5 hover:bg-white/10 text-white border-white/10 justify-center gap-3 font-medium h-12"
>
<FontAwesomeIcon icon={faEnvelope} className="text-lg" />
<MailIcon className="text-lg" />
Sign up
<FontAwesomeIcon icon={faArrowRight} className="text-xs" />
<ArrowRightIcon className="text-xs" />
</Button>
</div>
) : view === "menu" ? (
@@ -128,7 +122,7 @@ export const DownloadModal = ({ isOpen, onClose }: DownloadModalProps) => {
className="rounded-full w-full bg-white/5 hover:bg-white/10 text-white border-white/10 justify-center gap-3 font-medium h-12"
onClick={() => setView("signup")}
>
<FontAwesomeIcon icon={faEnvelope} className="text-lg" />
<MailIcon className="text-lg" />
Sign up with Email
</Button>
</div>
@@ -1,13 +1,4 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faTiktok,
faDiscord,
faYoutube,
faGithubAlt,
faLinkedinIn,
faInstagram,
faRedditAlien,
} from "@fortawesome/free-brands-svg-icons";
import { DiscordIcon, GithubAltIcon, InstagramIcon, LinkedinInIcon, RedditAlienIcon, TiktokIcon, YoutubeIcon } from "@storyteller/icons";
import {
SOCIAL_LINKS,
USE_WEBAPP_FOR_APP_FEATURES,
@@ -28,37 +19,37 @@ const navigation = {
{
name: "Discord",
href: SOCIAL_LINKS.DISCORD,
icon: (props: any) => <FontAwesomeIcon icon={faDiscord} {...props} />,
icon: (props: any) => <DiscordIcon {...props} />,
},
{
name: "YouTube",
href: SOCIAL_LINKS.YOUTUBE,
icon: (props: any) => <FontAwesomeIcon icon={faYoutube} {...props} />,
icon: (props: any) => <YoutubeIcon {...props} />,
},
{
name: "TikTok",
href: SOCIAL_LINKS.TIKTOK,
icon: (props: any) => <FontAwesomeIcon icon={faTiktok} {...props} />,
icon: (props: any) => <TiktokIcon {...props} />,
},
{
name: "GitHub",
href: SOCIAL_LINKS.GITHUB,
icon: (props: any) => <FontAwesomeIcon icon={faGithubAlt} {...props} />,
icon: (props: any) => <GithubAltIcon {...props} />,
},
{
name: "Instagram",
href: SOCIAL_LINKS.INSTAGRAM,
icon: (props: any) => <FontAwesomeIcon icon={faInstagram} {...props} />,
icon: (props: any) => <InstagramIcon {...props} />,
},
{
name: "LinkedIn",
href: SOCIAL_LINKS.LINKEDIN,
icon: (props: any) => <FontAwesomeIcon icon={faLinkedinIn} {...props} />,
icon: (props: any) => <LinkedinInIcon {...props} />,
},
{
name: "Reddit",
href: SOCIAL_LINKS.REDDIT,
icon: (props: any) => <FontAwesomeIcon icon={faRedditAlien} {...props} />,
icon: (props: any) => <RedditAlienIcon {...props} />,
},
],
};
@@ -68,7 +59,7 @@ export default function Example() {
<footer className="bg-transparent z-20">
<div className="mx-auto max-w-7xl overflow-hidden px-4 sm:px-6 py-16 sm:py-16 lg:px-8 flex flex-col gap-12 items-center">
{/* <Button
icon={faArrowDownToLine}
icon={ArrowDownToLineIcon}
className="w-fit"
onClick={() => window.open("/download", "_self")}
>
@@ -1,10 +1,8 @@
import { type ReactNode } from "react";
import { Link } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faSpinnerThird,
type IconDefinition,
} from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Button } from "@storyteller/ui-button";
import { type PopoverItem } from "@storyteller/ui-popover";
import Seo from "../../components/seo";
@@ -19,7 +17,7 @@ interface CreateMediaPageShellProps {
authChecked: boolean;
isLoggedIn: boolean;
// Unauthenticated state
heroIcon: IconDefinition;
heroIcon: LucideIcon;
heroTitle: string;
heroSubtitle: string;
// Content
@@ -58,10 +56,9 @@ export function CreateMediaPageShell({
if (!authChecked) {
return (
<div className="flex h-screen items-center justify-center bg-[#101014]">
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-4xl text-primary/80"
/>
<LoaderCircleIcon
className="animate-spin text-4xl text-primary/80" />
</div>
);
}
@@ -90,7 +87,7 @@ export function CreateMediaPageShell({
<div className="h-[600px] w-[600px] rounded-full bg-gradient-to-br from-primary/30 via-blue-500/20 to-teal-400/10 opacity-40 blur-[120px]" />
</div>
<div className="relative z-10 flex min-h-screen flex-col items-center justify-center px-4">
<FontAwesomeIcon
<DynamicIcon
icon={heroIcon}
className="mb-6 text-5xl text-white/20"
/>
@@ -1,6 +1,5 @@
import { memo } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircleExclamation, faXmark } from "@fortawesome/pro-solid-svg-icons";
import { CircleAlertIcon, XIcon } from "lucide-react";
import { getCreatorIconPathForModelId } from "@storyteller/model-list";
export interface FailedCardProps {
@@ -26,10 +25,9 @@ export const FailedCard = memo(function FailedCard({
return (
<div className="relative aspect-square w-full overflow-hidden rounded-lg bg-red-500/10">
<div className="flex h-full flex-col items-center justify-center gap-2 px-4 sm:px-6">
<FontAwesomeIcon
icon={faCircleExclamation}
className="text-2xl text-red-400"
/>
<CircleAlertIcon
className="text-2xl text-red-400" />
<span className="text-center text-xs font-medium text-red-400">
{failureReason || "Generation failed"}
</span>
@@ -42,7 +40,7 @@ export const FailedCard = memo(function FailedCard({
onClick={() => onDismiss(id)}
className="mt-1 flex items-center gap-1.5 rounded-md bg-white/5 px-3 py-1.5 text-xs text-white/50 transition-colors hover:bg-white/10 hover:text-white/70"
>
<FontAwesomeIcon icon={faXmark} />
<XIcon />
Dismiss
</button>
</div>
@@ -1,16 +1,7 @@
import { memo, useCallback, useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowDownToLine,
faArrowRotateRight,
faCheck,
faCube,
faImage,
faLink,
faSpinnerThird,
faVideo,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowDownToLineIcon, BoxIcon, CheckIcon, ImageIcon, LinkIcon, LoaderCircleIcon, RotateCwIcon, VideoIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { PLACEHOLDER_IMAGES } from "@storyteller/common";
import { Tooltip } from "@storyteller/ui-tooltip";
import { toast } from "../toast/toast";
@@ -197,7 +188,7 @@ export const GalleryCard = memo(function GalleryCard({
? getCreatorIconPathForModelId(item.modelId)
: null;
const mediaIcon = item.mediaClass === "video" ? faVideo : is3D ? faCube : faImage;
const mediaIcon = item.mediaClass === "video" ? VideoIcon : is3D ? BoxIcon : ImageIcon;
const mediaLabel = item.mediaClass === "video" ? "Video" : is3D ? "3D" : "Image";
const handleLoad = useCallback(
@@ -326,10 +317,9 @@ export const GalleryCard = memo(function GalleryCard({
{retrying ? (
<>
<div className="flex h-full w-full flex-col items-center justify-center gap-2">
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-lg text-white/30"
/>
<LoaderCircleIcon
className="animate-spin text-lg text-white/30" />
<span className="text-[10px] text-white/30">
Loading thumbnail…
</span>
@@ -357,7 +347,7 @@ export const GalleryCard = memo(function GalleryCard({
/>
) : (
<div className="flex h-full w-full items-center justify-center">
<FontAwesomeIcon icon={mediaIcon} className="text-xl text-white/20" />
<DynamicIcon icon={mediaIcon} className="text-xl text-white/20" />
</div>
)}
</div>
@@ -366,7 +356,7 @@ export const GalleryCard = memo(function GalleryCard({
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-between gap-2 bg-gradient-to-t from-black/70 to-transparent px-2 pb-2 pt-6 opacity-0 transition-opacity group-hover:opacity-100">
<div className="pointer-events-auto flex min-w-0 flex-wrap items-center gap-1.5">
<div className="flex items-center gap-1.5 rounded-lg bg-black/60 px-2.5 py-1 text-xs font-medium text-white/90">
<FontAwesomeIcon icon={mediaIcon} className="text-[10px]" />
<DynamicIcon icon={mediaIcon} className="text-[10px]" />
{mediaLabel}
</div>
{modelDisplayName && modelIconPath && (
@@ -391,8 +381,8 @@ export const GalleryCard = memo(function GalleryCard({
disabled={isRecreating}
aria-label="Recreate"
>
<FontAwesomeIcon
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
<DynamicIcon
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
/>
</button>
@@ -406,7 +396,7 @@ export const GalleryCard = memo(function GalleryCard({
onClick={handleMakeVideo}
aria-label="Make Video"
>
<FontAwesomeIcon icon={faVideo} className="text-sm" />
<VideoIcon className="text-sm" />
</button>
</Tooltip>
)}
@@ -417,8 +407,8 @@ export const GalleryCard = memo(function GalleryCard({
onClick={handleShare}
aria-label="Share"
>
<FontAwesomeIcon
icon={shareCopied ? faCheck : faLink}
<DynamicIcon
icon={shareCopied ? CheckIcon : LinkIcon}
className="text-sm"
/>
</button>
@@ -432,8 +422,8 @@ export const GalleryCard = memo(function GalleryCard({
className="flex h-7 w-7 items-center justify-center rounded-md text-white/85 transition-colors hover:bg-white/15 hover:text-white disabled:opacity-60"
aria-label="Download"
>
<FontAwesomeIcon
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
<DynamicIcon
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
className={`text-sm ${isDownloading ? "animate-spin" : ""}`}
/>
</button>
@@ -1,6 +1,5 @@
import { memo } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon } from "lucide-react";
import { getCreatorIconPathForModelId } from "@storyteller/model-list";
export interface PendingCardProps {
@@ -52,10 +51,9 @@ export const PendingCard = memo(function PendingCard({
</div>
)}
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2">
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-2xl text-white/20"
/>
<LoaderCircleIcon
className="animate-spin text-2xl text-white/20" />
{progressPercent != null && (
<span className="text-xs tabular-nums text-white/40">
{progressPercent}%
@@ -1,8 +1,7 @@
import { Button } from "@storyteller/ui-button";
import { isMobile, isMacOs } from "react-device-detect";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faWindows, faApple } from "@fortawesome/free-brands-svg-icons";
import { faRocket } from "@fortawesome/pro-solid-svg-icons";
import { RocketIcon } from "lucide-react";
import { DynamicIcon, AppleIcon, WindowsIcon } from "@storyteller/icons";
import {
DOWNLOAD_LINKS,
DOWNLOADS_ENABLED,
@@ -40,7 +39,7 @@ export const LandingActionButtons = ({
as="link"
href="/pricing"
>
<FontAwesomeIcon icon={faRocket} />
<RocketIcon />
{creditsButtonText}
</Button>
{DOWNLOADS_ENABLED && (
@@ -51,7 +50,7 @@ export const LandingActionButtons = ({
href={downloadUrl}
onClick={onDownloadClick}
>
<FontAwesomeIcon icon={isMacOs ? faApple : faWindows} />
<DynamicIcon icon={isMacOs ? AppleIcon : WindowsIcon} />
Download for {isMacOs ? "Mac" : "Windows"}
</Button>
<a
@@ -4,22 +4,8 @@ import { Button } from "@storyteller/ui-button";
import { toast } from "../toast/toast";
import { Gravatar } from "@storyteller/ui-gravatar";
import type { UserInfo } from "@storyteller/api";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowDownToLine,
faArrowRotateRight,
faCheck,
faCircleInfo,
faCopy,
faImage,
faLink,
faPencil,
faSpinnerThird,
faTrashCan,
faUser,
faVideo,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowDownToLineIcon, CheckIcon, CopyIcon, ImageIcon, InfoIcon, LinkIcon, LoaderCircleIcon, PencilIcon, RotateCwIcon, Trash2Icon, UserIcon, VideoIcon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import {
getContextImageThumbnail,
THUMBNAIL_SIZES,
@@ -159,7 +145,7 @@ export function LightboxDetails({
className="absolute top-3 right-3 z-20 flex h-8 w-8 items-center justify-center rounded-full bg-black/40 text-white/70 transition-colors hover:bg-black/60 hover:text-white"
aria-label="Close"
>
<FontAwesomeIcon icon={faXmark} className="h-4 w-4" />
<XIcon className="h-4 w-4" />
</button>
)}
@@ -195,7 +181,7 @@ export function LightboxDetails({
/>
) : (
<div className="h-9 w-9 shrink-0 flex items-center justify-center rounded-xl bg-white/10 text-white/50 border border-white/5">
<FontAwesomeIcon icon={faUser} />
<UserIcon />
</div>
)}
<div className="flex flex-col gap-1">
@@ -213,7 +199,7 @@ export function LightboxDetails({
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
<FontAwesomeIcon icon={faPencil} />
<PencilIcon />
<span>Prompt</span>
</div>
{promptData.text && (
@@ -221,8 +207,8 @@ export function LightboxDetails({
onClick={handleCopyPrompt}
className="flex items-center gap-1.5 text-xs text-white/60 hover:text-white transition-colors"
>
<FontAwesomeIcon
icon={promptCopy.copied ? faCheck : faCopy}
<DynamicIcon
icon={promptCopy.copied ? CheckIcon : CopyIcon}
className="h-3 w-3"
/>
<span>{promptCopy.copied ? "Copied" : "Copy"}</span>
@@ -255,7 +241,7 @@ export function LightboxDetails({
{promptData.contextImages && promptData.contextImages.length > 0 && (
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
<FontAwesomeIcon icon={faImage} />
<ImageIcon />
<span>Reference Images</span>
</div>
<div className="grid grid-cols-5 gap-2">
@@ -287,7 +273,7 @@ export function LightboxDetails({
{hasInfoSection && (
<div className="space-y-2">
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
<FontAwesomeIcon icon={faCircleInfo} />
<InfoIcon />
<span>Information</span>
</div>
@@ -374,7 +360,7 @@ export function LightboxDetails({
<div className="grid grid-cols-2 gap-2">
<Button
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
icon={shareCopy.copied ? faCheck : faLink}
icon={shareCopy.copied ? CheckIcon : LinkIcon}
variant="secondary"
onClick={handleCopyShareLink}
>
@@ -401,8 +387,8 @@ export function LightboxDetails({
: "bg-ui-controls/20 text-white/60 cursor-not-allowed"
}`}
>
<FontAwesomeIcon
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
<DynamicIcon
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
className={isDownloading ? "animate-spin" : ""}
/>
{isDownloading ? "Downloading…" : "Download"}
@@ -410,7 +396,7 @@ export function LightboxDetails({
</div>
{onMakeVideo && (
<Button
icon={faVideo}
icon={VideoIcon}
className="w-full shadow-lg shadow-brand-primary/20"
variant="primary"
onClick={onMakeVideo}
@@ -420,7 +406,7 @@ export function LightboxDetails({
)}
{onRecreate && promptData.hasToken && (
<Button
icon={faArrowRotateRight}
icon={RotateCwIcon}
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
variant="secondary"
onClick={onRecreate}
@@ -430,7 +416,7 @@ export function LightboxDetails({
)}
{onDelete && mediaToken && (
<Button
icon={faTrashCan}
icon={Trash2Icon}
className="w-full bg-red-500/10 hover:bg-red-500/20 text-red-500 border border-red-500/20"
variant="destructive"
onClick={onDelete}
@@ -440,7 +426,7 @@ export function LightboxDetails({
)}
{showDownloadAppCta && (
<Button
icon={faArrowDownToLine}
icon={ArrowDownToLineIcon}
className="w-full shadow-lg shadow-brand-primary/20"
variant="primary"
onClick={() => {
@@ -4,12 +4,7 @@ import { toast } from "../toast/toast";
import { useEffect, useState, useMemo, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { MediaFilesApi, PromptsApi, type UserInfo } from "@storyteller/api";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronLeft,
faChevronRight,
faTrashCan,
} from "@fortawesome/pro-solid-svg-icons";
import { ChevronLeftIcon, ChevronRightIcon, Trash2Icon } from "lucide-react";
import { addCorsParam, PLACEHOLDER_IMAGES } from "@storyteller/common";
import { ActionReminderModal } from "@storyteller/ui-action-reminder-modal";
import { Viewer3D } from "@storyteller/ui-viewer-3d";
@@ -475,7 +470,7 @@ export function Lightbox({
className="absolute left-2 sm:left-3 top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full bg-black/50 text-white/70 sm:opacity-0 transition-opacity duration-200 hover:bg-black/70 hover:text-white sm:group-hover/nav:opacity-100"
aria-label="Previous item"
>
<FontAwesomeIcon icon={faChevronLeft} className="text-lg" />
<ChevronLeftIcon className="text-lg" />
</button>
)}
{onNavigateNext && (
@@ -487,7 +482,7 @@ export function Lightbox({
className="absolute right-2 sm:right-3 top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full bg-black/50 text-white/70 sm:opacity-0 transition-opacity duration-200 hover:bg-black/70 hover:text-white sm:group-hover/nav:opacity-100"
aria-label="Next item"
>
<FontAwesomeIcon icon={faChevronRight} className="text-lg" />
<ChevronRightIcon className="text-lg" />
</button>
)}
</div>
@@ -529,7 +524,7 @@ export function Lightbox({
onPrimaryAction={handleDelete}
primaryActionText="Delete"
secondaryActionText="Cancel"
primaryActionIcon={faTrashCan}
primaryActionIcon={Trash2Icon}
primaryActionBtnClassName="bg-red-500/10 hover:bg-red-500/20 text-red-500"
/>
</>
@@ -14,18 +14,8 @@ import { Fragment } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { USER_FEATURE_FLAGS, UsersApi } from "@storyteller/api";
import { useSession, invalidateSession } from "../../lib/session";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faBars,
faXmark,
faGem,
faGift,
faArrowRight,
faChevronDown,
faLifeRing,
faRocket,
} from "@fortawesome/pro-solid-svg-icons";
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
import { ArrowRightIcon, ChevronDownIcon, GemIcon, GiftIcon, LifeBuoyIcon, MenuIcon, RocketIcon, XIcon } from "lucide-react";
import { DynamicIcon, DiscordIcon } from "@storyteller/icons";
import {
appLink,
SOCIAL_LINKS,
@@ -161,10 +151,9 @@ export default function Navbar() {
)}
>
{entry.name}
<FontAwesomeIcon
icon={faChevronDown}
className="text-[9px] transition-transform duration-200 group-data-[state=open]:rotate-180"
/>
<ChevronDownIcon
className="text-[9px] transition-transform duration-200 group-data-[state=open]:rotate-180" />
</Link>
</NavigationMenu.Trigger>
) : (
@@ -176,10 +165,9 @@ export default function Navbar() {
)}
>
{entry.name}
<FontAwesomeIcon
icon={faChevronDown}
className="text-[9px] transition-transform duration-200 group-data-[state=open]:rotate-180"
/>
<ChevronDownIcon
className="text-[9px] transition-transform duration-200 group-data-[state=open]:rotate-180" />
</NavigationMenu.Trigger>
)}
<NavigationMenu.Content className="absolute top-full left-0 mt-2 rounded-xl border border-white/[0.08] bg-[#1a1a1a] shadow-xl overflow-hidden">
@@ -229,7 +217,7 @@ export default function Navbar() {
to="/pricing"
className="hidden xl:flex h-8 items-center gap-1.5 px-3 rounded-lg text-sm font-medium text-white/90 hover:text-white hover:bg-white/[0.04] transition-all"
>
<FontAwesomeIcon icon={faGem} className="text-[11px]" />
<GemIcon className="text-[11px]" />
Pricing
</Link>
@@ -238,10 +226,9 @@ export default function Navbar() {
href={WEBAPP_URL}
className="group h-8 flex items-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white hover:bg-white/90 transition-all shadow-sm"
>
<FontAwesomeIcon
icon={faRocket}
className="text-[10px]"
/>
<RocketIcon
className="text-[10px]" />
Launch App
</a>
)}
@@ -282,10 +269,9 @@ export default function Navbar() {
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
)}
>
<FontAwesomeIcon
icon={faLifeRing}
className="text-[11px] text-white/50"
/>
<LifeBuoyIcon
className="text-[11px] text-white/50" />
Support
</button>
)}
@@ -300,10 +286,9 @@ export default function Navbar() {
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
)}
>
<FontAwesomeIcon
icon={faGift}
className="text-[11px] text-white/50"
/>
<GiftIcon
className="text-[11px] text-white/50" />
Referrals
</a>
)}
@@ -320,10 +305,9 @@ export default function Navbar() {
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
)}
>
<FontAwesomeIcon
icon={faDiscord}
className="text-[11px] text-white/50"
/>
<DiscordIcon
className="text-[11px] text-white/50" />
Join Discord
</a>
)}
@@ -351,7 +335,7 @@ export default function Navbar() {
to="/pricing"
className="h-8 flex items-center gap-1.5 px-3 rounded-lg text-sm font-medium text-white/90 hover:text-white hover:bg-white/[0.04] transition-all"
>
<FontAwesomeIcon icon={faGem} className="text-[11px]" />
<GemIcon className="text-[11px]" />
Pricing
</Link>
{USE_WEBAPP_FOR_APP_FEATURES ? (
@@ -359,10 +343,9 @@ export default function Navbar() {
href={WEBAPP_URL}
className="group h-8 flex items-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white hover:bg-white/90 transition-all shadow-sm"
>
<FontAwesomeIcon
icon={faRocket}
className="text-[10px]"
/>
<RocketIcon
className="text-[10px]" />
Launch App
</a>
) : (
@@ -378,10 +361,9 @@ export default function Navbar() {
className="group h-8 flex items-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white hover:bg-white/90 transition-all shadow-sm"
>
Sign up
<FontAwesomeIcon
icon={faArrowRight}
className="text-[10px] transition-transform group-hover:translate-x-0.5"
/>
<ArrowRightIcon
className="text-[10px] transition-transform group-hover:translate-x-0.5" />
</Link>
</>
)}
@@ -392,8 +374,8 @@ export default function Navbar() {
<div className="flex items-center gap-1.5 lg:hidden">
<DisclosureButton className="flex h-8 w-8 items-center justify-center rounded-lg text-white/70 hover:text-white hover:bg-white/[0.06] transition-colors">
<span className="sr-only">Open main menu</span>
<FontAwesomeIcon
icon={open ? faXmark : faBars}
<DynamicIcon
icon={open ? XIcon : MenuIcon}
className="text-base"
/>
</DisclosureButton>
@@ -508,10 +490,9 @@ export default function Navbar() {
href={WEBAPP_URL}
className="group h-10 flex items-center justify-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white active:bg-white/90 transition-all shadow-sm"
>
<FontAwesomeIcon
icon={faRocket}
className="text-[10px]"
/>
<RocketIcon
className="text-[10px]" />
Launch App
</DisclosureButton>
)}
@@ -521,10 +502,9 @@ export default function Navbar() {
to="/pricing"
className="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-[12px] font-medium text-white/70 bg-white/[0.06] active:bg-white/10 transition-colors"
>
<FontAwesomeIcon
icon={faGem}
className="text-[10px]"
/>
<GemIcon
className="text-[10px]" />
Pricing
</DisclosureButton>
<DisclosureButton
@@ -534,10 +514,9 @@ export default function Navbar() {
rel="noopener noreferrer"
className="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-[12px] font-medium text-white/70 bg-white/[0.06] active:bg-white/10 transition-colors"
>
<FontAwesomeIcon
icon={faDiscord}
className="text-[10px]"
/>
<DiscordIcon
className="text-[10px]" />
Discord
</DisclosureButton>
{hasReferralsFlag && (
@@ -546,10 +525,9 @@ export default function Navbar() {
href={webappUrl("/referrals")}
className="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-[12px] font-medium text-white/70 bg-white/[0.06] active:bg-white/10 transition-colors"
>
<FontAwesomeIcon
icon={faGift}
className="text-[10px]"
/>
<GiftIcon
className="text-[10px]" />
Referrals
</DisclosureButton>
)}
@@ -579,7 +557,7 @@ export default function Navbar() {
href={WEBAPP_URL}
className="group h-10 flex items-center justify-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white active:bg-white/90 transition-all shadow-sm"
>
<FontAwesomeIcon icon={faRocket} className="text-[10px]" />
<RocketIcon className="text-[10px]" />
Launch App
</DisclosureButton>
) : (
@@ -1,19 +1,7 @@
import { Tooltip } from "@storyteller/ui-tooltip";
import { PopoverMenu } from "@storyteller/ui-popover";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faListCheck,
faSpinnerThird,
faXmark,
faTrashAlt,
faTasks,
faBroom,
faBomb,
faCircleExclamation,
faTriangleExclamation,
faCopy,
faCheck,
} from "@fortawesome/pro-solid-svg-icons";
import { BombIcon, BrushIcon, CheckIcon, CircleAlertIcon, CopyIcon, ListChecksIcon, LoaderCircleIcon, Trash2Icon, TriangleAlertIcon, XIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { Modal } from "@storyteller/ui-modal";
import { useEffect, useMemo, useRef, useState } from "react";
import { JobsApi, JobStatus } from "@storyteller/api";
@@ -192,8 +180,8 @@ const CopyPromptButton = ({ prompt }: { prompt: string }) => {
setTimeout(() => setCopied(false), 3000);
}}
>
<FontAwesomeIcon
icon={copied ? faCheck : faCopy}
<DynamicIcon
icon={copied ? CheckIcon : CopyIcon}
className={copied ? "text-green-400" : ""}
/>
</button>
@@ -226,11 +214,10 @@ const InProgressCard = ({
<div className="rounded-md p-2 transition-colors hover:bg-ui-controls/40">
<div className="flex items-center gap-2.5">
<div className="flex h-[72px] w-[72px] shrink-0 items-center justify-center overflow-hidden rounded bg-ui-controls">
<FontAwesomeIcon
icon={faSpinnerThird}
<LoaderCircleIcon
className="animate-spin text-base-fg/60"
size="lg"
/>
size="lg" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between text-sm">
@@ -252,10 +239,9 @@ const InProgressCard = ({
zIndex={50}
delay={100}
>
<FontAwesomeIcon
icon={faTriangleExclamation}
className="h-3 w-3 shrink-0 text-yellow-400/60 transition-all hover:text-yellow-400"
/>
<TriangleAlertIcon
className="h-3 w-3 shrink-0 text-yellow-400/60 transition-all hover:text-yellow-400" />
</Tooltip>
)}
</div>
@@ -294,7 +280,7 @@ const InProgressCard = ({
onDismiss();
}}
>
<FontAwesomeIcon icon={faXmark} />
<XIcon />
</button>
)}
</div>
@@ -362,7 +348,7 @@ const CompletedCard = ({
onDismiss();
}}
>
<FontAwesomeIcon icon={faXmark} />
<XIcon />
</button>
)}
</div>
@@ -382,11 +368,10 @@ const FailedCard = ({
<div className="rounded-md p-2 transition-colors hover:bg-ui-controls/40">
<div className="flex items-center gap-2.5">
<div className="flex h-[72px] w-[72px] shrink-0 items-center justify-center overflow-hidden rounded bg-red-500/10">
<FontAwesomeIcon
icon={faCircleExclamation}
<CircleAlertIcon
className="text-red-400"
size="lg"
/>
size="lg" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between text-sm">
@@ -451,7 +436,7 @@ const FailedCard = ({
onDismiss();
}}
>
<FontAwesomeIcon icon={faXmark} />
<XIcon />
</button>
)}
</div>
@@ -712,7 +697,7 @@ export const TaskQueue = () => {
title: "",
message: null,
primaryActionText: "",
primaryActionIcon: faTrashAlt,
primaryActionIcon: Trash2Icon,
primaryActionBtnClassName: "",
onConfirm: async () => {},
});
@@ -729,7 +714,7 @@ export const TaskQueue = () => {
</span>
),
primaryActionText: "Clear completed",
primaryActionIcon: faBroom,
primaryActionIcon: BrushIcon,
primaryActionBtnClassName:
"bg-green-500/10 hover:bg-green-500/20 text-green-500",
onConfirm: async () => {
@@ -748,7 +733,7 @@ export const TaskQueue = () => {
</span>
),
primaryActionText: "Clear stale",
primaryActionIcon: faTrashAlt,
primaryActionIcon: Trash2Icon,
primaryActionBtnClassName:
"bg-orange-500/10 hover:bg-orange-500/20 text-orange-500",
onConfirm: async () => {
@@ -767,7 +752,7 @@ export const TaskQueue = () => {
</span>
),
primaryActionText: "Clear failed",
primaryActionIcon: faTrashAlt,
primaryActionIcon: Trash2Icon,
primaryActionBtnClassName:
"bg-red-500/10 hover:bg-red-500/20 text-red-500",
onConfirm: async () => {
@@ -787,7 +772,7 @@ export const TaskQueue = () => {
</span>
),
primaryActionText: "Nuke all",
primaryActionIcon: faBomb,
primaryActionIcon: BombIcon,
primaryActionBtnClassName:
"bg-red-500/10 hover:bg-red-500/20 text-red-500",
onConfirm: async () => {
@@ -991,7 +976,7 @@ export const TaskQueue = () => {
{hasNothing ? (
<div className="flex w-full flex-col items-center justify-center p-5 text-base-fg/60">
<div className="flex items-center gap-2.5 text-sm opacity-60">
<FontAwesomeIcon icon={faTasks} /> No tasks yet
<ListChecksIcon /> No tasks yet
</div>
</div>
) : (
@@ -1020,7 +1005,7 @@ export const TaskQueue = () => {
className="text-xs tracking-wide text-red-400/70 transition-colors hover:text-red-300"
onClick={() => handleClearFailed()}
>
<FontAwesomeIcon icon={faXmark} className="mr-1" />
<XIcon className="mr-1" />
Clear failed
</button>
</div>
@@ -1043,7 +1028,7 @@ export const TaskQueue = () => {
className="text-xs tracking-wide text-base-fg/50 transition-colors hover:text-base-fg/100"
onClick={() => handleClearCompleted()}
>
<FontAwesomeIcon icon={faXmark} className="mr-1" />
<XIcon className="mr-1" />
Clear completed
</button>
</div>
@@ -1090,12 +1075,11 @@ export const TaskQueue = () => {
align="end"
triggerIcon={
inProgressCount > 0 ? (
<FontAwesomeIcon
icon={faSpinnerThird}
className="animate-spin text-[11px]"
/>
<LoaderCircleIcon
className="animate-spin text-[11px]" />
) : (
<FontAwesomeIcon icon={faListCheck} className="text-[11px]" />
<ListChecksIcon className="text-[11px]" />
)
}
onOpenChange={handleOpenChange}
@@ -1140,28 +1124,28 @@ export const TaskQueue = () => {
className="rounded-full flex h-9 items-center justify-center bg-green-500/10 px-3 text-green-500 hover:bg-green-500/20"
onClick={() => handleClearCompleted()}
>
<FontAwesomeIcon icon={faBroom} className="mr-1.5" />
<BrushIcon className="mr-1.5" />
Clear completed
</Button>
<Button
className="rounded-full flex h-9 items-center justify-center bg-orange-500/10 px-3 text-orange-500 hover:bg-orange-500/20"
onClick={() => handleClearStale()}
>
<FontAwesomeIcon icon={faTrashAlt} className="mr-1.5" />
<Trash2Icon className="mr-1.5" />
Clear stale
</Button>
<Button
className="rounded-full flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-400 hover:bg-red-500/20"
onClick={() => handleClearFailed()}
>
<FontAwesomeIcon icon={faTrashAlt} className="mr-1.5" />
<Trash2Icon className="mr-1.5" />
Clear failed
</Button>
<Button
className="rounded-full flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-500 hover:bg-red-500/20"
onClick={() => handleRemoveAll()}
>
<FontAwesomeIcon icon={faBomb} className="mr-1.5" />
<BombIcon className="mr-1.5" />
Remove all
</Button>
<div className="mr-2 h-4 w-[1px] bg-base-fg/10" />
@@ -1,9 +1,8 @@
import { faServer, faBan, faUnlock, faKey, faGlobe, faCloud, faImage, faCreditCard, faTimes } from "@fortawesome/pro-solid-svg-icons";
import { faGithub } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { BanIcon, CloudIcon, CreditCardIcon, GlobeIcon, ImageIcon, KeyIcon, LockOpenIcon, ServerIcon, XIcon } from "lucide-react";
import { DynamicIcon, GithubIcon } from "@storyteller/icons";
export const OwnershipComparison = () => {
const websiteIcons = [faCloud, faGlobe, faImage, faServer];
const websiteIcons = [CloudIcon, GlobeIcon, ImageIcon, ServerIcon];
return (
<div className="relative z-10 w-full max-w-[1400px] mx-auto px-4 sm:px-8 lg:px-12 py-12 md:py-32 overflow-visible">
@@ -47,10 +46,10 @@ export const OwnershipComparison = () => {
zIndex: 10 + i,
}}
>
<FontAwesomeIcon icon={icon} className="text-xl" />
<DynamicIcon icon={icon} className="text-xl" />
<div className="absolute -top-3 -right-3 w-7 h-7 bg-[#ef4444] rounded-full flex items-center justify-center border-4 border-[#121214] text-white text-xs shadow-md">
<FontAwesomeIcon icon={faTimes} className="font-bold" />
<XIcon className="font-bold" />
</div>
</div>
);
@@ -67,10 +66,10 @@ export const OwnershipComparison = () => {
<div className="flex flex-wrap justify-center gap-3 text-xs sm:text-sm font-bold tracking-wide uppercase">
<div className="flex items-center gap-2 text-[#ffc9c9] bg-[#ef4444]/10 px-4 py-2.5 rounded-xl border border-[#ef4444]/20 shadow-lg shadow-red-900/10">
<FontAwesomeIcon icon={faBan} className="text-[#ef4444]" /> NO OWNERSHIP
<BanIcon className="text-[#ef4444]" /> NO OWNERSHIP
</div>
<div className="flex items-center gap-2 text-[#ffc9c9] bg-[#ef4444]/10 px-4 py-2.5 rounded-xl border border-[#ef4444]/20 shadow-lg shadow-red-900/10">
<FontAwesomeIcon icon={faCreditCard} className="text-[#ef4444]" /> MONTHLY FEES
<CreditCardIcon className="text-[#ef4444]" /> MONTHLY FEES
</div>
</div>
</div>
@@ -89,7 +88,7 @@ export const OwnershipComparison = () => {
<img src="/images/services/artcraft.svg" alt="ArtCraft" className="w-[55%] h-[55%] object-contain drop-shadow-md brightness-0 invert" />
</div>
<div className="absolute -right-6 -bottom-4 w-16 h-16 bg-[#24292e] rounded-2xl flex items-center justify-center text-white text-3xl border-4 border-[#1C1C20] z-20 shadow-xl translate-x-2 translate-y-2">
<FontAwesomeIcon icon={faGithub} />
<GithubIcon />
</div>
</div>
@@ -105,10 +104,10 @@ export const OwnershipComparison = () => {
<div className="flex flex-wrap justify-center gap-3 text-xs sm:text-sm font-bold tracking-wide uppercase">
<div className="flex items-center gap-2 text-white bg-primary/20 px-4 py-2.5 rounded-xl border border-primary/30 shadow-lg shadow-primary/10">
<FontAwesomeIcon icon={faUnlock} className="text-primary" /> YOURS FOREVER
<LockOpenIcon className="text-primary" /> YOURS FOREVER
</div>
<div className="flex items-center gap-2 text-white bg-primary/20 px-4 py-2.5 rounded-xl border border-primary/30 shadow-lg shadow-primary/10">
<FontAwesomeIcon icon={faKey}className="text-primary" /> BYO KEYS
<KeyIcon className="text-primary" /> BYO KEYS
</div>
</div>
@@ -1,16 +1,6 @@
import { useState, useRef, useCallback, useEffect } from "react";
import { Modal } from "@storyteller/ui-modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faPlus,
faArrowLeft,
faUpload,
faUserGroup,
faSpinnerThird,
faXmark,
faPen,
faTrashAlt,
} from "@fortawesome/pro-solid-svg-icons";
import { ArrowLeftIcon, LoaderCircleIcon, PenIcon, PlusIcon, Trash2Icon, UploadIcon, UsersIcon, XIcon } from "lucide-react";
import { twMerge } from "tailwind-merge";
import {
CharactersApi,
@@ -334,7 +324,7 @@ const CharacterListView = ({
className="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-lg border-2 border-dashed border-white/10 bg-white/[0.05] text-white/60 transition-colors hover:border-white/25 hover:text-white/80"
>
<div className="flex aspect-square w-full flex-col items-center justify-center gap-2">
<FontAwesomeIcon icon={faPlus} className="text-lg" />
<PlusIcon className="text-lg" />
<span className="text-sm font-medium">Create New</span>
</div>
</button>
@@ -354,14 +344,13 @@ const CharacterListView = ({
/>
) : (
<div className="flex h-full w-full items-center justify-center text-white/20">
<FontAwesomeIcon icon={faUserGroup} className="text-2xl" />
<UsersIcon className="text-2xl" />
</div>
)}
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-black/40">
<FontAwesomeIcon
icon={faSpinnerThird}
className="text-lg text-white/80 animate-spin"
/>
<LoaderCircleIcon
className="text-lg text-white/80 animate-spin" />
<span className="text-xs font-medium text-white/80">
Creating...
</span>
@@ -397,10 +386,9 @@ const CharacterListView = ({
/>
) : (
<div className="flex h-full w-full items-center justify-center text-white/20">
<FontAwesomeIcon
icon={faUserGroup}
className="text-2xl"
/>
<UsersIcon
className="text-2xl" />
</div>
)}
</div>
@@ -421,7 +409,7 @@ const CharacterListView = ({
}}
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-black/80"
>
<FontAwesomeIcon icon={faPen} className="text-[10px]" />
<PenIcon className="text-[10px]" />
</button>
<button
onClick={(e) => {
@@ -430,10 +418,9 @@ const CharacterListView = ({
}}
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-red-500"
>
<FontAwesomeIcon
icon={faTrashAlt}
className="text-[10px]"
/>
<Trash2Icon
className="text-[10px]" />
</button>
</div>
)}
@@ -546,7 +533,7 @@ const EditCharacterView = ({
onClick={onBack}
className="flex items-center justify-center text-white/60 transition-colors hover:text-white"
>
<FontAwesomeIcon icon={faArrowLeft} />
<ArrowLeftIcon />
</button>
<h2 className="text-xl font-bold text-white">Edit Character</h2>
</div>
@@ -795,7 +782,7 @@ const NewCharacterView = ({
onClick={onBack}
className="flex items-center justify-center text-white/60 transition-colors hover:text-white"
>
<FontAwesomeIcon icon={faArrowLeft} />
<ArrowLeftIcon />
</button>
<h2 className="text-xl font-bold text-white">New Character</h2>
</div>
@@ -825,10 +812,9 @@ const NewCharacterView = ({
/>
{!images[0]!.mediaToken && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
<FontAwesomeIcon
icon={faSpinnerThird}
className="text-white animate-spin"
/>
<LoaderCircleIcon
className="text-white animate-spin" />
</div>
)}
<button
@@ -838,15 +824,14 @@ const NewCharacterView = ({
}}
className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500"
>
<FontAwesomeIcon icon={faXmark} className="text-sm" />
<XIcon className="text-sm" />
</button>
</div>
) : (
<div className="flex h-full w-full flex-col items-center justify-center text-white/60">
<FontAwesomeIcon
icon={faUpload}
className="mb-2 text-xl text-white/40"
/>
<UploadIcon
className="mb-2 text-xl text-white/40" />
<p className="text-sm">Upload reference image</p>
<p className="mb-3 text-xs text-white/40">
Click or drag an image here
@@ -8,14 +8,8 @@ import {
type ReactNode,
} from "react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronDown,
faChevronUp,
faMusic,
faUserGroup,
faVideo,
} from "@fortawesome/pro-solid-svg-icons";
import { ChevronDownIcon, ChevronUpIcon, MusicIcon, UsersIcon, VideoIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { GenerateIconButton } from "@storyteller/ui-button";
import { Tooltip } from "@storyteller/ui-tooltip";
import {
@@ -890,14 +884,13 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
className="h-full w-full object-cover"
/>
) : item.type === "character" ? (
<FontAwesomeIcon
icon={faUserGroup}
className="h-3.5 w-3.5 text-white/60"
/>
<UsersIcon
className="h-3.5 w-3.5 text-white/60" />
) : (
<FontAwesomeIcon
<DynamicIcon
icon={
item.type === "video" ? faVideo : faMusic
item.type === "video" ? VideoIcon : MusicIcon
}
className="h-3.5 w-3.5 text-white/60"
/>
@@ -977,8 +970,8 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
onClick={toggleExpand}
className="px-3 py-0.5 text-white/30 transition-colors hover:text-white/90"
>
<FontAwesomeIcon
icon={isExpanded ? faChevronUp : faChevronDown}
<DynamicIcon
icon={isExpanded ? ChevronUpIcon : ChevronDownIcon}
className="text-xs"
/>
</button>
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faImages, faMusic, faVideo } from "@fortawesome/pro-solid-svg-icons";
import { ImagesIcon, MusicIcon, VideoIcon } from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import { toast } from "../toast/toast";
// ── Types ───────────────────────────────────────────────────────────────
@@ -235,10 +235,10 @@ export function PromptBoxDropOverlay({
const kinds = [
...(acceptsImages
? [{ icon: faImages, label: keyframeMode ? "Frames" : "Images" }]
? [{ icon: ImagesIcon, label: keyframeMode ? "Frames" : "Images" }]
: []),
...(acceptsVideos ? [{ icon: faVideo, label: "Video" }] : []),
...(acceptsAudio ? [{ icon: faMusic, label: "Audio" }] : []),
...(acceptsVideos ? [{ icon: VideoIcon, label: "Video" }] : []),
...(acceptsAudio ? [{ icon: MusicIcon, label: "Audio" }] : []),
];
const rejected = dragState === "reject";
@@ -256,7 +256,7 @@ export function PromptBoxDropOverlay({
className="promptbox-drop-chip flex h-9 w-9 items-center justify-center rounded-lg bg-white/10 text-base-fg/90"
style={{ animationDelay: `${i * 50}ms` }}
>
<FontAwesomeIcon icon={kind.icon} className="text-sm" />
<DynamicIcon icon={kind.icon} className="text-sm" />
</div>
))}
</div>
@@ -2,8 +2,7 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import { Tooltip } from "@storyteller/ui-tooltip";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faUpRightAndDownLeftFromCenter } from "@fortawesome/pro-solid-svg-icons";
import { Maximize2Icon } from "lucide-react";
// Webapp "focus mode" for the prompt editor. Mirrors the desktop lib's
// (@storyteller/ui-promptbox) fullscreen feature but lives here because the
@@ -35,10 +34,9 @@ export const PromptFullscreenButton = ({
onClick={onClick}
className="flex h-6 w-6 items-center justify-center rounded-full bg-base-fg/5 text-base-fg/50 transition-colors hover:bg-base-fg/10 hover:text-base-fg/90 focus:outline-none"
>
<FontAwesomeIcon
icon={faUpRightAndDownLeftFromCenter}
className="h-3 w-3"
/>
<Maximize2Icon
className="h-3 w-3" />
</button>
</Tooltip>
</div>
@@ -1,8 +1,7 @@
import { useState } from "react";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { LoaderCircleIcon } from "lucide-react";
import { UsersApi, type UserInfo } from "@storyteller/api";
import { refreshSession, updateSessionUser } from "../../lib/session";
import { toast } from "../toast/toast";
@@ -490,7 +489,7 @@ function FormActions({
disabled={submitting || disabled}
>
{submitting ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
<LoaderCircleIcon className="animate-spin" />
) : (
"Save"
)}

Some files were not shown because too many files have changed in this diff Show More