Merge pull request #9 from PR0M3TH3AN/unstable
Added sidebar and massive performance improvements
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 586 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
5
assets/svg/about-icon.svg
Normal 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 |
7
assets/svg/beacon-icon.svg
Normal 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
@@ -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 |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
5
assets/svg/blog-icon.svg
Normal 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 |
Before Width: | Height: | Size: 519 B After Width: | Height: | Size: 519 B |
5
assets/svg/default-profile.svg
Normal 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
@@ -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 |
5
assets/svg/explore-icon.svg
Normal 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 |
5
assets/svg/getting-started-icon.svg
Normal 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 |
5
assets/svg/github-icon.svg
Normal 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 |
5
assets/svg/guidelines-icon.svg
Normal 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
@@ -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
@@ -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 |
5
assets/svg/links-icon.svg
Normal 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 |
5
assets/svg/mobile-sidebar-menu-icon.svg
Normal 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
@@ -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 |
5
assets/svg/roadmap-icon.svg
Normal 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 |
Before Width: | Height: | Size: 513 B After Width: | Height: | Size: 513 B |
5
assets/svg/subscriptions-icon.svg
Normal 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 |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
7
bitvid_logo/beacon-icon.svg
Normal 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
@@ -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 |
5
bitvid_logo/guidelines-icon.svg
Normal 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
bitvid_logo/home-icon.svg
Normal 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
bitvid_logo/ipns-icon.svg
Normal 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 |
151
components/disclaimer.html
Normal 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>
|
@@ -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
@@ -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>
|
@@ -1,7 +1,3 @@
|
||||

|
||||
|
||||
# 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?
|
@@ -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**
|
@@ -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
@@ -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)
|
@@ -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
|
@@ -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
@@ -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>
|
@@ -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 you’re also blacklisting authors by npub)
|
||||
// 2) Check author if you’re 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 video’s 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
@@ -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
@@ -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 ▼";
|
||||
} else {
|
||||
footerDropdownButton.innerHTML = "Less ▲";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@@ -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
@@ -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
@@ -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.
|
||||
};
|
@@ -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 haven’t 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);
|
||||
}
|
@@ -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",
|
||||
|
226
src/about.html
@@ -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>
|
@@ -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>
|
@@ -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>
|
648
src/index.html
@@ -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>
|
226
src/ipns.html
@@ -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>
|
@@ -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();
|
@@ -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>";
|
||||
}
|
||||
}
|
223
src/roadmap.html
@@ -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>
|
@@ -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"
|
||||
}
|
32
src/sw.min.js → sw.min.js
vendored
@@ -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, auto‐close 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);
|
||||
});
|
||||
})();
|