diff --git a/assets/products/app-store-download.svg b/assets/products/app-store-download.svg
new file mode 100755
index 0000000..16c0496
--- /dev/null
+++ b/assets/products/app-store-download.svg
@@ -0,0 +1,46 @@
+<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
+  <title>Download_on_the_App_Store_Badge_US-UK_RGB_wht_092917</title>
+  <g>
+    <g>
+      <g>
+        <path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z"/>
+        <path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z" style="fill: #fff"/>
+      </g>
+      <g id="_Group_" data-name="&lt;Group&gt;">
+        <g id="_Group_2" data-name="&lt;Group&gt;">
+          <g id="_Group_3" data-name="&lt;Group&gt;">
+            <path id="_Path_" data-name="&lt;Path&gt;" d="M24.99671,19.88935a5.14625,5.14625,0,0,1,2.45058-4.31771,5.26776,5.26776,0,0,0-4.15039-2.24376c-1.74624-.1833-3.43913,1.04492-4.329,1.04492-.90707,0-2.27713-1.02672-3.75247-.99637a5.52735,5.52735,0,0,0-4.65137,2.8367c-2.01111,3.482-.511,8.59939,1.41551,11.414.96388,1.37823,2.09037,2.91774,3.56438,2.86315,1.4424-.05983,1.98111-.91977,3.7222-.91977,1.72494,0,2.23035.91977,3.73427.88506,1.54777-.02512,2.52292-1.38435,3.453-2.77563a11.39931,11.39931,0,0,0,1.579-3.21589A4.97284,4.97284,0,0,1,24.99671,19.88935Z"/>
+            <path id="_Path_2" data-name="&lt;Path&gt;" d="M22.15611,11.47681a5.06687,5.06687,0,0,0,1.159-3.62989,5.15524,5.15524,0,0,0-3.33555,1.72582,4.82131,4.82131,0,0,0-1.18934,3.4955A4.26259,4.26259,0,0,0,22.15611,11.47681Z"/>
+          </g>
+        </g>
+        <g>
+          <path d="M42.30178,27.13965h-4.7334l-1.13672,3.35645H34.42678l4.4834-12.418h2.083l4.4834,12.418H43.43752Zm-4.24316-1.54883h3.752L39.961,20.14355H39.9092Z"/>
+          <path d="M55.1592,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.64455,21.34766,55.1592,23.16406,55.1592,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30178,29.01563,53.249,27.81934,53.249,25.96973Z"/>
+          <path d="M65.12453,25.96973c0,2.81348-1.50635,4.62109-3.77881,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C63.6094,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91064,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26662,29.01563,63.21389,27.81934,63.21389,25.96973Z"/>
+          <path d="M71.70949,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51758-3.61426,2.625,0,4.42383,1.47168,4.48438,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60547,1.626,3.60547,3.44238,0,2.32324-1.84961,3.77832-4.793,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z"/>
+          <path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z"/>
+          <path d="M86.064,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72512,30.6084,86.064,28.82617,86.064,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40137,1.16211-2.40137,3.10742c0,1.96191.89551,3.10645,2.40137,3.10645S92.7593,27.93164,92.7593,25.96973Z"/>
+          <path d="M96.18508,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z"/>
+          <path d="M109.38332,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10109,25.13477Z"/>
+        </g>
+      </g>
+    </g>
+    <g id="_Group_4" data-name="&lt;Group&gt;">
+      <g>
+        <path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z"/>
+        <path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z"/>
+        <path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z"/>
+        <path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z"/>
+        <path d="M59.09377,8.437h.88867v6.26074h-.88867Z"/>
+        <path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z"/>
+        <path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z"/>
+        <path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z"/>
+        <path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z"/>
+        <path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z"/>
+        <path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z"/>
+        <path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z"/>
+        <path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z"/>
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/assets/products/solar-network/alpha.webp b/assets/products/solar-network/alpha.webp
new file mode 100755
index 0000000..7276d56
Binary files /dev/null and b/assets/products/solar-network/alpha.webp differ
diff --git a/assets/products/solar-network/ft-chat.png b/assets/products/solar-network/ft-chat.png
new file mode 100755
index 0000000..2c828f6
Binary files /dev/null and b/assets/products/solar-network/ft-chat.png differ
diff --git a/assets/products/solar-network/ft-dashboard.png b/assets/products/solar-network/ft-dashboard.png
new file mode 100755
index 0000000..cb7c934
Binary files /dev/null and b/assets/products/solar-network/ft-dashboard.png differ
diff --git a/assets/products/solar-network/ft-explore.png b/assets/products/solar-network/ft-explore.png
new file mode 100755
index 0000000..67a6894
Binary files /dev/null and b/assets/products/solar-network/ft-explore.png differ
diff --git a/assets/products/solar-network/ft-news.png b/assets/products/solar-network/ft-news.png
new file mode 100755
index 0000000..3884d14
Binary files /dev/null and b/assets/products/solar-network/ft-news.png differ
diff --git a/assets/products/solar-network/ft-posting.png b/assets/products/solar-network/ft-posting.png
new file mode 100755
index 0000000..5851cf0
Binary files /dev/null and b/assets/products/solar-network/ft-posting.png differ
diff --git a/assets/products/solar-network/ft-stickers.png b/assets/products/solar-network/ft-stickers.png
new file mode 100755
index 0000000..cd25eaf
Binary files /dev/null and b/assets/products/solar-network/ft-stickers.png differ
diff --git a/assets/products/solar-network/icon.png b/assets/products/solar-network/icon.png
new file mode 100755
index 0000000..c9f3308
Binary files /dev/null and b/assets/products/solar-network/icon.png differ
diff --git a/assets/products/solar-network/labeled.webp b/assets/products/solar-network/labeled.webp
new file mode 100755
index 0000000..2120f8b
Binary files /dev/null and b/assets/products/solar-network/labeled.webp differ
diff --git a/components/PostList.vue b/components/PostList.vue
index c9b509a..aea347d 100644
--- a/components/PostList.vue
+++ b/components/PostList.vue
@@ -19,8 +19,8 @@ async function loadPost({ done }: any) {
     offset: posts.value.length.toString(),
   })
 
