Merge pull request #9 from PR0M3TH3AN/unstable

Added sidebar and massive performance improvements
This commit is contained in:
thePR0M3TH3AN
2025-02-07 13:12:22 -05:00
committed by GitHub
115 changed files with 32492 additions and 2122 deletions

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 160 KiB

View File

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 586 B

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12,0C5.373,0 0,5.373 0,12C0,18.627 5.373,24 12,24C18.627,24 24,18.627 24,12C24,5.373 18.627,0 12,0ZM9.967,16.01C10.531,14.221 11.599,12.078 11.788,11.536C12.061,10.749 11.577,10.4 10.048,11.745L9.708,11.105C11.452,9.208 15.043,8.779 13.821,11.718C13.058,13.553 12.512,14.792 12.2,15.748C11.745,17.141 12.894,16.576 14.019,15.537C14.172,15.787 14.222,15.868 14.375,16.156C11.877,18.534 9.104,18.744 9.967,16.01ZM14.709,7.841C14.177,8.294 13.389,8.284 12.948,7.819C12.507,7.354 12.581,6.611 13.112,6.158C13.644,5.705 14.432,5.716 14.873,6.18C15.312,6.646 15.24,7.389 14.709,7.841Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.0349343,0,0,0.0349343,-55.5414,-28.6416)">
<path d="M1878.62,1496.58C1719.5,1470.41 1597.95,1332.09 1597.95,1165.6C1597.95,980.47 1748.25,830.166 1933.38,830.166C2118.51,830.166 2268.82,980.47 2268.82,1165.6C2268.82,1331.86 2147.6,1470.03 1988.8,1496.47L1988.8,1466.79C2131.38,1440.68 2239.59,1315.68 2239.59,1165.6C2239.59,996.604 2102.38,859.399 1933.38,859.399C1764.39,859.399 1627.18,996.604 1627.18,1165.6C1627.18,1315.91 1735.72,1441.07 1878.62,1466.91L1878.62,1496.58ZM1878.62,1400.68C1771.77,1375.85 1692.06,1279.96 1692.06,1165.6C1692.06,1032.41 1800.19,924.274 1933.38,924.274C2066.57,924.274 2174.71,1032.41 2174.71,1165.6C2174.71,1279.73 2095.32,1375.46 1988.8,1400.53L1988.8,1370.37C2079.02,1345.98 2145.48,1263.49 2145.48,1165.6C2145.48,1048.54 2050.44,953.507 1933.38,953.507C1816.33,953.507 1721.29,1048.54 1721.29,1165.6C1721.29,1263.73 1788.07,1346.38 1878.62,1370.55L1878.62,1400.68ZM1878.62,1304.01C1823.55,1282.18 1784.56,1228.41 1784.56,1165.6C1784.56,1083.46 1851.24,1016.77 1933.38,1016.77C2015.52,1016.77 2082.21,1083.46 2082.21,1165.6C2082.21,1228.16 2043.53,1281.76 1988.8,1303.75L1988.8,1271.59C2026.93,1251.59 2052.98,1211.62 2052.98,1165.6C2052.98,1099.6 1999.39,1046.01 1933.38,1046.01C1867.38,1046.01 1813.79,1099.6 1813.79,1165.6C1813.79,1211.88 1840.13,1252.05 1878.62,1271.93L1878.62,1304.01ZM1933.38,1109.82C1964.17,1109.82 1989.16,1134.82 1989.16,1165.6C1989.16,1196.39 1964.17,1221.38 1933.38,1221.38C1902.6,1221.38 1877.6,1196.39 1877.6,1165.6C1877.6,1134.82 1902.6,1109.82 1933.38,1109.82Z" style="fill:rgb(92,111,138);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

7
assets/svg/beta-icon.svg Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1.09255,0,0,1.09255,-0.61419,-0.557138)">
<path d="M11.546,1.337C17.151,1.337 21.702,5.888 21.702,11.493C21.702,17.099 17.151,21.65 11.546,21.65C5.94,21.65 1.389,17.099 1.389,11.493C1.389,5.888 5.94,1.337 11.546,1.337ZM10.288,13.666C10.735,14.248 11.352,14.54 12.138,14.54C12.878,14.54 13.491,14.29 13.975,13.792C14.46,13.293 14.702,12.697 14.702,12.003C14.702,11.141 14.3,10.526 13.495,10.158C13.924,9.994 14.274,9.743 14.544,9.407C14.814,9.071 14.949,8.687 14.949,8.254C14.949,7.757 14.757,7.325 14.375,6.959C13.992,6.593 13.445,6.409 12.734,6.409C11.929,6.409 11.274,6.652 10.768,7.136C10.262,7.62 9.882,8.454 9.628,9.638L8.143,16.578L9.666,16.578L10.288,13.666ZM11.8,9.445L11.532,10.619C11.611,10.616 11.672,10.614 11.715,10.614C12.741,10.614 13.254,11.043 13.254,11.901C13.254,12.398 13.108,12.773 12.817,13.025C12.525,13.277 12.221,13.403 11.902,13.403C11.566,13.403 11.283,13.287 11.052,13.057C10.822,12.826 10.706,12.491 10.706,12.051C10.706,11.819 10.744,11.524 10.819,11.166L11.162,9.595C11.266,9.112 11.373,8.744 11.484,8.49C11.595,8.236 11.76,8.03 11.98,7.871C12.2,7.712 12.449,7.632 12.728,7.632C12.953,7.632 13.137,7.698 13.278,7.831C13.419,7.963 13.49,8.124 13.49,8.313C13.49,8.592 13.372,8.851 13.136,9.091C12.9,9.33 12.53,9.45 12.026,9.45C11.983,9.45 11.908,9.448 11.8,9.445Z" style="fill:rgb(92,111,138);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

5
assets/svg/blog-icon.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M13.744,8C13.744,8 15.266,0 10.409,0L2,0L2,24L22,24L22,11C22,7.581 16.753,7.255 13.744,8ZM18,19L6,19L6,18L18,18L18,19ZM18,16L6,16L6,15L18,15L18,16ZM18,13L6,13L6,12L18,12L18,13ZM14.568,0.075C16.77,1.249 20.506,4.958 22,6.956C20.714,6.056 17.956,5.299 15.909,5.777C16.131,4.309 15.724,1.243 14.568,0.075Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 817 B

View File

Before

Width:  |  Height:  |  Size: 519 B

After

Width:  |  Height:  |  Size: 519 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12,0C5.373,0 0,5.373 0,12C0,18.627 5.373,24 12,24C18.627,24 24,18.627 24,12C24,5.373 18.627,0 12,0ZM12,22C8.877,22 6.086,20.559 4.251,18.31C4.51,17.722 5.034,17.315 6.118,17.064C8.362,16.546 10.577,16.083 9.511,14.119C6.356,8.299 8.612,5 12,5C15.322,5 17.634,8.177 14.489,14.119C13.454,16.071 15.589,16.535 17.882,17.064C18.964,17.314 19.492,17.719 19.753,18.305C17.917,20.558 15.125,22 12,22Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 909 B

5
assets/svg/dns-icon.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12,0C18.623,0 24,5.377 24,12C24,18.623 18.623,24 12,24C5.377,24 0,18.623 0,12C0,5.377 5.377,0 12,0M14.557,16L9.442,16C9.988,18.46 10.883,20.114 12,21.744C13.194,20.003 14.041,18.334 14.557,16M7.4,16L2.833,16C4.069,18.825 6.537,20.972 9.588,21.716C8.54,19.983 7.805,18.058 7.4,16M21.167,16L16.6,16C16.209,17.988 15.505,19.887 14.425,21.694C17.437,20.931 19.942,18.799 21.167,16M7.162,10L2.2,10C1.933,11.313 1.933,12.685 2.2,14L7.115,14C6.996,12.671 7.014,11.328 7.162,10M14.823,10L9.176,10C9.011,11.326 8.991,12.672 9.123,14L14.876,14C15.009,12.672 14.987,11.327 14.823,10M21.8,10L16.837,10C16.985,11.328 17.003,12.671 16.885,14L21.8,14C22.06,12.715 22.073,11.352 21.8,10M9.644,2.271C6.567,3.003 4.077,5.157 2.833,8L7.486,8C7.921,5.958 8.664,4.015 9.644,2.271M11.999,2.223C10.91,3.993 10.089,5.676 9.536,8L14.463,8C13.929,5.754 13.126,4.052 11.999,2.223M14.367,2.292C15.38,4.104 16.1,6.052 16.513,8L21.167,8C19.935,5.184 17.405,3.042 14.367,2.292" style="fill:rgb(92,111,138);"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12,0C18.623,0 24,5.377 24,12C24,18.623 18.623,24 12,24C5.377,24 0,18.623 0,12C0,5.377 5.377,0 12,0ZM12,2C17.519,2 22,6.481 22,12C22,17.519 17.519,22 12,22C6.481,22 2,17.519 2,12C2,6.481 6.481,2 12,2ZM13.476,14.955C14.464,14.55 15.233,13.744 15.592,12.739L18,6L11.328,8.387C10.322,8.747 9.517,9.518 9.112,10.506L6.047,18L13.476,14.955ZM13.354,10.669C13.905,11.22 13.905,12.115 13.354,12.665C12.803,13.216 11.909,13.216 11.358,12.665C10.807,12.115 10.807,11.22 11.358,10.669C11.909,10.118 12.803,10.118 13.354,10.669Z" style="fill:rgb(92,111,138);"/>
</svg>

After

Width:  |  Height:  |  Size: 1013 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M6.676,9.18C5.25,9.171 3.459,9.944 2.093,11.31C1.572,11.831 1.114,12.439 0.76,13.122C1.992,12.189 3.307,11.897 4.846,12.761C5.299,11.562 5.902,10.343 6.676,9.18ZM14.83,17.323C13.566,18.149 12.324,18.745 11.249,19.165C12.112,20.705 11.82,22.018 10.888,23.25C11.572,22.897 12.179,22.438 12.7,21.916C14.07,20.547 14.844,18.751 14.83,17.323ZM19.957,4.035C19.613,4.011 19.276,4 18.946,4C11.777,4 7.697,9.465 6.213,13.86L10.152,17.8C14.677,16.18 20,12.251 20,5.158C20,4.792 19.986,4.418 19.957,4.035ZM11.717,12.293C11.391,11.968 11.391,11.44 11.717,11.115C12.042,10.789 12.57,10.789 12.895,11.115C13.221,11.441 13.221,11.968 12.895,12.293C12.569,12.619 12.042,12.619 11.717,12.293ZM14.073,9.937C13.422,9.287 13.422,8.231 14.073,7.58C14.724,6.929 15.779,6.929 16.429,7.58C17.08,8.231 17.08,9.286 16.429,9.937C15.779,10.587 14.725,10.587 14.073,9.937ZM1.641,20.315L0.886,19.56L5.226,15.237L5.981,15.992L1.641,20.315ZM5.79,21.862L5.035,21.107L8.064,18.053L8.819,18.808L5.79,21.862ZM0.755,24L-0,23.245L5.373,17.881L6.129,18.636L0.755,24ZM21.838,9.709C21.65,10.327 21.165,10.811 20.547,11C21.165,11.188 21.65,11.672 21.838,12.291C22.027,11.672 22.511,11.188 23.129,11C22.511,10.812 22.027,10.328 21.838,9.709ZM7.183,3.205C6.936,4.015 6.302,4.648 5.493,4.895C6.303,5.142 6.936,5.776 7.183,6.585C7.431,5.776 8.064,5.142 8.873,4.895C8.063,4.648 7.431,4.015 7.183,3.205ZM5.356,0C5.157,0.649 4.65,1.157 4,1.355C4.65,1.554 5.157,2.062 5.356,2.71C5.554,2.061 6.062,1.553 6.71,1.355C6.062,1.157 5.555,0.649 5.356,0ZM10.743,0C10.427,1.035 9.616,1.846 8.58,2.163C9.616,2.479 10.427,3.289 10.743,4.326C11.059,3.29 11.87,2.48 12.905,2.163C11.87,1.846 11.06,1.035 10.743,0ZM21.838,13.64C21.522,14.676 20.711,15.486 19.675,15.803C20.711,16.119 21.522,16.965 21.838,18C22.154,16.964 22.965,16.119 24,15.803C22.965,15.486 22.154,14.676 21.838,13.64Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12,0C5.374,0 0,5.373 0,12C0,17.302 3.438,21.8 8.207,23.387C8.806,23.498 9,23.126 9,22.81L9,20.576C5.662,21.302 4.967,19.16 4.967,19.16C4.421,17.773 3.634,17.404 3.634,17.404C2.545,16.659 3.717,16.675 3.717,16.675C4.922,16.759 5.556,17.912 5.556,17.912C6.626,19.746 8.363,19.216 9.048,18.909C9.155,18.134 9.466,17.604 9.81,17.305C7.145,17 4.343,15.971 4.343,11.374C4.343,10.063 4.812,8.993 5.579,8.153C5.455,7.85 5.044,6.629 5.696,4.977C5.696,4.977 6.704,4.655 8.997,6.207C9.954,5.941 10.98,5.808 12,5.803C13.02,5.808 14.047,5.941 15.006,6.207C17.297,4.655 18.303,4.977 18.303,4.977C18.956,6.63 18.545,7.851 18.421,8.153C19.191,8.993 19.656,10.064 19.656,11.374C19.656,15.983 16.849,16.998 14.177,17.295C14.607,17.667 15,18.397 15,19.517L15,22.81C15,23.129 15.192,23.504 15.801,23.386C20.566,21.797 24,17.3 24,12C24,5.373 18.627,0 12,0Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M23.334,11.96C22.621,11.234 22.462,10.131 22.941,9.233C23.283,8.593 23.307,7.832 23.005,7.171C22.704,6.511 22.112,6.029 21.404,5.869C20.413,5.644 19.682,4.802 19.601,3.788C19.542,3.065 19.15,2.41 18.539,2.018C17.93,1.625 17.172,1.54 16.489,1.789C15.533,2.136 14.463,1.821 13.847,1.013C13.407,0.437 12.723,0.098 11.997,0.098C11.272,0.098 10.588,0.437 10.148,1.013C9.535,1.822 8.465,2.137 7.509,1.79C6.827,1.542 6.069,1.627 5.459,2.019C4.849,2.411 4.456,3.066 4.398,3.789C4.316,4.803 3.586,5.646 2.595,5.87C1.887,6.03 1.295,6.512 0.994,7.172C0.693,7.832 0.717,8.594 1.059,9.233C1.538,10.13 1.379,11.234 0.667,11.96C0.158,12.477 -0.08,13.202 0.023,13.92C0.126,14.638 0.559,15.267 1.193,15.62C2.081,16.115 2.545,17.13 2.337,18.125C2.19,18.835 2.381,19.573 2.856,20.121C3.332,20.67 4.036,20.965 4.758,20.919C5.774,20.856 6.711,21.459 7.075,22.408C7.334,23.086 7.895,23.603 8.592,23.807C9.287,24.011 10.039,23.879 10.623,23.45C11.442,22.847 12.559,22.847 13.377,23.45C13.961,23.88 14.713,24.012 15.408,23.807C16.105,23.603 16.666,23.085 16.926,22.408C17.289,21.459 18.227,20.855 19.242,20.919C19.966,20.965 20.669,20.67 21.144,20.121C21.619,19.573 21.811,18.835 21.663,18.125C21.456,17.13 21.919,16.115 22.808,15.62C23.441,15.266 23.873,14.638 23.977,13.92C24.081,13.202 23.842,12.477 23.334,11.96ZM10.75,17.39L6.25,13.026L8.107,11.169L10.75,13.675L16.393,7.891L18.25,9.748L10.75,17.39Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

