This commit is contained in:
cash
2026-03-29 23:50:49 -05:00
commit eb5e194331
56 changed files with 4010 additions and 0 deletions

BIN
frontend/static/bloom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,652 @@
/* casper color.css */
/* discord */
:root {
--discord-primary: #5865F2;
--discord-light: #E0E3FF;
--discord-dark-1: #2C2F33;
--discord-dark-2: #23272A;
--discord-text: #FFFFFF;
}
/* bandcamp */
:root {
--bandcamp-primary: #629AA9;
--bandcamp-light: #A0C3CC;
--bandcamp-dark-1: #1C1C1C;
--bandcamp-dark-2: #0F0F0F;
--bandcamp-text: #FFFFFF;
}
/* youtube */
:root {
--youtube-primary: #FF0000;
--youtube-light: #FF6666;
--youtube-dark-1: #282828;
--youtube-dark-2: #1F1F1F;
--youtube-text: #FFFFFF;
}
/* soundcloud */
:root {
--soundcloud-primary: #FF7700;
--soundcloud-light: #FFA64D;
--soundcloud-dark-1: #282828;
--soundcloud-dark-2: #1F1F1F;
--soundcloud-text: #FFFFFF;
}
/* twitter/x */
:root {
--twitter-primary: #1DA1F2;
--twitter-light: #A4D9F9;
--twitter-dark-1: #14171A;
--twitter-dark-2: #0D0F12;
--twitter-text: #FFFFFF;
}
:root {
--gray-1: #fcfcfd;
--gray-2: #f8f9fa;
--gray-3: #f1f3f5;
--gray-4: #e9ecef;
--gray-5: #dee2e6;
--gray-6: #ced4da;
--gray-7: #adb5bd;
--gray-8: #868e96;
--gray-9: #495057;
--gray-10: #343a40;
--gray-11: #212529;
--gray-12: #121619;
}
:root {
--mauve-1: #fcfcfd;
--mauve-2: #f8f8fa;
--mauve-3: #f3f3f6;
--mauve-4: #edeff1;
--mauve-5: #e8e8ea;
--mauve-6: #e2e2e5;
--mauve-7: #dcdde0;
--mauve-8: #d4d4d8;
--mauve-9: #babac0;
--mauve-10: #a1a1a8;
--mauve-11: #85858e;
--mauve-12: #3c3c44;
}
:root {
--slate-1: #f8f9fa;
--slate-2: #f1f3f5;
--slate-3: #e9ecef;
--slate-4: #e2e6ea;
--slate-5: #d9dde1;
--slate-6: #ced2d7;
--slate-7: #bec4ca;
--slate-8: #adb5bd;
--slate-9: #8b9aa7;
--slate-10: #728198;
--slate-11: #5c697f;
--slate-12: #3d5060;
}
:root {
--sage-1: #f8f9f4;
--sage-2: #f1f3e9;
--sage-3: #e9ece0;
--sage-4: #e2e6d6;
--sage-5: #dae0cc;
--sage-6: #ced4c1;
--sage-7: #bfc6b3;
--sage-8: #acb3a1;
--sage-9: #909982;
--sage-10: #767e69;
--sage-11: #5d6551;
--sage-12: #3c3f37;
}
:root {
--olive-1: #fbfefd;
--olive-2: #f7fbfa;
--olive-3: #f2f7f3;
--olive-4: #edf3ed;
--olive-5: #e8efe7;
--olive-6: #e1e9e0;
--olive-7: #dbe3da;
--olive-8: #d4ddd3;
--olive-9: #b8c0b9;
--olive-10: #9aa39c;
--olive-11: #7c867f;
--olive-12: #343d35;
}
:root {
--sand-1: #fcfaf7;
--sand-2: #faf6f1;
--sand-3: #f6f1ec;
--sand-4: #f2ece6;
--sand-5: #efebe1;
--sand-6: #ebe8dd;
--sand-7: #e7e4d8;
--sand-8: #e3dfd4;
--sand-9: #d0c9bf;
--sand-10: #b9b2a9;
--sand-11: #a29b93;
--sand-12: #79736d;
}
:root {
--gold-1: #fdfdfc;
--gold-2: #fbf9f2;
--gold-3: #f5f2e9;
--gold-4: #eeeadd;
--gold-5: #e5dfd0;
--gold-6: #dad1bd;
--gold-7: #cbbda4;
--gold-8: #b8a383;
--gold-9: #978365;
--gold-10: #8c795d;
--gold-11: #776750;
--gold-12: #3b352b;
}
:root {
--bronze-1: #fdfcfc;
--bronze-2: #fdf8f6;
--bronze-3: #f8f1ee;
--bronze-4: #f2e8e4;
--bronze-5: #eaddd7;
--bronze-6: #e0cec7;
--bronze-7: #d1b9b0;
--bronze-8: #bfa094;
--bronze-9: #a18072;
--bronze-10: #957468;
--bronze-11: #846358;
--bronze-12: #43302b;
}
:root {
--brown-1: #fefdfc;
--brown-2: #fcf9f6;
--brown-3: #f8f1eb;
--brown-4: #f2e8de;
--brown-5: #eaddd0;
--brown-6: #e0cebf;
--brown-7: #d1b9a4;
--brown-8: #bfa083;
--brown-9: #a18065;
--brown-10: #977459;
--brown-11: #84634a;
--brown-12: #433026;
}
:root {
--yellow-1: #fdfdfc;
--yellow-2: #fefce9;
--yellow-3: #fffab8;
--yellow-4: #fff394;
--yellow-5: #ffe770;
--yellow-6: #f3d768;
--yellow-7: #e4c767;
--yellow-8: #d5b862;
--yellow-9: #ffe629;
--yellow-10: #ffdc00;
--yellow-11: #946800;
--yellow-12: #35290f;
}
:root {
--amber-1: #fefdfb;
--amber-2: #fff9ed;
--amber-3: #fff4d5;
--amber-4: #ffecbc;
--amber-5: #ffe3a2;
--amber-6: #ffd386;
--amber-7: #f3ba63;
--amber-8: #ee9d2b;
--amber-9: #ffb224;
--amber-10: #ffa01c;
--amber-11: #ad5700;
--amber-12: #4e2009;
}
:root {
--orange-1: #fefcfb;
--orange-2: #fff7ed;
--orange-3: #ffefd6;
--orange-4: #ffe4b5;
--orange-5: #ffd599;
--orange-6: #ffc182;
--orange-7: #f5a65b;
--orange-8: #ec8a31;
--orange-9: #f76808;
--orange-10: #ed5f00;
--orange-11: #bd4b00;
--orange-12: #451e11;
}
:root {
--tomato-1: #fffcfc;
--tomato-2: #fff8f7;
--tomato-3: #ffefed;
--tomato-4: #ffe6e2;
--tomato-5: #fdd8d3;
--tomato-6: #fac7be;
--tomato-7: #f3b0a2;
--tomato-8: #ea9280;
--tomato-9: #e54d2e;
--tomato-10: #db4324;
--tomato-11: #ca3214;
--tomato-12: #341711;
}
:root {
--red-1: #fffcfc;
--red-2: #fff7f7;
--red-3: #ffefef;
--red-4: #ffe5e5;
--red-5: #fdd8d8;
--red-6: #f9c6c6;
--red-7: #f3aeaf;
--red-8: #eb9091;
--red-9: #e5484d;
--red-10: #dc3d43;
--red-11: #cd2b31;
--red-12: #381316;
}
:root {
--ruby-1: #fffcfd;
--ruby-2: #fff7f9;
--ruby-3: #feeff3;
--ruby-4: #ffe5eb;
--ruby-5: #fdd8e2;
--ruby-6: #f9c6d6;
--ruby-7: #f3aec4;
--ruby-8: #eb91af;
--ruby-9: #e54666;
--ruby-10: #db3b5b;
--ruby-11: #ca244d;
--ruby-12: #3a0c1c;
}
:root {
--crimson-1: #fffcfd;
--crimson-2: #fff7fb;
--crimson-3: #feeff6;
--crimson-4: #fce5f0;
--crimson-5: #f9d8e7;
--crimson-6: #f4c6db;
--crimson-7: #edadc8;
--crimson-8: #e58fb1;
--crimson-9: #e93d82;
--crimson-10: #df3476;
--crimson-11: #cb1d63;
--crimson-12: #35111f;
}
:root {
--pink-1: #fefcfd;
--pink-2: #fff7fb;
--pink-3: #feeff6;
--pink-4: #fce5f0;
--pink-5: #f9d8e7;
--pink-6: #f3c6db;
--pink-7: #ecadc8;
--pink-8: #e38fb1;
--pink-9: #d6409f;
--pink-10: #d23197;
--pink-11: #cd1d8d;
--pink-12: #3b0a2a;
}
:root {
--plum-1: #fefcff;
--plum-2: #fdf7fd;
--plum-3: #f8eff9;
--plum-4: #f3e7f4;
--plum-5: #eddcee;
--plum-6: #e3cce5;
--plum-7: #d3b3d8;
--plum-8: #be8cca;
--plum-9: #ab4aba;
--plum-10: #a43cb3;
--plum-11: #9c2bad;
--plum-12: #340c3b;
}
:root {
--purple-1: #fefcfe;
--purple-2: #fbf7fc;
--purple-3: #f7eff9;
--purple-4: #f2e7f4;
--purple-5: #eadcf0;
--purple-6: #e0cdec;
--purple-7: #d1b5e7;
--purple-8: #bc8dec;
--purple-9: #8e4ec6;
--purple-10: #8445b9;
--purple-11: #793aab;
}
:root {
--violet-1: #fdfcfe;
--violet-2: #fbfaff;
--violet-3: #f5f2ff;
--violet-4: #ede9fe;
--violet-5: #e4defc;
--violet-6: #d7cff9;
--violet-7: #c4b8f3;
--violet-8: #aa99ec;
--violet-9: #6e56cf;
--violet-10: #644fc1;
--violet-11: #5746af;
--violet-12: #20134b;
}
:root {
--iris-1: #fdfdff;
--iris-2: #f8f8ff;
--iris-3: #f0f1fe;
--iris-4: #e6e7ff;
--iris-5: #dadcff;
--iris-6: #cbceff;
--iris-7: #b8bcff;
--iris-8: #9b9eff;
--iris-9: #5b5bd6;
--iris-10: #5151c4;
--iris-11: #3e3eb0;
--iris-12: #1b1b66;
}
:root {
--indigo-1: #fdfdfe;
--indigo-2: #f8faff;
--indigo-3: #f0f4ff;
--indigo-4: #e6edfe;
--indigo-5: #d9e2fc;
--indigo-6: #c6d4f9;
--indigo-7: #aec0f5;
--indigo-8: #8da4ef;
--indigo-9: #3e63dd;
--indigo-10: #3a5ccc;
--indigo-11: #3451b2;
--indigo-12: #101d46;
}
:root {
--blue-1: #fbfdff;
--blue-2: #f4faff;
--blue-3: #e6f4fe;
--blue-4: #d5efff;
--blue-5: #c2e5ff;
--blue-6: #acd8fc;
--blue-7: #8ec8f6;
--blue-8: #5eb1ef;
--blue-9: #0091ff;
--blue-10: #0588f0;
--blue-11: #0d74ce;
--blue-12: #113264;
}
:root {
--sky-1: #f9feff;
--sky-2: #f1fafd;
--sky-3: #e1f6fb;
--sky-4: #d1f0f7;
--sky-5: #bee7f2;
--sky-6: #a9dce9;
--sky-7: #8dcedc;
--sky-8: #60bcd0;
--sky-9: #7ce2fe;
--sky-10: #74daf8;
--sky-11: #00749e;
--sky-12: #1d3e46;
}
:root {
--cyan-1: #fafdfe;
--cyan-2: #f2fbfd;
--cyan-3: #e7f9fb;
--cyan-4: #d8f3f6;
--cyan-5: #c4eaef;
--cyan-6: #aadee6;
--cyan-7: #84cdda;
--cyan-8: #3db9cf;
--cyan-9: #00a2c7;
--cyan-10: #0797b9;
--cyan-11: #107d98;
--cyan-12: #0d3c48;
}
:root {
--teal-1: #fafefd;
--teal-2: #f3fbf9;
--teal-3: #e6f7f4;
--teal-4: #d8f1ec;
--teal-5: #c7e8e2;
--teal-6: #b3dcd5;
--teal-7: #94ccc5;
--teal-8: #5bb9b1;
--teal-9: #12a594;
--teal-10: #0d9b8a;
--teal-11: #008573;
--teal-12: #0d3d38;
}
:root {
--jade-1: #f9fefd;
--jade-2: #effdf9;
--jade-3: #e1f9f2;
--jade-4: #d3f3ea;
--jade-5: #c2ebe0;
--jade-6: #ade0d3;
--jade-7: #8ecfbf;
--jade-8: #4cbba5;
--jade-9: #00a383;
--jade-10: #00997b;
--jade-11: #00826c;
--jade-12: #0d3c37;
}
:root {
--mint-1: #f9fefd;
--mint-2: #f2fcfa;
--mint-3: #dff9f2;
--mint-4: #ccf3ea;
--mint-5: #b8eae0;
--mint-6: #a1ded2;
--mint-7: #83cdc0;
--mint-8: #4fb9ab;
--mint-9: #86ead4;
--mint-10: #7de0cb;
--mint-11: #027d6c;
--mint-12: #16433c;
}
:root {
--green-1: #fbfefc;
--green-2: #f4fbf6;
--green-3: #e6f6eb;
--green-4: #d6f1df;
--green-5: #c4e8d1;
--green-6: #adddc0;
--green-7: #8eceaa;
--green-8: #5bb98b;
--green-9: #30a46c;
--green-10: #2b9a66;
--green-11: #218358;
--green-12: #193b2d;
}
:root {
--grass-1: #fbfefb;
--grass-2: #f3fbf3;
--grass-3: #e6f6e6;
--grass-4: #d6f1d6;
--grass-5: #c4e8c4;
--grass-6: #addcad;
--grass-7: #8ecf8e;
--grass-8: #5bb95b;
--grass-9: #30a930;
--grass-10: #2b9e2b;
--grass-11: #218821;
--grass-12: #193b19;
}
:root {
--background-color: #0d0d0d;
--text-color: #ffffff;
--light-purple: #c7a4f5;
--light-purple-hover: #dbb5fc;
--error-color: #FF5C5C;
--success-color: #7FD1AE;
--border-color: #2a2a2a;
--card-bg: #151515;
--input-bg: #1c1c1c;
--placeholder-color: #555;
--button-bg: #2d2d2d;
--button-border: #444;
--button-bg-hover: #3f3f3f;
--button-border-hover: #666;
--button-focus: #444;
--button-loading-bg: #555;
--button-loading-border: #666;
--button-loading-text: #ccc;
--login-card-border: #777;
--checkbox-bg: #333;
--checkbox-accent: #999;
--fetching-glow: #8aff8a;
--fetching-mid: #797979;
--progress-track-bg: #444;
--progress-indicator-bg: #77c;
--format-bg: #222;
--close-btn-color: #444;
--close-btn-hover: #e33;
--spinner-border: #777;
--spinner-top: #bbb;
}
.error-badge {
background-color: var(--error-color) !important;
color: #fff !important;
}
.success-badge {
background-color: var(--success-color) !important;
color: #000 !important;
}
:root,
[data-radix-themes="dark"] {
--badge-fill-error: rgba(239, 68, 68, 0.1);
--badge-border-error: #ef4444;
--badge-text-error: #f87171;
--error-bg: #a33636;
}
[data-radix-themes="light"] {
--badge-fill-error: rgba(255, 0, 0, 0.05);
--badge-border-error: #ff6b6b;
--badge-text-error: #e11d48;
}
.radix-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: 0.77rem;
font-weight: 500;
font-family: 'Cascadia Code', monospace;
border-radius: 5px;
border: 1px solid;
text-transform: none;
letter-spacing: 0.02em;
margin: 0.25rem;
white-space: nowrap;
user-select: none;
transition: all 0.2s ease-in-out;
background-color: #2a2a2a;
}
.badge-row {
display: flex;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
gap: 0.77rem;
text-align: center;
margin: 0.5rem auto;
}
/*
#a47d3f Golden brown (bottom bars)
#113264 Deep blue (top right)
#4e2009 Dark brown (top center)
#3a0c1c Deep burgundy (top left)
*/
.radix-badge.videos-badge {
background: var(--ruby-12);
border-color: var(--ruby-6);
/*color: var(--ruby-4); */
color:white;
}
.radix-badge.usage-badge {
background: var(--blue-12);
border-color: var(--blue-5);
/*color: var(--mauve-1); */
color:white;
}
.radix-badge.time-badge {
background: var(--violet-11);
border-color: var(--amber-6);
/*color: var(--amber-3); */
color:white;
}
.radix-badge.xp-level-badge,
.radix-badge.xp-total-badge {
background: var(--teal-12);
border-color: var(--teal-6);
/*color: var(--teal-1); */
color:white;
}
/* Tiers */
.radix-badge.tier-badge.rank-1 {
background: var(--amber-12);
border-color: var(--amber-5);
color: var(--mauve-1);
}
.radix-badge.tier-badge.rank-2 {
background: var(--gray12);
border-color: var(--gray-5);
color: var(--gray-12);
}
.radix-badge.tier-badge.rank-3 {
background: var(--brown-12);
border-color: var(--orange-5);
color: var(--gray-1);
}
.radix-badge.tier-badge.stamped {
background: var(--cyan-12);
border-color: var(--blue-5);
color: var(--gray-1);
}
.radix-badge.tier-badge.valid {
background: var(--green-12);
border-color: var(--green-5);
color: var(--gray-1);
}
.radix-badge.tier-badge.online {
background: var(--gray-11);
border-color: var(--gray-5);
color: var(--gray-1);
}
.radix-badge.tier-badge.limited {
background: var(--amber-12);
border-color: var(--amber-6);
color: var(--gray-1);
}
.radix-badge.tier-badge.offline {
background: var(--red-12);
border-color: var(--red-6);
color: var(--gray-1);
}
#platformBadge {
display: none;
margin-top: 0.55rem;
opacity: 0;
}
.badge-youtube {
background-color: var(--red-12)!important;
border-color: var(--red-8)!important;
color: white;
}
.badge-soundcloud {
background-color: var(--orange-9);
border-color: var(--orange-5);
color: black;
}
.badge-twitterx {
background-color: var(--gray-9);
border-color: var(--gray-5);
color: white;
}
.badge-bandcamp {
background-color: var(--indigo-9);
border-color: var(--indigo-5);
color: white;
}

