- {error}
+
+
+
)
@@ -178,3 +382,15 @@ async function hashFile(file: File) {
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')
}
+
+function formatBytes(bytes: number) {
+ if (!Number.isFinite(bytes)) return '0 B'
+ const units = ['B', 'KB', 'MB', 'GB']
+ let size = bytes
+ let unit = 0
+ while (size >= 1024 && unit < units.length - 1) {
+ size /= 1024
+ unit += 1
+ }
+ return `${size.toFixed(size < 10 && unit > 0 ? 1 : 0)} ${units[unit]}`
+}
diff --git a/src/styles.css b/src/styles.css
index 2d66b976..eb08f5c5 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -125,6 +125,13 @@ code {
box-shadow: 0 10px 20px rgba(29, 26, 23, 0.12);
}
+.btn:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+ box-shadow: none;
+ transform: none;
+}
+
.btn-primary {
background: linear-gradient(135deg, var(--accent), var(--accent-deep));
color: white;
@@ -185,6 +192,283 @@ code {
padding: 32px 24px 72px;
}
+.upload-shell {
+ position: relative;
+}
+
+.upload-header {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: 24px;
+ margin-bottom: 28px;
+}
+
+.upload-kicker {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 600;
+ color: var(--accent-deep);
+ text-transform: uppercase;
+ letter-spacing: 0.16em;
+ font-size: 0.7rem;
+}
+
+.upload-title {
+ font-family: var(--font-display);
+ font-size: clamp(2.2rem, 3.5vw, 3rem);
+ letter-spacing: -0.03em;
+ margin: 8px 0 10px;
+}
+
+.upload-subtitle {
+ color: var(--ink-soft);
+ max-width: 560px;
+ line-height: 1.6;
+ margin: 0;
+}
+
+.upload-badge {
+ background: linear-gradient(140deg, #ffddc9, #ffe9df);
+ color: #9a3a24;
+ border-radius: 999px;
+ padding: 12px 18px;
+ font-weight: 700;
+ box-shadow: 0 12px 24px rgba(255, 107, 74, 0.18);
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.upload-badge-sub {
+ font-size: 0.7rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.upload-card {
+ background: linear-gradient(180deg, #ffffff 0%, #fff6f1 100%);
+ border-radius: calc(var(--radius-lg) + 6px);
+ border: 1px solid rgba(255, 107, 74, 0.18);
+ padding: 28px;
+ box-shadow: 0 24px 60px rgba(29, 26, 23, 0.12);
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.upload-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1.1fr) minmax(260px, 0.7fr);
+ gap: 28px;
+}
+
+.upload-fields {
+ display: grid;
+ gap: 18px;
+}
+
+.upload-field {
+ display: grid;
+ gap: 10px;
+ font-weight: 600;
+}
+
+.upload-field span {
+ font-size: 0.9rem;
+ color: var(--ink-soft);
+}
+
+.upload-input {
+ border: 1px solid rgba(29, 26, 23, 0.14);
+ border-radius: 14px;
+ padding: 12px 14px;
+ background: rgba(255, 255, 255, 0.9);
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8);
+}
+
+.upload-input:focus {
+ border-color: rgba(255, 107, 74, 0.6);
+ box-shadow:
+ 0 0 0 2px rgba(255, 107, 74, 0.2),
+ inset 0 0 0 1px rgba(255, 255, 255, 0.8);
+}
+
+.upload-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 16px;
+}
+
+.upload-side {
+ display: grid;
+ gap: 16px;
+ align-content: start;
+}
+
+.dropzone {
+ border: 2px dashed rgba(255, 107, 74, 0.5);
+ border-radius: 24px;
+ padding: 20px;
+ background: radial-gradient(circle at top, #fff3ec 0%, #ffffff 65%);
+ display: grid;
+ gap: 12px;
+ text-align: center;
+ cursor: pointer;
+ transition:
+ transform 0.2s ease,
+ box-shadow 0.2s ease,
+ border-color 0.2s ease;
+}
+
+.dropzone:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 18px 30px rgba(255, 107, 74, 0.15);
+}
+
+.dropzone.is-dragging {
+ border-color: var(--accent);
+ box-shadow: 0 20px 40px rgba(255, 107, 74, 0.25);
+ background: radial-gradient(circle at top, #ffe1d3 0%, #fff6f1 70%);
+}
+
+.dropzone-icon {
+ width: 54px;
+ height: 54px;
+ border-radius: 16px;
+ margin: 0 auto;
+ display: grid;
+ place-items: center;
+ font-size: 1.4rem;
+ background: linear-gradient(160deg, #ff6b4a, #f08b5f);
+ color: white;
+ box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.4);
+}
+
+.dropzone-input {
+ display: none;
+}
+
+.upload-summary {
+ border-radius: 20px;
+ background: rgba(255, 255, 255, 0.7);
+ border: 1px solid rgba(29, 26, 23, 0.08);
+ padding: 16px;
+ display: grid;
+ gap: 10px;
+ font-size: 0.9rem;
+}
+
+.upload-requirement {
+ padding: 6px 12px;
+ border-radius: 999px;
+ background: rgba(255, 107, 74, 0.12);
+ color: #9a3a24;
+ font-weight: 600;
+ width: fit-content;
+}
+
+.upload-requirement.ok {
+ background: rgba(43, 198, 164, 0.18);
+ color: #1a6b5b;
+}
+
+.upload-filelist {
+ display: grid;
+ gap: 6px;
+ max-height: 220px;
+ overflow: auto;
+}
+
+.upload-filelist .upload-file {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ font-size: 0.85rem;
+ color: var(--ink-soft);
+}
+
+.upload-file span:first-child {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.upload-remove {
+ border: none;
+ background: transparent;
+ color: var(--accent-deep);
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.upload-remove:hover {
+ text-decoration: underline;
+}
+
+.upload-more {
+ font-size: 0.8rem;
+ color: var(--ink-soft);
+}
+
+.upload-muted {
+ color: var(--ink-soft);
+ margin: 0;
+}
+
+.upload-notes {
+ background: #fff8f3;
+ border: 1px solid rgba(255, 107, 74, 0.12);
+ border-radius: 18px;
+ padding: 16px;
+ font-size: 0.85rem;
+ color: var(--ink-soft);
+}
+
+.upload-notes ul {
+ padding-left: 18px;
+ margin: 8px 0 0;
+ display: grid;
+ gap: 6px;
+}
+
+.upload-footer {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+}
+
+.upload-validation {
+ display: grid;
+ gap: 6px;
+ font-size: 0.85rem;
+ color: #9a3a24;
+}
+
+.upload-validation-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.upload-validation-item::before {
+ content: "•";
+}
+
+.upload-ready {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #1a6b5b;
+}
+
+.upload-error {
+ color: #b84a3a;
+}
+
.section-title {
font-family: var(--font-display);
font-size: 1.8rem;
@@ -259,6 +543,17 @@ code {
background: transparent;
}
+@media (max-width: 900px) {
+ .upload-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .upload-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
.mono {
font-family: var(--font-mono);
}