-  if (props.publisher) {
-    searchQueries.set("author", props.publisher)
+  if (props.author) {
+    searchQueries.set("author", props.author)
   }
   if (props.realm) {
     searchQueries.set("realm", props.realm)
diff --git a/lang/en-US.json b/lang/en-US.json
index b144376..f3fbc0c 100644
--- a/lang/en-US.json
+++ b/lang/en-US.json
@@ -69,6 +69,16 @@
   "continueReading": "Continue Reading",
   "download": "Download",
   "downloadDescription": "Pick the right version for you",
+  "downloadSwitchPrerelease": "Switch to pre-release",
+  "downloadSwitchRelease": "Switch to regular release",
+  "downloadForApple": "Looking for iOS / macOS version?",
+  "downloadTestFlight": "TestFlight",
+  "downloadTestFlightDescription": "For pre-release version",
+  "downloadForDesktop": "Looking for desktop version?",
+  "downloadForDesktopDescription": "If the release does not contain the desktop version, you can still download the latest build from GitHub Action",
+  "downloadWithoutDownload": "Want have a try without downloading?",
+  "downloadWeb": "Web",
+  "downloadWebChina": "with China Mainland Optimized",
   "attachmentUpload": "Upload new",
   "attachmentCreate": "Create Attachment",
   "attachmentCreateCaption": "Use Solar Network host your files",
@@ -76,5 +86,6 @@
   "attachmentUploadCompleted": "Uploaded",
   "upload": "Upload",
   "cancel": "Cancel",
-  "seeMore": "See more"
+  "seeMore": "See more",
+  "solarNetworkDescription": "A open, free, and friendly social network."
 }
diff --git a/lang/zh-CN.json b/lang/zh-CN.json
index 326eb5c..42a31aa 100644
--- a/lang/zh-CN.json
+++ b/lang/zh-CN.json
@@ -69,6 +69,16 @@
   "continueReading": "继续阅读",
   "download": "下载",
   "downloadDescription": "选择适合你的版本下载",
+  "downloadSwitchPrerelease": "切换至预发行版本",
+  "downloadSwitchRelease": "切换至稳定版本",
+  "downloadForApple": "使用 iOS / macOS 的设备?",
+  "downloadTestFlight": "测试飞机 (TestFlight)",
+  "downloadTestFlightDescription": "提供预发行版本",
+  "downloadForDesktop": "使用桌面设备?",
+  "downloadForDesktopDescription": "通常如果发行未包含桌面版本,你仍然可以从 GitHub Action 处下载最新的构建",
+  "downloadWithoutDownload": "想不下载尝试一下?",
+  "downloadWeb": "网页版",
+  "downloadWebChina": "中国大陆特供版本 (优化过的内容分发网络)",
   "attachmentUpload": "新传附件",
   "attachmentCreate": "新建附件",
   "attachmentCreateCaption": "使用 Solar Network 来托管你的文件",
@@ -76,5 +86,6 @@
   "attachmentUploadCompleted": "上传完成",
   "upload": "上传",
   "cancel": "取消",
-  "seeMore": "查看更多"
+  "seeMore": "查看更多",
+  "solarNetworkDescription": "开放、包容、和谐"
 }