5
assets/svg/home-icon.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12,6.453L21,14.828L21,24L15,24L15,18L9,18L9,24L3,24L3,14.828L12,6.453ZM24,12.148L12,1L0,12.133L1.361,13.598L12,3.73L22.639,13.613L24,12.148Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 656 B

5
assets/svg/ipns-icon.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M21,7.702L12.5,12.322L12.5,22C14.067,21.135 18.879,18.483 20.477,17.601C20.8,17.424 21,17.082 21,16.71L21,7.702ZM11.5,12.321L3,7.599L3,16.605C3,16.975 3.197,17.313 3.514,17.492C5.104,18.39 9.93,21.115 11.5,22L11.5,12.321ZM3.421,6.692L12,11.455L20.672,6.742C20.672,6.742 14.041,3.004 12.486,2.128C12.335,2.043 12.167,2 12,2C11.832,2 11.665,2.043 11.514,2.128C9.959,3.004 3.421,6.692 3.421,6.692Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 909 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M21.337,0.458L17.4,4.394C14.951,2.928 11.73,3.25 9.62,5.36L5.359,9.622C3.249,11.732 2.926,14.953 4.391,17.402L0.456,21.337C-0.153,21.946 -0.153,22.934 0.456,23.544C1.065,24.154 2.054,24.153 2.663,23.544L6.598,19.609C9.047,21.074 12.268,20.753 14.378,18.642L18.639,14.381C20.749,12.271 21.072,9.05 19.607,6.6L23.543,2.664C24.152,2.055 24.152,1.067 23.543,0.457C22.934,-0.153 21.946,-0.152 21.337,0.458ZM16.475,12.215L12.214,16.476C11.319,17.37 10.038,17.645 8.904,17.303L10.536,15.671C11.145,15.062 11.145,14.074 10.536,13.464C9.927,12.854 8.938,12.855 8.329,13.464L6.698,15.096C6.355,13.963 6.631,12.681 7.524,11.787L11.785,7.526C12.68,6.632 13.961,6.357 15.095,6.699L13.464,8.329C12.855,8.938 12.855,9.926 13.464,10.536C14.073,11.146 15.062,11.145 15.671,10.536L17.301,8.906C17.644,10.039 17.368,11.321 16.475,12.215Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M22,16.75C22,16.336 21.664,16 21.25,16L2.75,16C2.336,16 2,16.336 2,16.75C2,17.164 2.336,17.5 2.75,17.5L21.25,17.5C21.664,17.5 22,17.164 22,16.75ZM22,11.75C22,11.336 21.664,11 21.25,11L2.75,11C2.336,11 2,11.336 2,11.75C2,12.164 2.336,12.5 2.75,12.5L21.25,12.5C21.664,12.5 22,12.164 22,11.75ZM22,6.75C22,6.336 21.664,6 21.25,6L2.75,6C2.336,6 2,6.336 2,6.75C2,7.164 2.336,7.5 2.75,7.5L21.25,7.5C21.664,7.5 22,7.164 22,6.75Z" style="fill:rgb(17,24,39);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 933 B

12
assets/svg/nostr-icon.svg Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g id="Artboard1" transform="matrix(0.217336,0,0,0.428004,4.79827,-2.20936)">
<rect x="-22.078" y="5.162" width="110.428" height="56.074" style="fill:none;"/>
<g id="nostr-head.svg" transform="matrix(0.143477,0,0,0.0728563,33.1364,33.1991)">
<g transform="matrix(1,0,0,1,-310,-310)">
<path d="M620,270.227L620,597.655C620,609.968 610.081,619.961 597.859,619.961L332.161,619.961C319.938,619.961 310.02,609.968 310.02,597.655L310.02,536.678C311.23,461.931 319.079,390.332 335.558,357.759C345.438,338.168 361.722,327.506 380.427,321.802C415.768,311.102 477.779,318.419 504.099,317.16C504.099,317.16 583.605,320.346 583.605,274.987C583.605,238.48 548.07,241.352 548.07,241.352C508.902,242.374 479.068,239.699 459.738,232.028C427.365,219.203 426.272,195.678 426.155,187.81C424.554,96.934 291.549,86.037 174.359,108.579C46.235,133.127 175.765,318.143 175.765,565.121L175.765,598.088C175.531,610.204 165.807,620 153.702,620L22.142,620C9.919,620 0,610.008 0,597.694L0,31.393C0,19.08 9.919,9.088 22.142,9.088L145.813,9.088C158.036,9.088 167.955,19.08 167.955,31.393C167.955,49.687 188.378,59.876 203.139,49.215C247.617,17.113 304.709,0 368.595,0C511.714,0 619.922,84.031 619.922,270.227L620,270.227ZM382.419,203.782C382.419,177.424 361.214,156.062 335.051,156.062C308.887,156.062 287.683,177.424 287.683,203.782C287.683,230.14 308.887,251.501 335.051,251.501C361.214,251.501 382.419,230.14 382.419,203.782Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M23.961,8.429C23.13,9.411 22.347,10.347 22,12.204L22,18.887L18,21.366L18,12.205C17.653,10.348 16.87,9.412 16.039,8.43C15.131,7.355 14,6.019 14,3.801L14.019,3.456L12,2L6.455,6L0,2L0,20L6.455,24L12,20L17.545,24L24,20L24,8.382L23.961,8.429ZM11,18.255L7,21.14L7,8.073L11,5.187L11,18.255ZM20,0C17.9,0 16,1.702 16,3.801C16,6.922 19.188,7.252 20,12C20.812,7.252 24,6.922 24,3.801C24,1.702 22.1,0 20,0ZM20,5.5C19.172,5.5 18.5,4.829 18.5,4C18.5,3.171 19.172,2.5 20,2.5C20.828,2.5 21.5,3.171 21.5,4C21.5,4.829 20.828,5.5 20,5.5Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 513 B

After

Width:  |  Height:  |  Size: 513 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M21.698,10.658L24,12L11.998,19L0,12L2.301,10.658L11.998,16.316L21.698,10.658ZM11.998,21.315L2.301,15.657L0,17L11.998,24L24,17L21.698,15.658L11.998,21.315ZM24,7L11.998,-0L0,7L11.998,14L24,7Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 704 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.0349343,0,0,0.0349343,-55.5414,-28.6416)">
<path d="M1878.62,1496.58C1719.5,1470.41 1597.95,1332.09 1597.95,1165.6C1597.95,980.47 1748.25,830.166 1933.38,830.166C2118.51,830.166 2268.82,980.47 2268.82,1165.6C2268.82,1331.86 2147.6,1470.03 1988.8,1496.47L1988.8,1466.79C2131.38,1440.68 2239.59,1315.68 2239.59,1165.6C2239.59,996.604 2102.38,859.399 1933.38,859.399C1764.39,859.399 1627.18,996.604 1627.18,1165.6C1627.18,1315.91 1735.72,1441.07 1878.62,1466.91L1878.62,1496.58ZM1878.62,1400.68C1771.77,1375.85 1692.06,1279.96 1692.06,1165.6C1692.06,1032.41 1800.19,924.274 1933.38,924.274C2066.57,924.274 2174.71,1032.41 2174.71,1165.6C2174.71,1279.73 2095.32,1375.46 1988.8,1400.53L1988.8,1370.37C2079.02,1345.98 2145.48,1263.49 2145.48,1165.6C2145.48,1048.54 2050.44,953.507 1933.38,953.507C1816.33,953.507 1721.29,1048.54 1721.29,1165.6C1721.29,1263.73 1788.07,1346.38 1878.62,1370.55L1878.62,1400.68ZM1878.62,1304.01C1823.55,1282.18 1784.56,1228.41 1784.56,1165.6C1784.56,1083.46 1851.24,1016.77 1933.38,1016.77C2015.52,1016.77 2082.21,1083.46 2082.21,1165.6C2082.21,1228.16 2043.53,1281.76 1988.8,1303.75L1988.8,1271.59C2026.93,1251.59 2052.98,1211.62 2052.98,1165.6C2052.98,1099.6 1999.39,1046.01 1933.38,1046.01C1867.38,1046.01 1813.79,1099.6 1813.79,1165.6C1813.79,1211.88 1840.13,1252.05 1878.62,1271.93L1878.62,1304.01ZM1933.38,1109.82C1964.17,1109.82 1989.16,1134.82 1989.16,1165.6C1989.16,1196.39 1964.17,1221.38 1933.38,1221.38C1902.6,1221.38 1877.6,1196.39 1877.6,1165.6C1877.6,1134.82 1902.6,1109.82 1933.38,1109.82Z" style="fill:rgb(92,111,138);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

5
bitvid_logo/dns-icon.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12,0C18.623,0 24,5.377 24,12C24,18.623 18.623,24 12,24C5.377,24 0,18.623 0,12C0,5.377 5.377,0 12,0M14.557,16L9.442,16C9.988,18.46 10.883,20.114 12,21.744C13.194,20.003 14.041,18.334 14.557,16M7.4,16L2.833,16C4.069,18.825 6.537,20.972 9.588,21.716C8.54,19.983 7.805,18.058 7.4,16M21.167,16L16.6,16C16.209,17.988 15.505,19.887 14.425,21.694C17.437,20.931 19.942,18.799 21.167,16M7.162,10L2.2,10C1.933,11.313 1.933,12.685 2.2,14L7.115,14C6.996,12.671 7.014,11.328 7.162,10M14.823,10L9.176,10C9.011,11.326 8.991,12.672 9.123,14L14.876,14C15.009,12.672 14.987,11.327 14.823,10M21.8,10L16.837,10C16.985,11.328 17.003,12.671 16.885,14L21.8,14C22.06,12.715 22.073,11.352 21.8,10M9.644,2.271C6.567,3.003 4.077,5.157 2.833,8L7.486,8C7.921,5.958 8.664,4.015 9.644,2.271M11.999,2.223C10.91,3.993 10.089,5.676 9.536,8L14.463,8C13.929,5.754 13.126,4.052 11.999,2.223M14.367,2.292C15.38,4.104 16.1,6.052 16.513,8L21.167,8C19.935,5.184 17.405,3.042 14.367,2.292" style="fill:rgb(92,111,138);"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M23.334,11.96C22.621,11.234 22.462,10.131 22.941,9.233C23.283,8.593 23.307,7.832 23.005,7.171C22.704,6.511 22.112,6.029 21.404,5.869C20.413,5.644 19.682,4.802 19.601,3.788C19.542,3.065 19.15,2.41 18.539,2.018C17.93,1.625 17.172,1.54 16.489,1.789C15.533,2.136 14.463,1.821 13.847,1.013C13.407,0.437 12.723,0.098 11.997,0.098C11.272,0.098 10.588,0.437 10.148,1.013C9.535,1.822 8.465,2.137 7.509,1.79C6.827,1.542 6.069,1.627 5.459,2.019C4.849,2.411 4.456,3.066 4.398,3.789C4.316,4.803 3.586,5.646 2.595,5.87C1.887,6.03 1.295,6.512 0.994,7.172C0.693,7.832 0.717,8.594 1.059,9.233C1.538,10.13 1.379,11.234 0.667,11.96C0.158,12.477 -0.08,13.202 0.023,13.92C0.126,14.638 0.559,15.267 1.193,15.62C2.081,16.115 2.545,17.13 2.337,18.125C2.19,18.835 2.381,19.573 2.856,20.121C3.332,20.67 4.036,20.965 4.758,20.919C5.774,20.856 6.711,21.459 7.075,22.408C7.334,23.086 7.895,23.603 8.592,23.807C9.287,24.011 10.039,23.879 10.623,23.45C11.442,22.847 12.559,22.847 13.377,23.45C13.961,23.88 14.713,24.012 15.408,23.807C16.105,23.603 16.666,23.085 16.926,22.408C17.289,21.459 18.227,20.855 19.242,20.919C19.966,20.965 20.669,20.67 21.144,20.121C21.619,19.573 21.811,18.835 21.663,18.125C21.456,17.13 21.919,16.115 22.808,15.62C23.441,15.266 23.873,14.638 23.977,13.92C24.081,13.202 23.842,12.477 23.334,11.96ZM10.75,17.39L6.25,13.026L8.107,11.169L10.75,13.675L16.393,7.891L18.25,9.748L10.75,17.39Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M12,6.453L21,14.828L21,24L15,24L15,18L9,18L9,24L3,24L3,14.828L12,6.453ZM24,12.148L12,1L0,12.133L1.361,13.598L12,3.73L22.639,13.613L24,12.148Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 656 B

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M21,7.702L12.5,12.322L12.5,22C14.067,21.135 18.879,18.483 20.477,17.601C20.8,17.424 21,17.082 21,16.71L21,7.702ZM11.5,12.321L3,7.599L3,16.605C3,16.975 3.197,17.313 3.514,17.492C5.104,18.39 9.93,21.115 11.5,22L11.5,12.321ZM3.421,6.692L12,11.455L20.672,6.742C20.672,6.742 14.041,3.004 12.486,2.128C12.335,2.043 12.167,2 12,2C11.832,2 11.665,2.043 11.514,2.128C9.959,3.004 3.421,6.692 3.421,6.692Z" style="fill:rgb(92,111,138);fill-rule:nonzero;"/>
</svg>

After

Width:  |  Height:  |  Size: 909 B

30444
blog.html Normal file

File diff suppressed because one or more lines are too long

151
components/disclaimer.html Normal file
View File

@@ -0,0 +1,151 @@
<!-- Disclaimer Modal (wide, matching other modals) -->
<div
id="disclaimerModal"
class="fixed inset-0 z-50 hidden"
style="background: transparent"
>
<!-- Dark/blur overlay -->
<div
class="absolute inset-0 z-10"
style="
background-color: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
"
></div>
<!-- Outer container with wide layout, just like content-appeals-form -->
<div
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
>
<!-- The .modal-content, same classes: bg-gray-900, w-full max-w-[90%], etc. -->
<div
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative"
>
<!-- Sticky top bar, if you want a top heading or 'X' button up here -->
<div
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between"
>
<!-- If you want an X to close, you can add it here, for example:
<button
id="closeDisclaimerBtn"
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-gray-300"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
-->
</div>
<!-- Main Content -->
<div class="p-6">
<!-- Scrollable disclaimers area -->
<div
class="space-y-6 text-gray-300"
style="max-height: 70vh; overflow-y: auto"
>
<!-- Example: Insert your disclaimers here -->
<div class="flex justify-center mb-8">
<img
src="assets/svg/bitvid-logo-dark-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</div>
<h2 class="text-2xl font-bold text-white mb-0">Welcome to bitvid</h2>
<!-- Warning Alert -->
<div
class="bg-yellow-900/20 border border-yellow-700/50 rounded-lg p-4 mb-6 flex items-start"
>
<svg
class="h-5 w-5 text-yellow-500 mt-0.5 mr-3 flex-shrink-0"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<p class="text-yellow-200">
This platform is currently in development and only supports Chrome
and Firefox-based browsers. Other browsers are not supported at
this time. You may encounter bugs or missing features. Give it a
sec. Videos might take 10 to 60 seconds to load initially.
</p>
</div>
<p>
bitvid is a decentralized video platform where content is shared
directly between users. We want you to understand a few important
points before you continue:
</p>
<div class="space-y-4">
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">Early Access Status</h3>
<p class="text-gray-400">
Currently, video posting is invite-only as we carefully scale
our platform. While anyone can watch videos, content creation is
limited to approved creators. This helps us maintain quality
content during our early stages.
</p>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">
Content Responsibility & Moderation
</h3>
<p class="text-gray-400">
While we don't host videos directly, we maintain community
standards through access control. Users who violate our
guidelines may be blocked from accessing the platform. All
content must follow local laws and platform guidelines.
</p>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">Platform Status</h3>
<p class="text-gray-400">
bitvid is a work in progress. Features may change or break, and
security improvements are ongoing. Your feedback and patience
help us build a better platform.
</p>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">Get Involved</h3>
<p class="text-gray-400">
Are you a developer? We'd love your help! Visit our GitHub
repository to contribute to building the future of decentralized
video sharing.
</p>
</div>
</div>
</div>
<!-- Button at bottom -->
<div class="mt-6">
<button
id="acceptDisclaimer"
class="w-full bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-black transition-colors duration-200"
>
I Understand
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -54,7 +54,7 @@
<div class="flex items-center space-x-4">
<img
id="profileModalAvatar"
src="assets/jpg/default-profile.jpg"
src="assets/svg/default-profile.svg"
alt="Profile"
class="w-16 h-16 object-cover rounded-full"
/>

