init elas atelier #1
@@ -61,13 +61,13 @@ export default function AssetManager({ mode = 'manage', onSelect }: Props) {
|
|||||||
const isImage = (name: string) => /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(name);
|
const isImage = (name: string) => /\.(jpg|jpeg|png|webp|gif|svg)$/i.test(name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="asset-mgr space-y-8">
|
||||||
{alert && (
|
{alert && (
|
||||||
<div
|
<div
|
||||||
className={`p-4 mb-6 text-sm font-display italic border ${
|
className={`asset-alert p-4 mb-6 text-sm font-display italic border ${
|
||||||
alert.type === 'success'
|
alert.type === 'success'
|
||||||
? 'bg-[color-mix(in_srgb,var(--green)_18%,transparent)] text-[var(--green)] border-[var(--green)]/40'
|
? 'asset-alert--ok bg-[color-mix(in_srgb,var(--green)_18%,transparent)] text-[var(--green)] border-[var(--green)]/40'
|
||||||
: 'bg-[color-mix(in_srgb,var(--red)_18%,transparent)] text-[var(--red)] border-[var(--red)]/40'
|
: 'asset-alert--err bg-[color-mix(in_srgb,var(--red)_18%,transparent)] text-[var(--red)] border-[var(--red)]/40'
|
||||||
}`}
|
}`}
|
||||||
style={{ borderRadius: 2 }}
|
style={{ borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
@@ -77,31 +77,31 @@ export default function AssetManager({ mode = 'manage', onSelect }: Props) {
|
|||||||
|
|
||||||
{/* Upload Zone */}
|
{/* Upload Zone */}
|
||||||
<div
|
<div
|
||||||
className="glass p-6 border-dashed border-2 border-[var(--surface2)] hover:border-[var(--mauve)] transition-colors group relative cursor-pointer"
|
className="asset-drop glass p-6 border-dashed border-2 border-[var(--surface2)] hover:border-[var(--mauve)] transition-colors group relative cursor-pointer"
|
||||||
onClick={() => fileRef.current?.click()}
|
onClick={() => fileRef.current?.click()}
|
||||||
onDragOver={e => { e.preventDefault(); e.stopPropagation(); }}
|
onDragOver={e => { e.preventDefault(); e.stopPropagation(); }}
|
||||||
onDrop={e => { e.preventDefault(); e.stopPropagation(); if (e.dataTransfer.files.length) handleUpload(e.dataTransfer.files); }}
|
onDrop={e => { e.preventDefault(); e.stopPropagation(); if (e.dataTransfer.files.length) handleUpload(e.dataTransfer.files); }}
|
||||||
>
|
>
|
||||||
<input ref={fileRef} type="file" multiple className="hidden" onChange={e => { if (e.target.files?.length) handleUpload(e.target.files); }} />
|
<input ref={fileRef} type="file" multiple className="hidden" onChange={e => { if (e.target.files?.length) handleUpload(e.target.files); }} />
|
||||||
<div className="text-center py-4">
|
<div className="asset-drop-body text-center py-4">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" className="mx-auto mb-4 text-[var(--subtext0)] group-hover:text-[var(--mauve)] transition-colors"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" className="mx-auto mb-4 text-[var(--subtext0)] group-hover:text-[var(--mauve)] transition-colors"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/></svg>
|
||||||
<p className="font-display italic text-xl text-[var(--text)] group-hover:text-[var(--mauve)] transition-colors">Click or drag to upload assets</p>
|
<p className="asset-drop-title font-display italic text-xl text-[var(--text)] group-hover:text-[var(--mauve)] transition-colors">Click or drag to upload assets</p>
|
||||||
<p className="font-sans text-xs uppercase tracking-[0.2em] text-[var(--subtext0)] mt-2">Any file type · up to 50 MB</p>
|
<p className="font-sans text-xs uppercase tracking-[0.2em] text-[var(--subtext0)] mt-2">Any file type · up to 50 MB</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grid */}
|
{/* Grid */}
|
||||||
{assets.length === 0 ? (
|
{assets.length === 0 ? (
|
||||||
<div className="text-center py-20 font-display italic text-[var(--subtext0)]">No assets uploaded yet.</div>
|
<div className="asset-empty text-center py-20 font-display italic text-[var(--subtext0)]">No assets uploaded yet.</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
<div className="asset-grid grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
||||||
{assets.map(asset => (
|
{assets.map(asset => (
|
||||||
<div
|
<div
|
||||||
key={asset.name}
|
key={asset.name}
|
||||||
className="group relative aspect-square overflow-hidden bg-[var(--surface0)] border border-[var(--surface2)]/60 hover:border-[var(--mauve)]/60 transition-colors shadow-md flex flex-col"
|
className="asset-tile group relative aspect-square overflow-hidden bg-[var(--surface0)] border border-[var(--surface2)]/60 hover:border-[var(--mauve)]/60 transition-colors shadow-md flex flex-col"
|
||||||
style={{ borderRadius: 2 }}
|
style={{ borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
<div className="flex-1 overflow-hidden bg-[var(--mantle)] relative cursor-pointer">
|
<div className="asset-thumb flex-1 overflow-hidden bg-[var(--mantle)] relative cursor-pointer">
|
||||||
{isImage(asset.name) ? (
|
{isImage(asset.name) ? (
|
||||||
<img src={asset.url} className="w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-opacity" alt={asset.name} />
|
<img src={asset.url} className="w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-opacity" alt={asset.name} />
|
||||||
) : (
|
) : (
|
||||||
@@ -111,7 +111,7 @@ export default function AssetManager({ mode = 'manage', onSelect }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Hover overlay */}
|
{/* Hover overlay */}
|
||||||
<div className="absolute inset-0 bg-[var(--crust)]/75 backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-3">
|
<div className="asset-actions absolute inset-0 bg-[var(--crust)]/75 backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-3">
|
||||||
{mode === 'select' && onSelect && (
|
{mode === 'select' && onSelect && (
|
||||||
<button
|
<button
|
||||||
onClick={() => onSelect(asset)}
|
onClick={() => onSelect(asset)}
|
||||||
@@ -129,7 +129,7 @@ export default function AssetManager({ mode = 'manage', onSelect }: Props) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-2 bg-[var(--surface0)] text-[10px] truncate border-t border-[var(--surface2)]/50 text-[var(--subtext1)] font-mono">{asset.name}</div>
|
<div className="asset-name p-2 bg-[var(--surface0)] text-[10px] truncate border-t border-[var(--surface2)]/50 text-[var(--subtext1)] font-mono">{asset.name}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2413,6 +2413,21 @@ html.cs-cursor-on.cybersigil .cs-cursor { opacity: 1; }
|
|||||||
content: "\00a0]";
|
content: "\00a0]";
|
||||||
color: color-mix(in srgb, currentColor 70%, transparent);
|
color: color-mix(in srgb, currentColor 70%, transparent);
|
||||||
}
|
}
|
||||||
|
/* Icon-only buttons get no brackets — `[ icon ]` reads odd and overflows
|
||||||
|
* fixed-width chrome. */
|
||||||
|
.cybersigil .btn--icon::before,
|
||||||
|
.cybersigil .btn--icon::after { content: none; }
|
||||||
|
/* Asset-tile hover actions: drop brackets + tighten so Insert+Delete fit
|
||||||
|
* the small square tiles. */
|
||||||
|
.cybersigil .asset-actions { gap: 0.4rem; }
|
||||||
|
.cybersigil .asset-actions .btn::before,
|
||||||
|
.cybersigil .asset-actions .btn::after { content: none; }
|
||||||
|
.cybersigil .asset-actions .btn {
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
font-size: 0.66rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-actions .btn--icon { padding: 0; }
|
||||||
.cybersigil .btn--primary {
|
.cybersigil .btn--primary {
|
||||||
color: var(--rosewater);
|
color: var(--rosewater);
|
||||||
border-color: var(--mauve);
|
border-color: var(--mauve);
|
||||||
@@ -2804,6 +2819,135 @@ html.cs-cursor-on.cybersigil .cs-cursor { opacity: 1; }
|
|||||||
font-family: var(--font-sans) !important;
|
font-family: var(--font-sans) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Asset library — a file-sector readout. Dashed drop-sector, scanline tile
|
||||||
|
* panels with a thorny sigil growth, `/path` filename bars, log-line alerts. */
|
||||||
|
.cybersigil .asset-alert {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
font-family: var(--font-sans) !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
font-size: 0.74rem !important;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-alert::before {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-alert--ok::before { content: ">> "; }
|
||||||
|
.cybersigil .asset-alert--err::before { content: "!! "; }
|
||||||
|
|
||||||
|
.cybersigil .asset-drop {
|
||||||
|
border-style: dashed !important;
|
||||||
|
border-color: color-mix(in srgb, var(--sky) 55%, transparent) !important;
|
||||||
|
background:
|
||||||
|
repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(0, 0, 0, 0) 0 2px,
|
||||||
|
color-mix(in srgb, var(--sky) 6%, transparent) 2px 3px,
|
||||||
|
rgba(0, 0, 0, 0) 3px 4px
|
||||||
|
),
|
||||||
|
color-mix(in srgb, var(--surface0) 74%, transparent) !important;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-drop:hover {
|
||||||
|
border-color: var(--mauve) !important;
|
||||||
|
box-shadow: 0 0 0 1px color-mix(in srgb, var(--mauve) 40%, transparent);
|
||||||
|
}
|
||||||
|
.cybersigil .asset-drop svg { stroke-width: 1.5; }
|
||||||
|
.cybersigil .asset-drop-title {
|
||||||
|
font-family: var(--font-display) !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-shadow: -1px 0 0 var(--sky), 1px 0 0 var(--mauve);
|
||||||
|
}
|
||||||
|
.cybersigil .asset-drop-title::before {
|
||||||
|
content: "> ";
|
||||||
|
color: var(--sky);
|
||||||
|
}
|
||||||
|
.cybersigil .asset-drop-title::after {
|
||||||
|
content: "_";
|
||||||
|
color: var(--mauve);
|
||||||
|
animation: cs-blink 1.05s steps(1, jump-none) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cybersigil .asset-empty {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-style: normal;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
color: var(--subtext0);
|
||||||
|
}
|
||||||
|
.cybersigil .asset-empty::before { content: "// "; color: var(--sky); opacity: 0.7; }
|
||||||
|
|
||||||
|
.cybersigil .asset-tile {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
border-color: color-mix(in srgb, var(--sky) 26%, transparent) !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px color-mix(in srgb, var(--sky) 16%, transparent),
|
||||||
|
0 0 0 1px color-mix(in srgb, var(--sky) 10%, transparent) !important;
|
||||||
|
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-tile::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
z-index: 2;
|
||||||
|
background-color: color-mix(in srgb, var(--sky) 70%, transparent);
|
||||||
|
-webkit-mask: var(--cs-corner) center / contain no-repeat;
|
||||||
|
mask: var(--cs-corner) center / contain no-repeat;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: background-color 0.16s ease;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-tile:hover {
|
||||||
|
border-color: var(--mauve) !important;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px color-mix(in srgb, var(--mauve) 36%, transparent),
|
||||||
|
4px 4px 0 0 var(--mauve) !important;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-tile:hover::after {
|
||||||
|
background-color: var(--mauve);
|
||||||
|
}
|
||||||
|
.cybersigil .asset-thumb {
|
||||||
|
background:
|
||||||
|
repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(0, 0, 0, 0) 0 2px,
|
||||||
|
color-mix(in srgb, var(--sky) 7%, transparent) 2px 3px,
|
||||||
|
rgba(0, 0, 0, 0) 3px 4px
|
||||||
|
),
|
||||||
|
var(--mantle);
|
||||||
|
}
|
||||||
|
.cybersigil .asset-thumb > div span {
|
||||||
|
font-family: var(--font-sans) !important;
|
||||||
|
color: var(--sky);
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-tile:hover .asset-thumb img {
|
||||||
|
filter:
|
||||||
|
drop-shadow(-2px 0 0 color-mix(in srgb, var(--mauve) 60%, transparent))
|
||||||
|
drop-shadow(2px 0 0 color-mix(in srgb, var(--sky) 60%, transparent))
|
||||||
|
saturate(0.92) contrast(1.05);
|
||||||
|
}
|
||||||
|
.cybersigil .asset-actions {
|
||||||
|
background: color-mix(in srgb, var(--crust) 82%, transparent) !important;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-name {
|
||||||
|
font-family: var(--font-sans) !important;
|
||||||
|
color: var(--sky);
|
||||||
|
border-top-color: color-mix(in srgb, var(--sky) 30%, transparent) !important;
|
||||||
|
background: var(--crust) !important;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.cybersigil .asset-name::before { content: "/"; opacity: 0.55; }
|
||||||
|
.cybersigil .asset-tile:hover .asset-name {
|
||||||
|
color: var(--mauve);
|
||||||
|
border-top-color: color-mix(in srgb, var(--mauve) 45%, transparent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Confirm dialog — hard terminal halt prompt. */
|
/* Confirm dialog — hard terminal halt prompt. */
|
||||||
.cybersigil .cdialog-panel {
|
.cybersigil .cdialog-panel {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user