diff --git a/layouts/dev-portal.vue b/layouts/dev-portal.vue
index 5ffdfff..b40a65c 100644
--- a/layouts/dev-portal.vue
+++ b/layouts/dev-portal.vue
@@ -38,7 +38,7 @@
 </template>
 
 <script setup lang="ts">
-import Logo from "../assets/logo-w-shadow.png"
+import Logo from "~/assets/logo-w-shadow.png"
 
 const { t } = useI18n()
 const openDrawer = ref(false)
diff --git a/package.json b/package.json
index 0748bac..298d1db 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
     "@nuxt/kit": "^3.16.0",
     "@nuxtjs/i18n": "^8.5.6",
     "@nuxtjs/sitemap": "^6.1.5",
+    "@octokit/rest": "^21.1.1",
     "@pinia/nuxt": "^0.5.5",
     "@vueuse/motion": "^3.0.3",
     "feed": "^4.2.2",
diff --git a/pages/gallery/[id].vue b/pages/gallery/[id].vue
index b3b5b86..3f82462 100644
--- a/pages/gallery/[id].vue
+++ b/pages/gallery/[id].vue
@@ -27,9 +27,9 @@
           <div class="flex flex-col" v-if="attachment?.metadata?.ratio">
             <span class="text-xs font-bold">Aspect Ratio</span>
             <span>
-            {{ attachment?.metadata?.width }}x{{ attachment?.metadata?.height }}
-            {{ attachment?.metadata?.ratio.toFixed(2) }}
-          </span>
+              {{ attachment?.metadata?.width }}x{{ attachment?.metadata?.height }}
+              {{ attachment?.metadata?.ratio.toFixed(2) }}
+            </span>
           </div>
           <div class="flex flex-col" v-if="attachment?.mimetype">
             <span class="text-xs font-bold">Mimetype</span>
@@ -44,13 +44,19 @@
 
       <div class="text-xs text-grey flex flex-col mx-[2.5ch]">
         <span>Solar Network Attachment Web Preview</span>
-        <span>Powered by <a class="underline" target="_blank" href="https://git.solsynth.dev/Hydrogen/Paperclip">Hydrogen.Paperclip</a></span>
+        <span
+          >Powered by
+          <a class="underline" target="_blank" href="https://git.solsynth.dev/Hydrogen/Paperclip"
+            >Hydrogen.Paperclip</a
+          ></span
+        >
       </div>
     </v-col>
   </v-row>
 </template>
 
 <script setup lang="ts">
+import { formatBytes } from "~/utils/format"
 import { useDisplay } from "vuetify"
 
 const route = useRoute()
@@ -61,7 +67,9 @@ const firstVideo = ref<string | null>()
 
 const isMediumScreen = useDisplay().mdAndUp
 