248
components/sidebar.html Normal file
View File

@@ -0,0 +1,248 @@
<aside
id="sidebar"
class="bg-gray-900 text-white transition-transform duration-300 ease-in-out fixed top-0 left-0 w-64 h-screen overflow-y-auto"
>
<div class="flex flex-col h-full">
<!-- Top Navigation Links -->
<div class="flex-1 overflow-y-auto px-4">
<div class="bg-gray-800 rounded-lg shadow-md mt-4 p-4">
<nav class="flex flex-col space-y-2">
<a
href="#view=most-recent-videos"
class="flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
>
<img
src="assets/svg/home-icon.svg"
alt="Home"
class="w-6 h-6 mr-3 flex-shrink-0"
/>
<span class="sidebar-text">Home</span>
</a>
<a
href="#view=explore"
class="flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
>
<img
src="assets/svg/explore-icon.svg"
alt="Explore"
class="w-6 h-6 mr-3 flex-shrink-0"
/>
<span class="sidebar-text">Explore</span>
</a>
<a
id="subscriptionsLink"
href="#view=subscriptions"
class="hidden flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
>
<img
src="assets/svg/subscriptions-icon.svg"
alt="Subscriptions"
class="w-6 h-6 mr-3 flex-shrink-0"
/>
<span class="sidebar-text">Subscriptions</span>
</a>
</nav>
</div>
</div>
<!-- Mobile Dropdown Button for Footer Links -->
<!-- Added p-4 around the button for spacing, and styling consistent with top links -->
<div class="md:hidden p-4">
<button
id="footerDropdownButton"
class="flex items-center w-full py-2 px-4 bg-gray-800 hover:bg-gray-700 text-sm font-semibold text-gray-100 rounded shadow-md focus:outline-none transition-colors duration-200"
>
<!-- Arrow on the left side -->
<svg
id="footerDropdownIcon"
class="w-4 h-4 mr-2 transform transition-transform duration-300"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
<span id="footerDropdownText">More</span>
</button>
</div>
<!-- Footer Links Container -->
<!-- On mobile: hidden by default; on desktop: visible and pushed to the bottom -->
<div id="footerLinksContainer" class="hidden md:block md:mt-auto">
<hr class="border-gray-700" />
<div class="p-4">
<nav class="space-y-2 text-sm opacity-70">
<a
href="#view=about"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/about-icon.svg"
alt="About"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">About</span>
</a>
<a
href="#view=community-guidelines"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/guidelines-icon.svg"
alt="Guidelines"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">Guidelines</span>
</a>
<a
href="#view=getting-started"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/getting-started-icon.svg"
alt="Getting Started"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">Getting Started</span>
</a>
<a
href="#view=roadmap"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/roadmap-icon.svg"
alt="Roadmap"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">Roadmap</span>
</a>
<a
href="#view=blog"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/blog-icon.svg"
alt="Blog"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">Blog</span>
</a>
<a
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/nostr-icon.svg"
alt="Nostr"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">Nostr</span>
</a>
<a
href="https://github.com/PR0M3TH3AN/bitvid"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/github-icon.svg"
alt="GitHub"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">GitHub</span>
</a>
<a
href="torrent/beacon.html"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/beacon-icon.svg"
alt="βeacon"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">βeacon</span>
</a>
<a
href="https://beta.bitvid.network/"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/beta-icon.svg"
alt="Beta"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">Beta</span>
</a>
<a
href="#view=links"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/links-icon.svg"
alt="Links"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">Links</span>
</a>
<a
href="#view=ipns"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img
src="assets/svg/ipns-icon.svg"
alt="IPNS"
class="w-5 h-5 mr-3"
/>
<span class="sidebar-text">IPNS</span>
</a>
<a
href="https://bitvid.network/"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<img src="assets/svg/dns-icon.svg" alt="DNS" class="w-5 h-5 mr-3" />
<span class="sidebar-text">DNS</span>
</a>
</nav>
</div>
</div>
</div>
<script>
(function () {
var btn = document.getElementById("footerDropdownButton");
var footerLinks = document.getElementById("footerLinksContainer");
if (btn && footerLinks) {
btn.addEventListener("click", function () {
footerLinks.classList.toggle("hidden");
var icon = document.getElementById("footerDropdownIcon");
var textSpan = document.getElementById("footerDropdownText");
if (footerLinks.classList.contains("hidden")) {
textSpan.textContent = "More";
if (icon) {
icon.classList.remove("rotate-180");
}
} else {
textSpan.textContent = "Less";
if (icon) {
icon.classList.add("rotate-180");
}
}
});
}
})();
</script>
</aside>

View File

