Finish solar network product page

This commit is contained in:
2025-03-19 23:40:10 +08:00
parent 1f2c4f33cb
commit 9726d8f805
4 changed files with 265 additions and 24 deletions

View File

@ -1,6 +1,6 @@
<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">
<section class="content-section flex flex-col items-center justify-center text-center" id="intro">
<div class="pt-1/3 mb-4 w-full relative">
<img :src="AlphaScreenshot" class="absolute bottom-2 left-0 right-0" />
<img
@ -23,12 +23,67 @@
<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>
<v-btn class="mt-4" color="primary" prepend-icon="mdi-arrow-down" href="#features">{{ t("learnMore") }}</v-btn>
</div>
</section>
<section class="content-section flex flex-col items-center justify-center text-center px-4" id="reminders">
<h1 class="text-3xl font-bold">{{ t("solarNetworkBeforeYouStart") }}</h1>
<section class="content-section flex flex-col items-center justify-center text-center" id="features">
<h2 class="text-3xl font-bold">{{ t("solarNetworkFeat") }}</h2>
<p class="text-lg mb-4">{{ t("solarNetworkFeatDescription") }}</p>
<v-card class="w-full">
<v-tabs v-model="featuresTab" align-tabs="center" color="primary">
<v-tab
:prepend-icon="feat.icon"
:text="feat.title"
:value="feat.icon"
v-for="feat in features"
@mouseover="featuresTab = feat.icon"
/>
</v-tabs>
<v-tabs-window v-model="featuresTab">
<v-tabs-window-item :value="feat.icon" v-for="feat in features">
<v-card flat>
<v-img :aspect-ratio="16 / 9" :src="feat.image" cover></v-img>
<v-card-text>
<p class="text-lg mb-1">
{{ feat.description }}
</p>
</v-card-text>
</v-card>
</v-tabs-window-item>
</v-tabs-window>
</v-card>
</section>
<section class="content-section flex flex-col items-center justify-center" id="highlight-posts">
<v-row class="w-full" dense>
<v-col cols="12" md="6">
<div
class="max-h-[500px] overflow-y-auto posts-container"
ref="highlight-posts"
v-if="highlightPosts.status.value === 'success'"
>
<div v-for="post in highlightPosts.data.value">
<post-item :post="post" force-show-content class="mx-0" />
</div>
</div>
<v-progress-circular v-else indeterminate />
</v-col>
<v-col cols="12" md="6" class="text-right">
<h2 class="text-3xl font-bold">{{ t("solarNetworkHighlightPosts") }}<sup>®</sup></h2>
<p>{{ t("solarNetworkHighlightPostsDescription") }}</p>
<v-btn variant="text" color="white" slim prepend-icon="mdi-plus" href="#reminders">{{
t("solarNetworkJumpIn")
}}</v-btn>
</v-col>
</v-row>
</section>
<section class="content-section flex flex-col items-center justify-center text-center" id="reminders">
<h2 class="text-3xl font-bold">{{ t("solarNetworkBeforeYouStart") }}</h2>
<p class="text-lg">{{ t("solarNetworkBeforeYouStartDescription") }}</p>
<div class="max-h-[500px] w-full mt-4 text-left">
@ -63,8 +118,8 @@
<nuxt-link class="underline text-sm" to="/terms">{{ t("solarNetworkToSCheck") }}</nuxt-link>
</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>
<section class="content-section flex flex-col items-center justify-center text-center" id="downloads">
<h3 class="text-3xl font-bold">{{ t("download") }}</h3>
<p class="text-lg">
File-hosting & versioning by
<nuxt-link class="underline" to="https://github.com/Solsynth/HyperNet.Surface" target="_blank">GitHub</nuxt-link
@ -82,8 +137,8 @@
>
{{ showPrerelease ? t("downloadSwitchRelease") : t("downloadSwitchPrerelease") }}
</v-btn>
<div class="max-h-[500px] w-full mt-4 text-left">
<v-row dense>
<div class="w-full mt-4 text-left">
<v-row dense class="flex-1">
<v-col cols="12" md="6">
<v-card
prepend-icon="mdi-alert-decagram"
@ -152,19 +207,101 @@
</v-row>
</div>
</section>
<section class="content-section flex flex-col items-center justify-center" id="help">
<h2 class="text-2xl font-bold text-center mb-4">{{ t("solarNetworkNeedHelp") }}</h2>
<div class="flex flex-col gap-2 w-[480px] max-w-screen">
<v-card :title="t('askHelpContactUs')" prepend-icon="mdi-email-fast" density="comfortable">
<v-card-text>
Contact our customer server at
<nuxt-link to="mailto:lily@solsynth.dev" class="underline">
<address>lily@solsynth.dev</address>
</nuxt-link>
</v-card-text>
</v-card>
<v-card :title="t('askHelpReadTheDocs')" prepend-icon="mdi-page-next" density="comfortable">
<v-card-text class="flex flex-col">
<nuxt-link to="https://kb.solsynth.dev" class="underline">Visit Goatpedia</nuxt-link>
<nuxt-link to="https://github.com/Solsynth/HyperNet.Surface/issues" class="underline"
>Visit GitHub Issue Tracker</nuxt-link
>
</v-card-text>
</v-card>
</div>
</section>
<copyright :service="['capital', 'roadsign']" />
</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 ScreenshotDashboard from "~/assets/products/solar-network/ft-dashboard.png"
import ScreenshotExplore from "~/assets/products/solar-network/ft-explore.png"
import ScreenshotChat from "~/assets/products/solar-network/ft-chat.png"
import ScreenshotNews from "~/assets/products/solar-network/ft-news.png"
import ScreenshotStickers from "~/assets/products/solar-network/ft-stickers.png"
import ScreenshotCompose from "~/assets/products/solar-network/ft-posting.png"
import AppStoreDownload from "~/assets/products/app-store-download.svg"
useHead({
title: "Solar Network",
})
import { formatBytes } from "~/utils/format"
import { Octokit } from "@octokit/rest"
const { t } = useI18n()
const featuresTab = ref()
interface FeatureItem {
title: string
description: string
icon: string
image: string
}
const features: FeatureItem[] = [
{
title: t("solarNetworkFeatDashboard"),
description: t("solarNetworkFeatDashboardDescription"),
icon: "mdi-view-dashboard",
image: ScreenshotDashboard,
},
{
title: t("solarNetworkFeatExplore"),
description: t("solarNetworkFeatExploreDescription"),
icon: "mdi-compass",
image: ScreenshotExplore,
},
{
title: t("solarNetworkFeatChat"),
description: t("solarNetworkFeatChatDescription"),
icon: "mdi-chat",
image: ScreenshotChat,
},
{
title: t("solarNetworkFeatNews"),
description: t("solarNetworkFeatNewsDescription"),
icon: "mdi-newspaper",
image: ScreenshotNews,
},
{
title: t("solarNetworkFeatStickers"),
description: t("solarNetworkFeatStickersDescription"),
icon: "mdi-sticker",
image: ScreenshotStickers,
},
{
title: t("solarNetworkFeatCompose"),
description: t("solarNetworkFeatComposeDescription"),
icon: "mdi-pencil",
image: ScreenshotCompose,
},
]
const latestRelease = useAsyncData("sn-latest-release", async () => {
const octo = new Octokit({})
const resp = await octo.repos.getLatestRelease({
@ -183,17 +320,97 @@ const latestPrerelease = useAsyncData("sn-latest-prerelease", async () => {
return resp.data[0]
})
const highlightPosts = useAsyncData("sn-highlight-posts", async () => {
const resp = await solarFetch("/cgi/co/recommendations")
const data = await resp.json()
return data
})
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,
)
const highlightPostContainer = useTemplateRef("highlight-posts")
function autoScroll() {
console.log("Auto scroll is called.")
const scrollSpeed = 1
let animationFrameId: number
let isScrolling = true
function scroll() {
if (!isScrolling) return
const container = highlightPostContainer.value!
if (container.scrollTop + container.clientHeight >= container.scrollHeight) {
container.scroll(0, 0)
} else {
container.scrollBy(0, scrollSpeed)
}
animationFrameId = requestAnimationFrame(scroll)
}
scroll()
const container = highlightPostContainer.value!
container.addEventListener("mouseenter", () => {
isScrolling = false
cancelAnimationFrame(animationFrameId)
})
container.addEventListener("mouseleave", () => {
if (!isScrolling) {
isScrolling = true
scroll()
}
})
}
watch(
highlightPostContainer,
(data) => {
if (data != null) {
autoScroll()
}
},
{ immediate: true, deep: true },
)
</script>
<style scoped>
.posts-container {
padding-top: 20px;
padding-bottom: 20px;
position: relative;
height: 500px;
overflow: hidden;
scrollbar-width: none;
mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 80%,
rgba(0, 0, 0, 0) 100%
);
-webkit-mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 1) 20%,
rgba(0, 0, 0, 1) 80%,
rgba(0, 0, 0, 0) 100%
);
}
.posts-container::-webkit-scrollbar {
display: none;
}
.content-section {
min-height: calc(100vh - 80px);
height: auto;
display: flex;
place-items: center;
}