View File

@@ -0,0 +1,240 @@
/*STYLE.CSS 25 Apr 2025*/
.dark-theme {
min-height: 100vh;
margin: 0;
overflow-y: hidden;/* default desktop */
font-family: sans-serif;
background-color: #0a0a0a;
color: var(--text-color);
}
@media (max-width: 600px) {
.dark-theme { overflow-y: auto; }
}
a { text-decoration: none; color: var(--light-purple); }
a:hover { color: var(--light-purple-hover); }
.user-card,
.download-section {
background-color: var(--card-bg);
padding: 1.5rem;
margin: 1rem 0;
border-radius: 8px;
border: 1px solid var(--border-color);
}
form { display: flex; flex-direction: column; gap: 1rem; }
.form-row { display: flex; flex-direction: column; gap: .3rem; }
input[type="text"],
select {
padding: .5rem;
border-radius: 4px;
border: 1px solid var(--border-color);
background-color: var(--input-bg);
color: var(--text-color);
}
input[type="text"]::placeholder{
color: var(--placeholder-color);
}
input[type="password"]::placeholder {
color: #e1e1e1;
}
input[type="password"],
select {
padding: .5rem;
border-radius: 4px;
border: 1px solid #151515;
background-color: transparent;
color: #e1e1e1;
}
input[type="password"]:focus,
select:focus {
outline: none;
border: 1px solid #151515;
box-shadow: none;
}
.radix-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: .5rem;
padding: .8rem 1.2rem;
font: 500 1rem/1 Sans-Serif;
background-color: var(--button-bg);
border: 1px solid var(--button-border);
border-radius: 5px;
color: var(--text-color);
cursor: pointer;
transition: background-color .2s, border-color .2s, opacity .2s;
}
.radix-button:hover { background-color: var(--button-bg-hover); border-color: var(--button-border-hover); }
.radix-button:focus { outline: none; box-shadow: 0 0 0 2px var(--button-focus); }
.radix-button:disabled { opacity: .6; cursor: not-allowed; }
.radix-button.loading { background-color: var(--button-loading-bg); border-color: var(--button-loading-border); color: var(--button-loading-text); cursor: progress; }
.centered-wrapper { width: 50%; max-width: 870px; margin: 0 auto; position: absolute; }
@media (max-width: 768px) { .centered-wrapper { width: 90%; } }
.login-card {
margin-top: 5px;
border: 1px solid var(--login-card-border);
width: 300px;
text-align: center;
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
background-color: transparent;
}
.main-container .bloom-logo {
position: absolute; top: 6px; left: 50%; transform: translate(-50%, -50%);
}
.center-container { position: relative; display: flex; flex-direction: column; align-items: center; }
.center-container .bloom-logo { position: relative; margin-bottom: 1rem; }
.main-container { position: relative; padding-top: 2.5rem; }
@media (max-width: 600px) { .main-container .bloom-logo { transform: translate(-30%, -30%); } }
@keyframes floatPulse { 0%{transform:translateY(0)}50%{transform:translateY(-4px)}100%{transform:translateY(0)} }
.bloom-logo {
height: 40px; width: 40px; border-radius: 6px; font-size: 1rem;
display: flex; align-items: center; justify-content: center; z-index: 10;
animation: floatPulse 2.4s ease-in-out infinite;
}
.checkbox-container { display: flex; align-items: center; gap: .5rem; background-color: var(--checkbox-bg); display:none;}
.remember-me { background-color: var(--checkbox-bg); accent-color: var(--checkbox-accent); width:16px; height:16px; cursor:pointer; display:none;}
#downloadURL:focus { outline: none; }
@keyframes breathe { 0%{border-color:var(--fetching-glow)}50%{border-color:var(--fetching-mid)}100%{border-color:var(--fetching-glow)} }
.fetching { animation: breathe 2.5s ease-in-out infinite; }
.xp-level-info { margin-top:5px; text-align:center; font-family:Arial,sans-serif; }
.radix-progress { width:80%; margin:8px auto; }
.radix-progress-track { background-color:var(--progress-track-bg); border-radius:5px; height:20px; overflow:hidden; }
.radix-progress-indicator { height:100%; background-color:var(--progress-indicator-bg); transition:width .5s ease; }
.format-selection-container {
display:none;
margin-top:1rem;
padding:1rem;
border:1px solid var(--border-color);
border-radius:8px;
background:var(--format-bg);
opacity:0;
position:relative;
}
.format-options { display:flex; flex-direction:column; gap:.3rem; margin:.8rem 0; }
.format-option input[type=radio]{ margin-right:8px; accent-color:#333; }
#closeFormatCard {
position:absolute; top:8px; right:12px; z-index:2;
font-size:20px; font-weight:bold; background:transparent; border:none;
color:var(--close-btn-color); cursor:pointer; transition:color .2s ease;
}
#closeFormatCard:hover { color: var(--close-btn-hover); }
.loading { opacity:.7; cursor:wait; }
@keyframes breathingPulse{
0% { transform:scale(1); opacity:.7; }
50% { transform:scale(1.02);opacity:1; }
100%{ transform:scale(1); opacity:.7; }
}
.breathing { animation: breathingPulse 1.4s ease-in-out infinite; display:flex; align-items:center; gap:.5rem; }
.radix-spinner {
width:20px; height:20px;
border:2px solid var(--spinner-border);
border-top:2px solid var(--spinner-top);
border-radius:50%;
animation:spin 1s linear infinite; margin-right:8px;
}
@keyframes spin { to{transform:rotate(360deg);} }
.radix-button.danger {
background-color: var(--error-bg);
border-color: var(--error-bg);
}
.radix-button.danger:hover { filter: brightness(0.9); }
.radix-button.hidden { display: none; }
.radix-button.small { padding: .4rem .6rem; font-size: .875rem; }
@keyframes breathe-green{0%{border-color:var(--fetching-glow)}50%{border-color:var(--fetching-mid)}100%{border-color:var(--fetching-glow)}}
@keyframes breathe-red{0%{border-color:var(--error-glow)}50%{border-color:var(--error-mid)}100%{border-color:var(--error-glow)}}
.fetching{animation:breathe-green 2.5s ease-in-out infinite;}
.erroring{animation:breathe-red 1.5s ease-in-out 1;}
:root[data-radix-theme="dark"]{
--error-glow:#ff5c5c;
--error-mid:#b83e3e;
}
@keyframes breathe-green{0%{border-color:var(--green-9)}50%{border-color:var(--green-7)}100%{border-color:var(--green-9)}}
@keyframes breathe-red {0%{border-color:var(--red-9)}50%{border-color:var(--red-7)}100%{border-color:var(--red-9)}}
.fetching{animation:breathe-green 2.5s ease-in-out infinite;}
.erroring{animation:breathe-red 1.6s ease-in-out 1;}
.radix-button.primary{background-color:var(--gray-10);border-color:var(--gray-10);color:var(--gray-1);}
.radix-button.primary:hover{filter:brightness(1.05);}
.radix-button.danger{background-color:var(--red-11);border-color:var(--red-10);color:var(--gray-1);}
.radix-button.danger:hover{filter:brightness(.95);}
@keyframes breatheText{0%{opacity:.8}50%{opacity:1}100%{opacity:.8}}
.breathing-btn{animation:breatheText 1.6s ease-in-out infinite;}
@keyframes breathe-green{0%{border-color:var(--green-9)}50%{border-color:var(--green-7)}100%{border-color:var(--green-9)}}
@keyframes breathe-red {0%{border-color:var(--red-9)}50%{border-color:var(--red-7)}100%{border-color:var(--red-9)}}
.fetching{animation:breathe-green 2.5s ease-in-out infinite;}
.erroring{animation:breathe-red 1.6s ease-in-out 1;}
#downloadURL:not(.fetching):not(.erroring):hover{
border-color:var(--gray-7);transition:border-color .15s ease;
}
.radix-badge.size-badge{
background-color:var(--gray-8);
border:1px solid var(--gray-7);
color:var(--gray-1);
padding:.15rem .45rem;
font-size:.75rem;
border-radius:4px;
white-space:nowrap;
display:inline-flex;
align-items:center;
gap:.2rem;
}
.radix-badge.size-badge{
padding:.15rem .45rem;font-size:.75rem;border-radius:4px;
display:inline-flex;align-items:center;gap:.15rem;
transition:background-color .25s ease,border-color .25s ease,color .25s ease;
}
.size-small {background-color:var(--green-9);border:1px solid var(--green-7);color:var(--gray-1);}
.size-medium{background-color:var(--amber-9);border:1px solid var(--amber-7);color:var(--gray-1);}
.size-large {background-color:var(--red-10);border:1px solid var(--red-8); color:var(--gray-1);}
.skeleton {
background:transparent;border:1px dashed var(--gray-7);color:transparent;
animation:skeletonPulse 1s ease-in-out infinite;
}
@keyframes skeletonPulse {0%,100%{opacity:.4}50%{opacity:1}}

