Webapp stylings (#1916)

This commit is contained in:
BFlat authored and GitHub committed 2026-09-01 10:25:21 +07:00
1 parent d2ad5820e0
commit 60bcfffc8d
156 files changed
+2512 -2753

No files matched your search

@@ -20,12 +20,12 @@ export const ButtonIcon = ({
...rest
}: ButtonIconProps) => {
const className = twMerge(
"box-content flex h-8 w-8 items-center justify-center rounded-none transition-colors duration-150",
"box-content flex h-8 w-8 items-center justify-center rounded-[3px] transition-colors duration-150",
bgFill
? "bg-ui-controls-button hover:bg-ui-controls-button/[0.75]"
: "bg-transparent hover:bg-ui-panel/[0.4]",
disabled && "opacity-50 hover:bg-transparent",
propsClassName
propsClassName,
);
return (
@@ -28,7 +28,7 @@ export const ButtonLink = ({
}
}
const baseClassName =
"font-mono text-xs font-semibold uppercase tracking-[0.15em] whitespace-nowrap rounded-none px-3.5 py-2 border border-transparent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-white/60 transition-colors duration-150";
"font-mono text-xs font-semibold uppercase tracking-[0.12em] whitespace-nowrap rounded-[3px] px-3.5 py-2 border border-transparent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-white/60 transition-colors duration-150";
const variantClassNames = getVariantClassNames(variant);
const className = twMerge(baseClassName, variantClassNames, propsClassName);
// END TODO
@@ -27,13 +27,15 @@ export const ToggleButton = ({
<Button
className={twMerge(
// 34px matches the sibling toolbar controls (GenerateButton, the
// PopoverMenu triggers); the 2px border is inside the box.
"flex h-[34px] items-center justify-center rounded-none border-2 border-transparent text-sm text-white backdrop-blur-lg transition-all",
// PopoverMenu triggers). Flat control surface + hairline border,
// same idiom as the PopoverMenu triggers beside it — the old glassy
// backdrop-blur look predates the brutalist system.
"flex h-[34px] items-center justify-center rounded-[3px] border border-ui-controls-border bg-ui-controls text-sm text-white transition-colors",
hasLabel ? "px-3" : "w-[34px]",
isActive
? "border-white/20 bg-brand-primary/40 hover:border-white/30 hover:bg-brand-primary/40"
: "bg-[#5F5F68]/60 hover:bg-[#5F5F68]/90",
className
? "border-white/30 bg-brand-primary/40 hover:bg-brand-primary/40"
: "hover:border-white/30 hover:bg-ui-controls/80",
className,
)}
variant="secondary"
onClick={onClick}
@@ -39,7 +39,9 @@ export const Button = ({
function getVariantClassNames(variant: string) {
switch (variant) {
case "secondary": {
return "bg-transparent text-base-fg border border-ui-controls-border hover:bg-white/10 hover:border-white/30";
// Resting bg-white/5 tint (matching the home grid cards) so the
// button reads as a surface, not a bare outline on the page bg.
return "bg-white/5 text-base-fg border border-ui-controls-border hover:bg-white/10 hover:border-white/30";
}
case "action": {
return "bg-ui-controls text-base-fg border border-ui-controls-border hover:bg-ui-controls/80";
@@ -62,7 +64,7 @@ export const Button = ({
);
const className = twMerge(
"w-fit rounded-none font-mono text-xs font-semibold uppercase tracking-[0.15em] px-3.5 py-2 border border-transparent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-white/60 transition-colors duration-150 flex gap-2 items-center justify-center",
"w-fit rounded-[3px] font-mono text-xs font-semibold uppercase tracking-[0.12em] px-3.5 py-2 border border-transparent focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-2 focus-visible:outline-white/60 transition-colors duration-150 flex gap-2 items-center justify-center",
getVariantClassNames(propsVariant),
propsClassName,
disabledClass,
@@ -97,7 +97,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
<span
aria-hidden
className={twMerge(
"relative inline-block shrink-0 rounded-none border transition-colors",
"relative inline-block shrink-0 rounded-[3px] border transition-colors",
boxDims,
isChecked
? "border-white bg-white text-black"
@@ -22,9 +22,9 @@ export const CloseButton = ({
<button
onClick={onClick}
className={twMerge(
"flex items-center justify-center rounded-none bg-black/40 text-white/60 transition-colors hover:bg-white hover:text-black",
"flex items-center justify-center rounded-[3px] bg-black/40 text-white/60 transition-colors hover:bg-white hover:text-black",
sizeClasses[size],
className
className,
)}
>
<XIcon />
@@ -25,7 +25,7 @@ export const FolderColorRow: React.FC<FolderColorRowProps> = ({
onClick={() => onSetColor(c)}
style={{ backgroundColor: c }}
className={twMerge(
"h-5 w-5 border border-black/20 transition-transform hover:scale-110",
"h-5 w-5 rounded-[3px] border border-black/20 transition-transform hover:scale-110",
colorCode === c &&
"ring-2 ring-white ring-offset-1 ring-offset-ui-panel",
)}
@@ -37,16 +37,16 @@ export const FolderColorRow: React.FC<FolderColorRowProps> = ({
aria-label="Clear color"
onClick={() => onSetColor(null)}
className={twMerge(
"flex h-5 w-5 items-center justify-center border border-ui-panel-border text-base-fg/50 hover:text-base-fg",
"flex h-5 w-5 items-center justify-center rounded-[3px] border border-ui-panel-border text-base-fg/50 hover:text-base-fg",
!colorCode && "ring-2 ring-white ring-offset-1 ring-offset-ui-panel",
)}
>
<XIcon className="text-[10px]" />
<XIcon className="text-[10px]" />
</button>
{/* Custom hex (native picker) */}
<label
title="Custom color"
className="relative h-5 w-5 cursor-pointer overflow-hidden border border-ui-panel-border"
className="relative h-5 w-5 cursor-pointer overflow-hidden rounded-[3px] border border-ui-panel-border"
style={{
background:
"conic-gradient(red, orange, yellow, lime, cyan, blue, magenta, red)",
@@ -55,7 +55,9 @@ export const FolderColorRow: React.FC<FolderColorRowProps> = ({
<input
type="color"
value={
colorCode && /^#[0-9a-fA-F]{6}$/.test(colorCode) ? colorCode : "#888888"
colorCode && /^#[0-9a-fA-F]{6}$/.test(colorCode)
? colorCode
: "#888888"
}
onChange={(e) => onSetColor(e.target.value)}
className="absolute -inset-1 h-[150%] w-[150%] cursor-pointer opacity-0"
@@ -62,7 +62,7 @@ export const FolderNameDialog: React.FC<FolderNameDialogProps> = ({
onKeyDown={(e) => {
if (e.key === "Enter") submit();
}}
className="w-full border border-ui-panel-border bg-ui-controls/40 px-3 py-1.5 text-sm text-base-fg outline-none focus:ring-1 focus:ring-primary/50"
className="w-full rounded-[3px] border border-ui-panel-border bg-ui-controls/40 px-3 py-1.5 text-sm text-base-fg outline-none focus:ring-1 focus:ring-primary/50"
autoFocus
/>
<div className="flex justify-end gap-2">
@@ -1,9 +1,4 @@
import React, {
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { CheckIcon, EllipsisIcon, MusicIcon, UploadIcon } from "lucide-react";
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
@@ -273,9 +268,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
<div className="flex h-full w-full flex-col bg-gradient-to-br from-white/[0.08] via-white/[0.04] to-white/[0.02]">
<div className="flex min-h-0 flex-1 items-center justify-center">
<div className="flex h-11 w-11 items-center justify-center bg-white/10 ring-1 ring-white/15">
<MusicIcon
className="text-lg text-white/70" />
<MusicIcon className="text-lg text-white/70" />
</div>
</div>
<p className="line-clamp-2 shrink-0 px-2.5 pb-2.5 text-left text-xs leading-snug text-white/75">
@@ -316,8 +309,8 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
</>
)}
{selected && (
<div className="absolute left-2 top-2 flex h-6 w-6 items-center justify-center bg-primary">
<CheckIcon className="text-sm" />
<div className="absolute left-2 top-2 flex h-6 w-6 items-center justify-center rounded-[3px] bg-primary">
<CheckIcon className="text-sm" />
</div>
)}
{disabled && (
@@ -356,9 +349,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
position="bottom"
align="end"
mode="default"
triggerIcon={
<EllipsisIcon className="text-base-fg" />
}
triggerIcon={<EllipsisIcon className="text-base-fg" />}
buttonClassName="h-7 w-7 p-0 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"
closeOnUnhover
@@ -394,7 +385,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
/>
<div
ref={ctxPanelRef}
className="fixed z-[9999] w-max min-w-44 border border-ui-panel-border bg-ui-panel p-1 shadow-xl"
className="fixed z-[9999] w-max min-w-44 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl"
style={{ left: ctxMenu.x, top: ctxMenu.y }}
>
<GalleryItemMenuItems
@@ -418,7 +409,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
{mode !== "select" && onBulkSelectToggle && (
<div
className={twMerge(
"absolute left-2 top-2 z-[2] flex h-5 w-5 items-center justify-center border-2 cursor-pointer transition-all duration-100",
"absolute left-2 top-2 z-[2] flex h-5 w-5 items-center justify-center rounded-[3px] border-2 cursor-pointer transition-all duration-100",
bulkSelected
? "bg-primary border-primary"
: "border-white/60 bg-black/40 hover:border-white",
@@ -432,11 +423,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
onBulkSelectToggle?.();
}}
>
{bulkSelected && (
<CheckIcon
className="text-[10px] text-white" />
)}
{bulkSelected && <CheckIcon className="text-[10px] text-white" />}
</div>
)}
{/* Media class badge on hover — bottom-left */}
@@ -452,7 +439,7 @@ export const GalleryDraggableItem: React.FC<GalleryDraggableItemProps> = ({
{/* Upload badge — bottom-right (always visible, even in select mode) */}
{item.isUpload && (
<div className="pointer-events-none absolute right-2 bottom-2 z-[1] flex h-5 w-5 items-center justify-center bg-black/50 backdrop-blur-lg text-white">
<UploadIcon className="text-[10px]" />
<UploadIcon className="text-[10px]" />
</div>
)}
{/* Conditionally wrap with Tooltip — hidden when selecting or in bulk mode */}
@@ -67,7 +67,7 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
}}
style={colorCode ? { borderColor: colorCode } : undefined}
className={twMerge(
"group/chip relative w-full aspect-square overflow-hidden border-2 border-ui-controls/40 bg-ui-controls/20 transition-[transform,border-color,background-color,box-shadow] duration-200 ease-out cursor-pointer hover:border-primary/60",
"group/chip relative w-full aspect-square overflow-hidden rounded-[3px] border-2 border-ui-controls/40 bg-ui-controls/20 transition-[transform,border-color,background-color,box-shadow] duration-200 ease-out cursor-pointer hover:border-primary/60",
// A dragged item hovering the folder makes it lift and glow — clear,
// tactile "drop here" feedback rather than a flat color swap.
"[&.folder-drag-over]:scale-[1.04] [&.folder-drag-over]:border-primary [&.folder-drag-over]:bg-primary/20 [&.folder-drag-over]:ring-2 [&.folder-drag-over]:ring-primary/40",
@@ -102,9 +102,9 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
) : (
<div className="absolute inset-0 flex items-center justify-center">
<FolderIcon
className={colorCode ? "text-4xl" : "text-4xl text-primary"}
style={colorCode ? { color: colorCode } : undefined} />
style={colorCode ? { color: colorCode } : undefined}
/>
</div>
)}
@@ -115,9 +115,7 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
{/* Star (favorite) */}
{hasStar && (
<StarIcon
className="absolute left-2 top-2 text-sm text-amber-400 drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]" />
<StarIcon className="absolute left-2 top-2 text-sm text-amber-400 drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]" />
)}
{/* Name + subfolder count */}
@@ -127,12 +125,12 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
still reads as a folder. */}
{hasArt && (
<FolderIcon
className={twMerge(
"flex-shrink-0 text-sm drop-shadow-[0_1px_2px_rgba(0,0,0,0.7)]",
colorCode ? "" : "text-primary",
)}
style={colorCode ? { color: colorCode } : undefined} />
style={colorCode ? { color: colorCode } : undefined}
/>
)}
<span
className={twMerge(
@@ -168,9 +166,9 @@ export const GalleryFolderChip: React.FC<GalleryFolderChipProps> = ({
e.stopPropagation();
openMenuAt(e.currentTarget as HTMLElement);
}}
className="absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center bg-black/40 text-white opacity-0 transition-opacity hover:bg-black/60 group-hover/chip:opacity-100 [@media(pointer:coarse)]:opacity-100"
className="absolute right-1.5 top-1.5 flex h-7 w-7 items-center justify-center rounded-[3px] bg-black/40 text-white opacity-0 transition-opacity hover:bg-black/60 group-hover/chip:opacity-100 [@media(pointer:coarse)]:opacity-100"
>
<EllipsisIcon className="text-sm" />
<EllipsisIcon className="text-sm" />
</span>
)}
</button>
@@ -1,5 +1,14 @@
import React, { useState } from "react";
import { ChevronRightIcon, EyeIcon, FolderIcon, FolderMinusIcon, FolderPlusIcon, PencilIcon, PlusIcon, Trash2Icon } from "lucide-react";
import {
ChevronRightIcon,
EyeIcon,
FolderIcon,
FolderMinusIcon,
FolderPlusIcon,
PencilIcon,
PlusIcon,
Trash2Icon,
} from "lucide-react";
import { twMerge } from "tailwind-merge";
import { GalleryItem } from "./gallery-modal";
import { GalleryFolder } from "./GalleryDraggableItem";
@@ -28,7 +37,7 @@ export interface GalleryItemMenuItemsProps {
}
const ROW =
"flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-base-fg text-sm whitespace-nowrap";
"flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-base-fg text-sm whitespace-nowrap";
/**
* The gallery-tile menu body (Open / Edit / Add to Folder ▸ / Remove from
@@ -60,7 +69,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
onOpen();
}}
>
<EyeIcon className="text-base-fg w-4" />
<EyeIcon className="text-base-fg w-4" />
<span>
{item.mediaClass === "video"
? "View video"
@@ -85,7 +94,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
close();
}}
>
<PencilIcon className="text-base-fg w-4" />
<PencilIcon className="text-base-fg w-4" />
<span>Edit image</span>
</button>
)}
@@ -103,22 +112,22 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
>
<button
type="button"
className="flex w-full items-center justify-between gap-2 px-2 py-2 hover:bg-ui-controls/60 text-base-fg text-sm whitespace-nowrap"
className="flex w-full items-center justify-between gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-base-fg text-sm whitespace-nowrap"
onClick={(e) => {
e.stopPropagation();
setFolderSubmenuOpen((v) => !v);
}}
>
<div className="flex items-center gap-2">
<FolderPlusIcon className="text-base-fg w-4" />
<FolderPlusIcon className="text-base-fg w-4" />
<span>Add to Folder</span>
</div>
<ChevronRightIcon
className={twMerge(
"text-[10px] text-base-fg/50 transition-transform",
COARSE_POINTER && folderSubmenuOpen && "rotate-90",
)} />
)}
/>
</button>
{folderSubmenuOpen &&
(COARSE_POINTER ? (
@@ -138,7 +147,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
</div>
) : (
<div className="absolute left-full top-0 -ml-1 pl-2 z-50">
<div className="max-h-64 overflow-y-auto w-max min-w-36 border border-ui-panel-border bg-ui-panel p-1 shadow-xl">
<div className="max-h-64 overflow-y-auto w-max min-w-36 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl">
<FolderList
item={item}
folders={folders}
@@ -165,7 +174,7 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
close();
}}
>
<FolderMinusIcon className="text-base-fg w-4" />
<FolderMinusIcon className="text-base-fg w-4" />
<span>Remove from folder</span>
</button>
)}
@@ -173,14 +182,14 @@ export const GalleryItemMenuItems: React.FC<GalleryItemMenuItemsProps> = ({
{onDelete && (
<button
type="button"
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm whitespace-nowrap"
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm whitespace-nowrap"
onClick={(e) => {
e.stopPropagation();
close();
onDelete();
}}
>
<Trash2Icon className="text-red w-4" />
<Trash2Icon className="text-red w-4" />
<span className="text-red">Delete</span>
</button>
)}
@@ -209,7 +218,7 @@ const FolderList = ({
<button
key={folder.id}
type="button"
className="flex w-full items-center gap-2 px-2 py-1.5 hover:bg-ui-controls/60 text-base-fg text-sm"
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-1.5 hover:bg-ui-controls/60 text-base-fg text-sm"
onClick={(e) => {
e.stopPropagation();
onAddToFolder?.([item.id], folder.id);
@@ -217,9 +226,9 @@ const FolderList = ({
}}
>
<FolderIcon
className={folder.colorCode ? "text-xs" : "text-primary text-xs"}
style={folder.colorCode ? { color: folder.colorCode } : undefined} />
style={folder.colorCode ? { color: folder.colorCode } : undefined}
/>
<span className="truncate">{folder.name}</span>
</button>
))}
@@ -228,14 +237,14 @@ const FolderList = ({
)}
<button
type="button"
className="flex w-full items-center gap-2 px-2 py-1.5 hover:bg-ui-controls/60 text-base-fg/70 text-sm whitespace-nowrap"
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-1.5 hover:bg-ui-controls/60 text-base-fg/70 text-sm whitespace-nowrap"
onClick={(e) => {
e.stopPropagation();
closeAll();
onCreateFolderFromMenu?.();
}}
>
<PlusIcon className="text-xs w-4" />
<PlusIcon className="text-xs w-4" />
<span>New Folder</span>
</button>
</>
@@ -523,14 +523,14 @@ const FolderContextMenuItems = ({
}) => (
<div
className={twMerge(
"min-w-44 border border-ui-panel-border bg-ui-panel p-1 shadow-xl z-[59]",
"min-w-44 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl z-[59]",
className,
)}
style={style}
>
<button
type="button"
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
onClick={() => onToggleStar(folderId, !hasStar)}
>
<StarIcon
@@ -548,7 +548,7 @@ const FolderContextMenuItems = ({
<div className="mx-1.5 my-1 border-t border-ui-panel-border" />
<button
type="button"
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
onClick={() => onNewSubfolder(folderId)}
>
<FolderPlusIcon className="w-4" />
@@ -556,7 +556,7 @@ const FolderContextMenuItems = ({
</button>
<button
type="button"
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm text-base-fg"
onClick={() => onRename(folderId)}
>
<PencilIcon className="w-4" />
@@ -564,7 +564,7 @@ const FolderContextMenuItems = ({
</button>
<button
type="button"
className="flex w-full items-center gap-2 px-2 py-2 hover:bg-ui-controls/60 text-sm text-red"
className="flex w-full items-center gap-2 rounded-[3px] px-2 py-2 hover:bg-ui-controls/60 text-sm text-red"
onClick={() => onDelete(folderId)}
>
<Trash2Icon className="w-4" />
@@ -2601,14 +2601,14 @@ export const GalleryModal = React.memo(
<div className="flex flex-wrap justify-between items-center gap-y-2">
<div className="flex items-center gap-3 flex-wrap gap-y-1">
{(mode === "view" || mode === "select") && (
<div className="flex items-center gap-0.5 bg-ui-controls/40 p-0.5 relative z-[51]">
<div className="flex items-center gap-0.5 rounded-[3px] bg-ui-controls/40 p-0.5 relative z-[51]">
{(["unsorted", "folders"] as const).map((t) => (
<button
key={t}
type="button"
onClick={() => switchGalleryTab(t)}
className={twMerge(
" px-3 py-1 text-sm font-medium transition-colors",
"rounded-[3px] px-3 py-1 text-sm font-medium transition-colors",
galleryTab === t
? "bg-ui-controls text-base-fg"
: "text-base-fg/60 hover:text-base-fg",
@@ -2625,7 +2625,7 @@ export const GalleryModal = React.memo(
<button
type="button"
onClick={() => handleOpenNewFolderModal(null)}
className="flex items-center gap-2 bg-ui-controls/60 px-3 py-1.5 text-sm font-medium text-base-fg hover:bg-ui-controls/90 transition-colors relative z-[51]"
className="flex items-center gap-2 rounded-[3px] bg-ui-controls/60 px-3 py-1.5 text-sm font-medium text-base-fg hover:bg-ui-controls/90 transition-colors relative z-[51]"
>
<FolderPlusIcon className="text-xs" />
New folder
@@ -2654,7 +2654,7 @@ export const GalleryModal = React.memo(
type="button"
onClick={handleCloseTagView}
aria-label="Close tag view"
className="flex h-7 w-7 items-center justify-center hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
className="flex h-7 w-7 items-center justify-center rounded-[3px] hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
>
<XIcon className="text-sm" />
</button>
@@ -2668,7 +2668,7 @@ export const GalleryModal = React.memo(
type="button"
onClick={handleCloseTagView}
aria-label="Close tags"
className="flex h-7 w-7 items-center justify-center hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
className="flex h-7 w-7 items-center justify-center rounded-[3px] hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
>
<XIcon className="text-sm" />
</button>
@@ -2714,7 +2714,7 @@ export const GalleryModal = React.memo(
/>
) : (
<h2
className="text-lg font-semibold cursor-pointer hover:bg-ui-controls/30 px-1.5 py-0 transition-colors truncate max-w-[20rem]"
className="text-lg font-semibold cursor-pointer rounded-[3px] hover:bg-ui-controls/30 px-1.5 py-0 transition-colors truncate max-w-[20rem]"
onClick={() => handleStartRename(activeFolderId!)}
>
{activeFolder.name}
@@ -2732,7 +2732,7 @@ export const GalleryModal = React.memo(
<button
type="button"
onClick={() => setFolderMenuOpen((v) => !v)}
className="flex h-7 w-7 items-center justify-center hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
className="flex h-7 w-7 items-center justify-center rounded-[3px] hover:bg-ui-controls/60 text-base-fg/60 hover:text-base-fg transition-colors"
>
<EllipsisIcon className="text-sm" />
</button>
@@ -2916,7 +2916,7 @@ export const GalleryModal = React.memo(
}
}}
className={twMerge(
"flex items-center justify-between gap-2 px-2.5 py-1.5 text-sm transition-colors",
"flex items-center justify-between gap-2 rounded-[3px] px-2.5 py-1.5 text-sm transition-colors",
isActive &&
!activeFolderId &&
!activeTagToken &&
@@ -2949,7 +2949,7 @@ export const GalleryModal = React.memo(
type="button"
onClick={() => handleOpenNewFolderModal(null)}
aria-label="New folder"
className="flex h-5 w-5 items-center justify-center text-base-fg/50 hover:bg-ui-controls/60 hover:text-base-fg transition-colors"
className="flex h-5 w-5 items-center justify-center rounded-[3px] text-base-fg/50 hover:bg-ui-controls/60 hover:text-base-fg transition-colors"
>
<PlusIcon className="text-[10px]" />
</button>
@@ -2982,7 +2982,7 @@ export const GalleryModal = React.memo(
: undefined
}
className={twMerge(
"flex w-full flex-shrink-0 items-center gap-2.5 px-2.5 py-1.5 text-sm transition-colors [&.folder-drag-over]:bg-primary/20 [&.folder-drag-over]:text-base-fg",
"flex w-full flex-shrink-0 items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-sm transition-colors [&.folder-drag-over]:bg-primary/20 [&.folder-drag-over]:text-base-fg",
activeRootFolderId === folder.id
? "bg-ui-controls/60 text-base-fg font-medium"
: "text-base-fg/70 hover:bg-ui-controls/30 hover:text-base-fg",
@@ -3025,7 +3025,7 @@ export const GalleryModal = React.memo(
handleOpenTagFromSidebar(tag.tag_token)
}
className={twMerge(
"flex w-full flex-shrink-0 items-center gap-2.5 px-2.5 py-1.5 text-sm transition-colors",
"flex w-full flex-shrink-0 items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-sm transition-colors",
activeTagToken === tag.tag_token
? "bg-ui-controls/60 text-base-fg font-medium"
: "text-base-fg/70 hover:bg-ui-controls/30 hover:text-base-fg",
@@ -3042,7 +3042,7 @@ export const GalleryModal = React.memo(
type="button"
onClick={handleOpenTagBrowser}
className={twMerge(
"flex w-full flex-shrink-0 items-center gap-2.5 px-2.5 py-1.5 text-sm transition-colors",
"flex w-full flex-shrink-0 items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-sm transition-colors",
tagBrowserOpen && !activeTagToken
? "bg-ui-controls/60 text-base-fg font-medium"
: "text-base-fg/50 hover:bg-ui-controls/30 hover:text-base-fg",
@@ -3341,7 +3341,7 @@ export const GalleryModal = React.memo(
</div>
{onLoginClick ? (
<button
className=" bg-primary px-5 py-2 text-sm font-semibold text-white transition-colors hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
className="rounded-[3px] bg-primary px-5 py-2 text-sm font-semibold text-white transition-colors hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50"
onClick={() => onLoginClick()}
>
Log in
@@ -3576,7 +3576,7 @@ export const GalleryModal = React.memo(
className="fixed inset-0 z-[59]"
onClick={() => setBulkFolderPopoverOpen(false)}
/>
<div className="absolute bottom-full mb-2 right-0 w-56 border border-ui-panel-border bg-ui-panel p-2 shadow-xl z-[60]">
<div className="absolute bottom-full mb-2 right-0 w-56 rounded-[3px] border border-ui-panel-border bg-ui-panel p-2 shadow-xl z-[60]">
{/* Folders */}
<div className="text-[11px] font-semibold uppercase tracking-wider text-base-fg/40 px-2 py-1">
Folders
@@ -3592,7 +3592,7 @@ export const GalleryModal = React.memo(
<button
key={folder.id}
type="button"
className="flex w-full items-center gap-2.5 px-2 py-1.5 hover:bg-ui-controls/50 text-sm text-base-fg transition-colors"
className="flex w-full items-center gap-2.5 rounded-[3px] px-2 py-1.5 hover:bg-ui-controls/50 text-sm text-base-fg transition-colors"
onClick={() => {
const ids = Array.from(bulkSelectedIds);
requestFolderDrop(ids, folder.id);
@@ -3607,7 +3607,7 @@ export const GalleryModal = React.memo(
<div className="mx-1.5 my-1 border-t border-ui-panel-border" />
<button
type="button"
className="flex w-full items-center gap-2.5 px-2 py-1.5 hover:bg-ui-controls/50 text-sm text-base-fg/70 transition-colors"
className="flex w-full items-center gap-2.5 rounded-[3px] px-2 py-1.5 hover:bg-ui-controls/50 text-sm text-base-fg/70 transition-colors"
onClick={() => {
const ids = Array.from(bulkSelectedIds);
setBulkFolderPopoverOpen(false);
@@ -3633,7 +3633,7 @@ export const GalleryModal = React.memo(
)}
<button
onClick={clearBulkSelection}
className="flex h-8 w-8 items-center justify-center bg-ui-controls/60 hover:bg-ui-controls/90 text-base-fg transition-colors"
className="flex h-8 w-8 items-center justify-center rounded-[3px] bg-ui-controls/60 hover:bg-ui-controls/90 text-base-fg transition-colors"
aria-label="Clear selection"
>
<XIcon className="text-base" />
@@ -37,7 +37,7 @@ export function CopyPromptButton({
type="button"
onClick={handleCopy}
aria-label="Copy prompt"
className="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"
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[3px] text-white/40 transition-colors hover:bg-white/10 hover:text-white"
>
<DynamicIcon icon={copied ? CheckIcon : CopyIcon} className="text-sm" />
</button>
@@ -32,7 +32,7 @@ export const FailedCard = memo(function FailedCard({
const iconPath = modelId ? getCreatorIconPathForModelId(modelId) : null;
return (
<div className="relative aspect-square w-full overflow-hidden rounded-lg bg-red-500/10">
<div className="relative aspect-square w-full overflow-hidden rounded-[3px] bg-red-500/10">
{refImageUrl && (
<img
src={refImageUrl}
@@ -42,9 +42,7 @@ export const FailedCard = memo(function FailedCard({
/>
)}
<div className="relative flex h-full flex-col items-center justify-center gap-2 px-4 sm:px-6">
<CircleAlertIcon
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>
@@ -58,7 +56,7 @@ export const FailedCard = memo(function FailedCard({
<button
type="button"
onClick={() => onDismiss(id)}
className="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"
className="flex items-center gap-1.5 rounded-[3px] bg-white/5 px-3 py-1.5 text-xs text-white/50 transition-colors hover:bg-white/10 hover:text-white/70"
>
<XIcon />
Dismiss
@@ -35,9 +35,9 @@ export const FailedRow = memo(function FailedRow({
const iconPath = modelId ? getCreatorIconPathForModelId(modelId) : null;
return (
<div className="flex items-center gap-3 rounded-lg px-2.5 py-2">
<div className="flex items-center gap-3 px-2.5 py-2">
{/* Error thumbnail — ref image (if any) faded behind the warning icon */}
<div className="relative flex size-[100px] shrink-0 items-center justify-center overflow-hidden rounded-md bg-red-500/10 leading-none">
<div className="relative flex size-[100px] shrink-0 items-center justify-center overflow-hidden bg-red-500/10 leading-none">
{refImageUrl && (
<img
src={refImageUrl}
@@ -46,9 +46,7 @@ export const FailedRow = memo(function FailedRow({
className="pointer-events-none absolute inset-0 h-full w-full object-cover opacity-10"
/>
)}
<CircleAlertIcon
className="relative text-2xl text-red-400" />
<CircleAlertIcon className="relative text-2xl text-red-400" />
</div>
{/* Reason + prompt + model */}
@@ -87,7 +85,7 @@ export const FailedRow = memo(function FailedRow({
<button
type="button"
onClick={() => onDismiss(id)}
className="flex shrink-0 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"
className="flex shrink-0 items-center gap-1.5 rounded-[3px] bg-white/5 px-3 py-1.5 text-xs text-white/50 transition-colors hover:bg-white/10 hover:text-white/70"
>
<XIcon />
Dismiss
@@ -24,7 +24,7 @@ export function GalleryAutoplayToggle() {
<div
role="group"
aria-label="Video preview playback"
className="flex items-center gap-0.5 rounded-lg border border-white/[0.08] bg-white/[0.04] p-0.5"
className="flex items-center gap-0.5 rounded-[3px] border border-white/[0.08] bg-white/[0.04] p-0.5"
>
{OPTIONS.map(({ autoplay, icon, label }) => {
const active = autoplayVideos === autoplay;
@@ -35,7 +35,7 @@ export function GalleryAutoplayToggle() {
aria-label={label}
aria-pressed={active}
onClick={() => setAutoplayVideos(autoplay)}
className={`flex h-6 w-6 items-center justify-center rounded-md text-xs transition-colors ${
className={`flex h-6 w-6 items-center justify-center rounded-[3px] text-xs transition-colors ${
active
? "bg-white/10 text-white"
: "text-white/45 hover:text-white/80"
@@ -1,5 +1,11 @@
import { memo, useCallback, useState, type ReactNode } from "react";
import { BoxIcon, CheckIcon, ImageIcon, MusicIcon, VideoIcon } from "lucide-react";
import {
BoxIcon,
CheckIcon,
ImageIcon,
MusicIcon,
VideoIcon,
} from "lucide-react";
import { DynamicIcon } from "@storyteller/icons";
import {
getCreatorIconPathForModelId,
@@ -162,7 +168,7 @@ export const GalleryCard = memo(function GalleryCard({
<div
role="button"
tabIndex={0}
className={`group relative block w-full rounded-lg bg-ui-controls/40 leading-none transition-shadow hover:ring-2 hover:ring-primary-400/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 cursor-pointer ${isSquare ? "aspect-square" : ""} ${selected ? "ring-2 ring-primary-400" : ""}`}
className={`group relative block w-full rounded-[3px] bg-ui-controls/40 leading-none transition-shadow hover:ring-2 hover:ring-primary-400/60 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400 cursor-pointer ${isSquare ? "aspect-square" : ""} ${selected ? "ring-2 ring-primary-400" : ""}`}
style={outerStyle}
onClick={handleCardClick}
onKeyDown={handleCardKeyDown}
@@ -184,9 +190,7 @@ export const GalleryCard = memo(function GalleryCard({
</p>
<div className="flex min-h-0 flex-1 items-center justify-center">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-white/10 ring-1 ring-white/15">
<MusicIcon
className="text-xl text-white/70" />
<MusicIcon className="text-xl text-white/70" />
</div>
</div>
{item.fullImage && (
@@ -213,25 +217,25 @@ export const GalleryCard = memo(function GalleryCard({
{/* Selection checkbox chip (select mode only) */}
{selectMode && item.fullImage && (
<div
className={`pointer-events-none absolute left-2 top-2 z-10 flex h-5 w-5 items-center justify-center rounded-md border transition-colors ${
className={`pointer-events-none absolute left-2 top-2 z-10 flex h-5 w-5 items-center justify-center rounded-[3px] border transition-colors ${
selected
? "border-primary-400 bg-primary-400 text-white"
: "border-white/60 bg-black/40 text-transparent"
}`}
>
<CheckIcon className="text-[10px]" />
<CheckIcon className="text-[10px]" />
</div>
)}
{/* Hover overlay with media type + model badges and quick actions */}
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-between gap-2 bg-gradient-to-t rounded-b-lg from-black/70 to-transparent px-2 pb-2 pt-6 opacity-0 transition-opacity group-hover:opacity-100">
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-between gap-2 bg-gradient-to-t rounded-b-[3px] 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">
<div className="flex items-center gap-1.5 bg-black/60 px-2.5 py-1 text-xs font-medium text-white/90">
<DynamicIcon icon={mediaIcon} className="text-[10px]" />
{mediaLabel}
</div>
{modelDisplayName && modelIconPath && (
<div className="flex items-center gap-1 rounded-lg bg-black/60 px-2 py-1 text-[10px] text-white/80">
<div className="flex items-center gap-1 bg-black/60 px-2 py-1 text-[10px] text-white/80">
<img
src={modelIconPath}
alt=""
@@ -243,7 +247,7 @@ export const GalleryCard = memo(function GalleryCard({
</div>
{actionsSlot && !selectMode && (
<div className="pointer-events-auto flex shrink-0 items-center gap-0.5 rounded-lg bg-black/60 p-1 backdrop-blur-sm">
<div className="pointer-events-auto flex shrink-0 items-center gap-0.5 rounded-[3px] bg-black/60 p-1 backdrop-blur-sm">
{actionsSlot}
</div>
)}
@@ -1,5 +1,12 @@
import { memo, useCallback, type ReactNode } from "react";
import { BoxIcon, CheckIcon, ImageIcon, MusicIcon, PlayIcon, VideoIcon } from "lucide-react";
import {
BoxIcon,
CheckIcon,
ImageIcon,
MusicIcon,
PlayIcon,
VideoIcon,
} from "lucide-react";
import {
getCreatorIconPathForModelId,
getModelDisplayName,
@@ -51,7 +58,13 @@ export const GalleryRow = memo(function GalleryRow({
: isAudio
? MusicIcon
: ImageIcon;
const mediaLabel = isVideo ? "Video" : is3D ? "3D" : isAudio ? "Audio" : "Image";
const mediaLabel = isVideo
? "Video"
: is3D
? "3D"
: isAudio
? "Audio"
: "Image";
const effectiveModelId = modelId ?? item.modelId;
const modelDisplayName = effectiveModelId
@@ -87,23 +100,23 @@ export const GalleryRow = memo(function GalleryRow({
tabIndex={0}
onClick={handleRowClick}
onKeyDown={handleRowKeyDown}
className={`group flex cursor-pointer items-center gap-3 rounded-lg px-2.5 py-2 transition-colors hover:bg-white/[0.04] focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400/60 ${selected ? "bg-primary-400/10" : ""}`}
className={`group flex cursor-pointer items-center gap-3 rounded-[3px] px-2.5 py-2 transition-colors hover:bg-white/[0.04] focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-400/60 ${selected ? "bg-primary-400/10" : ""}`}
>
{/* Selection checkbox (select mode only) */}
{selectMode && (
<div
className={`pointer-events-none flex h-5 w-5 shrink-0 items-center justify-center rounded-md border transition-colors ${
className={`pointer-events-none flex h-5 w-5 shrink-0 items-center justify-center rounded-[3px] border transition-colors ${
selected
? "border-primary-400 bg-primary-400 text-white"
: "border-white/60 bg-black/40 text-transparent"
} ${item.fullImage ? "" : "invisible"}`}
>
<CheckIcon className="text-[10px]" />
<CheckIcon className="text-[10px]" />
</div>
)}
{/* Thumbnail */}
<div className="relative size-[100px] shrink-0 overflow-hidden rounded-md bg-ui-controls/40 leading-none">
<div className="relative size-[100px] shrink-0 overflow-hidden bg-ui-controls/40 leading-none">
<GalleryThumbnail
thumbnail={item.thumbnail}
stillThumbnail={item.stillThumbnail}
@@ -116,9 +129,7 @@ export const GalleryRow = memo(function GalleryRow({
{isVideo && item.thumbnail && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-black/55 backdrop-blur-sm">
<PlayIcon
className="ml-0.5 text-[9px] text-white/90" />
<PlayIcon className="ml-0.5 text-[9px] text-white/90" />
</span>
</div>
)}
@@ -127,7 +138,7 @@ export const GalleryRow = memo(function GalleryRow({
{/* Title + model / media meta */}
<div className="min-w-0 flex-1">
{loading ? (
<div className="relative h-4 w-2/3 max-w-[280px] overflow-hidden rounded bg-white/[0.06]">
<div className="relative h-4 w-2/3 max-w-[280px] overflow-hidden bg-white/[0.06]">
<div className="animate-shimmer absolute inset-0" />
</div>
) : (
@@ -144,7 +155,7 @@ export const GalleryRow = memo(function GalleryRow({
</div>
)}
{isAudio && item.fullImage && (
<div className="mt-1.5 max-w-md rounded-md bg-white/[0.04]">
<div className="mt-1.5 max-w-md bg-white/[0.04]">
<WaveformAudioPlayer
src={item.fullImage}
durationMillis={item.durationMillis}
@@ -11,14 +11,14 @@ export function GallerySelectToggle() {
const label = active ? "Exit selection" : "Select items";
return (
<div className="flex items-center rounded-lg border border-white/[0.08] bg-white/[0.04] p-0.5">
<div className="flex items-center rounded-[3px] border border-white/[0.08] bg-white/[0.04] p-0.5">
<Tooltip content={label} position="bottom" delay={300}>
<button
type="button"
aria-label={label}
aria-pressed={active}
onClick={() => setActive(!active)}
className={`flex h-6 w-6 items-center justify-center rounded-md text-xs transition-colors ${
className={`flex h-6 w-6 items-center justify-center rounded-[3px] text-xs transition-colors ${
active
? "bg-white/10 text-white"
: "text-white/45 hover:text-white/80"
@@ -26,7 +26,7 @@ export function GalleryViewToggle() {
<div
role="group"
aria-label="Gallery layout"
className="flex items-center gap-0.5 rounded-lg border border-white/[0.08] bg-white/[0.04] p-0.5"
className="flex items-center gap-0.5 rounded-[3px] border border-white/[0.08] bg-white/[0.04] p-0.5"
>
{OPTIONS.map(({ mode, icon, label }) => {
const active = viewMode === mode;
@@ -37,7 +37,7 @@ export function GalleryViewToggle() {
aria-label={label}
aria-pressed={active}
onClick={() => setViewMode(mode)}
className={`flex h-6 w-6 items-center justify-center rounded-md text-xs transition-colors ${
className={`flex h-6 w-6 items-center justify-center rounded-[3px] text-xs transition-colors ${
active
? "bg-white/10 text-white"
: "text-white/45 hover:text-white/80"
@@ -36,18 +36,16 @@ export const PendingCard = memo(function PendingCard({
const iconPath = getCreatorIconPathForModelId(modelId);
return (
<div className="group relative aspect-square w-full overflow-hidden rounded-lg bg-white/[0.03]">
<div className="group relative aspect-square w-full overflow-hidden rounded-[3px] bg-white/[0.03]">
<div className="animate-shimmer h-full w-full" />
{batchCount != null && batchCount > 1 && (
<div className="absolute left-2 right-2 top-2 z-10 rounded-md bg-black/60 px-2.5 py-1.5 text-center text-[10px] leading-snug text-white/70 backdrop-blur-sm">
<div className="absolute left-2 right-2 top-2 z-10 bg-black/60 px-2.5 py-1.5 text-center text-[10px] leading-snug text-white/70 backdrop-blur-sm">
Generating {batchCount} {batchNoun(mediaClass)} · Results may appear
one at a time
</div>
)}
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2">
<LoaderCircleIcon
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}%
@@ -38,9 +38,9 @@ export const PendingRow = memo(function PendingRow({
const iconPath = getCreatorIconPathForModelId(modelId);
return (
<div className="group flex items-center gap-3 rounded-lg px-2.5 py-2">
<div className="group flex items-center gap-3 px-2.5 py-2">
{/* Thumbnail placeholder */}
<div className="relative size-[100px] shrink-0 overflow-hidden rounded-md bg-white/[0.03] leading-none">
<div className="relative size-[100px] shrink-0 overflow-hidden bg-white/[0.03] leading-none">
<div className="animate-shimmer h-full w-full" />
<div className="absolute inset-0 flex items-center justify-center">
<LoaderCircleIcon
@@ -28,7 +28,7 @@ export const Input = React.forwardRef(
errorMessage,
...rest
}: InputProps,
ref: React.ForwardedRef<HTMLInputElement>
ref: React.ForwardedRef<HTMLInputElement>,
) => {
return (
<div className={twMerge("flex flex-col", className)}>
@@ -45,13 +45,13 @@ export const Input = React.forwardRef(
ref={ref}
id={id ? id : label ? label : undefined}
className={twMerge(
"h-10 w-full rounded-none px-3 py-2.5 outline-none",
"h-10 w-full rounded-[3px] px-3 py-2.5 outline-none",
"bg-ui-panel text-base-fg placeholder-base-fg/40",
"border border-ui-panel-border transition-colors duration-150 ease-in-out hover:border-white/40 focus:border-white focus:!outline-none",
"disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:border-ui-panel-border",
icon && "pl-10",
isError && "border-red-500 focus:border-red-500",
inputClassName
inputClassName,
)}
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
if (onFocus) {
@@ -71,7 +71,7 @@ export const Input = React.forwardRef(
</div>
</div>
);
}
},
);
Input.displayName = "Input";
@@ -147,19 +147,13 @@ const ExpandButton = ({ className, size = "md" }: ExpandButtonProps) => {
aria-label={expanded ? "Restore modal size" : "Expand modal"}
onClick={toggleExpanded}
className={twMerge(
"flex items-center justify-center rounded-none bg-black/40 text-white/60 transition-colors hover:bg-white hover:text-black",
"flex items-center justify-center rounded-[3px] bg-black/40 text-white/60 transition-colors hover:bg-white hover:text-black",
sizeClasses[size],
"relative z-[70]",
className,
)}
>
<DynamicIcon
icon={
expanded
? Minimize2Icon
: Maximize2Icon
}
/>
<DynamicIcon icon={expanded ? Minimize2Icon : Maximize2Icon} />
</button>
);
};
@@ -107,7 +107,7 @@ function ProviderTooltipContent({
onFinished?.();
}}
type="button"
className={`group flex cursor-pointer items-center justify-between rounded-lg px-2 py-2 transition-all ${
className={`group flex cursor-pointer items-center justify-between rounded-[3px] px-2 py-2 transition-all ${
selectedProvider === p
? "bg-ui-controls/70 border-l-4 border-primary"
: "hover:bg-ui-controls/50"
@@ -229,7 +229,7 @@ export function ClassyModelSelector({
: undefined;
const iconProvider = prov ?? allowedProviders[0];
return iconProvider ? (
<div className="mr-1 rounded-md p-1.5 bg-ui-controls/60 group-hover:bg-ui-controls/80 transition-colors">
<div className="mr-1 p-1.5 bg-ui-controls/60 group-hover:bg-ui-controls/80 transition-colors">
<span className="text-base-fg/70 group-hover:text-base-fg/90 text-lg">
{getProviderIcon(iconProvider)}
</span>
@@ -241,7 +241,7 @@ export function ClassyModelSelector({
isSameModel(item.model as Model | undefined, selectedModel) &&
selectedProvider &&
hasMultipleProviders ? (
<div className="mr-1 rounded-md p-1.5 bg-primary/60 group-hover:bg-primary/80 transition-colors">
<div className="mr-1 p-1.5 bg-primary/60 group-hover:bg-primary/80 transition-colors">
<span className="text-base-fg/70 group-hover:text-base-fg/90 text-lg">
{getProviderIcon(selectedProvider)}
</span>
@@ -313,7 +313,7 @@ export function ClassyModelSelector({
mode="hoverSelect"
maxListHeight={maxListHeight}
{...popoverProps}
buttonClassName="rounded-xl bg-ui-controls/90 hover:bg-ui-controls text-left shadow-sm px-3 py-1 gap-3 border border-ui-controls-border"
buttonClassName="rounded-[3px] bg-ui-controls/90 hover:bg-ui-controls text-left shadow-sm px-3 py-1 gap-3 border border-ui-controls-border"
renderTrigger={(selectedItem) => {
const modelTitle =
selectedItem?.label ?? selectedModel?.selectorName ?? "";
@@ -340,9 +340,7 @@ export function ClassyModelSelector({
</span>
</div>
</div>
<ChevronUpIcon
className="text-base text-base-fg/70 self-center" />
<ChevronUpIcon className="text-base text-base-fg/70 self-center" />
</div>
);
}}
@@ -81,9 +81,11 @@ export const BoardGrid = ({
const [viewportH, setViewportH] = useState(0);
const [marquee, setMarquee] = useState<Marquee | null>(null);
const [focusedId, setFocusedId] = useState<string | null>(null);
const marqueeRef = useRef<{ active: boolean; moved: boolean; additive: boolean }>(
{ active: false, moved: false, additive: false },
);
const marqueeRef = useRef<{
active: boolean;
moved: boolean;
additive: boolean;
}>({ active: false, moved: false, additive: false });
useLayoutEffect(() => {
const el = scrollRef.current;
@@ -103,7 +105,10 @@ export const BoardGrid = ({
const groups = useMemo<SectionGroup[] | null>(() => {
if (!sections || sections.length === 0) return null;
const known = new Set(sections.map((s) => s.id));
const bySection = new Map<string | null, Array<{ id: string; aspect: number }>>();
const bySection = new Map<
string | null,
Array<{ id: string; aspect: number }>
>();
for (const it of items) {
const key = it.sectionId && known.has(it.sectionId) ? it.sectionId : null;
const entry = { id: it.id, aspect: it.aspect };
@@ -119,7 +124,10 @@ export const BoardGrid = ({
}));
const ungrouped = bySection.get(null) ?? [];
return ungrouped.length > 0
? [{ id: null, name: "Ungrouped", collapsed: false, items: ungrouped }, ...named]
? [
{ id: null, name: "Ungrouped", collapsed: false, items: ungrouped },
...named,
]
: named;
}, [items, sections]);
@@ -313,7 +321,7 @@ export const BoardGrid = ({
})}
{marquee && marqueeRef.current.moved && (
<div
className="pointer-events-none absolute rounded-[4px] border border-primary bg-primary/10"
className="pointer-events-none absolute rounded-[3px] border border-primary bg-primary/10"
style={{
left: Math.min(marquee.x0, marquee.x1),
top: Math.min(marquee.y0, marquee.y1),
@@ -1,6 +1,12 @@
import { useContext, useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom";
import { FilmIcon, PlusIcon, RepeatIcon, SplineIcon, XIcon } from "lucide-react";
import {
FilmIcon,
PlusIcon,
RepeatIcon,
SplineIcon,
XIcon,
} from "lucide-react";
import { EngineContext } from "../../contexts/EngineContext/EngineContext";
import {
addBakedClipToObject,
@@ -11,16 +17,17 @@ import {
setClipLoop,
setClipTransitionEasing,
} from "../../actions";
import {
usePageSceneStore,
type ClipLane,
} from "../../PageSceneStore";
import { usePageSceneStore, type ClipLane } from "../../PageSceneStore";
import {
DEFAULT_EASING,
DEFAULT_TIMELINE_FPS,
} from "../../engine/timeline/types";
import { ToastTypes } from "../../enums";
import { fractionToTime, quantizeToFrame, timeToFraction } from "./timelineUtils";
import {
fractionToTime,
quantizeToFrame,
timeToFraction,
} from "./timelineUtils";
interface TimelineClipRowProps {
objectUuid: string;
@@ -62,9 +69,7 @@ export const TimelineClipRow = ({
// effectively invisible.
const [pickerAnchor, setPickerAnchor] = useState<DOMRect | null>(null);
const pickerMenuRef = useRef<HTMLDivElement>(null);
const selectedLaneId = usePageSceneStore(
(s) => s.timelineSelectedClipLaneId,
);
const selectedLaneId = usePageSceneStore((s) => s.timelineSelectedClipLaneId);
// A fixed-position popup can't follow its button, so any outside scroll
// or resize closes it (scrolling INSIDE the popup's own list is exempt).
@@ -154,14 +159,14 @@ export const TimelineClipRow = ({
return (
<div className="flex items-center gap-3 py-0.5">
<div className="flex w-32 shrink-0 items-center gap-2 truncate ps-6 text-xs text-base-fg/50">
<FilmIcon className="h-3 w-3 opacity-60" />
<FilmIcon className="h-3 w-3 opacity-60" />
<span className="truncate">Animation</span>
{bakedClips && bakedClips.length > 0 && (
<div className="shrink-0">
<button
type="button"
title="Add one of this object's baked animations"
className={`flex h-4 w-4 items-center justify-center rounded-[4px] transition-colors ${
className={`flex h-4 w-4 items-center justify-center rounded-[3px] transition-colors ${
pickerAnchor
? "bg-brand-primary text-white"
: "bg-white/10 text-base-fg/70 hover:bg-white/20 hover:text-base-fg"
@@ -172,7 +177,7 @@ export const TimelineClipRow = ({
)
}
>
<PlusIcon className="h-2.5 w-2.5" />
<PlusIcon className="h-2.5 w-2.5" />
</button>
{pickerAnchor &&
ReactDOM.createPortal(
@@ -283,7 +288,7 @@ export const TimelineClipRow = ({
editor && setClipLoop(editor, lane.id, !strip.loop)
}
>
<RepeatIcon className="h-2.5 w-2.5" />
<RepeatIcon className="h-2.5 w-2.5" />
</button>
<span className="min-w-0 flex-1 truncate">{strip.name}</span>
@@ -305,7 +310,7 @@ export const TimelineClipRow = ({
.setTimelineSelectedClipLane(null);
}}
>
<XIcon className="h-2.5 w-2.5" />
<XIcon className="h-2.5 w-2.5" />
</button>
)}
@@ -359,7 +364,7 @@ export const TimelineClipRow = ({
onPointerDown={(e) => e.stopPropagation()}
onClick={() => toggleTransition(lane)}
>
<SplineIcon className="h-2 w-2" />
<SplineIcon className="h-2 w-2" />
</button>
);
})}
@@ -15,7 +15,14 @@ import {
} from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import { Button } from "@storyteller/ui-button";
import { CheckIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, CircleCheckIcon, InfoIcon } from "lucide-react";
import {
CheckIcon,
ChevronDownIcon,
ChevronRightIcon,
ChevronUpIcon,
CircleCheckIcon,
InfoIcon,
} from "lucide-react";
import { Model, ModelInfo } from "@storyteller/model-list";
import { Tooltip } from "@storyteller/ui-tooltip";
@@ -136,7 +143,7 @@ function PortalTooltip({
zIndex: 9999,
}}
className={twMerge(
"pointer-events-auto rounded-none bg-ui-panel p-3 border border-ui-panel-border text-base-fg",
"pointer-events-auto rounded-[3px] bg-ui-panel p-3 border border-ui-panel-border text-base-fg",
className,
)}
>
@@ -237,7 +244,7 @@ function InfoHint({ content }: { content: ReactNode }) {
if (!pinnedRef.current) setOpen(false);
}}
>
<InfoIcon className="h-3.5 w-3.5" />
<InfoIcon className="h-3.5 w-3.5" />
</button>
{open &&
createPortal(
@@ -253,7 +260,7 @@ function InfoHint({ content }: { content: ReactNode }) {
transform: "translate(-50%, -100%)",
zIndex: 10000,
}}
className="pointer-events-auto rounded-none border border-ui-panel-border bg-ui-panel px-3 py-1.5 text-center text-xs leading-relaxed text-base-fg"
className="pointer-events-auto rounded-[3px] border border-ui-panel-border bg-ui-panel px-3 py-1.5 text-center text-xs leading-relaxed text-base-fg"
onClick={(e) => e.stopPropagation()}
onMouseEnter={() => {
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
@@ -349,7 +356,7 @@ function RichListRow({
if (!item.disabled) onClick?.();
}}
className={twMerge(
"group flex cursor-pointer items-center gap-3 rounded-none px-2 py-2 transition-colors",
"group flex cursor-pointer items-center gap-3 rounded-[3px] px-2 py-2 transition-colors",
item.selected ? "bg-ui-controls/70" : "hover:bg-ui-controls/50",
!item.selected && active ? "bg-ui-controls/50" : "",
item.disabled ? "!cursor-not-allowed opacity-50" : "",
@@ -404,10 +411,8 @@ function RichListRow({
{rightNode ??
(item.selected &&
(item.selectedRight ?? (
<span className="ml-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-none bg-white">
<CheckIcon
className="text-[11px] font-bold text-black" />
<span className="ml-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-[3px] bg-white">
<CheckIcon className="text-[11px] font-bold text-black" />
</span>
)))}
</div>
@@ -629,7 +634,7 @@ function SubmenuFlyout({
transform: "translateY(-50%)",
zIndex: 9999,
}}
className="pointer-events-auto w-[340px] rounded-none border border-ui-panel-border bg-ui-panel p-1.5 text-base-fg"
className="pointer-events-auto w-[340px] rounded-[3px] border border-ui-panel-border bg-ui-panel p-1.5 text-base-fg"
>
<div className="mb-1 mt-0.5 px-1.5 text-sm font-normal text-base-fg opacity-70">
{item.label}
@@ -643,9 +648,7 @@ function SubmenuFlyout({
canScrollUp ? "opacity-100" : "opacity-0",
)}
>
<ChevronUpIcon
className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
<ChevronUpIcon className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
</div>
<div
ref={scrollRef}
@@ -695,9 +698,7 @@ function SubmenuFlyout({
canScrollDown ? "opacity-100" : "opacity-0",
)}
>
<ChevronDownIcon
className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
<ChevronDownIcon className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
</div>
</div>
</div>,
@@ -850,7 +851,7 @@ export const PopoverMenu = ({
};
const className = twMerge(
"text-sm font-medium rounded-none px-2.5 py-1.5",
"text-sm font-medium rounded-[3px] px-2.5 py-1.5",
"flex gap-2 items-center justify-center outline-none",
"transition-colors duration-150",
"bg-ui-controls px-3 text-base-fg hover:bg-ui-controls/80 border border-ui-controls-border",
@@ -1051,9 +1052,7 @@ export const PopoverMenu = ({
<span className="opacity-70">{triggerLabel}</span>
<div className="flex items-center gap-2">
<span className="truncate">{selectedItem.label}</span>
<ChevronUpIcon
className="text-sm" />
<ChevronUpIcon className="text-sm" />
</div>
</div>
) : null}
@@ -1091,7 +1090,7 @@ export const PopoverMenu = ({
<div
ref={panelContentRef}
className={twMerge(
"z-10 min-w-48 mt-2 rounded-none bg-ui-panel p-1.5 border border-ui-panel-border overflow-visible",
"z-10 min-w-48 mt-2 rounded-[3px] bg-ui-panel p-1.5 border border-ui-panel-border overflow-visible",
position === "top" ? "mb-2" : "mt-2",
panelClassName,
)}
@@ -1130,9 +1129,7 @@ export const PopoverMenu = ({
canScrollUp ? "opacity-100" : "opacity-0",
)}
>
<ChevronUpIcon
className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
<ChevronUpIcon className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
</div>
<div
ref={scrollContainerRef}
@@ -1164,9 +1161,7 @@ export const PopoverMenu = ({
item={item}
active={openTooltipIdx === index}
rightNode={
<ChevronRightIcon
className="ml-1 shrink-0 text-xs text-base-fg/50" />
<ChevronRightIcon className="ml-1 shrink-0 text-xs text-base-fg/50" />
}
/>
</SubmenuFlyout>
@@ -1231,18 +1226,14 @@ export const PopoverMenu = ({
canScrollDown ? "opacity-100" : "opacity-0",
)}
>
<ChevronDownIcon
className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
<ChevronDownIcon className="animate-bounce text-sm text-base-fg/60 drop-shadow [animation-duration:1.1s]" />
</div>
</div>
) : mode === "hoverSelect" ? (
<div className="relative flex flex-col text-sm text-base-fg overflow-visible">
{maxListHeight && canScrollUp && (
<div className="absolute top-0 left-0 right-0 z-20 flex justify-center bg-gradient-to-b from-ui-panel via-ui-panel/80 to-transparent py-1.5 pointer-events-none">
<ChevronUpIcon
className="text-base-fg/60 text-xs animate-bounce" />
<ChevronUpIcon className="text-base-fg/60 text-xs animate-bounce" />
</div>
)}
<div
@@ -1280,7 +1271,7 @@ export const PopoverMenu = ({
}
}}
className={twMerge(
"group flex cursor-pointer items-start gap-2 rounded-none px-2 py-2 transition-all",
"group flex cursor-pointer items-start gap-2 rounded-[3px] px-2 py-2 transition-all",
item.selected
? "bg-white/10 border-l-2 border-white"
: "hover:bg-ui-controls/50",
@@ -1375,7 +1366,7 @@ export const PopoverMenu = ({
position="right"
delay={item.tooltipDelayMs ?? 300}
interactive
className="!pointer-events-auto z-50 min-w-48 rounded-none bg-ui-panel p-1.5 border border-ui-panel-border"
className="!pointer-events-auto z-50 min-w-48 rounded-[3px] bg-ui-panel p-1.5 border border-ui-panel-border"
onOpenChange={(open) =>
setOpenTooltipIdx((prev) =>
open
@@ -1401,9 +1392,7 @@ export const PopoverMenu = ({
</div>
{maxListHeight && canScrollDown && (
<div className="absolute bottom-0 left-0 right-0 z-20 flex justify-center bg-gradient-to-t from-ui-panel via-ui-panel/80 to-transparent py-1.5 pointer-events-none">
<ChevronDownIcon
className="text-base-fg/60 text-xs animate-bounce" />
<ChevronDownIcon className="text-base-fg/60 text-xs animate-bounce" />
</div>
)}
{showAddButton && onAdd && (
@@ -1440,7 +1429,7 @@ export const PopoverMenu = ({
position="right"
delay={item.tooltipDelayMs ?? 1000}
interactive
className="!pointer-events-auto z-50 min-w-48 rounded-none bg-ui-panel p-1.5 border border-ui-panel-border"
className="!pointer-events-auto z-50 min-w-48 rounded-[3px] bg-ui-panel p-1.5 border border-ui-panel-border"
onOpenChange={(open) =>
setOpenTooltipIdx((prev) =>
open ? index : prev === index ? null : prev,
@@ -1450,6 +1439,7 @@ export const PopoverMenu = ({
<Button
className={twMerge(
"flex w-full items-center shadow-none justify-between px-1.5",
"font-sans text-sm font-medium normal-case tracking-normal",
"bg-transparent hover:bg-ui-controls/60",
openTooltipIdx === index
? "bg-ui-controls/60"
@@ -1504,16 +1494,14 @@ export const PopoverMenu = ({
{mode === "toggle" && (
<span
className={twMerge(
"ml-2 h-5 w-5 rounded-none border flex items-center justify-center transition-colors",
"ml-2 h-5 w-5 rounded-[3px] border flex items-center justify-center transition-colors",
item.selected
? "border-white bg-white"
: "border-transparent bg-transparent",
)}
>
{item.selected && (
<CheckIcon
className="text-black text-xs font-bold" />
<CheckIcon className="text-black text-xs font-bold" />
)}
</span>
)}
@@ -1523,6 +1511,7 @@ export const PopoverMenu = ({
<Button
className={twMerge(
"flex w-full items-center shadow-none justify-between px-1.5",
"font-sans text-sm font-medium normal-case tracking-normal",
"bg-transparent hover:bg-ui-controls/60",
mode === "toggle" && item.selected
? "hover:bg-ui-controls/80"
@@ -1567,16 +1556,14 @@ export const PopoverMenu = ({
{mode === "toggle" && (
<span
className={twMerge(
"ml-2 h-5 w-5 rounded-none border flex items-center justify-center transition-colors",
"ml-2 h-5 w-5 rounded-[3px] border flex items-center justify-center transition-colors",
item.selected
? "border-white bg-white"
: "border-transparent bg-transparent",
)}
>
{item.selected && (
<CheckIcon
className="text-black text-xs font-bold" />
<CheckIcon className="text-black text-xs font-bold" />
)}
</span>
)}
@@ -42,7 +42,8 @@ interface PricingContentProps {
export function PricingContent({ title, subtitle }: PricingContentProps) {
const subscriptionStore = useSubscriptionState();
const hasActiveSub = subscriptionStore.hasPaidPlan();
const activePlanId = subscriptionStore.subscriptionInfo?.productSlug || "free";
const activePlanId =
subscriptionStore.subscriptionInfo?.productSlug || "free";
const [billingType, setBillingType] = useState("yearly");
const isYearly = billingType === "yearly";
@@ -58,7 +59,7 @@ export function PricingContent({ title, subtitle }: PricingContentProps) {
const handleUpdatePaymentMethod = async () => {
await invoke(
"storyteller_open_customer_portal_update_payment_method_command"
"storyteller_open_customer_portal_update_payment_method_command",
);
};
@@ -170,7 +171,7 @@ export function PricingContent({ title, subtitle }: PricingContentProps) {
tabs={billingTabs}
activeTab={billingType}
onTabChange={setBillingType}
className="w-fit rounded-none border border-white/15 bg-white/5"
className="w-fit rounded-[3px] border border-white/15 bg-white/5"
tabClassName="w-24 text-md"
/>
<span className="pointer-events-none absolute -left-6 -top-3 rounded-none bg-primary px-3 py-0.5 text-sm font-medium text-white">
@@ -403,7 +404,7 @@ const Feature = ({
: "border border-white/20 text-transparent",
)}
>
{included && <CheckIcon className="text-xs" />}
{included && <CheckIcon className="text-xs" />}
</div>
<span
className={twMerge(
@@ -320,7 +320,7 @@ const CharacterListView = ({
{Array.from({ length: 8 }).map((_, i) => (
<div
key={i}
className="flex flex-col overflow-hidden rounded-lg border border-transparent bg-base-fg/[0.05]"
className="flex flex-col overflow-hidden rounded-[3px] border border-transparent bg-base-fg/[0.05]"
>
<div className="aspect-square w-full overflow-hidden">
<div
@@ -332,7 +332,7 @@ const CharacterListView = ({
</div>
<div className="px-2 py-1.5 flex justify-center bg-base-fg/[0.04]">
<div
className="h-3 w-2/3 rounded bg-base-fg/[0.08]"
className="h-3 w-2/3 bg-base-fg/[0.08]"
style={{
animation: `charPulse 1.8s ease-in-out ${i * 0.07 + 0.1}s infinite`,
}}
@@ -352,7 +352,7 @@ const CharacterListView = ({
{/* Create New card */}
<button
onClick={onCreateClick}
className="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-lg border-2 border-dashed border-base-fg/10 bg-base-fg/[0.05] text-base-fg/60 transition-colors hover:border-base-fg/25 hover:text-base-fg/80"
className="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-[3px] border-2 border-dashed border-base-fg/10 bg-base-fg/[0.05] text-base-fg/60 transition-colors hover:border-base-fg/25 hover:text-base-fg/80"
>
<div className="flex aspect-square w-full flex-col items-center justify-center gap-2">
<PlusIcon className="text-lg" />
@@ -364,7 +364,7 @@ const CharacterListView = ({
{pendingCharacters.map((pending) => (
<div
key={`pending-${pending.name}`}
className="relative flex flex-col overflow-hidden rounded-lg border border-transparent bg-base-fg/[0.05]"
className="relative flex flex-col overflow-hidden rounded-[3px] border border-transparent bg-base-fg/[0.05]"
>
<div className="aspect-square w-full overflow-hidden bg-base-fg/[0.05]">
{pending.previewUrl ? (
@@ -401,7 +401,7 @@ const CharacterListView = ({
return (
<div
key={character.token}
className="group relative flex flex-col overflow-hidden rounded-lg border border-transparent bg-base-fg/[0.05] transition-colors hover:border-base-fg/25 hover:bg-base-fg/10"
className="group relative flex flex-col overflow-hidden rounded-[3px] border border-transparent bg-base-fg/[0.05] transition-colors hover:border-base-fg/25 hover:bg-base-fg/10"
>
<button
onClick={() => onSelectCharacter?.(character)}
@@ -570,7 +570,7 @@ const EditCharacterView = ({
{/* Avatar preview */}
{character.maybe_avatar?.cdn_url && (
<div className="flex h-56 max-h-56 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-base-fg/[0.05]">
<div className="flex h-56 max-h-56 shrink-0 items-center justify-center overflow-hidden bg-base-fg/[0.05]">
<img
src={character.maybe_avatar.cdn_url}
alt={character.name}
@@ -605,7 +605,7 @@ const EditCharacterView = ({
placeholder="Description..."
rows={3}
autoComplete="off"
className="w-full resize-none rounded-lg px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
className="w-full resize-none rounded-[3px] px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
/>
</div>
</div>
@@ -829,7 +829,7 @@ const NewCharacterView = ({
<div
ref={dropZoneRef}
className={twMerge(
"flex h-56 max-h-56 shrink-0 cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-base-fg/20 bg-base-fg/[0.05] transition-colors overflow-hidden",
"flex h-56 max-h-56 shrink-0 cursor-pointer flex-col items-center justify-center rounded-[3px] border-2 border-dashed border-base-fg/20 bg-base-fg/[0.05] transition-colors overflow-hidden",
isDragging && "border-blue-400 bg-blue-500/10",
)}
onDragEnter={handleDragEnter}
@@ -880,14 +880,14 @@ const NewCharacterView = ({
>
<button
onClick={() => setIsGalleryOpen(true)}
className="flex items-center gap-2 rounded-lg bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
className="flex items-center gap-2 rounded-[3px] bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
>
<ImagesIcon className="text-xs" />
Choose from Library
</button>
<button
onClick={() => fileInputRef.current?.click()}
className="flex items-center gap-2 rounded-lg bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
className="flex items-center gap-2 rounded-[3px] bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
>
<UploadIcon className="text-xs" />
Upload Image
@@ -931,7 +931,7 @@ const NewCharacterView = ({
placeholder="Description..."
rows={3}
autoComplete="off"
className="w-full resize-none rounded-lg px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
className="w-full resize-none rounded-[3px] px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
/>
</div>
</div>
@@ -115,7 +115,7 @@ const AudioRefTile = ({
}, []);
return (
<div className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer flex items-center justify-center">
<div className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer flex items-center justify-center">
<button
onClick={handleTogglePlay}
className="flex items-center justify-center w-full h-full"
@@ -255,7 +255,7 @@ export const ImagePromptRow = ({
style={style}
{...(allowReorder ? { ...attributes, ...listeners } : {})}
className={twMerge(
"glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 transition-opacity group",
"glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 transition-opacity group",
allowReorder
? "cursor-move hover:border-white/80"
: "cursor-pointer hover:border-white/80",
@@ -845,7 +845,7 @@ export const ImagePromptRow = ({
)}
<div
className={twMerge(
"absolute left-0 glass w-full rounded-t-2xl flex",
"absolute left-0 glass w-full rounded-t-[3px] flex",
showVideoReferenceSection ? "-top-[144px]" : "-top-[72px]",
className,
)}
@@ -916,7 +916,7 @@ export const ImagePromptRow = ({
.map((image, index) => (
<div
key={image.id}
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
onClick={() => onImageClick?.(image)}
>
<img
@@ -953,7 +953,7 @@ export const ImagePromptRow = ({
return (
<div
key={id}
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30"
className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30"
>
<div className="absolute inset-0">
<img
@@ -1006,7 +1006,7 @@ export const ImagePromptRow = ({
>
<Button
variant="action"
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-lg w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-[3px] w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
onClick={() => {
if (allowUpload) handleUploadClickStart();
else {
@@ -1045,7 +1045,7 @@ export const ImagePromptRow = ({
<div className="flex gap-2 items-center">
{endFrameImage ? (
<div
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
onClick={() => onImageClick?.(endFrameImage)}
>
<img
@@ -1066,7 +1066,7 @@ export const ImagePromptRow = ({
</button>
</div>
) : uploadingEnd ? (
<div className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30">
<div className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30">
<div className="absolute inset-0">
<img
src={URL.createObjectURL(uploadingEnd.file)}
@@ -1115,7 +1115,7 @@ export const ImagePromptRow = ({
>
<Button
variant="action"
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-lg w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-[3px] w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
onClick={() => {
if (allowUploadEnd) handleUploadClickEnd();
else {
@@ -1156,7 +1156,7 @@ export const ImagePromptRow = ({
{referenceVideos.map((video) => (
<div
key={video.id}
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer"
className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer"
>
<video
src={video.url}
@@ -1181,7 +1181,7 @@ export const ImagePromptRow = ({
</div>
))}
{uploadingVideo && (
<div className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30">
<div className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30">
<div className="absolute inset-0">
<video
src={URL.createObjectURL(uploadingVideo.file)}
@@ -1231,7 +1231,7 @@ export const ImagePromptRow = ({
>
<Button
variant="action"
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-lg w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-[3px] w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
onClick={handleUploadClickVideo}
>
<PlusIcon
@@ -1271,7 +1271,7 @@ export const ImagePromptRow = ({
/>
))}
{uploadingAudio && (
<div className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 flex items-center justify-center">
<div className="glass relative aspect-square overflow-hidden rounded-[3px] w-14 border-2 border-white/30 flex items-center justify-center">
<LoaderCircleIcon
className="h-6 w-6 animate-spin text-white" />
@@ -1281,7 +1281,7 @@ export const ImagePromptRow = ({
!uploadingAudio && (
<Button
variant="action"
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-lg w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
className="bg-ui-controls/40 hover:bg-ui-controls/60 aspect-square w-full overflow-hidden rounded-[3px] w-14 border-dashed border-2 border-black/5 dark:border-white/25 transition-all"
onClick={handleUploadClickAudio}
>
<PlusIcon
@@ -1296,7 +1296,7 @@ export const ImagePromptRow = ({
</div>
<div className="flex items-center">
<div className="w-[1px] h-full bg-white/10" />
<div className="flex items-center gap-2 w-[1px] h-full bg-base-fg/20 dark:bg-base-fg/10 rounded-lg" />
<div className="flex items-center gap-2 w-[1px] h-full bg-base-fg/20 dark:bg-base-fg/10" />
<div className="p-2">
<Button
variant="action"
@@ -20,7 +20,7 @@ const ANCHOR_GAP = 4;
const OPEN_GRACE_MS = 700;
const MENU_ROW =
"flex w-full items-center gap-2 rounded-md px-2 py-2 text-sm text-base-fg transition-colors hover:bg-ui-controls/60 cursor-pointer";
"flex w-full items-center gap-2 rounded-[3px] px-2 py-2 text-sm text-base-fg transition-colors hover:bg-ui-controls/60 cursor-pointer";
// Header subtitle and empty-replace-list copy per chip type.
const TYPE_COPY: Record<MentionItem["type"], { title: string; empty: string }> =
@@ -182,7 +182,7 @@ export function MentionChipMenu({
onWheel={(e) => e.stopPropagation()}
onTouchMove={(e) => e.stopPropagation()}
className={twMerge(
"fixed z-[9999] w-56 rounded-lg border border-ui-panel-border bg-ui-panel p-1 shadow-xl",
"fixed z-[9999] w-56 rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl",
"transform-gpu transition duration-75 ease-out",
entered
? "translate-y-0 opacity-100"
@@ -210,7 +210,7 @@ export function MentionChipMenu({
onClick={onPreview}
title="View full size"
aria-label={`View ${currentName}`}
className="group/avatar relative shrink-0 cursor-pointer overflow-hidden rounded-md"
className="group/avatar relative shrink-0 cursor-pointer overflow-hidden rounded-[3px]"
>
<ChipAvatar
preview={currentPreview}
@@ -255,7 +255,7 @@ export function MentionChipMenu({
<div className="flex items-center gap-2 px-2 py-1.5">
<button
type="button"
className="flex h-6 w-6 items-center justify-center rounded-md text-base-fg/60 transition-colors hover:bg-ui-controls/60 hover:text-base-fg"
className="flex h-6 w-6 items-center justify-center rounded-[3px] text-base-fg/60 transition-colors hover:bg-ui-controls/60 hover:text-base-fg"
onClick={() => setView("menu")}
aria-label="Back"
>
@@ -312,7 +312,7 @@ function ChipAvatar({
}) {
const FallbackIcon = type === "character" ? UserIcon : ImageIcon;
return (
<div className="flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-md border border-white/10 bg-black/20">
<div className="flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden border border-white/10 bg-black/20">
{preview ? (
<img src={preview} alt={name} className="h-full w-full object-cover" />
) : (
@@ -83,9 +83,9 @@ interface ChipMenuState {
// itself bg-ui-controls, so the default bg-ui-controls/80 chip disappears
// against it.
const CHIP_CLASS =
"mention-chip mx-0.5 inline-flex max-w-[10rem] cursor-pointer items-center gap-1 rounded-md border border-white/10 bg-ui-controls/80 px-1.5 py-0.5 align-middle leading-tight transition-colors hover:border-white/25 hover:bg-white/15 [@media(hover:none)]:bg-black/30 [@media(hover:none)]:border-white/15";
"mention-chip mx-0.5 inline-flex max-w-[10rem] cursor-pointer items-center gap-1 rounded-[3px] border border-white/10 bg-ui-controls/80 px-1.5 py-0.5 align-middle leading-tight transition-colors hover:border-white/25 hover:bg-white/15 [@media(hover:none)]:bg-black/30 [@media(hover:none)]:border-white/15";
const CHIP_IMG_CLASS =
"pointer-events-none h-4 w-4 shrink-0 rounded object-cover select-none";
"pointer-events-none h-4 w-4 shrink-0 object-cover select-none";
const CHIP_NAME_CLASS = "pointer-events-none truncate";
/** Coarse-pointer (touch) device — no hover, on-screen keyboard. */
@@ -496,7 +496,7 @@ function MentionDropdown({
onWheel={(e) => e.stopPropagation()}
onTouchMove={(e) => e.stopPropagation()}
onScroll={() => (lastScrollTs.current = performance.now())}
className="fixed z-[9999] w-64 max-w-[calc(100vw-2rem)] max-h-72 overflow-y-auto rounded-lg border border-ui-panel-border bg-ui-panel p-1 shadow-xl"
className="fixed z-[9999] w-64 max-w-[calc(100vw-2rem)] max-h-72 overflow-y-auto rounded-[3px] border border-ui-panel-border bg-ui-panel p-1 shadow-xl"
style={{
pointerEvents: "auto",
...(placement
@@ -523,7 +523,7 @@ function MentionDropdown({
key={item.token ?? `${item.label}-${i}`}
type="button"
className={twMerge(
"flex w-full items-center gap-2 rounded-md px-2 py-2 text-sm text-base-fg transition-colors cursor-pointer",
"flex w-full items-center gap-2 rounded-[3px] px-2 py-2 text-sm text-base-fg transition-colors cursor-pointer",
i === activeIndex ? "bg-ui-controls/80" : "hover:bg-ui-controls/60",
)}
// Selection happens on pointerup, not click: on mobile the
@@ -570,7 +570,7 @@ function MentionDropdown({
onClick={() => onSelect(item)}
onMouseEnter={() => onHover(i)}
>
<div className="h-8 w-8 flex-shrink-0 overflow-hidden rounded-md border border-white/10 flex items-center justify-center bg-black/20">
<div className="h-8 w-8 flex-shrink-0 overflow-hidden border border-white/10 flex items-center justify-center bg-black/20">
{item.type === "character" && item.preview ? (
<img
src={item.preview}
@@ -388,7 +388,7 @@ export const PromptBox2D = ({
)}
<div
className={twMerge(
"glass relative w-full rounded-2xl p-4",
"glass relative w-full rounded-[3px] p-4",
selectedImageModel?.canUseImagePrompt &&
isImageRowVisible &&
"rounded-t-none",
@@ -433,7 +433,7 @@ export const PromptBox2D = ({
ref={textareaRef}
rows={1}
placeholder="Describe your image..."
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -580,14 +580,14 @@ export const PromptBox2D = ({
referenceImages={referenceImages}
setReferenceImages={setReferenceImages}
uploadImage={uploadImage as any}
className="relative top-auto rounded-2xl"
className="relative top-auto rounded-[3px]"
/>
) : undefined
}
>
<textarea
placeholder="Describe your image..."
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -704,7 +704,7 @@ export const PromptBox3D = ({
)}
<div
className={twMerge(
"glass relative w-full rounded-2xl p-4",
"glass relative w-full rounded-[3px] p-4",
isPromptBoxFocused ? "!border !border-primary" : "",
selectedImageModel?.canUseImagePrompt &&
isImageRowVisible &&
@@ -755,7 +755,7 @@ export const PromptBox3D = ({
ref={textareaRef}
rows={1}
placeholder="Describe your image..."
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[300px]" : "max-h-[5.5em]"}`}
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none ${isExpanded ? "max-h-[300px]" : "max-h-[5.5em]"}`}
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -1052,14 +1052,14 @@ export const PromptBox3D = ({
referenceImages={referenceImages}
setReferenceImages={setReferenceImages}
uploadImage={uploadImage as any}
className="relative top-auto rounded-2xl"
className="relative top-auto rounded-[3px]"
/>
) : undefined
}
>
<textarea
placeholder="Describe your image..."
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -544,7 +544,7 @@ export const PromptBoxAudio = ({
<div className="relative z-20 flex flex-col">
<div
className={twMerge(
"glass relative w-full rounded-2xl p-4",
"glass relative w-full rounded-[3px] p-4",
isFocused
? "ring-1 ring-primary border-primary"
: "ring-1 ring-transparent",
@@ -565,7 +565,7 @@ export const PromptBoxAudio = ({
ref={textareaRef}
rows={1}
placeholder="Describe the music or sound you want..."
className="promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
className="promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -666,7 +666,7 @@ export const PromptBoxAudio = ({
>
<textarea
placeholder="Describe the music or sound you want..."
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -330,7 +330,7 @@ export const PromptBoxEdit = ({
{(supportsMaskedInpainting || isNanoBananaModel) && (
<div
className={twMerge(
"glass w-fit mx-auto rounded-xl px-2 py-2 flex items-center gap-3",
"glass w-fit mx-auto rounded-[3px] px-2 py-2 flex items-center gap-3",
selectedImageModel?.canUseImagePrompt &&
isImageRowVisible &&
"mb-[72px]",
@@ -346,7 +346,7 @@ export const PromptBoxEdit = ({
<Tooltip content="Undo" position="top" delay={200}>
<button
onClick={onUndo}
className="flex h-8 w-8 items-center justify-center rounded-lg text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed"
className="flex h-8 w-8 items-center justify-center rounded-[3px] text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed"
disabled={!onUndo}
>
<RotateCcwIcon className="h-4 w-4" />
@@ -355,7 +355,7 @@ export const PromptBoxEdit = ({
<Tooltip content="Redo" position="top" delay={200}>
<button
onClick={onRedo}
className="flex h-8 w-8 items-center justify-center rounded-lg text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed"
className="flex h-8 w-8 items-center justify-center rounded-[3px] text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-30 disabled:cursor-not-allowed"
disabled={!onRedo}
>
<Redo2Icon className="h-4 w-4" />
@@ -383,7 +383,7 @@ export const PromptBoxEdit = ({
)}
<div
className={twMerge(
"glass relative w-[860px] rounded-xl p-4",
"glass relative w-[860px] rounded-[3px] p-4",
isFocused && "ring-1 ring-primary border-primary",
selectedImageModel?.canUseImagePrompt &&
isImageRowVisible &&
@@ -428,7 +428,7 @@ export const PromptBoxEdit = ({
ref={textareaRef}
rows={1}
placeholder="Write what you want to change in your image and click generate..."
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
className={`promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none ${isExpanded ? "max-h-[500px]" : "max-h-[5.5em]"}`}
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -583,14 +583,14 @@ export const PromptBoxEdit = ({
referenceImages={referenceImages}
setReferenceImages={setReferenceImages}
uploadImage={uploadImage}
className="relative top-auto rounded-2xl"
className="relative top-auto rounded-[3px]"
/>
) : undefined
}
>
<textarea
placeholder="Write what you want to change in your image and click generate..."
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -24,7 +24,7 @@ export class PromptBoxErrorBoundary extends Component<Props, State> {
override render() {
if (this.state.hasError) {
return (
<div className="flex items-center justify-center rounded-xl border border-red-500/30 bg-red-500/10 px-6 py-4 text-sm text-red-300">
<div className="flex items-center justify-center rounded-[3px] border border-red-500/30 bg-red-500/10 px-6 py-4 text-sm text-red-300">
<span>Something went wrong with the prompt box.&nbsp;</span>
<button
className="underline hover:text-red-200"
@@ -455,7 +455,7 @@ export const PromptBoxImage = ({
<div className="relative z-20 flex flex-col">
<div
className={twMerge(
"glass relative w-full rounded-2xl p-4",
"glass relative w-full rounded-[3px] p-4",
isFocused
? "ring-1 ring-primary border-primary"
: "ring-1 ring-transparent",
@@ -476,7 +476,7 @@ export const PromptBoxImage = ({
ref={textareaRef}
rows={1}
placeholder="Describe what you want in the image..."
className="promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
className="promptbox-scrollbar text-md mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 transition-[height] duration-200 ease-out focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -613,7 +613,7 @@ export const PromptBoxImage = ({
>
<textarea
placeholder="Describe what you want in the image..."
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -1307,7 +1307,7 @@ export const PromptBoxVideo = ({
<button
type="button"
onClick={() => setIsCharactersModalOpen(true)}
className="flex h-9 items-center justify-center gap-1 rounded-lg border border-ui-controls-border bg-ui-controls px-3 text-sm font-medium text-base-fg transition-all duration-150 hover:bg-ui-controls/80 active:scale-95"
className="flex h-9 items-center justify-center gap-1 rounded-[3px] border border-ui-controls-border bg-ui-controls px-3 text-sm font-medium text-base-fg transition-all duration-150 hover:bg-ui-controls/80 active:scale-95"
>
@Characters
</button>
@@ -1332,7 +1332,7 @@ export const PromptBoxVideo = ({
<div className="relative z-20 flex flex-col gap-3">
<div
className={twMerge(
"glass relative w-full rounded-2xl p-4",
"glass relative w-full rounded-[3px] p-4",
isFocused
? "ring-1 ring-primary border-primary"
: "ring-1 ring-transparent",
@@ -1347,7 +1347,7 @@ export const PromptBoxVideo = ({
keyframeMode={!isReferenceMode}
/>
{selectedModel?.textToVideoSupported === false && (
<div className="mb-2 flex items-center gap-1.5 rounded-md bg-ui-controls/60 px-2.5 py-1.5 text-xs text-base-fg/70">
<div className="mb-2 flex items-center gap-1.5 bg-ui-controls/60 px-2.5 py-1.5 text-xs text-base-fg/70">
<InfoIcon
className="h-3 w-3 shrink-0" />
@@ -1374,7 +1374,7 @@ export const PromptBoxVideo = ({
? "Use @Image1, @Video1, @Audio1... to reference uploads in prompt..."
: "Describe what you want to happen in the video..."
}
className="promptbox-scrollbar text-md relative mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg"
className="promptbox-scrollbar text-md relative mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg"
onKeyDown={(e) => {
if (e.key !== "Enter") return;
const isSubmitCombo = enterToGenerate && !e.shiftKey;
@@ -1397,7 +1397,7 @@ export const PromptBoxVideo = ({
ref={textareaRef}
rows={1}
placeholder="Describe what you want to happen in the video..."
className="promptbox-scrollbar text-md relative mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none"
className="promptbox-scrollbar text-md relative mb-2 min-h-[2.5em] w-full resize-y overflow-y-auto rounded-[3px] bg-transparent pb-2 pr-8 pt-1 text-base-fg placeholder-base-fg/60 focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -1638,13 +1638,13 @@ export const PromptBoxVideo = ({
? "Use @Image1, @Video1, @Audio1... to reference uploads in prompt..."
: "Describe what you want to happen in the video..."
}
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg"
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg"
style={{ resize: "none" }}
/>
) : (
<textarea
placeholder="Describe what you want to happen in the video..."
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
className="promptbox-scrollbar text-md h-full min-h-0 w-full resize-none overflow-y-auto rounded-[3px] bg-transparent text-base-fg placeholder-base-fg/60 focus:outline-none"
value={prompt}
onChange={handleChange}
onPaste={handlePaste}
@@ -16,7 +16,7 @@ import { twMerge } from "tailwind-merge";
// `buttonClassName` to PopoverMenu triggers (e.g. the generation count
// picker) so they match this button.
export const PROMPT_TOOLBAR_ICON_BUTTON_CLASSES =
"flex h-9 w-9 items-center justify-center rounded-lg border-0 bg-transparent px-0 text-base-fg/50 shadow-none transition-colors hover:bg-base-fg/10 hover:text-base-fg/90";
"flex h-9 w-9 items-center justify-center rounded-[3px] border-0 bg-transparent px-0 text-base-fg/50 shadow-none transition-colors hover:bg-base-fg/10 hover:text-base-fg/90";
interface PromptClearAllButtonProps {
onClick: () => void;
@@ -242,7 +242,7 @@ export const AudioReferenceRow = forwardRef<
type="button"
onClick={() => audioInputRef.current?.click()}
disabled={isUploadingAudio}
className="flex h-9 items-center gap-1.5 rounded-lg border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
className="flex h-9 items-center gap-1.5 rounded-[3px] border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
>
<PlusIcon className="h-3 w-3" />
{isUploadingAudio ? "Uploading…" : "Add audio"}
@@ -252,7 +252,7 @@ export const AudioReferenceRow = forwardRef<
type="button"
onClick={onPickAudioFromLibrary}
disabled={isUploadingAudio}
className="flex h-9 items-center gap-1.5 rounded-lg border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
className="flex h-9 items-center gap-1.5 rounded-[3px] border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
>
<FolderOpenIcon className="h-3 w-3" />
From library
@@ -270,7 +270,7 @@ export const AudioReferenceRow = forwardRef<
{referenceImages.map((image) => (
<div
key={image.id}
className="group relative h-9 w-9 overflow-hidden rounded-lg border border-ui-controls-border"
className="group relative h-9 w-9 overflow-hidden rounded-[3px] border border-ui-controls-border"
>
<img
src={image.url}
@@ -294,7 +294,7 @@ export const AudioReferenceRow = forwardRef<
type="button"
onClick={() => imageInputRef.current?.click()}
disabled={isUploadingImage}
className="flex h-9 items-center gap-1.5 rounded-lg border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
className="flex h-9 items-center gap-1.5 rounded-[3px] border border-dashed border-ui-controls-border bg-ui-controls/50 px-3 text-sm text-base-fg/70 transition-colors hover:bg-ui-controls hover:text-base-fg disabled:cursor-wait disabled:opacity-60"
>
<PlusIcon className="h-3 w-3" />
{isUploadingImage ? "Uploading…" : "Add image"}
@@ -337,7 +337,7 @@ function AudioRefTile({
}, [audio.url, isPlaying]);
return (
<div className="group flex h-9 items-center gap-2 rounded-lg border border-ui-controls-border bg-ui-controls px-2.5">
<div className="group flex h-9 items-center gap-2 rounded-[3px] border border-ui-controls-border bg-ui-controls px-2.5">
<button
type="button"
aria-label={isPlaying ? "Stop" : "Play"}
@@ -356,7 +356,7 @@ function AudioRefTile({
type="button"
aria-label="Remove audio"
onClick={() => onRemove(audio.id)}
className="flex h-4 w-4 items-center justify-center rounded text-base-fg/40 transition-colors hover:text-base-fg"
className="flex h-4 w-4 items-center justify-center rounded-[3px] text-base-fg/40 transition-colors hover:text-base-fg"
>
<XIcon className="h-2.5 w-2.5" />
</button>
@@ -64,7 +64,7 @@ export function AudioTuningPopover({
key={hz}
type="button"
onClick={() => onSampleRateChange?.(hz)}
className={`rounded-md border px-2 py-1 text-xs font-medium transition-colors ${
className={`rounded-[3px] border px-2 py-1 text-xs font-medium transition-colors ${
hz === sampleRateHz
? "border-white/30 bg-white/15 text-base-fg"
: "border-transparent bg-white/5 text-base-fg/60 hover:bg-white/10 hover:text-base-fg"
@@ -77,7 +77,7 @@ export function SoundsSettingsPopover({
<button
type="button"
onClick={() => onBpmChange?.(null)}
className="rounded bg-white/5 px-1.5 py-0.5 text-[10px] font-medium text-base-fg/60 transition-colors hover:bg-white/10 hover:text-base-fg"
className="rounded-[3px] bg-white/5 px-1.5 py-0.5 text-[10px] font-medium text-base-fg/60 transition-colors hover:bg-white/10 hover:text-base-fg"
>
Auto
</button>
@@ -107,7 +107,7 @@ export function SoundsSettingsPopover({
type="button"
title={entry.label}
onClick={() => onMusicalKeyChange?.(entry.value)}
className={`rounded-md border px-1.5 py-1 text-xs font-medium transition-colors ${
className={`rounded-[3px] border px-1.5 py-1 text-xs font-medium transition-colors ${
entry.value === musicalKey
? "border-white/30 bg-white/15 text-base-fg"
: "border-transparent bg-white/5 text-base-fg/60 hover:bg-white/10 hover:text-base-fg"
@@ -43,7 +43,7 @@ export function StylePromptRow({
type="button"
aria-label="Clear style"
onClick={() => onChange("")}
className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-base-fg/40 transition-colors hover:bg-white/10 hover:text-base-fg"
className="flex h-5 w-5 shrink-0 items-center justify-center rounded-[3px] text-base-fg/40 transition-colors hover:bg-white/10 hover:text-base-fg"
>
<XIcon className="h-3 w-3" />
</button>
@@ -88,7 +88,7 @@ export const DeckCard = ({
return (
<div
className={twMerge(
"glass group relative aspect-square w-14 shrink-0 overflow-hidden rounded-lg border-2 border-white/30 transition-all duration-200",
"glass group relative aspect-square w-14 shrink-0 overflow-hidden rounded-[3px] border-2 border-white/30 transition-all duration-200",
!item.uploading &&
"cursor-pointer hover:border-white/80 hover:cursor-zoom-in",
item.kind === "audio" && "hover:cursor-pointer",
@@ -143,12 +143,12 @@ export const DeckCard = ({
)}
{item.kind === "video" && (
<div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center rounded bg-black/60 text-white">
<div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center bg-black/60 text-white">
<VideoIcon className="h-2.5 w-2.5" />
</div>
)}
{item.kind === "audio" && (
<div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center rounded bg-black/60 text-white">
<div className="pointer-events-none absolute left-[3px] top-[3px] flex h-4 w-4 items-center justify-center bg-black/60 text-white">
<MusicIcon className="h-2.5 w-2.5" />
</div>
)}
@@ -235,7 +235,7 @@ export const DeckAddMenu = ({
key={action.key}
type="button"
onClick={action.onSelect}
className="flex w-full items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left text-[13px] font-medium text-base-fg transition-colors hover:bg-white/10"
className="flex w-full items-center gap-2.5 rounded-[3px] px-2.5 py-1.5 text-left text-[13px] font-medium text-base-fg transition-colors hover:bg-white/10"
>
<DynamicIcon
icon={
@@ -274,13 +274,13 @@ export const DeckPreviewModal = ({ item, onClose }: DeckPreviewModalProps) => (
src={item.previewUrl ?? item.url}
controls
autoPlay
className="max-h-[90vh] max-w-[90vw] rounded-lg object-contain"
className="max-h-[90vh] max-w-[90vw] object-contain"
/>
) : (
<img
src={item.previewUrl ?? item.url}
alt={item.name}
className="max-h-[90vh] max-w-[90vw] rounded-lg object-contain"
className="max-h-[90vh] max-w-[90vw] object-contain"
/>
))}
</Modal>
@@ -142,7 +142,7 @@ export const DeckSlotCard = ({
type="button"
onClick={() => enabledActions[0]?.onSelect()}
className={twMerge(
"glass flex aspect-square w-14 flex-col items-center justify-center gap-0.5 rounded-lg border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:z-10 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25",
"glass flex aspect-square w-14 flex-col items-center justify-center gap-0.5 rounded-[3px] border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:z-10 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25",
tiltClass,
)}
>
@@ -252,7 +252,7 @@ export const ReferenceDeck = ({
<button
type="button"
onClick={handleAddClick}
className="glass flex aspect-square w-14 -rotate-6 flex-col items-center justify-center gap-0.5 rounded-lg border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25"
className="glass flex aspect-square w-14 -rotate-6 flex-col items-center justify-center gap-0.5 rounded-[3px] border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all duration-200 hover:rotate-0 hover:scale-105 hover:bg-ui-controls/60 dark:border-white/25"
>
<PlusIcon className="text-lg opacity-80" />
<span className="text-[9px] font-medium leading-none opacity-70">
@@ -270,7 +270,7 @@ export const ReferenceDeck = ({
const expandedPanel = (
<div
className={twMerge(
"glass w-max rounded-xl border border-white/10 p-2 shadow-2xl",
"glass w-max rounded-[3px] border border-white/10 p-2 shadow-2xl",
// In-flow (fullscreen modal): cap at the container width so the
// cards wrap into rows instead of overflowing the modal.
alwaysExpanded
@@ -296,7 +296,7 @@ export const ReferenceDeck = ({
<button
type="button"
onClick={onClearAll}
className="flex items-center gap-1 rounded px-1 py-0.5 text-[11px] text-base-fg/60 transition-colors hover:text-red-400"
className="flex items-center gap-1 rounded-[3px] px-1 py-0.5 text-[11px] text-base-fg/60 transition-colors hover:text-red-400"
>
<Trash2Icon className="h-2.5 w-2.5" />
Clear all
@@ -362,7 +362,7 @@ export const ReferenceDeck = ({
<button
type="button"
onClick={handleAddClick}
className="glass flex aspect-square w-14 shrink-0 items-center justify-center rounded-lg border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all hover:bg-ui-controls/60 dark:border-white/25"
className="glass flex aspect-square w-14 shrink-0 items-center justify-center rounded-[3px] border-2 border-dashed border-black/5 bg-ui-controls/40 text-base-fg transition-all hover:bg-ui-controls/60 dark:border-white/25"
>
<PlusIcon className="text-xl opacity-80" />
</button>,
@@ -444,7 +444,7 @@ export function PromptBoxDropOverlay({
return (
<div
className={twMerge(
"promptbox-drop-overlay pointer-events-none absolute inset-0 z-40 flex flex-col items-center justify-center gap-2 rounded-2xl border-2 border-dashed bg-[#161618]/85 backdrop-blur-sm",
"promptbox-drop-overlay pointer-events-none absolute inset-0 z-40 flex flex-col items-center justify-center gap-2 rounded-[3px] border-2 border-dashed bg-[#161618]/85 backdrop-blur-sm",
rejected ? "border-red-400/80" : "border-primary",
)}
>
@@ -452,7 +452,7 @@ export function PromptBoxDropOverlay({
{kinds.map((kind, i) => (
<div
key={kind.label}
className="promptbox-drop-chip flex h-9 w-9 items-center justify-center rounded-lg bg-white/10 text-base-fg/90"
className="promptbox-drop-chip flex h-9 w-9 items-center justify-center bg-white/10 text-base-fg/90"
style={{ animationDelay: `${i * 50}ms` }}
>
<DynamicIcon icon={kind.icon} className="text-sm" />
@@ -28,7 +28,7 @@ export const Select = ({
className,
}: ListDropdownProps) => {
const selectedOption = options.find(
(option: SelectOption) => option.value === value
(option: SelectOption) => option.value === value,
) || { label: placeholder || "", value: "" };
return (
@@ -38,7 +38,7 @@ export const Select = ({
<>
<ListboxButton
id={id}
className="relative h-10 w-full cursor-pointer rounded-none bg-ui-controls text-base-fg border border-ui-controls-border py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-colors duration-150 ease-in-out sm:text-sm focus:!outline-none hover:border-white/40"
className="relative h-10 w-full cursor-pointer rounded-[3px] bg-ui-controls text-base-fg border border-ui-controls-border py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-colors duration-150 ease-in-out sm:text-sm focus:!outline-none hover:border-white/40"
>
<span
className={twMerge("block truncate", !value && "opacity-50")}
@@ -46,7 +46,7 @@ export const Select = ({
{selectedOption.label}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5">
<ChevronDownIcon aria-hidden="true" />
<ChevronDownIcon aria-hidden="true" />
</span>
</ListboxButton>
@@ -57,7 +57,7 @@ export const Select = ({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-none bg-ui-panel border border-ui-panel-border text-base focus:outline-none sm:text-sm">
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-[3px] bg-ui-panel border border-ui-panel-border text-base focus:outline-none sm:text-sm">
{options.map((option, itemIdx) => (
<ListboxOption
key={itemIdx}
@@ -66,7 +66,7 @@ export const Select = ({
"relative cursor-pointer select-none py-2 pl-7 pr-2 transition-colors duration-150 ease-in-out",
!selected ? "text-base-fg/90" : "text-base-fg",
selected && "bg-white/10",
focus && "bg-white text-black"
focus && "bg-white text-black",
)
}
value={option.value}
@@ -82,10 +82,7 @@ export const Select = ({
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-2.5">
<CheckIcon
aria-hidden="true"
className="text-xs" />
<CheckIcon aria-hidden="true" className="text-xs" />
</span>
) : null}
</>
@@ -9,13 +9,18 @@ interface SwitchProps {
offClassName?: string;
}
export function Switch({ enabled, setEnabled, className, offClassName }: SwitchProps) {
export function Switch({
enabled,
setEnabled,
className,
offClassName,
}: SwitchProps) {
return (
<HeadlessSwitch checked={enabled} onChange={setEnabled} as={Fragment}>
{({ checked, disabled }) => (
<button
className={clsx(
"group inline-flex h-6 w-11 items-center rounded-none border transition-colors",
"group inline-flex h-6 w-11 items-center rounded-[3px] border transition-colors",
checked
? "border-white bg-white"
: (offClassName ?? "border-ui-controls-border bg-transparent"),
@@ -26,8 +31,10 @@ export function Switch({ enabled, setEnabled, className, offClassName }: SwitchP
<span className="sr-only">Enable notifications</span>
<span
className={clsx(
"size-4 rounded-none transition",
checked ? "translate-x-[22px] bg-black" : "translate-x-1 bg-white/70",
"size-4 rounded-[2px] transition",
checked
? "translate-x-[22px] bg-black"
: "translate-x-1 bg-white/70",
)}
/>
</button>
@@ -72,14 +72,14 @@ export const TabSelector: React.FC<TabSelectorProps> = ({
<TabGroup selectedIndex={selectedIndex} onChange={handleTabChange}>
<TabList
className={twMerge(
"glass glass-no-hover relative inline-flex min-w-fit overflow-x-auto rounded-none p-0.5 py-1 !shadow-none",
"glass glass-no-hover relative inline-flex min-w-fit overflow-x-auto rounded-[3px] p-0.5 py-1 !shadow-none",
listClassName,
)}
>
{/* Animated indicator */}
<div
className={twMerge(
"absolute top-1 z-10 h-[calc(100%-8px)] rounded-none bg-white transition-all duration-200 ease-in-out",
"absolute top-1 z-10 h-[calc(100%-8px)] rounded-[2px] bg-white transition-all duration-200 ease-in-out",
indicatorClassName,
)}
style={{
@@ -99,7 +99,7 @@ export const TabSelector: React.FC<TabSelectorProps> = ({
disabled={disabled}
className={({ selected }) =>
twMerge(
"relative z-20 mx-0.5 min-w-max rounded-none border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out",
"relative z-20 mx-0.5 min-w-max rounded-[2px] border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out",
"focus-visible:outline-none focus-visible:ring-0",
selected
? twMerge("text-black", selectedTabClassName)
@@ -57,6 +57,9 @@ export function Toaster({
<RHToaster
position={position}
toastOptions={{
style: {
borderRadius: "3px",
},
success: {
style: {
background: "#ffffff",
@@ -175,7 +175,8 @@ export const Tooltip = ({
// in Chromium/WebKit (our glass surfaces use backdrop-blur), so a fixed
// tooltip inside one would otherwise be positioned/clipped incorrectly.
const backdropFilter =
cs.backdropFilter || (cs as unknown as Record<string, string>)["webkitBackdropFilter"];
cs.backdropFilter ||
(cs as unknown as Record<string, string>)["webkitBackdropFilter"];
if (
cs.transform !== "none" ||
cs.willChange === "transform" ||
@@ -342,54 +343,54 @@ export const Tooltip = ({
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
ref={setTooltipRef}
{...(portal ? { "data-modal-outside-safe": "" } : {})}
onPointerEnter={() => interactive && setIsHoveringTooltip(true)}
onPointerLeave={() => interactive && setIsHoveringTooltip(false)}
onClick={() => {
if (closeOnClick) {
setIsShowing(false);
}
}}
style={{
...getStyleForPosition(),
transitionDelay: `${delay}ms`,
transitionProperty: "opacity",
transitionDuration: "200ms",
transitionTimingFunction: "ease-out",
zIndex,
}}
className={twMerge(
isFixed ? "fixed" : "absolute",
"w-max rounded-none bg-ui-panel border border-ui-panel-border",
interactive
? "pointer-events-auto p-3"
: "px-2.5 py-1.5 font-mono text-[11px] font-medium uppercase tracking-[0.1em] pointer-events-none",
"text-base-fg",
className ? className : "",
)}
>
{interactive ? (
content
) : (
<div className="flex flex-col gap-1">
{content}
{imageSrc && (
<img
src={imageSrc}
alt="tooltip"
className="mb-1 aspect-square w-56 rounded-none"
/>
)}
{description && (
<p className="font-sans text-xs font-normal normal-case tracking-normal text-base-fg/80">
{description}
</p>
)}
</div>
)}
</div>
<div
ref={setTooltipRef}
{...(portal ? { "data-modal-outside-safe": "" } : {})}
onPointerEnter={() => interactive && setIsHoveringTooltip(true)}
onPointerLeave={() => interactive && setIsHoveringTooltip(false)}
onClick={() => {
if (closeOnClick) {
setIsShowing(false);
}
}}
style={{
...getStyleForPosition(),
transitionDelay: `${delay}ms`,
transitionProperty: "opacity",
transitionDuration: "200ms",
transitionTimingFunction: "ease-out",
zIndex,
}}
className={twMerge(
isFixed ? "fixed" : "absolute",
"w-max rounded-[3px] bg-ui-panel border border-ui-panel-border",
interactive
? "pointer-events-auto p-3"
: "px-2.5 py-1.5 font-mono text-[11px] font-medium uppercase tracking-[0.1em] pointer-events-none",
"text-base-fg",
className ? className : "",
)}
>
{interactive ? (
content
) : (
<div className="flex flex-col gap-1">
{content}
{imageSrc && (
<img
src={imageSrc}
alt="tooltip"
className="mb-1 aspect-square w-56 rounded-none"
/>
)}
{description && (
<p className="font-sans text-xs font-normal normal-case tracking-normal text-base-fg/80">
{description}
</p>
)}
</div>
)}
</div>
</Transition>
);