-const { data: attachment } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/uc/attachments/${route.params.id}/meta`)
+const { data: attachment } = await useFetch<any>(
+  `${config.public.solarNetworkApi}/cgi/uc/attachments/${route.params.id}/meta`,
+)
 
 definePageMeta({
   layout: "minimal",
@@ -76,15 +84,19 @@ if (!attachment.value) {
 
 const title = computed(() => `Attachment ${attachment.value?.id}`)
 
-watch(attachment, (value) => {
-  if (value.mimetype.split("/")[0] == "image") {
-    firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
-  }
+watch(
+  attachment,
+  (value) => {
+    if (value.mimetype.split("/")[0] == "image") {
+      firstImage.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
+    }
 
-  if (value.mimetype.split("/")[0] == "video") {
-    firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
-  }
-}, { immediate: true, deep: true })
+    if (value.mimetype.split("/")[0] == "video") {
+      firstVideo.value = `${config.public.solarNetworkApi}/cgi/uc/attachments/${value.id}`
+    }
+  },
+  { immediate: true, deep: true },
+)
 
 useHead({
   title: title.value,
@@ -106,16 +118,4 @@ useSeoMeta({
   publisher: "Solar Network",
   ogSiteName: "Solsynth Capital",
 })
-
-function formatBytes(bytes: number, decimals = 2) {
-  if (!+bytes) return "0 Bytes"
-
-  const k = 1024
-  const dm = decimals < 0 ? 0 : decimals
-  const sizes = ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
-
-  const i = Math.floor(Math.log(bytes) / Math.log(k))
-
-  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
-}
 </script>
diff --git a/pages/gallery/new.vue b/pages/gallery/new.vue
index 5d74cc4..2e4803f 100644
--- a/pages/gallery/new.vue
+++ b/pages/gallery/new.vue
@@ -93,7 +93,7 @@ onMounted(() => {
 
 const poolOptions = [
   { label: "Interactive", description: "Public indexable, no lifecycle.", value: "interactive" },
-  { label: "Messaging", description: "Has lifecycle, will delete after 14 days.", value: "messaging" },
+  { label: "Messaging", description: "Has lifecycle, will be deleted after 14 days.", value: "messaging" },
   { label: "Sticker", description: "Public indexable, privilege required.", value: "sticker", disabled: true },
   { label: "Dedicated Pool", description: "Your own configuration, coming soon.", value: "dedicated", disabled: true },
 ]
@@ -201,12 +201,12 @@ async function uploadSingleMultipart(chunkId: string) {
   const chunkIdx: number = multipartInfo.value["file_chunks"][chunkId]
   const chunk = content.value.slice(chunkIdx * multipartSize.value, (chunkIdx + 1) * multipartSize.value)
 
-  const data = new FormData()
-  data.set("file", chunk)
-
   const resp = await solarFetch(`/cgi/uc/attachments/multipart/${multipartInfo.value.rid}/${chunkId}`, {
     method: "POST",
-    body: data,
+    body: chunk,
+    headers: {
+      "Content-Type": "application/octet-stream",
+    },
     signal: AbortSignal.timeout(3 * 60 * 1000),
   })
   if (resp.status != 200) throw new Error(await resp.text())
diff --git a/pages/index.vue b/pages/index.vue
index 9b05ff8..cb240d9 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -1,4 +1,6 @@
 <template>
+  <canvas ref="canvasRef" class="fixed top-0 left-0 w-screen h-screen opacity-50"></canvas>
+
   <v-container class="flex flex-col my-2 px-12 gap-[4rem]">
     <section class="content-section flex flex-col items-center justify-center text-center px-4">
       <img
@@ -10,6 +12,7 @@
           enter: {
             y: 0,
             opacity: 1,
+            transition: { duration: 0.8 }
           },
         }"
         :src="Logo"
@@ -74,7 +77,7 @@
 </template>
 
 <script setup lang="ts">
-import Logo from "../assets/logo-w-shadow.png"
+import Logo from "~/assets/logo-w-shadow.png"
 
 import { getLocale } from "~/utils/locale"
 
@@ -100,6 +103,88 @@ const { data: products } = await useAsyncData("products", () => {
     .limit(5)
     .find()
 })
+
+const canvasRef = ref(null)
+
+onMounted(() => {
+  const canvas: HTMLCanvasElement = canvasRef.value!
+  const ctx = canvas.getContext("2d")!
+  const dpr = window.devicePixelRatio || 1;
+  canvas.width = window.innerWidth * dpr;
+  canvas.height = window.innerHeight * dpr;
+
+  let particles: Particle[] = []
+  const numParticles = 100
+
+  class Particle {
+    x: number
+    y: number
+    vx: number
+    vy: number
+    size: number
+
+    constructor() {
+      this.x = Math.random() * canvas.width
+      this.y = Math.random() * canvas.height
+      this.vx = (Math.random() - 0.5) * 1.5
+      this.vy = (Math.random() - 0.5) * 1.5
+      this.size = Math.random() * 3 + 1
+    }
+
+    move() {
+      this.x += this.vx
+      this.y += this.vy
+      if (this.x <= 0 || this.x >= canvas.width) this.vx *= -1
+      if (this.y <= 0 || this.y >= canvas.height) this.vy *= -1
+    }
+
+    draw() {
+      ctx.beginPath();
+      ctx.arc(this.x * dpr, this.y * dpr, this.size * dpr, 0, Math.PI * 2);
+      ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
+      ctx.fill();
+    }
+  }
+
+  function init() {
+    particles = []
+    for (let i = 0; i < numParticles; i++) {
+      particles.push(new Particle())
+    }
+  }
+
+  function drawLines() {
+    for (let i = 0; i < particles.length; i++) {
+      for (let j = i + 1; j < particles.length; j++) {
+        let dx = particles[i].x - particles[j].x;
+        let dy = particles[i].y - particles[j].y;
+        let distance = Math.sqrt(dx * dx + dy * dy);
+
+        if (distance < 100) {
+          ctx.beginPath();
+          ctx.moveTo(particles[i].x * dpr, particles[i].y * dpr);
+          ctx.lineTo(particles[j].x * dpr, particles[j].y * dpr);
+          ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
+          ctx.lineWidth = 0.5 * dpr;
+          ctx.stroke();
+        }
+      }
+    }
+  }
+
+  function animate() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height)
+    particles.forEach((p) => {
+      p.move()
+      p.draw()
+    })
+    drawLines()
+    requestAnimationFrame(animate)
+  }
+
+  init()
+  animate()
+})
 </script>
 
 <style scoped>
diff --git a/pages/products/solar-network.vue b/pages/products/solar-network.vue
new file mode 100644
index 0000000..752e3bc
--- /dev/null
+++ b/pages/products/solar-network.vue
@@ -0,0 +1,185 @@
+<template>
+  <v-container class="flex flex-col my-2 px-12 gap-[4rem]">
+    <section class="content-section flex flex-col items-center justify-center text-center px-4" id="intro">
+      <div class="pt-1/3 mb-4 w-full relative">
+        <img :src="AlphaScreenshot" class="absolute bottom-2 left-0 right-0" />
+        <img
+          v-motion="{
+            initial: {
+              y: 100,
+              opacity: 0,
+            },
+            enter: {
+              y: 0,
+              opacity: 1,
+              transition: { duration: 0.8 },
+            },
+          }"
+          :src="Icon"
+          alt="Solar Network Logo"
+          class="w-32 h-32 p-2 z-10 mx-auto icon-glow bg-white dark:bg-black shadow-2xl rounded-xl"
+        />
+      </div>
+      <div>
+        <h1 class="text-4xl font-bold">Solar Network</h1>
+        <p class="mt-2 text-lg">{{ t("solarNetworkDescription") }}</p>
+        <v-btn class="mt-4" color="primary" prepend-icon="mdi-arrow-down" href="#products">{{ t("learnMore") }}</v-btn>
+      </div>
+    </section>
+
+    <section class="content-section flex flex-col items-center justify-center text-center px-4" id="downloads">
+      <h1 class="text-3xl font-bold">{{ t("download") }}</h1>
+      <p class="text-lg">
+        File-hosting & versioning by
+        <nuxt-link class="underline" to="https://github.com/Solsynth/HyperNet.Surface" target="_blank">GitHub</nuxt-link
+        ><sup>®</sup>
+      </p>
+      <v-btn
+        v-if="hasPrerelease"
+        slim
+        density="compact"
+        prepend-icon="mdi-beta"
+        variant="text"
+        style="text-transform: none"
+        color="white"
+        @click="showPrerelease = !showPrerelease"
+      >
+        {{ showPrerelease ? t("downloadSwitchRelease") : t("downloadSwitchPrerelease") }}
+      </v-btn>
+      <div class="max-h-[500px] w-full mt-4 text-left">
+        <v-row dense>
+          <v-col cols="12" md="6">
+            <v-card
+              prepend-icon="mdi-alert-decagram"
+              :title="showPrerelease ? 'Latest pre-release' : 'Latest release'"
+              density="comfortable"
+            >
+              <v-card-text v-if="currentRelease.status.value === 'success'">
+                <p class="text-xs">
+                  <code>{{ currentRelease.data.value?.tag_name }}</code>
+                </p>
+                <p class="font-bold text-lg">{{ latestRelease.data.value?.name }}</p>
+                <article class="prose prose-sm max-h-[360px] overflow-y-auto" style="max-width: unset">
+                  <m-d-c :value="currentRelease.data.value!.body!" />
+                </article>
+              </v-card-text>
+              <div v-else>
+                <v-progress-circular class="px-5 my-3" indeterminate />
+              </div>
+            </v-card>
+          </v-col>
+          <v-col cols="12" md="6">
+            <v-card prepend-icon="mdi-download" title="Distributions" density="comfortable">
+              <div v-if="currentRelease.status.value === 'success'">
+                <v-list density="comfortable" slim>
+                  <v-list-item
+                    v-for="asset in currentRelease.data.value!.assets"
+                    :key="asset.id"
+                    :title="asset.label ?? asset.name"
+                    :subtitle="formatBytes(asset.size)"
+                    :href="asset.browser_download_url"
+                    target="_blank"
+                  />
+                </v-list>
+              </div>
+              <div v-else>
+                <v-progress-circular class="px-5 my-3" indeterminate />
+              </div>
+              <v-card-text>
+                <p class="text-sm opacity-50 mb-2">{{ t('downloadForApple') }}</p>
+                <div class="flex align-center gap-2.5">
+                  <nuxt-link
+                    to="https://apps.apple.com/us/app/solian/id6499032345?itscg=30200&itsct=apps_box_link&mttnsubad=6499032345"
+                    target="_blank"
+                  >
+                    <img :src="AppStoreDownload" />
+                  </nuxt-link>
+                  <div>
+                    <nuxt-link to="https://testflight.apple.com/join/YJ0lmN6O" target="_blank" class="underline">
+                      {{ t('downloadTestFlight') }}
+                    </nuxt-link>
+                    <p class="text-xs opacity-40">{{ t('downloadTestFlightDescription') }}</p>
+                  </div>
+                </div>
+
+                <p class="text-sm opacity-50 mt-4">{{ t('downloadForDesktop') }}</p>
+                <p class="text-sm">{{ t('downloadForDesktopDescription') }}</p>
+
+                <p class="text-sm opacity-50 mt-4">{{ t('downloadWithoutDownload') }}</p>
+                <div class="text-sm flex gap-2 underline">
+                  <nuxt-link to="https://sn.solsynth.dev" target="_blank">{{ t('downloadWeb') }}</nuxt-link>
+                  <nuxt-link to="https://sn.solsynth.dev?cdn=cn" target="_blank"
+                    >{{ t('downloadWebChina') }}</nuxt-link
+                  >
+                </div>
+              </v-card-text>
+            </v-card>
+          </v-col>
+        </v-row>
+      </div>
+    </section>
+  </v-container>
+</template>
+
+<script lang="ts" setup>
+import Icon from "~/assets/products/solar-network/icon.png"
+import AlphaScreenshot from "~/assets/products/solar-network/alpha.webp"
+import AppStoreDownload from "~/assets/products/app-store-download.svg"
+
+import { formatBytes } from "~/utils/format"
+import { Octokit } from "@octokit/rest"
+
+const { t } = useI18n()
+
+const latestRelease = useAsyncData("sn-latest-release", async () => {
+  const octo = new Octokit({})
+  const resp = await octo.repos.getLatestRelease({
+    owner: "Solsynth",
+    repo: "HyperNet.Surface",
+  })
+  return resp.data
+})
+const latestPrerelease = useAsyncData("sn-latest-prerelease", async () => {
+  const octo = new Octokit({})
+  const resp = await octo.repos.listReleases({
+    owner: "Solsynth",
+    repo: "HyperNet.Surface",
+    per_page: 1,
+  })
+  return resp.data[0]
+})
+
+const showPrerelease = ref(false)
+
+const currentRelease = computed(() => (showPrerelease.value ? latestPrerelease : latestRelease))
+const hasPrerelease = computed<boolean>(
+  () => latestPrerelease.data?.value?.tag_name != latestRelease.data?.value?.tag_name,
+)
+</script>
+
+<style scoped>
+.content-section {
+  min-height: calc(100vh - 80px);
+  display: flex;
+  place-items: center;
+}
+
+.icon-glow {
+  -webkit-filter: drop-shadow(0 0 7px rgba(0, 0, 0, 0.5));
+  filter: drop-shadow(0 0 7px rgba(0, 0, 0, 0.5));
+}
+
+@media (prefers-color-scheme: dark) {
+  .icon-glow {
+    -webkit-filter: invert() drop-shadow(0 0 7px rgba(255, 255, 255, 0.5));
+    filter: invert() drop-shadow(0 0 7px rgba(255, 255, 255, 0.5));
+  }
+}
+</style>
+
+<style>
+body,
+html {
+  scroll-behavior: smooth;
+}
+</style>
diff --git a/pages/publishers/[name].vue b/pages/publishers/[name].vue
index 1a7a0fc..cc94b93 100644
--- a/pages/publishers/[name].vue
+++ b/pages/publishers/[name].vue
@@ -27,7 +27,7 @@
         </v-col>
         <v-col row="12" lg="4" order="first" order-lg="last">
           <div class="sticky top-0 h-fit">
-            <v-card prepend-icon="mdi-identifier" title="About">
+            <v-card prepend-icon="mdi-information-outline" title="About">
               <v-card-text>
                 <p><b>Description</b></p>
                 <p>{{ account.description }}</p>
@@ -53,7 +53,7 @@ const config = useRuntimeConfig()
 
 const tab = ref(1)
 
-const { data: account } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/publisher/${route.params.name}`)
+const { data: account } = await useFetch<any>(`${config.public.solarNetworkApi}/cgi/co/publishers/${route.params.name}`)
 
 if (account.value == null) {
   throw createError({
diff --git a/pages/users/[name].vue b/pages/users/[name].vue
index 99de7d0..1747385 100644
--- a/pages/users/[name].vue
+++ b/pages/users/[name].vue
@@ -12,16 +12,7 @@
       </div>
 
       <div class="mb-7">
-        <v-card rounded="xl" class="mx-[-5px]">
-          <v-tabs
-            v-model="tab"
-            align-tabs="start"
-            color="primary"
-            hide-slider
-          >
-            <v-tab :value="1">{{ t("userActivity") }}</v-tab>
-          </v-tabs>
-        </v-card>
+
       </div>
 
       <v-row>
@@ -46,10 +37,6 @@
 </template>
 
 <script setup lang="ts">
-definePageMeta({
-  alias: ["/@:name(.*)*"],
-})
-
 const { t } = useI18n()
 const route = useRoute()
 const config = useRuntimeConfig()
@@ -67,6 +54,4 @@ if (account.value == null) {
 
 const urlOfAvatar = computed(() => account.value?.avatar ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.avatar}` : void 0)
 const urlOfBanner = computed(() => account.value?.banner ? `${config.public.solarNetworkApi}/cgi/uc/attachments/${account.value.banner}` : void 0)
-
-const externalOpenLink = computed(() => `${config.public.solianUrl}/accounts/view/${route.params.name}`)
 </script>
diff --git a/utils/format.ts b/utils/format.ts
new file mode 100644
index 0000000..1e19e41
--- /dev/null
+++ b/utils/format.ts
@@ -0,0 +1,11 @@
+export function formatBytes(bytes: number, decimals = 2) {
+  if (!+bytes) return "0 Bytes"
+
+  const k = 1024
+  const dm = decimals < 0 ? 0 : decimals
+  const sizes = ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
+
+  const i = Math.floor(Math.log(bytes) / Math.log(k))
+
+  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
+}