1
frontend/static/js/anime.min.js vendored Normal file

File diff suppressed because one or more lines are too long

259
frontend/static/js/app.js Normal file
View File

@@ -0,0 +1,259 @@
/* frontend/static/js/app.js synced with backend changes */
/* jul 15 2025 */
const $ = (id) => document.getElementById(id);
const qs = (s) => encodeURIComponent(s);
const anime = window?.anime;
/* DOM references */
const urlForm = $("urlForm");
const urlInput = $("downloadURL");
const fmtCard = $("formatCard");
const platBadge = $("platformBadge");
const fmtOpt = $("formatOptions");
const hidUrl = $("hiddenUrl");
const hidSid = $("hiddenSid");
const dlBtn = $("downloadBtn");
const cancelBtn = $("cancelBtn");
const fmtTitle = $("formatTitle");
const fmtForm = $("formatForm");
const closeBtn = $("closeFormatCard");
/* Mutable state */
let fetching = false;
let downloading = false;
let esStream = null;
const sizeCache = Object.create(null);
let baseSid = sessionStorage.getItem("sid");
if (!baseSid) {
baseSid = crypto.randomUUID();
sessionStorage.setItem("sid", baseSid);
}
let sidSeq = 0;
const newSid = () => `${baseSid}-${(sidSeq++).toString(36)}`;
const hide = (el) => { el.classList.add("hidden"); el.style.display = "none"; };
const show = (el, display="block") => { el.classList.remove("hidden"); el.style.display = display; };
const errBlink = (el) => {
el.classList.add("erroring");
el.addEventListener("animationend", () => el.classList.remove("erroring"), { once:true });
};
function resetBtn() {
dlBtn.disabled = true;
dlBtn.classList.remove("loading");
dlBtn.textContent = "Download";
}
/* Fadeout animation for format card */
function fadeOutCard() {
if (fmtCard.classList.contains("hidden")) return;
anime({
targets: fmtCard,
opacity: [1,0],
translateY: [0,-10],
easing: "easeInQuad",
duration: 300,
complete: () => {
hide(fmtCard);
fmtOpt.innerHTML = "";
}
});
platBadge.style.display = "none";
}
function resetUI() {
downloading = false;
fadeOutCard();
resetBtn();
urlInput.value = "";
urlInput.disabled = false;
urlInput.classList.remove("fetching");
urlInput.focus();
}
/* SSE helpers */
function closeStream() { esStream?.close(); esStream = null; }
function startSSE(sid, onFinish = () => {}) {
closeStream();
esStream = new EventSource(`/api/progress/${sid}`);
esStream.onmessage = ({ data }) => {
const j = JSON.parse(data);
if (["finished","error","cancelled","cached"].includes(j.status)) {
finish();
onFinish();
}
};
esStream.onerror = closeStream;
}
/* Finalise flow (download finished / errored / cancelled) */
function finish() {
closeStream();
resetUI();
}
/* Cancel current download or dismiss card */
function cancelFlow() {
if (downloading) {
fetch(`/cancel_download?sid=${hidSid.value}`, { method:"POST" })
.finally(finish);
} else {
fadeOutCard();
}
}
urlInput.addEventListener("keydown", (e) => {
if (e.key !== "Enter") return;
e.preventDefault();
if (!fetching && !downloading) urlForm.requestSubmit();
});
urlForm.addEventListener("submit", async (e) => {
e.preventDefault();
if (fetching || downloading) return;
const raw = urlInput.value.trim();
if (!raw) { errBlink(urlInput); return; }
fetching = true;
urlInput.classList.add("fetching");
urlInput.disabled = true;
const maxRetries = 3;
let lastError = null;
let result = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const r = await fetch("/choose_format", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: raw })
});
result = await r.json().catch(() => ({ error: "invalid json" }));
if (r.ok && result && !result.error && Array.isArray(result.formats)) {
break;
}
lastError = result?.error || "Unknown error";
} catch (err) {
lastError = "Network error";
}
if (attempt < maxRetries) {
await new Promise(res => setTimeout(res, 750 * attempt));
}
}
if (!result || result.error || !Array.isArray(result.formats)) {
errBlink(urlInput);
console.warn("choose_format failed:", lastError);
fetching = false;
urlInput.classList.remove("fetching");
urlInput.disabled = false;
return;
}
if (!result.sid) {
errBlink(urlInput);
console.error("Missing sid from server response");
fetching = false;
urlInput.classList.remove("fetching");
urlInput.disabled = false;
return;
}
const sid = result.sid;
hidSid.value = sid; // ✅ Use server-issued SID only
hidUrl.value = result.url;
fmtTitle.textContent = result.title || "Select format";
const platform = (result.platform || "other").toLowerCase();
platBadge.textContent = platform === "twitterx" ? "X / Twitter" :
platform[0].toUpperCase() + platform.slice(1);
platBadge.className = `radix-badge badge-${platform}`;
show(platBadge, "inline-flex");
platBadge.style.opacity = 0;
anime({ targets: platBadge, translateY: [-10, 0], opacity: [0, 1], duration: 300 });
fmtOpt.innerHTML = "";
const sourceUrl = result.url;
result.formats.forEach((f) => {
const row = document.createElement("div");
row.className = "format-option";
const radio = document.createElement("input");
radio.type = "radio";
radio.id = `f_${f.format_id}`;
radio.name = "format_id";
radio.value = f.format_id;
const label = document.createElement("label");
label.htmlFor = radio.id;
label.textContent = f.label;
row.append(radio, label);
// Size prefetch
let hoverTimer = null;
const fetchSize = async () => {
if (sizeCache[f.format_id] || downloading) return;
try {
const szResp = await fetch(`/format_size?url=${qs(sourceUrl)}&fmt_id=${f.format_id}`);
const js = await szResp.json();
if (szResp.ok && js.size) {
const mb = js.size / 1_048_576;
sizeCache[f.format_id] = `${(mb > 99 ? Math.round(mb) : mb.toFixed(1))}\u00A0MB`;
label.dataset.size = sizeCache[f.format_id];
}
} catch {}
};
row.addEventListener("mouseenter", () => hoverTimer = setTimeout(fetchSize, 1200));
row.addEventListener("mouseleave", () => clearTimeout(hoverTimer));
radio.addEventListener("change", () => { dlBtn.disabled = false; });
fmtOpt.append(row);
});
show(fmtCard);
fmtCard.style.opacity = 0;
anime({ targets: fmtCard, opacity: [0, 1], translateY: [-10, 0], duration: 300 });
fetching = false;
urlInput.classList.remove("fetching");
});
fmtForm.addEventListener("submit", (e) => {
e.preventDefault();
if (downloading) return;
const fmtId = new FormData(fmtForm).get("format_id");
if (!fmtId) { errBlink(dlBtn); return; }
downloading = true;
const sid = hidSid.value;
dlBtn.disabled = true;
dlBtn.classList.add("loading");
dlBtn.textContent = "Downloading";
startSSE(sid);
const ifr = document.createElement("iframe");
ifr.style.display = "none";
ifr.src = `/download_file?sid=${sid}&url=${qs(hidUrl.value)}&format_id=${fmtId}`;
document.body.appendChild(ifr);
/* fadeoutcard not needed */
fadeOutCard();
});
cancelBtn.addEventListener("click", cancelFlow);
closeBtn .addEventListener("click", cancelFlow);