mirror of
https://github.com/nimbold/Firelink.git
synced 2026-07-26 12:08:27 +00:00
feat(ui): modernize add download window
This commit is contained in:
@@ -687,15 +687,30 @@ export const AddDownloadsModal = () => {
|
||||
icon: LucideIcon;
|
||||
color: string;
|
||||
}) => (
|
||||
<div className="flex flex-col bg-bg-input/50 border border-border-modal/40 rounded-lg p-2.5 shadow-sm">
|
||||
<div className="add-download-summary-card flex flex-col">
|
||||
<div className="flex items-center gap-1.5 text-text-muted mb-1">
|
||||
<Icon size={12} className={color} />
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider">{title}</span>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-wider">{title}</span>
|
||||
</div>
|
||||
<span className="text-sm font-semibold text-text-primary truncate">{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const selectMediaFormat = (index: number) => {
|
||||
if (selectedItemIndex === null) return;
|
||||
const newItems = [...parsedItems];
|
||||
const selectedItem = newItems[selectedItemIndex];
|
||||
const format = selectedItem?.formats?.[index];
|
||||
if (!selectedItem || !format) return;
|
||||
|
||||
selectedItem.selectedFormat = index;
|
||||
selectedItem.size = format.detail || 'Unknown';
|
||||
selectedItem.sizeBytes = format.bytes || 0;
|
||||
const baseName = selectedItem.file.substring(0, selectedItem.file.lastIndexOf('.')) || selectedItem.file;
|
||||
selectedItem.file = `${baseName}.${format.ext}`;
|
||||
setParsedItems(newItems);
|
||||
};
|
||||
|
||||
const requiredBytes = parsedItems.reduce((acc, item) => acc + (item.sizeBytes || 0), 0);
|
||||
const requiredStr = requiredBytes > 0
|
||||
? (requiredBytes < 1024 * 1024 ? `${(requiredBytes / 1024).toFixed(1)} KB`
|
||||
@@ -716,24 +731,24 @@ export const AddDownloadsModal = () => {
|
||||
/>
|
||||
)}
|
||||
<div className="app-modal-backdrop fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div className="app-modal w-[900px] h-[650px] flex flex-col overflow-hidden text-sm">
|
||||
<div className="app-modal add-download-modal w-[900px] h-[650px] flex flex-col overflow-hidden text-sm">
|
||||
|
||||
{/* Main Content Split */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
|
||||
{/* Left Column: URLs and Preview */}
|
||||
<div className="w-[55%] border-r border-border-modal flex flex-col bg-main-bg/50">
|
||||
<div className="p-5 flex-1 flex flex-col gap-5">
|
||||
<div className="add-download-left w-[55%] border-r border-border-modal flex flex-col">
|
||||
<div className="add-download-pane p-5 flex-1 flex flex-col gap-5">
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-text-primary font-semibold">
|
||||
<div className="add-download-section-title flex items-center gap-2">
|
||||
<Link size={16} className="text-blue-500" />
|
||||
Download Links
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
className="w-full h-32 bg-bg-input/80 border border-border-modal rounded-lg p-3 text-[13px] text-text-primary focus:outline-none focus:border-accent resize-none font-mono shadow-inner transition-colors"
|
||||
className="add-download-control add-download-links-input w-full h-32 p-3 text-[13px] resize-none font-mono"
|
||||
placeholder="Paste HTTP, HTTPS, FTP, or SFTP URLs here..."
|
||||
value={urls}
|
||||
onChange={(e) => setUrls(e.target.value)}
|
||||
@@ -743,7 +758,7 @@ export const AddDownloadsModal = () => {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMetadataRefreshNonce(value => value + 1)}
|
||||
className="flex items-center gap-1.5 text-[11px] text-blue-500 hover:text-blue-400 font-medium"
|
||||
className="add-download-link-button flex items-center gap-1.5 text-[11px] font-medium"
|
||||
>
|
||||
<RefreshCw size={12} /> Refresh Metadata
|
||||
</button>
|
||||
@@ -758,19 +773,19 @@ export const AddDownloadsModal = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 flex-1 overflow-hidden">
|
||||
<div className="flex items-center gap-2 text-text-primary font-semibold">
|
||||
<div className="add-download-section-title flex items-center gap-2">
|
||||
<ArrowRight size={16} className="text-blue-500" />
|
||||
Preview
|
||||
</div>
|
||||
<div className="flex-1 border border-border-modal rounded-lg overflow-hidden bg-bg-input/30 flex flex-col">
|
||||
<div className="bg-sidebar-bg/50 border-b border-border-modal px-3 py-2 flex text-[11px] font-semibold text-text-muted uppercase tracking-wider">
|
||||
<div className="add-download-preview flex-1 overflow-hidden flex flex-col">
|
||||
<div className="add-download-preview-header px-3 py-2 flex text-[11px] font-semibold text-text-muted uppercase tracking-wider">
|
||||
<div className="flex-[2]">File</div>
|
||||
<div className="flex-1">Size</div>
|
||||
<div className="flex-[1.5]">Status</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-2 space-y-1">
|
||||
<div className="flex-1 overflow-y-auto p-2 space-y-1" role="listbox" aria-label="Download preview">
|
||||
{parsedItems.length === 0 ? (
|
||||
<div className="h-full flex items-center justify-center text-text-muted text-xs italic">
|
||||
<div className="add-download-empty h-full flex items-center justify-center text-text-muted text-xs">
|
||||
No links added yet.
|
||||
</div>
|
||||
) : (
|
||||
@@ -778,10 +793,19 @@ export const AddDownloadsModal = () => {
|
||||
<div
|
||||
key={i}
|
||||
onClick={() => setSelectedItemIndex(i)}
|
||||
className={`flex flex-col text-xs px-2 py-2 cursor-pointer rounded-md transition-all group ${
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
setSelectedItemIndex(i);
|
||||
}
|
||||
}}
|
||||
role="option"
|
||||
aria-selected={selectedItemIndex === i}
|
||||
tabIndex={0}
|
||||
className={`add-download-preview-row flex flex-col text-xs px-2 py-2 cursor-pointer rounded-md group ${
|
||||
selectedItemIndex === i
|
||||
? 'bg-blue-500/10 border border-blue-500/30 shadow-sm'
|
||||
: 'hover:bg-item-hover border border-transparent'
|
||||
? 'is-selected border'
|
||||
: 'border border-transparent'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center w-full">
|
||||
@@ -808,16 +832,16 @@ export const AddDownloadsModal = () => {
|
||||
</div>
|
||||
|
||||
{/* Right Column: Settings */}
|
||||
<div className="w-[45%] flex flex-col overflow-y-auto bg-bg-modal">
|
||||
<div className="p-6 space-y-7">
|
||||
<div className="add-download-settings w-[45%] flex flex-col overflow-y-auto">
|
||||
<div className="p-6 space-y-5">
|
||||
|
||||
{/* Media Format (Dynamic) */}
|
||||
{selectedItemIndex !== null && parsedItems[selectedItemIndex]?.isMedia && (
|
||||
<section className="app-card relative overflow-hidden p-4">
|
||||
<section className="add-download-section add-download-media-section relative overflow-hidden p-4">
|
||||
<div className="absolute top-0 right-0 p-2 opacity-10">
|
||||
<Video size={48} />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-text-primary mb-3 relative z-10">
|
||||
<div className="add-download-section-title flex items-center gap-2 mb-3 relative z-10">
|
||||
<Video size={16} className="text-purple-500" /> Media Format
|
||||
</div>
|
||||
|
||||
@@ -830,25 +854,25 @@ export const AddDownloadsModal = () => {
|
||||
<div className="space-y-3 relative z-10">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-[10px] uppercase font-bold tracking-wider text-text-muted">Available Streams</label>
|
||||
<div className="flex flex-col gap-1 max-h-48 overflow-y-auto pr-1">
|
||||
<div className="flex flex-col gap-1 max-h-48 overflow-y-auto pr-1" role="radiogroup" aria-label="Available media streams">
|
||||
{parsedItems[selectedItemIndex].formats!.map((f, idx) => {
|
||||
const isSelected = parsedItems[selectedItemIndex].selectedFormat === idx;
|
||||
const Icon = f.type === 'Audio' ? Music : Film;
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
const newItems = [...parsedItems];
|
||||
newItems[selectedItemIndex].selectedFormat = idx;
|
||||
newItems[selectedItemIndex].size = f.detail || 'Unknown';
|
||||
newItems[selectedItemIndex].sizeBytes = f.bytes || 0;
|
||||
// Update filename extension
|
||||
const baseName = newItems[selectedItemIndex].file.substring(0, newItems[selectedItemIndex].file.lastIndexOf('.')) || newItems[selectedItemIndex].file;
|
||||
newItems[selectedItemIndex].file = `${baseName}.${f.ext}`;
|
||||
setParsedItems(newItems);
|
||||
onClick={() => selectMediaFormat(idx)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
selectMediaFormat(idx);
|
||||
}
|
||||
}}
|
||||
className={`flex items-center justify-between px-3 py-2 rounded-lg cursor-pointer text-xs border transition-all ${
|
||||
isSelected ? 'bg-purple-500/10 border-purple-500/30 text-purple-600 dark:text-purple-400 font-semibold shadow-sm' : 'bg-bg-input border-border-modal text-text-secondary hover:border-border-modal/80 hover:bg-item-hover/50'
|
||||
role="radio"
|
||||
aria-checked={isSelected}
|
||||
tabIndex={0}
|
||||
className={`add-download-format-row flex items-center justify-between px-3 py-2 cursor-pointer text-xs border ${
|
||||
isSelected ? 'is-selected text-purple-600 dark:text-purple-400 font-semibold' : 'text-text-secondary'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
@@ -874,8 +898,8 @@ export const AddDownloadsModal = () => {
|
||||
)}
|
||||
|
||||
{/* Save Location */}
|
||||
<section>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-text-primary mb-3">
|
||||
<section className="add-download-section">
|
||||
<div className="add-download-section-title flex items-center gap-2 mb-3">
|
||||
<FolderPlus size={16} className="text-blue-500" /> Save Location
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
@@ -883,11 +907,12 @@ export const AddDownloadsModal = () => {
|
||||
type="text"
|
||||
readOnly
|
||||
value={saveLocation}
|
||||
className="flex-1 bg-bg-input border border-border-modal rounded-md px-3 py-1.5 text-xs text-text-muted font-mono"
|
||||
className="add-download-control flex-1 px-3 py-1.5 text-xs text-text-muted font-mono"
|
||||
aria-label="Save location"
|
||||
/>
|
||||
<button
|
||||
onClick={handleBrowse}
|
||||
className="bg-item-hover hover:bg-item-hover/80 text-text-primary border border-border-modal px-3 py-1.5 rounded-md text-xs font-medium transition-colors"
|
||||
className="add-download-button add-download-button-secondary px-3 text-xs font-medium"
|
||||
>
|
||||
Browse
|
||||
</button>
|
||||
@@ -895,14 +920,14 @@ export const AddDownloadsModal = () => {
|
||||
</section>
|
||||
|
||||
{/* Transfer Settings */}
|
||||
<section>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-text-primary mb-3">
|
||||
<section className="add-download-section">
|
||||
<div className="add-download-section-title flex items-center gap-2 mb-3">
|
||||
<Settings size={16} className="text-blue-500" /> Transfer Settings
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-xs text-text-secondary font-medium">Target Queue</label>
|
||||
<select value={selectedQueueId} onChange={e=>setSelectedQueueId(e.target.value)} className="w-32 bg-bg-input border border-border-modal rounded-lg px-2 py-1 text-xs text-text-primary focus:border-accent focus:outline-none">
|
||||
<select value={selectedQueueId} onChange={e=>setSelectedQueueId(e.target.value)} className="add-download-control add-download-select w-32 px-2 py-1 text-xs" aria-label="Target queue">
|
||||
{queues.map(q => (
|
||||
<option key={q.id} value={q.id}>{q.name}</option>
|
||||
))}
|
||||
@@ -912,19 +937,19 @@ export const AddDownloadsModal = () => {
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-xs text-text-secondary font-medium">Connections per File</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<input type="range" min="1" max="16" value={connections} onChange={e=>setConnections(Number(e.target.value))} className="w-24 accent-blue-500" disabled={parsedItems.some(i => i.isMedia)} />
|
||||
<span className="text-xs text-text-primary font-mono w-4 text-right">{connections}</span>
|
||||
<input type="range" min="1" max="16" value={connections} onChange={e=>setConnections(Number(e.target.value))} className="add-download-range w-24" disabled={parsedItems.some(i => i.isMedia)} aria-label="Connections per file" />
|
||||
<span className="add-download-value text-xs text-text-primary font-mono w-6 text-center">{connections}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center gap-2 text-xs text-text-secondary font-medium cursor-pointer">
|
||||
<input type="checkbox" checked={speedLimitEnabled} onChange={e=>setSpeedLimitEnabled(e.target.checked)} className="rounded border-border-modal text-blue-500 focus:ring-blue-500/20" />
|
||||
<input type="checkbox" checked={speedLimitEnabled} onChange={e=>setSpeedLimitEnabled(e.target.checked)} className="add-download-checkbox" />
|
||||
Limit speed per file
|
||||
</label>
|
||||
{speedLimitEnabled && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<input type="number" value={speedLimit} onChange={e=>setSpeedLimit(e.target.value)} className="w-16 bg-bg-input border border-border-modal rounded-lg px-2 py-1 text-xs font-mono text-text-primary focus:border-accent focus:outline-none" />
|
||||
<input type="number" value={speedLimit} onChange={e=>setSpeedLimit(e.target.value)} className="add-download-control w-16 px-2 py-1 text-xs font-mono" aria-label="Speed limit per file" />
|
||||
<span className="text-[10px] text-text-muted">KiB/s</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -933,28 +958,29 @@ export const AddDownloadsModal = () => {
|
||||
</section>
|
||||
|
||||
{/* Authorization */}
|
||||
<section>
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-text-primary mb-3">
|
||||
<section className="add-download-section">
|
||||
<div className="add-download-section-title flex items-center gap-2 mb-3">
|
||||
<Shield size={16} className="text-blue-500" /> Authorization
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-xs text-text-secondary font-medium cursor-pointer mb-3">
|
||||
<input type="checkbox" checked={useAuth} onChange={e=>setUseAuth(e.target.checked)} className="rounded border-border-modal text-blue-500 focus:ring-blue-500/20" />
|
||||
<input type="checkbox" checked={useAuth} onChange={e=>setUseAuth(e.target.checked)} className="add-download-checkbox" />
|
||||
Use authorization
|
||||
</label>
|
||||
|
||||
{useAuth && (
|
||||
<div className="space-y-2.5 pl-5 border-l-2 border-border-modal/50">
|
||||
<input type="text" value={username} onChange={e=>setUsername(e.target.value)} placeholder="Username" className="w-full bg-bg-input border border-border-modal rounded-lg px-3 py-1.5 text-xs text-text-primary focus:border-accent focus:outline-none" />
|
||||
<input type="password" value={password} onChange={e=>setPassword(e.target.value)} placeholder="Password" className="w-full bg-bg-input border border-border-modal rounded-lg px-3 py-1.5 text-xs text-text-primary focus:border-accent focus:outline-none" />
|
||||
<input type="text" value={username} onChange={e=>setUsername(e.target.value)} placeholder="Username" className="add-download-control w-full px-3 py-1.5 text-xs" />
|
||||
<input type="password" value={password} onChange={e=>setPassword(e.target.value)} placeholder="Password" className="add-download-control w-full px-3 py-1.5 text-xs" />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Advanced */}
|
||||
<section className="pt-2 border-t border-border-modal/50">
|
||||
<section className="add-download-section add-download-advanced">
|
||||
<button
|
||||
onClick={() => setAdvancedExpanded(!advancedExpanded)}
|
||||
className="flex items-center gap-2 text-sm font-semibold text-text-primary w-full hover:text-blue-500 transition-colors"
|
||||
className="add-download-advanced-toggle flex items-center gap-2 text-sm font-semibold text-text-primary w-full"
|
||||
aria-expanded={advancedExpanded}
|
||||
>
|
||||
{advancedExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
Advanced Transfer
|
||||
@@ -963,30 +989,30 @@ export const AddDownloadsModal = () => {
|
||||
{advancedExpanded && (
|
||||
<div className="mt-4 space-y-4 pl-6">
|
||||
<label className="flex items-center gap-2 text-xs text-text-secondary font-medium cursor-pointer">
|
||||
<input type="checkbox" checked={checksumEnabled} onChange={e=>setChecksumEnabled(e.target.checked)} className="rounded border-border-modal text-blue-500 focus:ring-blue-500/20" />
|
||||
<input type="checkbox" checked={checksumEnabled} onChange={e=>setChecksumEnabled(e.target.checked)} className="add-download-checkbox" />
|
||||
Verify Checksum
|
||||
</label>
|
||||
|
||||
{checksumEnabled && (
|
||||
<div className="flex gap-2">
|
||||
<select value={checksumAlgo} onChange={e=>setChecksumAlgo(e.target.value)} className="w-24 bg-bg-input border border-border-modal rounded-lg px-2 text-xs text-text-primary focus:border-accent focus:outline-none">
|
||||
<select value={checksumAlgo} onChange={e=>setChecksumAlgo(e.target.value)} className="add-download-control add-download-select w-24 px-2 text-xs" aria-label="Checksum algorithm">
|
||||
<option>MD5</option><option>SHA-1</option><option>SHA-256</option>
|
||||
</select>
|
||||
<input type="text" value={checksumValue} onChange={e=>setChecksumValue(e.target.value)} placeholder="Expected digest" className="flex-1 bg-bg-input border border-border-modal rounded-lg px-3 py-1.5 text-xs font-mono text-text-primary focus:border-accent focus:outline-none" />
|
||||
<input type="text" value={checksumValue} onChange={e=>setChecksumValue(e.target.value)} placeholder="Expected digest" className="add-download-control flex-1 px-3 py-1.5 text-xs font-mono" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-[10px] uppercase font-bold tracking-wider text-text-muted mb-1">Headers</label>
|
||||
<textarea value={headers} onChange={e=>setHeaders(e.target.value)} className="w-full h-12 bg-bg-input border border-border-modal rounded-lg px-3 py-1.5 text-xs font-mono text-text-primary focus:border-accent focus:outline-none resize-none" />
|
||||
<textarea value={headers} onChange={e=>setHeaders(e.target.value)} className="add-download-control w-full h-12 px-3 py-1.5 text-xs font-mono resize-none" aria-label="Request headers" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] uppercase font-bold tracking-wider text-text-muted mb-1">Cookies</label>
|
||||
<input type="text" value={cookies} onChange={e=>setCookies(e.target.value)} placeholder="name=value; other=value" className="w-full bg-bg-input border border-border-modal rounded-lg px-3 py-1.5 text-xs font-mono text-text-primary focus:border-accent focus:outline-none" />
|
||||
<input type="text" value={cookies} onChange={e=>setCookies(e.target.value)} placeholder="name=value; other=value" className="add-download-control w-full px-3 py-1.5 text-xs font-mono" aria-label="Cookies" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-[10px] uppercase font-bold tracking-wider text-text-muted mb-1">Mirrors</label>
|
||||
<textarea value={mirrors} onChange={e=>setMirrors(e.target.value)} className="w-full h-12 bg-bg-input border border-border-modal rounded-lg px-3 py-1.5 text-xs font-mono text-text-primary focus:border-accent focus:outline-none resize-none" />
|
||||
<textarea value={mirrors} onChange={e=>setMirrors(e.target.value)} className="add-download-control w-full h-12 px-3 py-1.5 text-xs font-mono resize-none" aria-label="Mirrors" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -997,25 +1023,25 @@ export const AddDownloadsModal = () => {
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-4 bg-sidebar-bg/50 border-t border-border-modal flex items-center shrink-0">
|
||||
<div className="add-download-footer p-4 flex items-center shrink-0">
|
||||
<div className="text-[11px] text-text-muted font-medium flex-1">
|
||||
{parsedItems.length === 0 ? "Paste one or more links." : `Ready to add ${parsedItems.length} download(s).`}
|
||||
</div>
|
||||
<div className="flex gap-2.5">
|
||||
<button onClick={() => toggleAddModal(false)} className="app-button border-transparent bg-transparent px-4 text-xs text-text-secondary">
|
||||
<button onClick={() => toggleAddModal(false)} className="add-download-button add-download-button-cancel px-4 text-xs">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleStart(false)}
|
||||
disabled={parsedItems.length === 0}
|
||||
className="app-button px-4 text-xs disabled:opacity-50"
|
||||
className="add-download-button add-download-button-secondary px-4 text-xs"
|
||||
>
|
||||
Add to Queue
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleStart(true)}
|
||||
disabled={parsedItems.length === 0}
|
||||
className="app-button app-button-primary px-5 text-xs disabled:opacity-50"
|
||||
className="add-download-button add-download-button-primary px-5 text-xs"
|
||||
>
|
||||
<Play size={12} fill="currentColor" /> Start Downloads
|
||||
</button>
|
||||
|
||||
+347
@@ -376,6 +376,353 @@
|
||||
animation: modal-in 150ms ease-out;
|
||||
}
|
||||
|
||||
/* Add Download window */
|
||||
.add-download-modal {
|
||||
--add-surface: hsl(var(--surface-raised) / 0.72);
|
||||
--add-highlight: hsl(0 0% 100% / 0.08);
|
||||
background:
|
||||
linear-gradient(180deg, hsl(var(--surface-overlay) / 0.94), hsl(var(--bg-modal) / 0.98));
|
||||
backdrop-filter: blur(22px) saturate(1.12);
|
||||
-webkit-backdrop-filter: blur(22px) saturate(1.12);
|
||||
border-color: hsl(var(--border-modal));
|
||||
border-radius: 14px;
|
||||
box-shadow:
|
||||
inset 0 1px 0 hsl(0 0% 100% / 0.08),
|
||||
0 28px 70px hsl(var(--shadow-color)),
|
||||
0 2px 10px hsl(var(--shadow-color));
|
||||
}
|
||||
|
||||
.add-download-left {
|
||||
background:
|
||||
radial-gradient(circle at 18% 0%, hsl(var(--accent-color) / 0.055), transparent 34%),
|
||||
hsl(var(--main-bg) / 0.56);
|
||||
}
|
||||
|
||||
.add-download-settings {
|
||||
background: hsl(var(--bg-modal) / 0.7);
|
||||
}
|
||||
|
||||
.add-download-section-title {
|
||||
color: hsl(var(--text-primary));
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
|
||||
.add-download-section {
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 11px;
|
||||
background:
|
||||
linear-gradient(180deg, var(--add-highlight), transparent 38%),
|
||||
var(--add-surface);
|
||||
padding: 14px;
|
||||
box-shadow:
|
||||
inset 0 1px 0 hsl(0 0% 100% / 0.045),
|
||||
0 1px 2px hsl(var(--shadow-color));
|
||||
}
|
||||
|
||||
.add-download-control {
|
||||
min-height: 30px;
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 7px;
|
||||
background:
|
||||
linear-gradient(180deg, hsl(0 0% 100% / 0.035), transparent),
|
||||
hsl(var(--bg-input) / 0.9);
|
||||
color: hsl(var(--text-primary));
|
||||
box-shadow:
|
||||
inset 0 1px 2px hsl(var(--shadow-color)),
|
||||
0 1px 0 hsl(0 0% 100% / 0.035);
|
||||
transition:
|
||||
border-color 120ms ease,
|
||||
background-color 120ms ease,
|
||||
box-shadow 120ms ease;
|
||||
}
|
||||
|
||||
.add-download-control:hover:not(:disabled):not(:read-only) {
|
||||
border-color: hsl(var(--text-muted) / 0.7);
|
||||
}
|
||||
|
||||
.add-download-control:focus,
|
||||
.add-download-control:focus-visible {
|
||||
border-color: hsl(var(--accent-color) / 0.9);
|
||||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 3px hsl(var(--accent-color) / 0.18),
|
||||
inset 0 1px 2px hsl(var(--shadow-color));
|
||||
}
|
||||
|
||||
.add-download-control:read-only {
|
||||
background: hsl(var(--bg-input) / 0.62);
|
||||
}
|
||||
|
||||
.add-download-control::placeholder {
|
||||
color: hsl(var(--text-muted) / 0.82);
|
||||
}
|
||||
|
||||
.add-download-links-input {
|
||||
border-radius: 10px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.add-download-link-button {
|
||||
border-radius: 6px;
|
||||
color: hsl(var(--accent-color));
|
||||
padding: 3px 5px;
|
||||
transition: background-color 120ms ease, color 120ms ease;
|
||||
}
|
||||
|
||||
.add-download-link-button:hover {
|
||||
background: hsl(var(--accent-color) / 0.1);
|
||||
}
|
||||
|
||||
.add-download-summary-card {
|
||||
min-width: 0;
|
||||
padding: 10px 11px;
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 10px;
|
||||
background:
|
||||
linear-gradient(145deg, hsl(0 0% 100% / 0.055), transparent 55%),
|
||||
var(--add-surface);
|
||||
box-shadow:
|
||||
inset 0 1px 0 hsl(0 0% 100% / 0.045),
|
||||
0 1px 2px hsl(var(--shadow-color));
|
||||
}
|
||||
|
||||
.add-download-preview {
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 11px;
|
||||
background: hsl(var(--bg-input) / 0.35);
|
||||
box-shadow: inset 0 1px 2px hsl(var(--shadow-color));
|
||||
}
|
||||
|
||||
.add-download-preview-header {
|
||||
border-bottom: 1px solid hsl(var(--border-modal));
|
||||
background: hsl(var(--surface-raised) / 0.66);
|
||||
}
|
||||
|
||||
.add-download-preview-row {
|
||||
transition:
|
||||
background-color 100ms ease,
|
||||
border-color 100ms ease,
|
||||
box-shadow 100ms ease;
|
||||
}
|
||||
|
||||
.add-download-preview-row:hover {
|
||||
background: hsl(var(--item-hover));
|
||||
}
|
||||
|
||||
.add-download-preview-row.is-selected {
|
||||
border-color: hsl(var(--accent-color) / 0.34);
|
||||
background: hsl(var(--accent-color) / 0.11);
|
||||
box-shadow: inset 3px 0 0 hsl(var(--accent-color) / 0.85);
|
||||
}
|
||||
|
||||
.add-download-preview-row:focus-visible,
|
||||
.add-download-format-row:focus-visible {
|
||||
outline: 2px solid hsl(var(--accent-color) / 0.6);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.add-download-empty {
|
||||
color: hsl(var(--text-muted) / 0.86);
|
||||
}
|
||||
|
||||
.add-download-media-section {
|
||||
border-color: hsl(272 70% 62% / 0.28);
|
||||
background:
|
||||
radial-gradient(circle at 100% 0%, hsl(272 75% 60% / 0.09), transparent 46%),
|
||||
var(--add-surface);
|
||||
}
|
||||
|
||||
.add-download-format-row {
|
||||
border-color: hsl(var(--border-modal));
|
||||
border-radius: 8px;
|
||||
background: hsl(var(--bg-input) / 0.7);
|
||||
transition:
|
||||
background-color 100ms ease,
|
||||
border-color 100ms ease,
|
||||
box-shadow 100ms ease;
|
||||
}
|
||||
|
||||
.add-download-format-row:hover {
|
||||
background: hsl(var(--item-hover));
|
||||
border-color: hsl(var(--text-muted) / 0.55);
|
||||
}
|
||||
|
||||
.add-download-format-row.is-selected {
|
||||
border-color: hsl(272 74% 62% / 0.42);
|
||||
background: hsl(272 74% 58% / 0.12);
|
||||
box-shadow: inset 3px 0 0 hsl(272 74% 62% / 0.9);
|
||||
}
|
||||
|
||||
.add-download-select {
|
||||
appearance: auto;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.add-download-value {
|
||||
min-width: 25px;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 5px;
|
||||
background: hsl(var(--bg-input) / 0.68);
|
||||
}
|
||||
|
||||
.add-download-checkbox {
|
||||
appearance: none;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
flex: 0 0 15px;
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 4px;
|
||||
background: hsl(var(--bg-input) / 0.92);
|
||||
box-shadow: inset 0 1px 1px hsl(var(--shadow-color));
|
||||
cursor: default;
|
||||
transition:
|
||||
border-color 100ms ease,
|
||||
background-color 100ms ease,
|
||||
box-shadow 100ms ease;
|
||||
}
|
||||
|
||||
.add-download-checkbox:hover {
|
||||
border-color: hsl(var(--text-muted) / 0.8);
|
||||
}
|
||||
|
||||
.add-download-checkbox:checked {
|
||||
border-color: hsl(var(--accent-color));
|
||||
background-color: hsl(var(--accent-color));
|
||||
background-image:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='m2.4 6.2 2.2 2.2 5-5' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.add-download-checkbox:focus-visible {
|
||||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 3px hsl(var(--accent-color) / 0.2),
|
||||
inset 0 1px 1px hsl(var(--shadow-color));
|
||||
}
|
||||
|
||||
.add-download-range {
|
||||
height: 16px;
|
||||
accent-color: hsl(var(--accent-color));
|
||||
}
|
||||
|
||||
.add-download-range:disabled {
|
||||
opacity: 0.42;
|
||||
}
|
||||
|
||||
.add-download-advanced {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.add-download-advanced-toggle {
|
||||
min-height: 42px;
|
||||
padding: 0 14px;
|
||||
border-radius: 10px;
|
||||
transition: background-color 100ms ease, color 100ms ease;
|
||||
}
|
||||
|
||||
.add-download-advanced-toggle:hover {
|
||||
background: hsl(var(--item-hover));
|
||||
color: hsl(var(--accent-color));
|
||||
}
|
||||
|
||||
.add-download-advanced > div {
|
||||
padding: 0 14px 14px;
|
||||
}
|
||||
|
||||
.add-download-footer {
|
||||
border-top: 1px solid hsl(var(--border-modal));
|
||||
background:
|
||||
linear-gradient(180deg, hsl(0 0% 100% / 0.035), transparent),
|
||||
hsl(var(--surface-raised) / 0.78);
|
||||
box-shadow: 0 -1px 0 hsl(0 0% 100% / 0.025);
|
||||
}
|
||||
|
||||
.add-download-button {
|
||||
display: inline-flex;
|
||||
min-height: 30px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 7px;
|
||||
color: hsl(var(--text-primary));
|
||||
font-weight: 560;
|
||||
letter-spacing: -0.005em;
|
||||
transition:
|
||||
background-color 100ms ease,
|
||||
border-color 100ms ease,
|
||||
box-shadow 100ms ease,
|
||||
transform 70ms ease,
|
||||
opacity 100ms ease;
|
||||
}
|
||||
|
||||
.add-download-button:active:not(:disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.add-download-button-secondary {
|
||||
border-color: hsl(var(--border-modal));
|
||||
background:
|
||||
linear-gradient(180deg, hsl(0 0% 100% / 0.055), transparent),
|
||||
hsl(var(--bg-input) / 0.88);
|
||||
box-shadow:
|
||||
inset 0 1px 0 hsl(0 0% 100% / 0.055),
|
||||
0 1px 2px hsl(var(--shadow-color));
|
||||
}
|
||||
|
||||
.add-download-button-secondary:hover:not(:disabled) {
|
||||
border-color: hsl(var(--text-muted) / 0.65);
|
||||
background:
|
||||
linear-gradient(hsl(var(--item-hover)), hsl(var(--item-hover))),
|
||||
hsl(var(--bg-input));
|
||||
}
|
||||
|
||||
.add-download-button-cancel {
|
||||
color: hsl(var(--text-secondary));
|
||||
}
|
||||
|
||||
.add-download-button-cancel:hover:not(:disabled) {
|
||||
background: hsl(var(--item-hover));
|
||||
color: hsl(var(--text-primary));
|
||||
}
|
||||
|
||||
.add-download-button-primary {
|
||||
border-color: hsl(var(--accent-color) / 0.86);
|
||||
background:
|
||||
linear-gradient(180deg, hsl(0 0% 100% / 0.16), transparent 52%),
|
||||
hsl(var(--accent-color));
|
||||
color: white;
|
||||
box-shadow:
|
||||
inset 0 1px 0 hsl(0 0% 100% / 0.18),
|
||||
0 1px 2px hsl(var(--accent-color) / 0.32);
|
||||
}
|
||||
|
||||
.add-download-button-primary:hover:not(:disabled) {
|
||||
background:
|
||||
linear-gradient(180deg, hsl(0 0% 100% / 0.2), transparent 52%),
|
||||
hsl(var(--accent-color) / 0.92);
|
||||
box-shadow:
|
||||
inset 0 1px 0 hsl(0 0% 100% / 0.2),
|
||||
0 2px 5px hsl(var(--accent-color) / 0.3);
|
||||
}
|
||||
|
||||
.add-download-button:disabled {
|
||||
opacity: 0.42;
|
||||
filter: saturate(0.55);
|
||||
}
|
||||
|
||||
.add-download-button:focus-visible,
|
||||
.add-download-link-button:focus-visible,
|
||||
.add-download-advanced-toggle:focus-visible {
|
||||
outline: 2px solid hsl(var(--accent-color) / 0.65);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.app-toast {
|
||||
border: 1px solid hsl(var(--border-modal));
|
||||
border-radius: 8px;
|
||||
|
||||
Reference in New Issue
Block a user