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

@@ -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