@@ -1,7 +1,3 @@
![](https://bitvid.netlify.app/assets/jpg/bitvid.jpg)
# About bitvid
Welcome to bitvid, a new kind of video platform that puts you in control. Unlike traditional video sites that keep your content on their servers, bitvid lets videos flow directly between creators and viewers. Think of it like a digital potluck where everyone brings and shares content directly with each other!
## What Makes bitvid Different?

View File

@@ -1,5 +1,3 @@
# **bitvid Community Guidelines**
Welcome to **bitvid**, a decentralized video-sharing platform built on Nostr. These Community Guidelines outline the types of content allowed and prohibited on the platform. As bitvid is still in early access, enforcement will occur at the client level, meaning violations will result in content being blocked from display rather than removed from relays. These policies will evolve as we implement robust user blocking and reporting features.
## **1. Content Principles**

View File

@@ -1,5 +1,3 @@
# Getting Started with bitvid
Ready to jump in? Here's everything you need to know to start watching and sharing videos on bitvid.
## Watching Videos

139
content/links.md Normal file
View File

@@ -0,0 +1,139 @@
Here is a collection of links to forms and pages.
---
## Forms
---
### Application Form
Main - [bitvid.network?modal=application](https://bitvid.network?modal=application)
Beta - [beta.bitvid.network?modal=application](https://beta.bitvid.network?modal=application)
---
### Bug Fix Form
Main - [bitvid.network?modal=bug](https://bitvid.network?modal=bug)
Beta - [beta.bitvid.network?modal=bug](https://beta.bitvid.network?modal=bug)
---
### Content Appeals Form
Main - [bitvid.network?modal=appeals](https://bitvid.network?modal=appeals)
Beta - [beta.bitvid.network?modal=appeals](https://beta.bitvid.network?modal=appeals)
---
### Feature Request Form
Main - [bitvid.network?modal=feature](https://bitvid.network?modal=feature)
Beta - [beta.bitvid.network?modal=feature](https://beta.bitvid.network?modal=feature)
---
### General Feedback Form
Main - [bitvid.network?modal=feedback](https://bitvid.network?modal=feedback)
Beta - [beta.bitvid.network?modal=feedback](https://beta.bitvid.network?modal=feedback)
---
## Views
---
### Home
Main - [bitvid.network#view=most-recent-videos](https://bitvid.network#view=most-recent-videos)
Beta - [beta.bitvid.network#view=most-recent-videos](https://beta.bitvid.network#view=most-recent-videos)
---
### Explore
Main - [bitvid.network#view=explore](https://bitvid.network#view=explore)
Beta - [beta.bitvid.network#view=explore](https://beta.bitvid.network#view=explore)
---
### Subscriptions
_Requires Nostr login_
Main - [bitvid.network#view=subscriptions](https://bitvid.network#view=subscriptions)
Beta - [beta.bitvid.network#view=subscriptions](https://beta.bitvid.network#view=subscriptions)
---
### About
Main - [bitvid.network#view=about](https://bitvid.network#view=about)
Beta - [beta.bitvid.network#view=about](https://beta.bitvid.network#view=about)
---
### Guidelines
Main - [bitvid.network#view=community-guidelines](https://bitvid.network#view=community-guidelines)
Beta - [beta.bitvid.network#view=community-guidelines](https://beta.bitvid.network#view=community-guidelines)
---
### Getting Started
Main - [bitvid.network#view=getting-started](https://bitvid.network#view=getting-started)
Beta - [beta.bitvid.network#view=getting-started](https://beta.bitvid.network#view=getting-started)
---
### Roadmap
Main - [bitvid.network#view=roadmap](https://bitvid.network#view=roadmap)
Beta - [beta.bitvid.network#view=roadmap](https://beta.bitvid.network#view=roadmap)
---
### βeacon
Main - [bitvid.network/torrent/beacon.html](https://bitvid.network/torrent/beacon.html)
Beta - [beta.bitvid.network/torrent/beacon.html](https://beta.bitvid.network/torrent/beacon.html)
---
### Beta
Main - [bitvid.network](https://bitvid.network)
Beta - [beta.bitvid.network](https://beta.bitvid.network)
---
### Links
Main - [bitvid.network#view=links](https://bitvid.network#view=links)
Beta - [beta.bitvid.network#view=links](https://beta.bitvid.network#view=links)
---
### IPNS
Main - [bitvid.network#view=ipns](https://bitvid.network#view=ipns)
Beta - [beta.bitvid.network#view=ipns](https://beta.bitvid.network#view=ipns)

View File

@@ -1,5 +1,3 @@
# Roadmap and Bug List
## UI Enhancements
- Add a copy Magnet button labeled "Seed".
@@ -13,10 +11,6 @@
## Bug Fixes
- Fix public key wrapping issue on smaller screens.
- Fix video editing failures.
- Resolve issue where reopening the same video doesn't work after closing the video player.
- Address "Video playback error: MEDIA_ELEMENT_ERROR: Empty src attribute" error.
- Fix "Dev Mode" publishing "Live Mode" notes—add a flag for dev mode posts.
## Feature Additions

View File

@@ -16,6 +16,9 @@ body {
background-color: var(--color-bg);
color: var(--color-text);
line-height: 1.5;
margin: 0;
padding: 0;
overflow-x: hidden; /* Disable horizontal scrolling */
}
header {
@@ -470,3 +473,56 @@ footer a:hover {
height: 100%;
object-fit: cover;
}
/* Sidebar default states */
#sidebar {
position: fixed;
top: 0;
left: 0;
width: 16rem; /* Tailwind's w-64 */
height: 100vh;
background-color: #0f172a;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
transition: transform 0.3s ease;
}
/* Mobile (max-width: 767px): Hide sidebar by default */
@media (max-width: 767px) {
#sidebar {
transform: translateX(-100%);
}
/* When the sidebar-open class is added, slide the sidebar in */
#sidebar.sidebar-open {
transform: translateX(0);
}
/* Optionally shift main content when sidebar is open */
#app.sidebar-open {
transform: translateX(16rem);
transition: transform 0.3s ease;
}
}
/* Desktop (min-width: 768px): Always show the sidebar */
@media (min-width: 768px) {
#sidebar {
transform: translateX(0) !important;
}
}
/* Collapsed/expanded classes if needed on desktop */
.sidebar-collapsed {
width: 4rem;
}
.sidebar-expanded {
width: 16rem;
}
.sidebar-collapsed .sidebar-text {
display: none;
}
/* Example: customizing the border & background in the sidebar */
#sidebar hr {
border-color: rgba(255, 255, 255, 0.1);
}

168
index.html Normal file
View File

@@ -0,0 +1,168 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | Decentralized Video Sharing</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="BitVid - Decentralized Video Sharing" />
<meta
property="og:description"
content="Share videos and follow creators freely, in a truly decentralized way."
/>
<meta property="og:image" content="assets/jpg/bitvid.jpg" />
<meta property="og:url" content="https://bitvid.network" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href="assets/favicon.ico" sizes="any" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="assets/png/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="assets/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Styles -->
<link href="css/tailwind.min.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" />
<link href="css/markdown.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<!--
SIDEBAR CONTAINER:
The <aside id="sidebar"> inside handles visibility.
By default, no special position or z-index on #sidebarContainer.
-->
<div id="sidebarContainer">
<!-- components/sidebar.html gets injected here -->
</div>
<!--
MAIN CONTENT:
md:ml-64 ensures content is shifted on desktop so the pinned sidebar doesn't overlap.
On mobile, we also toggle .sidebar-open to shift the entire content.
-->
<div id="app" class="md:ml-64 px-4 py-8 min-h-screen flex flex-col">
<!-- Header -->
<header class="mb-8 flex items-center w-full">
<!-- Mobile hamburger button (hidden on md+) -->
<!-- New button -->
<button
id="mobileMenuBtn"
class="md:hidden ml-2 mr-4 flex items-center justify-center w-10 h-10 rounded-full bg-transparent hover:bg-transparent focus:outline-none focus:ring-2 focus:ring-[#0f172a] z-[60]"
>
<img
src="assets/svg/mobile-sidebar-menu-icon.svg"
alt="Mobile Sidebar Menu Icon"
class="w-6 h-6"
/>
</button>
<!-- Logo -->
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
<!-- Buttons on the far right -->
<div class="ml-auto flex items-center space-x-4">
<!-- Login Button -->
<button
id="loginButton"
style="background-color: #1e293b"
class="inline-flex items-center justify-center w-12 h-12 rounded-full text-white text-sm font-bold leading-none hover:bg-[#e6002c] focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
>
<img
src="assets/svg/default-profile.svg"
alt="Profile Icon"
class="w-9 h-9"
/>
</button>
<!-- Upload Button (hidden by default) -->
<button
id="uploadButton"
style="background-color: #fe0032"
class="hidden inline-flex items-center justify-center w-12 h-12 rounded-full text-white text-xl font-bold leading-none hover:bg-[#e6002c] focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
>
+
</button>
<!-- Profile Button (hidden by default) -->
<button
id="profileButton"
class="hidden inline-flex items-center justify-center w-12 h-12 rounded-full bg-black text-white text-sm leading-none hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black"
>
<div class="w-10 h-10 rounded-full overflow-hidden">
<img
id="profileAvatar"
src="assets/svg/default-profile.svg"
alt="Profile"
class="w-full h-full object-cover"
/>
</div>
</button>
</div>
</header>
<!-- Error Container -->
<div
id="errorContainer"
class="hidden bg-red-100 text-red-900 p-4 rounded-md mb-4"
></div>
<!-- Success Container -->
<div
id="successContainer"
class="hidden bg-green-100 text-green-900 p-4 rounded-md mb-4"
></div>
<!-- Dynamic views (like most-recent-videos.html, etc.) -->
<main id="viewContainer" class="flex-grow mb-8"></main>
<!-- Modal Container (external modals) -->
<div id="modalContainer"></div>
<!-- Tagline / Slogan -->
<div class="text-center mb-8">
<h2 class="text-2xl font-bold text-gray-500 tracking-wide">
seed. zap. subscribe.
</h2>
</div>
</div>
<!-- Additional Scripts -->
<script src="js/libs/nostr.bundle.js"></script>
<script type="module">
import { nip19, SimplePool } from "https://esm.sh/nostr-tools@1.8.3";
window.NostrTools = { nip19, SimplePool };
</script>
<script type="module" src="js/config.js"></script>
<script type="module" src="js/lists.js"></script>
<script type="module" src="js/accessControl.js"></script>
<script type="module" src="js/webtorrent.js"></script>
<script type="module" src="js/nostr.js"></script>
<script type="module" src="js/viewManager.js"></script>
<script type="module" src="js/app.js"></script>
<script type="module" src="js/disclaimer.js"></script>
<script type="module" src="js/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
</body>
</html>

View File

@@ -4,7 +4,7 @@ import { loadView } from "./viewManager.js";
import { nostrClient } from "./nostr.js";
import { torrentClient } from "./webtorrent.js";
import { isDevMode } from "./config.js";
import { disclaimerModal } from "./disclaimer.js";
import disclaimerModal from "./disclaimer.js";
import { initialBlacklist, initialEventBlacklist } from "./lists.js";
/**
@@ -14,6 +14,48 @@ function fakeDecrypt(str) {
return str.split("").reverse().join("");
}
/**
* Simple IntersectionObserver-based lazy loader for images (or videos).
*
* Usage:
* const mediaLoader = new MediaLoader();
* mediaLoader.observe(imgElement);
*
* This will load the real image source from `imgElement.dataset.lazy`
* once the image enters the viewport.
*/
class MediaLoader {
constructor(rootMargin = "50px") {
this.observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const el = entry.target;
const lazySrc = el.dataset.lazy;
if (lazySrc) {
el.src = lazySrc;
delete el.dataset.lazy;
}
// Stop observing once loaded
this.observer.unobserve(el);
}
}
},
{ rootMargin }
);
}
observe(el) {
if (el.dataset.lazy) {
this.observer.observe(el);
}
}
disconnect() {
this.observer.disconnect();
}
}
class bitvidApp {
constructor() {
// Basic auth/display elements
@@ -22,6 +64,9 @@ class bitvidApp {
this.userStatus = document.getElementById("userStatus") || null;
this.userPubKey = document.getElementById("userPubKey") || null;
// Lazy-loading helper for images
this.mediaLoader = new MediaLoader();
// Optional: a "profile" button or avatar (if used)
this.profileButton = document.getElementById("profileButton") || null;
this.profileAvatar = document.getElementById("profileAvatar") || null;
@@ -65,6 +110,9 @@ class bitvidApp {
this.copyMagnetBtn = null;
this.shareBtn = null;
// Hide/Show Subscriptions Link
this.subscriptionsLink = null;
// Notification containers
this.errorContainer = document.getElementById("errorContainer") || null;
this.successContainer = document.getElementById("successContainer") || null;
@@ -140,18 +188,38 @@ class bitvidApp {
// 4. Connect to Nostr
await nostrClient.init();
// Grab the "Subscriptions" link by its id in the sidebar
this.subscriptionsLink = document.getElementById("subscriptionsLink");
const savedPubKey = localStorage.getItem("userPubKey");
if (savedPubKey) {
// Auto-login if a pubkey was saved
this.login(savedPubKey, false);
// If the user was already logged in, show the Subscriptions link
if (this.subscriptionsLink) {
this.subscriptionsLink.classList.remove("hidden");
}
}
// 5. Setup general event listeners, show disclaimers
this.setupEventListeners();
disclaimerModal.show();
// 6. Load the default view (most-recent-videos.html)
await loadView("views/most-recent-videos.html");
// 6) Load the default view ONLY if there's no #view= already
if (!window.location.hash || !window.location.hash.startsWith("#view=")) {
console.log(
"[app.init()] No #view= in the URL, loading default home view"
);
await loadView("views/most-recent-videos.html");
} else {
console.log(
"[app.init()] Found hash:",
window.location.hash,
"so skipping default load"
);
}
// 7. Once loaded, get a reference to #videoList
this.videoList = document.getElementById("videoList");
@@ -478,22 +546,36 @@ class bitvidApp {
await this.cleanup();
});
// 8) Handle back/forward nav => hide video modal
// 8) Handle back/forward navigation => hide video modal
window.addEventListener("popstate", async () => {
console.log("[popstate] user navigated back/forward; cleaning modal...");
await this.hideModal();
});
// Event delegation for the “Application Form” button inside the login modal
// 9) Event delegation on the video list container for playing videos
if (this.videoList) {
this.videoList.addEventListener("click", (event) => {
const magnetTrigger = event.target.closest("[data-play-magnet]");
if (magnetTrigger) {
// For a normal left-click (button 0, no Ctrl/Cmd), prevent navigation:
if (event.button === 0 && !event.ctrlKey && !event.metaKey) {
event.preventDefault(); // Stop browser from following the href
const magnet = magnetTrigger.dataset.playMagnet;
this.playVideo(magnet);
}
}
});
}
// 10) Event delegation for the “Application Form” button inside the login modal
document.addEventListener("click", (event) => {
if (event.target && event.target.id === "openApplicationModal") {
// 1) Hide the login modal
// Hide the login modal
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
}
// 2) Show the application modal
// Show the application modal
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.remove("hidden");
@@ -511,12 +593,12 @@ class bitvidApp {
{ kinds: [0], authors: [pubkey], limit: 1 },
]);
let displayName = "User";
let picture = "assets/jpg/default-profile.jpg";
let picture = "assets/svg/default-profile.svg";
if (events.length && events[0].content) {
const data = JSON.parse(events[0].content);
displayName = data.name || data.display_name || "User";
picture = data.picture || "assets/jpg/default-profile.jpg";
picture = data.picture || "assets/svg/default-profile.svg";
}
// If you have a top-bar avatar (profileAvatar)
@@ -535,6 +617,56 @@ class bitvidApp {
}
}
async fetchAndRenderProfile(pubkey, forceRefresh = false) {
const now = Date.now();
// 1) Check if we have a cached entry
const cacheEntry = this.profileCache.get(pubkey);
if (!forceRefresh && cacheEntry && now - cacheEntry.timestamp < 60000) {
// If it's less than 60 seconds old, just update DOM with it
this.updateProfileInDOM(pubkey, cacheEntry.profile);
return;
}
// 2) Otherwise, fetch from Nostr
try {
const userEvents = await nostrClient.pool.list(nostrClient.relays, [
{ kinds: [0], authors: [pubkey], limit: 1 },
]);
if (userEvents.length > 0 && userEvents[0].content) {
const data = JSON.parse(userEvents[0].content);
const profile = {
name: data.name || data.display_name || "Unknown",
picture: data.picture || "assets/svg/default-profile.svg",
};
// Cache it
this.profileCache.set(pubkey, { profile, timestamp: now });
// Update DOM
this.updateProfileInDOM(pubkey, profile);
}
} catch (err) {
console.error("Profile fetch error:", err);
}
}
updateProfileInDOM(pubkey, profile) {
// For any .author-pic[data-pubkey=...]
const picEls = document.querySelectorAll(
`.author-pic[data-pubkey="${pubkey}"]`
);
picEls.forEach((el) => {
el.src = profile.picture;
});
// For any .author-name[data-pubkey=...]
const nameEls = document.querySelectorAll(
`.author-name[data-pubkey="${pubkey}"]`
);
nameEls.forEach((el) => {
el.textContent = profile.name;
});
}
/**
* Actually handle the upload form submission.
*/
@@ -617,6 +749,11 @@ class bitvidApp {
this.profileButton.classList.remove("hidden");
}
// Show the "Subscriptions" link if it exists
if (this.subscriptionsLink) {
this.subscriptionsLink.classList.remove("hidden");
}
// (Optional) load the user's own Nostr profile
this.loadOwnProfile(pubkey);
@@ -663,6 +800,11 @@ class bitvidApp {
this.profileButton.classList.add("hidden");
}
// Hide the Subscriptions link
if (this.subscriptionsLink) {
this.subscriptionsLink.classList.add("hidden");
}
// Clear localStorage
localStorage.removeItem("userPubKey");
@@ -701,35 +843,47 @@ class bitvidApp {
* Hide the video modal.
*/
async hideModal() {
// 1) Clear intervals
// 1) Clear intervals, cleanup, etc. (unchanged)
if (this.activeIntervals && this.activeIntervals.length) {
this.activeIntervals.forEach((id) => clearInterval(id));
this.activeIntervals = [];
}
// 2) Cleanup resources (this stops the torrent, etc.)
try {
await fetch("/webtorrent/cancel/", { mode: "no-cors" });
} catch (err) {
// ignore
}
await this.cleanup();
// 3) Hide the modal
// 2) Hide the modal
if (this.playerModal) {
this.playerModal.style.display = "none";
this.playerModal.classList.add("hidden");
}
this.currentMagnetUri = null;
// 4) Revert ?v= param in the URL
window.history.replaceState({}, "", window.location.pathname);
// 3) Remove only `?v=` but **keep** the hash
const url = new URL(window.location.href);
url.searchParams.delete("v"); // remove ?v= param
const newUrl = url.pathname + url.search + url.hash;
window.history.replaceState({}, "", newUrl);
}
/**
* Subscribe to videos (older + new) and render them as they come in.
*/
async loadVideos() {
console.log("Starting loadVideos...");
async loadVideos(forceFetch = false) {
console.log("Starting loadVideos... (forceFetch =", forceFetch, ")");
// We do NOT decode initialEventBlacklist here.
// That happens once in the constructor, creating this.blacklistedEventIds.
// If forceFetch is true, unsubscribe from the old subscription to start fresh
if (forceFetch && this.videoSubscription) {
// Call unsubscribe on the subscription object directly.
this.videoSubscription.unsub();
this.videoSubscription = null;
}
// The rest of your existing logic:
if (!this.videoSubscription) {
if (this.videoList) {
this.videoList.innerHTML = `
@@ -738,7 +892,7 @@ class bitvidApp {
</p>`;
}
// Create a single subscription
// Create a new subscription
this.videoSubscription = nostrClient.subscribeVideos(() => {
const updatedAll = nostrClient.getActiveVideos();
@@ -749,7 +903,7 @@ class bitvidApp {
return false;
}
// 2) Check author (if youre also blacklisting authors by npub)
// 2) Check author if youre blacklisting authors by npub
const authorNpub = this.safeEncodeNpub(video.pubkey) || video.pubkey;
if (initialBlacklist.includes(authorNpub)) {
return false;
@@ -760,6 +914,15 @@ class bitvidApp {
this.renderVideoList(filteredVideos);
});
// *** IMPORTANT ***: Unsubscribe once we get the historical EOSE
// so that we do not hold an open subscription forever:
if (this.videoSubscription) {
this.videoSubscription.on("eose", () => {
this.videoSubscription.unsub();
console.log("[loadVideos] unsubscribed after EOSE");
});
}
} else {
// Already subscribed: just show what's cached
const allCached = nostrClient.getActiveVideos();
@@ -794,10 +957,10 @@ class bitvidApp {
return olderMatches.length > 0;
}
// 4) Build the DOM for each video in newestActive
async renderVideoList(videos) {
if (!this.videoList) return;
// Check if there's anything to show
if (!videos || videos.length === 0) {
this.videoList.innerHTML = `
<p class="flex justify-center items-center h-full w-full text-center text-gray-500">
@@ -809,13 +972,14 @@ class bitvidApp {
// Sort newest first
videos.sort((a, b) => b.created_at - a.created_at);
// <-- NEW: Convert allEvents map => array to check older overshadowed events
// Convert allEvents to an array for checking older overshadowed events
const fullAllEventsArray = Array.from(nostrClient.allEvents.values());
const fragment = document.createDocumentFragment();
const htmlList = videos.map((video, index) => {
videos.forEach((video, index) => {
if (!video.id || !video.title) {
console.error("Video missing ID/title:", video);
return "";
return;
}
const nevent = window.NostrTools.nip19.neventEncode({ id: video.id });
@@ -829,32 +993,31 @@ class bitvidApp {
: "border-none";
const timeAgo = this.formatTimeAgo(video.created_at);
// 1) Do we have an older version?
// Check if there's an older version (for revert button)
let hasOlder = false;
if (canEdit && video.videoRootId) {
hasOlder = this.hasOlderVersion(video, fullAllEventsArray);
}
// 2) If we do => show revert button
const revertButton = hasOlder
? `
<button
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
onclick="app.handleRevertVideo(${index}); document.getElementById('settingsDropdown-${index}').classList.add('hidden');"
>
Revert
</button>
`
<button
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
data-revert-index="${index}"
>
Revert
</button>
`
: "";
// 3) Gear menu
// Gear menu (only shown if canEdit)
const gearMenu = canEdit
? `
<div class="relative inline-block ml-3 overflow-visible">
<button
type="button"
class="inline-flex items-center p-2 rounded-full text-gray-400 hover:text-gray-200 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500"
onclick="document.getElementById('settingsDropdown-${index}').classList.toggle('hidden')"
data-settings-dropdown="${index}"
>
<img
src="assets/svg/video-settings-gear.svg"
@@ -869,14 +1032,14 @@ class bitvidApp {
<div class="py-1">
<button
class="block w-full text-left px-4 py-2 text-sm text-gray-100 hover:bg-gray-700"
onclick="app.handleEditVideo(${index}); document.getElementById('settingsDropdown-${index}').classList.add('hidden');"
data-edit-index="${index}"
>
Edit
</button>
${revertButton}
<button
class="block w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-red-700 hover:text-white"
onclick="app.handleFullDeleteVideo(${index}); document.getElementById('settingsDropdown-${index}').classList.add('hidden');"
data-delete-all-index="${index}"
>
Delete All
</button>
@@ -886,42 +1049,26 @@ class bitvidApp {
`
: "";
// 4) Build the card markup...
// Card markup
const cardHtml = `
<div class="video-card bg-gray-900 rounded-lg overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 ${highlightClass}">
<a
href="${shareUrl}"
target="_blank"
rel="noopener noreferrer"
data-play-magnet="${encodeURIComponent(video.magnet)}"
class="block cursor-pointer relative group"
onclick="if (event.button === 0 && !event.ctrlKey && !event.metaKey) {
event.preventDefault();
app.playVideo('${encodeURIComponent(video.magnet)}');
}"
>
<div class="ratio-16-9">
<img
src="assets/jpg/video-thumbnail-fallback.jpg"
data-real-src="${this.escapeHTML(video.thumbnail)}"
data-lazy="${this.escapeHTML(video.thumbnail)}"
alt="${this.escapeHTML(video.title)}"
onload="
const realSrc = this.getAttribute('data-real-src');
if (realSrc) {
const that = this;
const testImg = new Image();
testImg.onload = function() {
that.src = realSrc;
};
testImg.src = realSrc;
}
"
/>
</div>
</a>
<div class="p-4">
<h3
class="text-lg font-bold text-white line-clamp-2 hover:text-blue-400 cursor-pointer mb-3"
onclick="app.playVideo('${encodeURIComponent(video.magnet)}')"
data-play-magnet="${encodeURIComponent(video.magnet)}"
>
${this.escapeHTML(video.title)}
</h3>
@@ -931,7 +1078,7 @@ class bitvidApp {
<img
class="author-pic"
data-pubkey="${video.pubkey}"
src="assets/jpg/default-profile.jpg"
src="assets/svg/default-profile.svg"
alt="Placeholder"
/>
</div>
@@ -953,132 +1100,83 @@ class bitvidApp {
</div>
`;
// Fire off a background fetch for the author's profile
// Turn the HTML into an element
const template = document.createElement("template");
template.innerHTML = cardHtml.trim();
const cardEl = template.content.firstElementChild;
// Fetch the author's profile info in the background
this.fetchAndRenderProfile(video.pubkey);
return cardHtml;
// Add the finished card to our fragment
fragment.appendChild(cardEl);
});
// Filter out any empty strings
const valid = htmlList.filter((x) => x.length > 0);
if (valid.length === 0) {
this.videoList.innerHTML = `
<p class="text-center text-gray-500">
No valid videos to display.
</p>`;
return;
}
// Clear the list and add our fragment
this.videoList.innerHTML = "";
this.videoList.appendChild(fragment);
// Finally inject into DOM
this.videoList.innerHTML = valid.join("");
}
// Lazy-load images
const lazyEls = this.videoList.querySelectorAll("[data-lazy]");
lazyEls.forEach((el) => this.mediaLoader.observe(el));
/**
* Retrieve the profile for a given pubkey (kind:0) and update the DOM.
*/
async fetchAndRenderProfile(pubkey, forceRefresh = false) {
const now = Date.now();
// -------------------------------
// Gear menu / button event listeners
// -------------------------------
// Check if we already have a cached entry for this pubkey:
const cacheEntry = this.profileCache.get(pubkey);
// If not forcing refresh, and we have a cache entry less than 60 sec old, use it:
if (!forceRefresh && cacheEntry && now - cacheEntry.timestamp < 60000) {
this.updateProfileInDOM(pubkey, cacheEntry.profile);
return;
}
// Otherwise, go fetch from the relay
try {
const userEvents = await nostrClient.pool.list(nostrClient.relays, [
{ kinds: [0], authors: [pubkey], limit: 1 },
]);
if (userEvents.length > 0 && userEvents[0].content) {
const data = JSON.parse(userEvents[0].content);
const profile = {
name: data.name || data.display_name || "Unknown",
picture: data.picture || "assets/jpg/default-profile.jpg",
};
// Store into the cache with a timestamp
this.profileCache.set(pubkey, {
profile,
timestamp: now,
});
// Now update the DOM elements
this.updateProfileInDOM(pubkey, profile);
}
} catch (err) {
console.error("Profile fetch error for pubkey:", pubkey, err);
}
}
/**
* Update all DOM elements that match this pubkey, e.g. .author-pic[data-pubkey=...]
*/
updateProfileInDOM(pubkey, profile) {
const picEls = document.querySelectorAll(
`.author-pic[data-pubkey="${pubkey}"]`
// Toggle the gear menu
const gearButtons = this.videoList.querySelectorAll(
"[data-settings-dropdown]"
);
picEls.forEach((el) => {
el.src = profile.picture;
gearButtons.forEach((button) => {
button.addEventListener("click", () => {
const index = button.getAttribute("data-settings-dropdown");
const dropdown = document.getElementById(`settingsDropdown-${index}`);
if (dropdown) {
dropdown.classList.toggle("hidden");
}
});
});
const nameEls = document.querySelectorAll(
`.author-name[data-pubkey="${pubkey}"]`
// Edit button
const editButtons = this.videoList.querySelectorAll("[data-edit-index]");
editButtons.forEach((button) => {
button.addEventListener("click", () => {
const index = button.getAttribute("data-edit-index");
const dropdown = document.getElementById(`settingsDropdown-${index}`);
if (dropdown) dropdown.classList.add("hidden");
// Assuming you have a method like this in your code:
this.handleEditVideo(index);
});
});
// Revert button
const revertButtons = this.videoList.querySelectorAll(
"[data-revert-index]"
);
nameEls.forEach((el) => {
el.textContent = profile.name;
revertButtons.forEach((button) => {
button.addEventListener("click", () => {
const index = button.getAttribute("data-revert-index");
const dropdown = document.getElementById(`settingsDropdown-${index}`);
if (dropdown) dropdown.classList.add("hidden");
// Assuming you have a method like this in your code:
this.handleRevertVideo(index);
});
});
}
/**
* Plays a video given its magnet URI.
* We simply look up which event has this magnet
* and then delegate to playVideoByEventId for
* consistent modal and metadata handling.
*/
async playVideo(magnetURI) {
try {
if (!magnetURI) {
this.showError("Invalid Magnet URI.");
return;
}
const decodedMagnet = decodeURIComponent(magnetURI);
// If we are already playing this exact magnet, do nothing.
if (this.currentMagnetUri === decodedMagnet) {
this.log("Same video requested - already playing");
return;
}
// 1) Check local 'videosMap' or 'nostrClient.getActiveVideos()'
let matchedVideo = Array.from(this.videosMap.values()).find(
(v) => v.magnet === decodedMagnet
);
if (!matchedVideo) {
// Instead of forcing a full `fetchVideos()`,
// try looking in the activeVideos from local cache:
const activeVideos = nostrClient.getActiveVideos();
matchedVideo = activeVideos.find((v) => v.magnet === decodedMagnet);
}
// If still not found, you can do a single event-based approach or just show an error:
if (!matchedVideo) {
this.showError("No matching video found in local cache.");
return;
}
// Update tracking
this.currentMagnetUri = decodedMagnet;
// Delegate to the main method
await this.playVideoByEventId(matchedVideo.id);
} catch (error) {
console.error("Error in playVideo:", error);
this.showError(`Playback error: ${error.message}`);
}
// Delete All button
const deleteAllButtons = this.videoList.querySelectorAll(
"[data-delete-all-index]"
);
deleteAllButtons.forEach((button) => {
button.addEventListener("click", () => {
const index = button.getAttribute("data-delete-all-index");
const dropdown = document.getElementById(`settingsDropdown-${index}`);
if (dropdown) dropdown.classList.add("hidden");
// Assuming you have a method like this in your code:
this.handleFullDeleteVideo(index);
});
});
}
/**
@@ -1350,55 +1448,48 @@ class bitvidApp {
* Helper to open a video by event ID (like ?v=...).
*/
async playVideoByEventId(eventId) {
// First, check if this event is blacklisted by event ID
if (this.blacklistedEventIds.has(eventId)) {
this.showError("This content has been removed or is not allowed.");
return;
}
try {
// 1) Check local subscription map
let video = this.videosMap.get(eventId);
// 2) If not in local map, attempt fallback fetch from getOldEventById
if (!video) {
video = await this.getOldEventById(eventId);
}
// 3) If still not found, show error and return
if (!video) {
this.showError("Video not found.");
return;
}
// **Check if videos author is blacklisted**
const authorNpub = this.safeEncodeNpub(video.pubkey) || video.pubkey;
if (initialBlacklist.includes(authorNpub)) {
this.showError("This content has been removed or is not allowed.");
return;
}
// 4) Decrypt magnet if private & owned
if (
video.isPrivate &&
video.pubkey === this.pubkey &&
!video.alreadyDecrypted
) {
this.log("Decrypting private magnet link...");
video.magnet = fakeDecrypt(video.magnet);
video.alreadyDecrypted = true;
}
// 5) Show the modal
this.currentVideo = video;
this.currentMagnetUri = video.magnet;
this.showModalWithPoster();
// 6) Update ?v= param in the URL
// Update ?v= param in the URL
const nevent = window.NostrTools.nip19.neventEncode({ id: eventId });
const newUrl =
window.location.pathname + `?v=${encodeURIComponent(nevent)}`;
const newUrl = `${window.location.pathname}?v=${encodeURIComponent(
nevent
)}`;
window.history.pushState({}, "", newUrl);
// 7) Optionally fetch the author profile
// Fetch author profile
let creatorProfile = {
name: "Unknown",
picture: `https://robohash.org/${video.pubkey}`,
@@ -1418,7 +1509,6 @@ class bitvidApp {
this.log("Error fetching creator profile:", error);
}
// 8) Render video details in modal
const creatorNpub = this.safeEncodeNpub(video.pubkey) || video.pubkey;
if (this.videoTitle) {
this.videoTitle.textContent = video.title || "Untitled";
@@ -1444,28 +1534,49 @@ class bitvidApp {
this.creatorAvatar.alt = creatorProfile.name;
}
// 9) Clean up any existing torrent instance before starting a new stream
await torrentClient.cleanup();
// 10) Append a cache-busting parameter to the magnet URI
const cacheBustedMagnet = video.magnet + "&ts=" + Date.now();
this.log("Starting video stream with:", cacheBustedMagnet);
// Autoplay preferences
const storedUnmuted = localStorage.getItem("unmutedAutoplay");
const userWantsUnmuted = storedUnmuted === "true";
this.modalVideo.muted = !userWantsUnmuted;
this.modalVideo.addEventListener("volumechange", () => {
localStorage.setItem(
"unmutedAutoplay",
(!this.modalVideo.muted).toString()
);
});
const realTorrent = await torrentClient.streamVideo(
cacheBustedMagnet,
this.modalVideo
);
// 11) Start intervals to update stats
this.modalVideo.play().catch((err) => {
this.log("Autoplay failed:", err);
if (!this.modalVideo.muted) {
this.log("Falling back to muted autoplay.");
this.modalVideo.muted = true;
this.modalVideo.play().catch((err2) => {
this.log("Muted autoplay also failed:", err2);
});
}
});
// Update torrent stats every 3s
const updateInterval = setInterval(() => {
if (!document.body.contains(this.modalVideo)) {
clearInterval(updateInterval);
return;
}
this.updateTorrentStatus(realTorrent);
}, 1000);
}, 3000);
this.activeIntervals.push(updateInterval);
// (Optional) Mirror small inline stats into the modal
// Mirror stats into the modal if needed
const mirrorInterval = setInterval(() => {
if (!document.body.contains(this.modalVideo)) {
clearInterval(mirrorInterval);
@@ -1476,7 +1587,6 @@ class bitvidApp {
const peers = document.getElementById("peers");
const speed = document.getElementById("speed");
const downloaded = document.getElementById("downloaded");
if (status && this.modalStatus) {
this.modalStatus.textContent = status.textContent;
}
@@ -1492,7 +1602,7 @@ class bitvidApp {
if (downloaded && this.modalDownloaded) {
this.modalDownloaded.textContent = downloaded.textContent;
}
}, 1000);
}, 3000);
this.activeIntervals.push(mirrorInterval);
} catch (error) {
this.log("Error in playVideoByEventId:", error);

39
js/disclaimer.js Normal file
View File

@@ -0,0 +1,39 @@
// js/disclaimer.js
class DisclaimerModal {
constructor() {
// Initialize elements when the disclaimer HTML is in the DOM.
this.init();
}
init() {
this.modal = document.getElementById("disclaimerModal");
this.acceptButton = document.getElementById("acceptDisclaimer");
if (this.acceptButton) {
this.acceptButton.addEventListener("click", () => {
this.hide();
});
}
}
hide() {
if (this.modal) {
this.modal.classList.add("hidden");
}
localStorage.setItem("hasSeenDisclaimer", "true");
}
show() {
// In case the modal hasn't been initialized yet.
if (!this.modal) {
this.init();
}
if (!localStorage.getItem("hasSeenDisclaimer") && this.modal) {
this.modal.classList.remove("hidden");
}
}
}
// Create and export a default instance.
const disclaimerModal = new DisclaimerModal();
export default disclaimerModal;

323
js/index.js Normal file
View File

@@ -0,0 +1,323 @@
// js/index.js
// 1) Load modals (login, application, etc.)
async function loadModal(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to load " + url);
}
const html = await response.text();
document
.getElementById("modalContainer")
.insertAdjacentHTML("beforeend", html);
console.log(url, "loaded");
} catch (err) {
console.error(err);
}
}
// 2) Load sidebar
async function loadSidebar(url, containerId) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to load " + url);
}
const html = await response.text();
document.getElementById(containerId).innerHTML = html;
console.log(url, "loaded into", containerId);
} catch (err) {
console.error(err);
}
}
// 3) Load the disclaimer (now separate)
async function loadDisclaimer(url, containerId) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to load " + url);
}
const html = await response.text();
document.getElementById(containerId).insertAdjacentHTML("beforeend", html);
console.log(url, "disclaimer loaded into", containerId);
} catch (err) {
console.error(err);
}
}
// 4) Load everything: modals, sidebar, disclaimers
Promise.all([
// Existing modals
loadModal("components/login-modal.html"),
loadModal("components/application-form.html"),
loadModal("components/content-appeals-form.html"),
// New forms
loadModal("components/general-feedback-form.html"),
loadModal("components/feature-request-form.html"),
loadModal("components/bug-fix-form.html"),
])
.then(() => {
console.log("Modals loaded.");
return loadSidebar("components/sidebar.html", "sidebarContainer");
})
.then(() => {
console.log("Sidebar loaded.");
// Attach mobile menu button toggle logic (for sidebar)
const mobileMenuBtn = document.getElementById("mobileMenuBtn");
const sidebar = document.getElementById("sidebar");
const app = document.getElementById("app");
if (mobileMenuBtn && sidebar && app) {
mobileMenuBtn.addEventListener("click", () => {
sidebar.classList.toggle("sidebar-open");
app.classList.toggle("sidebar-open");
});
}
// Attach "More" button toggle logic for footer links
const footerDropdownButton = document.getElementById(
"footerDropdownButton"
);
if (footerDropdownButton) {
footerDropdownButton.addEventListener("click", () => {
const footerLinksContainer = document.getElementById(
"footerLinksContainer"
);
if (!footerLinksContainer) return;
footerLinksContainer.classList.toggle("hidden");
if (footerLinksContainer.classList.contains("hidden")) {
footerDropdownButton.innerHTML = "More &#9660;";
} else {
footerDropdownButton.innerHTML = "Less &#9650;";
}
});
}
// Load and set up sidebar navigation
return import("./sidebar.js").then((module) => {
module.setupSidebarNavigation();
});
})
.then(() => {
// Now load the disclaimer
return loadDisclaimer("components/disclaimer.html", "modalContainer");
})
.then(() => {
console.log("Disclaimer loaded.");
// 1) Login button => open login modal
const loginNavBtn = document.getElementById("loginButton");
if (loginNavBtn) {
loginNavBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.remove("hidden");
}
});
}
// 2) Close login modal
const closeLoginBtn = document.getElementById("closeLoginModal");
if (closeLoginBtn) {
closeLoginBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
}
});
}
// 3) "Application Form" => open application form
const openAppFormBtn = document.getElementById("openApplicationModal");
if (openAppFormBtn) {
openAppFormBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
}
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.remove("hidden");
}
});
}
// 4) Close application form
const closeNostrFormBtn = document.getElementById("closeNostrFormModal");
if (closeNostrFormBtn) {
closeNostrFormBtn.addEventListener("click", () => {
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.add("hidden");
}
// If user hasn't seen disclaimer, show it
if (!localStorage.getItem("hasSeenDisclaimer")) {
const disclaimerModal = document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.remove("hidden");
}
}
});
}
// Once everything is loaded, handle the query params (modal? v?) & disclaimers
handleQueryParams();
// Listen for hash changes
window.addEventListener("hashchange", handleHashChange);
// Also run once on initial load
handleHashChange();
});
/* -------------------------------------------
HELPER FUNCTIONS FOR QUERY AND HASH
-------------------------------------------- */
/**
* Sets the location.hash to "#view=<viewName>",
* removing any ?modal=... or ?v=... from the query string.
*/
export function setHashView(viewName) {
const url = new URL(window.location.href);
url.searchParams.delete("modal");
url.searchParams.delete("v");
const newUrl = url.pathname + url.search + `#view=${viewName}`;
window.history.replaceState({}, "", newUrl);
handleHashChange();
}
/**
* Sets a query param (e.g. ?modal=xxx or ?v=yyy),
* removing any "#view=..." from the hash to avoid collisions.
*/
export function setQueryParam(key, value) {
const url = new URL(window.location.href);
url.hash = "";
url.searchParams.set(key, value);
const newUrl = url.pathname + url.search;
window.history.replaceState({}, "", newUrl);
handleQueryParams();
}
/**
* Check the current URL for ?modal=..., ?v=..., etc.
* Open the correct modals or disclaimers as needed.
*/
function handleQueryParams() {
const urlParams = new URLSearchParams(window.location.search);
const modalParam = urlParams.get("modal");
if (modalParam === "appeals") {
const appealsModal = document.getElementById("contentAppealsModal");
if (appealsModal) {
appealsModal.classList.remove("hidden");
}
const closeAppealsBtn = document.getElementById("closeContentAppealsModal");
if (closeAppealsBtn) {
closeAppealsBtn.addEventListener("click", () => {
const appealsModal = document.getElementById("contentAppealsModal");
if (appealsModal) {
appealsModal.classList.add("hidden");
}
if (!localStorage.getItem("hasSeenDisclaimer")) {
const disclaimerModal = document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.remove("hidden");
}
}
});
}
} else if (modalParam === "application") {
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.remove("hidden");
}
} else {
const hasSeenDisclaimer = localStorage.getItem("hasSeenDisclaimer");
if (!hasSeenDisclaimer) {
const disclaimerModal = document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.remove("hidden");
}
}
}
if (modalParam === "feedback") {
const feedbackModal = document.getElementById("generalFeedbackModal");
if (feedbackModal) {
feedbackModal.classList.remove("hidden");
}
} else if (modalParam === "feature") {
const featureModal = document.getElementById("featureRequestModal");
if (featureModal) {
featureModal.classList.remove("hidden");
}
} else if (modalParam === "bug") {
const bugModal = document.getElementById("bugFixModal");
if (bugModal) {
bugModal.classList.remove("hidden");
}
}
const closeFeedbackBtn = document.getElementById("closeGeneralFeedbackModal");
if (closeFeedbackBtn) {
closeFeedbackBtn.addEventListener("click", () => {
const feedbackModal = document.getElementById("generalFeedbackModal");
if (feedbackModal) {
feedbackModal.classList.add("hidden");
}
});
}
const closeFeatureBtn = document.getElementById("closeFeatureRequestModal");
if (closeFeatureBtn) {
closeFeatureBtn.addEventListener("click", () => {
const featureModal = document.getElementById("featureRequestModal");
if (featureModal) {
featureModal.classList.add("hidden");
}
});
}
const closeBugBtn = document.getElementById("closeBugFixModal");
if (closeBugBtn) {
closeBugBtn.addEventListener("click", () => {
const bugModal = document.getElementById("bugFixModal");
if (bugModal) {
bugModal.classList.add("hidden");
}
});
}
}
/**
* Handle #view=... in the hash and load the correct partial view.
*/
function handleHashChange() {
console.log("handleHashChange called, current hash =", window.location.hash);
const hash = window.location.hash || "";
const match = hash.match(/^#view=(.+)/);
if (!match || !match[1]) {
import("./viewManager.js").then(({ loadView, viewInitRegistry }) => {
loadView("views/most-recent-videos.html").then(() => {
const initFn = viewInitRegistry["most-recent-videos"];
if (typeof initFn === "function") {
initFn();
}
});
});
return;
}
const viewName = match[1];
const viewUrl = `views/${viewName}.html`;
import("./viewManager.js").then(({ loadView, viewInitRegistry }) => {
loadView(viewUrl).then(() => {
const initFn = viewInitRegistry[viewName];
if (typeof initFn === "function") {
initFn();
}
});
});
}

View File

@@ -544,9 +544,6 @@ class NostrClient {
return true;
}
/**
* subscribeVideos => old approach
*/
/**
* Subscribe to *all* videos (old and new) with a single subscription,
* then call onVideo() each time a new or updated event arrives.
@@ -612,6 +609,7 @@ class NostrClient {
}
});
// Return the subscription object directly.
return sub;
}

29
js/sidebar.js Normal file
View File

@@ -0,0 +1,29 @@
// sidebar.js
import { setHashView } from "./index.js";
export function setupSidebarNavigation() {
const sidebarLinks = document.querySelectorAll('#sidebar a[href^="#view="]');
sidebarLinks.forEach((link) => {
link.addEventListener("click", (e) => {
e.preventDefault();
// e.g. "#view=about"
const href = link.getAttribute("href") || "";
const match = href.match(/^#view=(.+)/);
if (!match) return;
const viewName = match[1]; // "about", "ipns", etc.
setHashView(viewName); // This changes the hash and loads the view.
// --- NEW: if on mobile, close the sidebar automatically. ---
if (window.innerWidth < 768) {
const sidebar = document.getElementById("sidebar");
const app = document.getElementById("app");
if (sidebar && app) {
sidebar.classList.remove("sidebar-open");
app.classList.remove("sidebar-open");
}
}
});
});
}

55
js/viewManager.js Normal file
View File

@@ -0,0 +1,55 @@
// js/viewManager.js
// Load a partial view by URL into the #viewContainer
// js/viewManager.js
export async function loadView(viewUrl) {
try {
const res = await fetch(viewUrl);
if (!res.ok) {
throw new Error(`Failed to load view: ${res.status}`);
}
const text = await res.text();
// DOMParser, parse out the body, inject
const parser = new DOMParser();
const doc = parser.parseFromString(text, "text/html");
const container = document.getElementById("viewContainer");
container.innerHTML = doc.body.innerHTML;
// Now copy and execute each script
const scriptTags = doc.querySelectorAll("script");
scriptTags.forEach((oldScript) => {
const newScript = document.createElement("script");
Array.from(oldScript.attributes).forEach((attr) => {
newScript.setAttribute(attr.name, attr.value);
});
newScript.textContent = oldScript.textContent;
container.appendChild(newScript);
});
} catch (err) {
console.error("View loading error:", err);
document.getElementById("viewContainer").innerHTML =
"<p class='text-center text-red-500'>Failed to load content.</p>";
}
}
export const viewInitRegistry = {
"most-recent-videos": () => {
if (window.app && window.app.loadVideos) {
window.app.videoList = document.getElementById("videoList");
window.app.loadVideos();
}
// Force the profiles to update after the new view is in place.
if (window.app && window.app.forceRefreshAllProfiles) {
window.app.forceRefreshAllProfiles();
}
},
explore: () => {
console.log("Explore view loaded.");
},
subscriptions: () => {
console.log("Subscriptions view loaded.");
},
// Add additional view-specific functions here as needed.
};

View File

@@ -1,10 +1,19 @@
//js/webtorrent.js
import WebTorrent from "./webtorrent.min.js";
export class TorrentClient {
constructor() {
this.client = new WebTorrent();
// Reusable objects and flags
this.client = null;
this.currentTorrent = null;
this.TIMEOUT_DURATION = 60000; // 60 seconds
// Service worker registration is cached
this.swRegistration = null;
this.serverCreated = false; // Indicates if we've called createServer on this.client
// Timeout for SW operations
this.TIMEOUT_DURATION = 60000;
}
log(msg) {
@@ -21,6 +30,22 @@ export class TorrentClient {
return /firefox/i.test(window.navigator.userAgent);
}
/**
* Makes sure we have exactly one WebTorrent client instance and one SW registration.
* Called once from streamVideo.
*/
async init() {
// 1) If the client doesn't exist, create it
if (!this.client) {
this.client = new WebTorrent();
}
// 2) If we havent registered the service worker yet, do it now
if (!this.swRegistration) {
this.swRegistration = await this.setupServiceWorker();
}
}
async waitForServiceWorkerActivation(registration) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
@@ -67,6 +92,7 @@ export class TorrentClient {
throw new Error("Service Worker not supported or disabled");
}
// Brave-specific logic
if (isBraveBrowser) {
this.log("Checking Brave configuration...");
if (!navigator.serviceWorker) {
@@ -78,6 +104,7 @@ export class TorrentClient {
throw new Error("Please enable WebRTC in Brave Shield settings");
}
// Unregister all existing service workers before installing a fresh one
const registrations = await navigator.serviceWorker.getRegistrations();
for (const reg of registrations) {
await reg.unregister();
@@ -87,12 +114,13 @@ export class TorrentClient {
this.log("Registering service worker at /sw.min.js...");
const registration = await navigator.serviceWorker.register(
"./sw.min.js",
"/sw.min.js",
{
scope: "./",
scope: "/",
updateViaCache: "none",
}
);
this.log("Service worker registered");
if (registration.installing) {
@@ -134,8 +162,8 @@ export class TorrentClient {
// Force the SW to check for updates
registration.update();
this.log("Service worker ready");
return registration;
} catch (error) {
this.log("Service worker setup error:", error);
@@ -143,7 +171,7 @@ export class TorrentClient {
}
}
// Minimal handleChromeTorrent
// Handle Chrome-based browsers
handleChromeTorrent(torrent, videoElement, resolve, reject) {
torrent.on("warning", (err) => {
if (err && typeof err.message === "string") {
@@ -203,7 +231,7 @@ export class TorrentClient {
});
}
// Minimal handleFirefoxTorrent
// Handle Firefox-based browsers
handleFirefoxTorrent(torrent, videoElement, resolve, reject) {
const file = torrent.files.find((f) =>
/\.(mp4|webm|mkv)$/.test(f.name.toLowerCase())
@@ -226,7 +254,7 @@ export class TorrentClient {
});
try {
file.streamTo(videoElement, { highWaterMark: 32 * 1024 });
file.streamTo(videoElement, { highWaterMark: 256 * 1024 });
this.currentTorrent = torrent;
resolve(torrent);
} catch (err) {
@@ -242,27 +270,27 @@ export class TorrentClient {
/**
* Initiates streaming of a torrent magnet to a <video> element.
* Ensures the service worker is registered first.
* Ensures the service worker is set up only once and the client is reused.
*/
async streamVideo(magnetURI, videoElement) {
try {
// 1) Setup service worker
const registration = await this.setupServiceWorker();
if (!registration || !registration.active) {
throw new Error("Service worker setup failed");
}
// 1) Make sure we have a WebTorrent client and a valid SW registration.
await this.init();
// Create the WebTorrent server with the registered service worker.
// Force the server to use '/webtorrent' as the URL prefix.
this.client.createServer({
controller: registration,
pathPrefix: "/webtorrent",
});
this.log("WebTorrent server created");
// 2) Create the server once if not already created.
if (!this.serverCreated) {
this.client.createServer({
controller: this.swRegistration,
pathPrefix: location.origin + "/webtorrent",
});
this.serverCreated = true;
this.log("WebTorrent server created");
}
const isFirefoxBrowser = this.isFirefox();
return new Promise((resolve, reject) => {
// 3) Add the torrent to the client and handle accordingly.
if (isFirefoxBrowser) {
this.log("Starting torrent download (Firefox path)");
this.client.add(
@@ -275,30 +303,35 @@ export class TorrentClient {
);
} else {
this.log("Starting torrent download (Chrome path)");
this.client.add(magnetURI, (torrent) => {
this.client.add(magnetURI, { strategy: "sequential" }, (torrent) => {
this.log("Torrent added (Chrome path):", torrent.name);
this.handleChromeTorrent(torrent, videoElement, resolve, reject);
});
}
});
} catch (error) {
this.log("Failed to setup video streaming:", error);
this.log("Failed to set up video streaming:", error);
throw error;
}
}
/**
* Clean up resources.
* You might decide to keep the client alive if you want to reuse torrents.
* Currently, this fully destroys the client and resets everything.
*/
async cleanup() {
try {
if (this.currentTorrent) {
this.currentTorrent.destroy();
}
// Destroy client entirely and set to null so a future streamVideo call starts fresh
if (this.client) {
await this.client.destroy();
this.client = new WebTorrent();
this.client = null;
}
this.currentTorrent = null;
this.serverCreated = false;
} catch (error) {
this.log("Cleanup error:", error);
}

View File

@@ -4,32 +4,32 @@
"description": "seed. zap. subscribe.",
"icons": [
{
"src": "src/assets/png/android-chrome-192x192.png",
"src": "assets/png/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "src/assets/png/android-chrome-512x512.png",
"src": "assets/png/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "src/assets/png/apple-touch-icon.png",
"src": "assets/png/apple-touch-icon.png",
"sizes": "180x180",
"type": "image/png"
},
{
"src": "src/assets/png/favicon-32x32.png",
"src": "assets/png/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "src/assets/png/favicon-16x16.png",
"src": "assets/png/favicon-16x16.png",
"sizes": "16x16",
"type": "image/png"
}
],
"start_url": "src/index.html",
"start_url": "index.html",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#0f172a",

View File

@@ -1,226 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | About</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="bitvid | About" />
<meta
property="og:description"
content="View and render markdown content dynamically."
/>
<meta
property="og:image"
content="https://bitvid.netlify.app/assets/jpg/bitvid.jpg"
/>
<meta property="og:url" content="https://bitvid.btc.us" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href="assets/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="assets/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Tailwind CSS -->
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
rel="stylesheet"
/>
<!-- Main Layout Styles -->
<link href="css/style.css" rel="stylesheet" />
<!-- Markdown-Specific Styles -->
<link href="css/markdown.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<div
id="app"
class="container mx-auto px-4 py-8 min-h-screen flex flex-col"
>
<!-- Header -->
<header class="mb-8">
<div class="flex items-start">
<!-- Logo links back to index.html (or "/") -->
<a href="index.html">
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</a>
</div>
</header>
<!-- Markdown Content Section -->
<main>
<!--
We give this section a white background and a shadow
just like you originally had for other cards.
-->
<div id="markdown-container" class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-4">Loading Content...</h2>
</div>
</main>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
href="http://bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.network
</a>
|
<a
href="https://bitvid.btc.us"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
<div class="mt-2 space-x-4">
<a
href="community-guidelines.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Community Guidelines
</a>
<a
href="https://github.com/PR0M3TH3AN/bitvid"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
<a
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Nostr
</a>
<a
href="https://habla.news/p/nprofile1qyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgdwaehxw309ahx7uewd3hkcqgswaehxw309ahx7um5wgh8w6twv5q3yamnwvaz7tm0venxx6rpd9hzuur4vgqzpzf6x8a95eyp99dmwm4zmkx4a3cxgrnwdtfe3ej504m3aqjk4ugldyww3a"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Blog
</a>
<a
href="getting-started.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Getting Started
</a>
<a
href="about.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
About
</a>
<a
href="roadmap.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Roadmap
</a>
<a
href="https://beta.bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Beta
</a>
<a
href="torrent/beacon.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
βeacon
</a>
</div>
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>
</div>
<!-- Marked.js (for converting markdown to HTML) -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Highlight.js (optional for code block highlighting) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
async function loadMarkdown() {
const response = await fetch("content/about.md");
if (response.ok) {
const markdown = await response.text();
const container = document.getElementById("markdown-container");
// Convert markdown to HTML
const html = marked.parse(markdown);
// Insert the HTML into the container
container.innerHTML = html;
// (Optional) Highlight code blocks
document.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
} else {
document.getElementById("markdown-container").innerHTML =
'<p class="text-red-500">Error loading content. Please try again later.</p>';
}
}
loadMarkdown();
</script>
</body>
</html>

View File

@@ -1,226 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | About</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="bitvid | Community Guidelines" />
<meta
property="og:description"
content="View and render markdown content dynamically."
/>
<meta
property="og:image"
content="https://bitvid.netlify.app/assets/jpg/bitvid.jpg"
/>
<meta property="og:url" content="https://bitvid.btc.us" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href=.ico" sizes="any" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="assets/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Tailwind CSS -->
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
rel="stylesheet"
/>
<!-- Main Layout Styles -->
<link href="css/style.css" rel="stylesheet" />
<!-- Markdown-Specific Styles -->
<link href="css/markdown.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<div
id="app"
class="container mx-auto px-4 py-8 min-h-screen flex flex-col"
>
<!-- Header -->
<header class="mb-8">
<div class="flex items-start">
<!-- Logo links back to index.html (or "/") -->
<a href="index.html">
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</a>
</div>
</header>
<!-- Markdown Content Section -->
<main>
<!--
We give this section a white background and a shadow
just like you originally had for other cards.
-->
<div id="markdown-container" class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-4">Loading Content...</h2>
</div>
</main>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
href="http://bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.network
</a>
|
<a
href="https://bitvid.btc.us"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
<div class="mt-2 space-x-4">
<a
href="community-guidelines.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Community Guidelines
</a>
<a
href="https://github.com/PR0M3TH3AN/bitvid"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
<a
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Nostr
</a>
<a
href="https://habla.news/p/nprofile1qyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgdwaehxw309ahx7uewd3hkcqgswaehxw309ahx7um5wgh8w6twv5q3yamnwvaz7tm0venxx6rpd9hzuur4vgqzpzf6x8a95eyp99dmwm4zmkx4a3cxgrnwdtfe3ej504m3aqjk4ugldyww3a"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Blog
</a>
<a
href="getting-started.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Getting Started
</a>
<a
href="about.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
About
</a>
<a
href="roadmap.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Roadmap
</a>
<a
href="https://beta.bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Beta
</a>
<a
href="torrent/beacon.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
βeacon
</a>
</div>
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>
</div>
<!-- Marked.js (for converting markdown to HTML) -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Highlight.js (optional for code block highlighting) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
async function loadMarkdown() {
const response = await fetch("content/community-guidelines.md");
if (response.ok) {
const markdown = await response.text();
const container = document.getElementById("markdown-container");
// Convert markdown to HTML
const html = marked.parse(markdown);
// Insert the HTML into the container
container.innerHTML = html;
// (Optional) Highlight code blocks
document.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
} else {
document.getElementById("markdown-container").innerHTML =
'<p class="text-red-500">Error loading content. Please try again later.</p>';
}
}
loadMarkdown();
</script>
</body>
</html>

View File

@@ -1,226 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | Getting Started</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="bitvid | Getting Started" />
<meta
property="og:description"
content="View and render markdown content dynamically."
/>
<meta
property="og:image"
content="https://bitvid.netlify.app/assets/jpg/bitvid.jpg"
/>
<meta property="og:url" content="https://bitvid.network" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href="assets/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="assets/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Tailwind CSS -->
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
rel="stylesheet"
/>
<!-- Main Layout Styles -->
<link href="css/style.css" rel="stylesheet" />
<!-- Markdown-Specific Styles -->
<link href="css/markdown.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<div
id="app"
class="container mx-auto px-4 py-8 min-h-screen flex flex-col"
>
<!-- Header -->
<header class="mb-8">
<div class="flex items-start">
<!-- Logo links back to index.html (or "/") -->
<a href="index.html">
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</a>
</div>
</header>
<!-- Markdown Content Section -->
<main>
<!--
We give this section a white background and a shadow
just like you originally had for other cards.
-->
<div id="markdown-container" class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-4">Loading Content...</h2>
</div>
</main>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
href="http://bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.network
</a>
|
<a
href="https://bitvid.btc.us"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
<div class="mt-2 space-x-4">
<a
href="community-guidelines.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Community Guidelines
</a>
<a
href="https://github.com/PR0M3TH3AN/bitvid"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
<a
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Nostr
</a>
<a
href="https://habla.news/p/nprofile1qyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgdwaehxw309ahx7uewd3hkcqgswaehxw309ahx7um5wgh8w6twv5q3yamnwvaz7tm0venxx6rpd9hzuur4vgqzpzf6x8a95eyp99dmwm4zmkx4a3cxgrnwdtfe3ej504m3aqjk4ugldyww3a"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Blog
</a>
<a
href="getting-started.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Getting Started
</a>
<a
href="about.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
About
</a>
<a
href="roadmap.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Roadmap
</a>
<a
href="https://beta.bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Beta
</a>
<a
href="torrent/beacon.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
βeacon
</a>
</div>
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>
</div>
<!-- Marked.js (for converting markdown to HTML) -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Highlight.js (optional for code block highlighting) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
async function loadMarkdown() {
const response = await fetch("content/getting-started.md");
if (response.ok) {
const markdown = await response.text();
const container = document.getElementById("markdown-container");
// Convert markdown to HTML
const html = marked.parse(markdown);
// Insert the HTML into the container
container.innerHTML = html;
// (Optional) Highlight code blocks
document.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
} else {
document.getElementById("markdown-container").innerHTML =
'<p class="text-red-500">Error loading content. Please try again later.</p>';
}
}
loadMarkdown();
</script>
</body>
</html>

View File

@@ -1,648 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | Decentralized Video Sharing</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="BitVid - Decentralized Video Sharing" />
<meta
property="og:description"
content="Share videos and follow creators freely, in a truly decentralized way."
/>
<meta property="og:image" content="assets/jpg/bitvid.jpg" />
<meta property="og:url" content="https://bitvid.network" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href="assets/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="assets/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Styles -->
<link href="css/tailwind.min.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<div
id="app"
class="container mx-auto px-4 py-8 min-h-screen flex flex-col"
>
<!-- Header -->
<header class="mb-8 flex items-center">
<!-- Logo on the left -->
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
<!-- Buttons on the far right -->
<div class="ml-auto flex items-center space-x-4">
<!-- Login Button -->
<button
id="loginButton"
style="background-color: #fe0032"
class="inline-flex items-center justify-center w-12 h-12 rounded-full text-white text-sm font-bold leading-none hover:bg-[#e6002c] focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
>
login
</button>
<!-- Upload Button (hidden by default) -->
<button
id="uploadButton"
style="background-color: #fe0032"
class="hidden inline-flex items-center justify-center w-12 h-12 rounded-full text-white text-xl font-bold leading-none hover:bg-[#e6002c] focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
>
+
</button>
<!-- Profile Button (hidden by default) -->
<button
id="profileButton"
class="hidden inline-flex items-center justify-center w-12 h-12 rounded-full bg-black text-white text-sm leading-none hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black"
>
<!-- This inner DIV is just an avatar container, if you like -->
<div class="w-10 h-10 rounded-full overflow-hidden">
<img
id="profileAvatar"
src="assets/jpg/default-profile.jpg"
alt="Profile"
class="w-full h-full object-cover"
/>
</div>
</button>
</div>
</header>
<!-- Error Container -->
<div
id="errorContainer"
class="hidden bg-red-100 text-red-900 p-4 rounded-md mb-4"
></div>
<!-- Success Container -->
<div
id="successContainer"
class="hidden bg-green-100 text-green-900 p-4 rounded-md mb-4"
></div>
<!-- Main container for dynamic views (most-recent-videos.html, etc.) -->
<main id="viewContainer" class="flex-grow mb-8"></main>
<!-- Modal Container (external modals will be injected here) -->
<div id="modalContainer"></div>
<!-- Tagline / Slogan -->
<div class="text-center mb-8">
<h2 class="text-2xl font-bold text-gray-500 tracking-wide">
seed. zap. subscribe.
</h2>
</div>
<!-- Disclaimer Modal (wide, matching other modals) -->
<div
id="disclaimerModal"
class="fixed inset-0 z-50 hidden"
style="background: transparent"
>
<!-- Dark/blur overlay -->
<div
class="absolute inset-0 z-10"
style="
background-color: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
"
></div>
<!-- Outer container with wide layout, just like content-appeals-form -->
<div
class="modal-container relative h-full w-full flex items-start justify-center overflow-y-auto z-20"
>
<!-- The .modal-content, same classes: bg-gray-900, w-full max-w-[90%], etc. -->
<div
class="modal-content bg-gray-900 w-full max-w-[90%] lg:max-w-6xl my-0 rounded-lg overflow-hidden relative"
>
<!-- Sticky top bar, if you want a top heading or 'X' button up here -->
<div
class="sticky top-0 bg-gradient-to-b from-black/80 to-transparent p-4 flex items-center justify-between"
>
<!-- If you want an X to close, you can add it here, for example:
<button
id="closeDisclaimerBtn"
class="flex items-center justify-center w-10 h-10 rounded-full bg-black/50 hover:bg-black/70 transition-all duration-200 backdrop-blur focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-black focus:ring-blue-500"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6 text-gray-300"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
-->
</div>
<!-- Main Content -->
<div class="p-6">
<!-- Scrollable disclaimers area -->
<div
class="space-y-6 text-gray-300"
style="max-height: 70vh; overflow-y: auto"
>
<!-- Example: Insert your disclaimers here -->
<div class="flex justify-center mb-8">
<img
src="assets/svg/bitvid-logo-dark-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</div>
<h2 class="text-2xl font-bold text-white mb-0">
Welcome to bitvid
</h2>
<!-- Warning Alert -->
<div
class="bg-yellow-900/20 border border-yellow-700/50 rounded-lg p-4 mb-6 flex items-start"
>
<svg
class="h-5 w-5 text-yellow-500 mt-0.5 mr-3 flex-shrink-0"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<p class="text-yellow-200">
This platform is currently in development and only supports
Chrome and Firefox-based browsers. Other browsers are not
supported at this time. You may encounter bugs or missing
features. Give it a sec. Videos might take 10 to 60 seconds
to load initially.
</p>
</div>
<p>
bitvid is a decentralized video platform where content is
shared directly between users. We want you to understand a few
important points before you continue:
</p>
<div class="space-y-4">
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">
Early Access Status
</h3>
<p class="text-gray-400">
Currently, video posting is invite-only as we carefully
scale our platform. While anyone can watch videos, content
creation is limited to approved creators. This helps us
maintain quality content during our early stages.
</p>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">
Content Responsibility & Moderation
</h3>
<p class="text-gray-400">
While we don't host videos directly, we maintain community
standards through access control. Users who violate our
guidelines may be blocked from accessing the platform. All
content must follow local laws and platform guidelines.
</p>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">
Platform Status
</h3>
<p class="text-gray-400">
bitvid is a work in progress. Features may change or
break, and security improvements are ongoing. Your
feedback and patience help us build a better platform.
</p>
</div>
<div class="bg-gray-800 rounded-lg p-4">
<h3 class="text-white font-semibold mb-2">Get Involved</h3>
<p class="text-gray-400">
Are you a developer? We'd love your help! Visit our GitHub
repository to contribute to building the future of
decentralized video sharing.
</p>
</div>
</div>
</div>
<!-- Button at bottom -->
<div class="mt-6">
<button
id="acceptDisclaimer"
class="w-full bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-black transition-colors duration-200"
>
I Understand
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
href="http://bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.network
</a>
|
<a
href="https://bitvid.btc.us"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
<div class="mt-2 space-x-4">
<a
href="community-guidelines.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Community Guidelines
</a>
<a
href="https://github.com/PR0M3TH3AN/bitvid"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
<a
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Nostr
</a>
<a
href="https://habla.news/p/nprofile1qyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgdwaehxw309ahx7uewd3hkcqgswaehxw309ahx7um5wgh8w6twv5q3yamnwvaz7tm0venxx6rpd9hzuur4vgqzpzf6x8a95eyp99dmwm4zmkx4a3cxgrnwdtfe3ej504m3aqjk4ugldyww3a"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Blog
</a>
<a
href="getting-started.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Getting Started
</a>
<a
href="about.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
About
</a>
<a
href="roadmap.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Roadmap
</a>
<a
href="https://beta.bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Beta
</a>
<a
href="torrent/beacon.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
βeacon
</a>
</div>
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>
</div>
<!-- Load external modal components + attach event listeners -->
<script>
async function loadModal(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to load " + url);
}
const html = await response.text();
document
.getElementById("modalContainer")
.insertAdjacentHTML("beforeend", html);
console.log(url, "loaded");
} catch (err) {
console.error(err);
}
}
Promise.all([
// Existing modals
loadModal("components/login-modal.html"),
loadModal("components/application-form.html"),
loadModal("components/content-appeals-form.html"),
// New forms
loadModal("components/general-feedback-form.html"),
loadModal("components/feature-request-form.html"),
loadModal("components/bug-fix-form.html"),
]).then(() => {
console.log("Modals loaded.");
//
// 1) Login button => open login modal
//
const loginNavBtn = document.getElementById("loginButton");
if (loginNavBtn) {
loginNavBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.remove("hidden");
}
});
}
//
// 2) Close login modal
//
const closeLoginBtn = document.getElementById("closeLoginModal");
if (closeLoginBtn) {
closeLoginBtn.addEventListener("click", () => {
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
}
});
}
//
// 3) “Application Form” button => open application form
//
const openAppFormBtn = document.getElementById("openApplicationModal");
if (openAppFormBtn) {
openAppFormBtn.addEventListener("click", () => {
// Hide the login modal first
const loginModal = document.getElementById("loginModal");
if (loginModal) {
loginModal.classList.add("hidden");
}
// Now show the application form modal
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.remove("hidden");
}
});
}
//
// 4) Close application form
//
const closeNostrFormBtn = document.getElementById(
"closeNostrFormModal"
);
if (closeNostrFormBtn) {
closeNostrFormBtn.addEventListener("click", () => {
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.add("hidden");
}
// ADDED: If user has not seen disclaimer yet, show it after application modal is closed
if (!localStorage.getItem("hasSeenDisclaimer")) {
const disclaimerModal =
document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.remove("hidden");
}
}
});
}
//
// 5) ?modal=appeals => open content appeals form
// ?modal=application => open application form
//
const urlParams = new URLSearchParams(window.location.search);
const modalParam = urlParams.get("modal");
if (modalParam === "appeals") {
const appealsModal = document.getElementById("contentAppealsModal");
if (appealsModal) {
appealsModal.classList.remove("hidden");
}
// ADDED: After user closes appeals, show disclaimer if needed
const closeAppealsBtn = document.getElementById(
"closeContentAppealsModal"
);
if (closeAppealsBtn) {
closeAppealsBtn.addEventListener("click", () => {
appealsModal.classList.add("hidden");
if (!localStorage.getItem("hasSeenDisclaimer")) {
const disclaimerModal =
document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.remove("hidden");
}
}
});
}
} else if (modalParam === "application") {
// Show application form, but DO NOT show disclaimer until user closes
const appModal = document.getElementById("nostrFormModal");
if (appModal) {
appModal.classList.remove("hidden");
}
// Note: The close event above (closeNostrFormBtn) handles the disclaimer after closing.
} else {
// If there's no special param in the URL, we can consider showing the disclaimer right away
const hasSeenDisclaimer = localStorage.getItem("hasSeenDisclaimer");
if (!hasSeenDisclaimer) {
const disclaimerModal = document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.remove("hidden");
}
}
}
//
// 6) Close content appeals modal (needed if user navigates w/o param, then opens appeals)
//
const closeAppealsBtn = document.getElementById(
"closeContentAppealsModal"
);
if (closeAppealsBtn) {
closeAppealsBtn.addEventListener("click", () => {
const appealsModal = document.getElementById("contentAppealsModal");
if (appealsModal) {
appealsModal.classList.add("hidden");
}
});
}
//
// 7) Disclaimer 'I Understand' Button
//
const acceptDisclaimerBtn = document.getElementById("acceptDisclaimer");
if (acceptDisclaimerBtn) {
acceptDisclaimerBtn.addEventListener("click", () => {
// Hide disclaimer
const disclaimerModal = document.getElementById("disclaimerModal");
if (disclaimerModal) {
disclaimerModal.classList.add("hidden");
}
// Store the fact that user has seen it
localStorage.setItem("hasSeenDisclaimer", "true");
});
}
//
// 8) Query param checks for the three new forms
// https://bitvid.network?modal=feedback => open generalFeedbackModal
// https://bitvid.network?modal=feature => open featureRequestModal
// https://bitvid.network?modal=bug => open bugFixModal
//
if (modalParam === "feedback") {
const feedbackModal = document.getElementById("generalFeedbackModal");
if (feedbackModal) {
feedbackModal.classList.remove("hidden");
}
} else if (modalParam === "feature") {
const featureModal = document.getElementById("featureRequestModal");
if (featureModal) {
featureModal.classList.remove("hidden");
}
} else if (modalParam === "bug") {
const bugModal = document.getElementById("bugFixModal");
if (bugModal) {
bugModal.classList.remove("hidden");
}
}
//
// 9) Close buttons for the three new forms
//
// general feedback
const closeFeedbackBtn = document.getElementById(
"closeGeneralFeedbackModal"
);
if (closeFeedbackBtn) {
closeFeedbackBtn.addEventListener("click", () => {
const feedbackModal = document.getElementById(
"generalFeedbackModal"
);
if (feedbackModal) {
feedbackModal.classList.add("hidden");
}
});
}
// feature request
const closeFeatureBtn = document.getElementById(
"closeFeatureRequestModal"
);
if (closeFeatureBtn) {
closeFeatureBtn.addEventListener("click", () => {
const featureModal = document.getElementById("featureRequestModal");
if (featureModal) {
featureModal.classList.add("hidden");
}
});
}
// bug fix
const closeBugBtn = document.getElementById("closeBugFixModal");
if (closeBugBtn) {
closeBugBtn.addEventListener("click", () => {
const bugModal = document.getElementById("bugFixModal");
if (bugModal) {
bugModal.classList.add("hidden");
}
});
}
});
</script>
<!-- Other Scripts -->
<script src="js/libs/nostr.bundle.js"></script>
<script type="module">
import { nip19, SimplePool } from "https://esm.sh/nostr-tools@1.8.3";
window.NostrTools = { nip19, SimplePool };
</script>
<script type="module" src="js/config.js"></script>
<script type="module" src="js/lists.js"></script>
<script type="module" src="js/accessControl.js"></script>
<script type="module" src="js/webtorrent.js"></script>
<script type="module" src="js/nostr.js"></script>
<script type="module" src="js/viewManager.js"></script>
<script type="module" src="js/app.js"></script>
</body>
</html>

View File

@@ -1,226 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | About</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="bitvid | IPNS" />
<meta
property="og:description"
content="View and render markdown content dynamically."
/>
<meta
property="og:image"
content="https://bitvid.netlify.app/assets/jpg/bitvid.jpg"
/>
<meta property="og:url" content="https://bitvid.btc.us" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href=.ico" sizes="any" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="assets/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Tailwind CSS -->
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
rel="stylesheet"
/>
<!-- Main Layout Styles -->
<link href="css/style.css" rel="stylesheet" />
<!-- Markdown-Specific Styles -->
<link href="css/markdown.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<div
id="app"
class="container mx-auto px-4 py-8 min-h-screen flex flex-col"
>
<!-- Header -->
<header class="mb-8">
<div class="flex items-start">
<!-- Logo links back to index.html (or "/") -->
<a href="index.html">
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</a>
</div>
</header>
<!-- Markdown Content Section -->
<main>
<!--
We give this section a white background and a shadow
just like you originally had for other cards.
-->
<div id="markdown-container" class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-4">Loading Content...</h2>
</div>
</main>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
href="http://bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.network
</a>
|
<a
href="https://bitvid.btc.us"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
<div class="mt-2 space-x-4">
<a
href="community-guidelines.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Community Guidelines
</a>
<a
href="https://github.com/PR0M3TH3AN/bitvid"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
<a
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Nostr
</a>
<a
href="https://habla.news/p/nprofile1qyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgdwaehxw309ahx7uewd3hkcqgswaehxw309ahx7um5wgh8w6twv5q3yamnwvaz7tm0venxx6rpd9hzuur4vgqzpzf6x8a95eyp99dmwm4zmkx4a3cxgrnwdtfe3ej504m3aqjk4ugldyww3a"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Blog
</a>
<a
href="getting-started.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Getting Started
</a>
<a
href="about.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
About
</a>
<a
href="roadmap.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Roadmap
</a>
<a
href="https://beta.bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Beta
</a>
<a
href="torrent/beacon.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
βeacon
</a>
</div>
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS:
<a href="ipns.html" class="text-blue-600 underline">
k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</a>
</p>
</footer>
</div>
<!-- Marked.js (for converting markdown to HTML) -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Highlight.js (optional for code block highlighting) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
async function loadMarkdown() {
const response = await fetch("content/ipns.md");
if (response.ok) {
const markdown = await response.text();
const container = document.getElementById("markdown-container");
// Convert markdown to HTML
const html = marked.parse(markdown);
// Insert the HTML into the container
container.innerHTML = html;
// (Optional) Highlight code blocks
document.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
} else {
document.getElementById("markdown-container").innerHTML =
'<p class="text-red-500">Error loading content. Please try again later.</p>';
}
}
loadMarkdown();
</script>
</body>
</html>

View File

@@ -1,36 +0,0 @@
class DisclaimerModal {
constructor() {
this.modal = document.getElementById("disclaimerModal");
this.acceptButton = document.getElementById("acceptDisclaimer");
// If user previously dismissed the disclaimer, we'll store "true" in localStorage:
this.hasSeenDisclaimer = localStorage.getItem("hasSeenDisclaimer");
// Set up the click event for the "I Understand" button
this.setupEventListeners();
}
setupEventListeners() {
if (this.acceptButton) {
this.acceptButton.addEventListener("click", () => {
// Hide the disclaimer by adding the "hidden" class
if (this.modal) {
this.modal.classList.add("hidden");
}
// Mark that the user has seen the disclaimer, so we don't show it again
localStorage.setItem("hasSeenDisclaimer", "true");
});
}
}
show() {
// Only show it if the user hasn't seen it before
if (!this.hasSeenDisclaimer) {
if (this.modal) {
this.modal.classList.remove("hidden");
}
}
}
}
// Export an instance that you can import in your main script
export const disclaimerModal = new DisclaimerModal();

View File

@@ -1,17 +0,0 @@
// js/viewManager.js
// Load a partial view by URL into the #viewContainer
export async function loadView(viewUrl) {
try {
const res = await fetch(viewUrl);
if (!res.ok) {
throw new Error(`Failed to load view: ${res.status}`);
}
const html = await res.text();
document.getElementById("viewContainer").innerHTML = html;
} catch (err) {
console.error("View loading error:", err);
document.getElementById("viewContainer").innerHTML =
"<p class='text-center text-red-500'>Failed to load content.</p>";
}
}

View File

@@ -1,223 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bitvid | Roadmap</title>
<!-- Open Graph Meta Tags -->
<meta property="og:title" content="bitvid | Roadmap" />
<meta
property="og:description"
content="View and render markdown content dynamically."
/>
<meta
property="og:image"
content="https://bitvid.netlify.app/assets/jpg/bitvid.jpg"
/>
<meta property="og:url" content="https://bitvid.btc.us" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<!-- App Icons -->
<link rel="icon" href="assets/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/png/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="assets/png/favicon-16x16.png"
/>
<link rel="manifest" href="/site.webmanifest" />
<meta name="theme-color" content="#0f172a" />
<!-- Tailwind CSS -->
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
rel="stylesheet"
/>
<!-- Main Layout Styles -->
<link href="css/style.css" rel="stylesheet" />
<!-- Markdown-Specific Styles -->
<link href="css/markdown.css" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<div
id="app"
class="container mx-auto px-4 py-8 min-h-screen flex flex-col"
>
<!-- Header -->
<header class="mb-8">
<div class="flex items-start">
<!-- Logo links back to index.html (or "/") -->
<a href="index.html">
<img
src="assets/svg/bitvid-logo-light-mode.svg"
alt="BitVid Logo"
class="h-16"
/>
</a>
</div>
</header>
<!-- Markdown Content Section -->
<main>
<!--
We give this section a white background and a shadow
just like you originally had for other cards.
-->
<div id="markdown-container" class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-2xl font-bold mb-4">Loading Content...</h2>
</div>
</main>
<!-- Footer -->
<footer class="mt-auto pb-8 text-center px-4">
<a
href="http://bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.network
</a>
|
<a
href="https://bitvid.btc.us"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.btc.us
</a>
|
<a
href="https://bitvid.eth.limo"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
bitvid.eth.limo
</a>
<div class="mt-2 space-x-4">
<a
href="community-guidelines.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Community Guidelines
</a>
<a
href="https://github.com/PR0M3TH3AN/bitvid"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
<a
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Nostr
</a>
<a
href="https://habla.news/p/nprofile1qyv8wumn8ghj7un9d3shjtnndehhyapwwdhkx6tpdsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7qgdwaehxw309ahx7uewd3hkcqgswaehxw309ahx7um5wgh8w6twv5q3yamnwvaz7tm0venxx6rpd9hzuur4vgqzpzf6x8a95eyp99dmwm4zmkx4a3cxgrnwdtfe3ej504m3aqjk4ugldyww3a"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_blank"
rel="noopener noreferrer"
>
Blog
</a>
<a
href="getting-started.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Getting Started
</a>
<a
href="about.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
About
</a>
<a
href="roadmap.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Roadmap
</a>
<a
href="https://beta.bitvid.network/"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
Beta
</a>
<a
href="torrent/beacon.html"
class="text-gray-500 hover:text-gray-400 transition-colors duration-200"
target="_self"
rel="noopener noreferrer"
>
βeacon
</a>
</div>
<p
class="mt-2 text-xs text-gray-600 font-mono break-all max-w-full overflow-hidden"
>
IPNS: k51qzi5uqu5dgwr4oejq9rk41aoe9zcupenby6iqecsk5byc7rx48uecd133a1
</p>
</footer>
</div>
<!-- Marked.js (for converting markdown to HTML) -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Highlight.js (optional for code block highlighting) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
async function loadMarkdown() {
const response = await fetch("content/roadmap.md");
if (response.ok) {
const markdown = await response.text();
const container = document.getElementById("markdown-container");
// Convert markdown to HTML
const html = marked.parse(markdown);
// Insert the HTML into the container
container.innerHTML = html;
// (Optional) Highlight code blocks
document.querySelectorAll("pre code").forEach((block) => {
hljs.highlightBlock(block);
});
} else {
document.getElementById("markdown-container").innerHTML =
'<p class="text-red-500">Error loading content. Please try again later.</p>';
}
}
loadMarkdown();
</script>
</body>
</html>

View File

@@ -1,41 +0,0 @@
{
"name": "bitvid - Decentralized Video Sharing",
"short_name": "bitvid",
"description": "seed. zap. subscribe.",
"icons": [
{
"src": "assets/png/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/png/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "assets/png/apple-touch-icon.png",
"sizes": "180x180",
"type": "image/png"
},
{
"src": "assets/png/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "assets/png/favicon-16x16.png",
"sizes": "16x16",
"type": "image/png"
}
],
"start_url": "index.html",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#0f172a",
"orientation": "portrait-primary",
"scope": "/",
"categories": ["video", "entertainment", "decentralized", "streaming"],
"related_applications": [],
"lang": "en"
}

View File

@@ -40,18 +40,20 @@
// Handle fetch events
self.addEventListener("fetch", (event) => {
const requestURL = event.request.url;
// Only handle WebTorrent streaming requests; let other requests proceed normally.
// Only handle WebTorrent streaming requests
if (!requestURL.includes("/webtorrent/")) {
return;
}
// Create a promise to handle the request
const responsePromise = (async () => {
// Handle keepalive requests
// 1) Keepalive check
if (requestURL.includes("/webtorrent/keepalive/")) {
return new Response();
}
// Handle cancel requests
// 2) Cancel check
if (requestURL.includes("/webtorrent/cancel/")) {
return new Response(
new ReadableStream({
@@ -62,13 +64,18 @@
);
}
// Handle streaming requests
// 3) Streaming requests
// We define an async function and immediately invoke it with `event`
return (async function ({ request }) {
const { url, method, headers, destination } = request;
// 3a) Find open window clients
const windowClients = await clients.matchAll({
type: "window",
includeUncontrolled: true,
});
// 3b) We send a message to each client with a MessageChannel
const [clientResponse, port] = await new Promise((resolve) => {
for (const client of windowClients) {
const channel = new MessageChannel();
@@ -89,6 +96,7 @@
}
});
// 3c) Setup a small helper to close the port
let timeoutId = null;
const closeChannel = () => {
port.postMessage(false);
@@ -96,7 +104,7 @@
port.onmessage = null;
};
// Clone and update headers to prevent caching.
// 3d) Build a Headers object that prevents caching
const responseHeaders = new Headers(clientResponse.headers);
responseHeaders.set(
"Cache-Control",
@@ -105,7 +113,7 @@
responseHeaders.set("Pragma", "no-cache");
responseHeaders.set("Expires", "0");
// If the response is not a streaming request, return it directly.
// 3e) If the response is not a streaming request, return it directly
if (clientResponse.body !== "STREAM") {
closeChannel();
return new Response(clientResponse.body, {
@@ -115,7 +123,7 @@
});
}
// Otherwise, stream the response via a ReadableStream.
// 3f) Otherwise, create a streaming response
return new Response(
new ReadableStream({
pull(controller) {
@@ -130,14 +138,17 @@
resolvePull();
};
// If not cancelled, autoclose after 5s of no data
// *** Increase the timeout to avoid frequent CPU wake-ups ***
if (!cancelled && destination !== "document") {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
closeChannel();
resolvePull();
}, 5000);
}, 30000); // 30 seconds
}
// Request next chunk from client
port.postMessage(true);
});
},
@@ -154,8 +165,7 @@
})(event);
})();
if (responsePromise) {
event.respondWith(responsePromise);
}
// respondWith the promise if it exists
event.respondWith(responsePromise);
});
})();

Some files were not shown because too many files have changed in this diff Show More