add portal

This commit is contained in:
Lyric
2024-06-24 19:17:13 +09:00
parent 3c1b525da7
commit 0db3c4b586
56 changed files with 1323 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
name: Deploy 1000h portal
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "1000h-portal/**"
pull_request:
branches:
- main
paths:
- "1000h-portal/**"
jobs:
deploy:
runs-on: ubuntu-latest
name: Deploy
steps:
# checkout the code
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: "**/node_modules"
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
- name: Setup node env
uses: actions/setup-node@master
with:
node-version: "20"
- name: Install dependencies
run: yarn install
- name: Build
run: yarn generate
- name: Deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy
workingDirectory: "1000h-portal"

2
1000h-portal/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.output/
dist/

75
1000h-portal/README.md Normal file
View File

@@ -0,0 +1,75 @@
# Nuxt 3 Minimal Starter
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install the dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

5
1000h-portal/app.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>

View File

@@ -0,0 +1,167 @@
<template>
<div
class="comments mt-[64px] md:mt-[96px] pb-[32px] md:pb-[64px] lg:mt-[128px]"
>
<div class="container m-auto">
<div class="top py-[32px] md:py-[64px] text-center">
<div class="hint text-[13px] md:text-[14px]">Comment</div>
<div class="title text-[24px] md:text-[32px]">用户评价</div>
</div>
</div>
<div class="items-container">
<div class="items">
<div v-for="(item, index) in items" :key="index" class="item">
<img class="quote" src="/icon/double-quote.svg" />
<div class="top">
<span>
<img width="56" height="56" :src="item.avatar" />
</span>
<div class="ml-3">
<div class="name">{{ item.name }}</div>
<div class="hint">{{ item.hint }}</div>
</div>
</div>
<div class="content">{{ item.text }}</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "Comments",
};
</script>
<script lang="ts" setup>
const items = ref([
{
avatar: "/images/avatar.png",
name: "花开富贵",
hint: "连续打卡 1,000 天",
text: "这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App",
},
{
avatar: "/images/avatar.png",
name: "花开富贵",
hint: "连续打卡 1,000 天",
text: "这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App",
},
{
avatar: "/images/avatar.png",
name: "花开富贵",
hint: "连续打卡 1,000 天",
text: "这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App",
},
{
avatar: "/images/avatar.png",
name: "花开富贵",
hint: "连续打卡 1,000 天",
text: "这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App",
},
{
avatar: "/images/avatar.png",
name: "花开富贵",
hint: "连续打卡 1,000 天",
text: "这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App",
},
{
avatar: "/images/avatar.png",
name: "花开富贵",
hint: "连续打卡 1,000 天",
text: "这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App",
},
{
avatar: "/images/avatar.png",
name: "花开富贵",
hint: "连续打卡 1,000 天",
text: "这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App这是一个非常好的App",
},
]);
</script>
<style lang="scss" scoped>
.comments {
background: linear-gradient(180deg, #e6f0f9 0%, #fff 100%);
background-repeat: no-repeat;
background-size: cover;
> .container .top {
.title {
font-weight: 600;
color: #3e5c77;
}
.hint {
font-family: "New York";
font-style: italic;
font-weight: 400;
opacity: 0.5;
color: #3e5c77;
}
}
.items-container {
overflow: hidden;
padding: 0 24px;
}
.items {
display: flex;
flex-wrap: nowrap;
gap: 16px;
animation: scroll 30s linear infinite;
.item {
padding: 24px;
border-radius: 4px;
background: #fff;
min-width: 340px;
position: relative;
.quote {
position: absolute;
right: 24px;
top: 32px;
width: 24px;
}
.top {
display: flex;
align-items: center;
.name {
font-size: 16px;
font-weight: 500;
}
.hint {
font-size: 12px;
font-weight: 400;
opacity: 0.6;
margin-top: 6px;
}
}
.content {
margin-top: 30px;
font-weight: 400;
line-height: 150%;
}
}
}
}
@keyframes scroll {
0% {
transform: translateX(20%);
}
100% {
transform: translateX(-80%);
}
}
</style>

View File

@@ -0,0 +1,153 @@
<template>
<div class="features mt-[64px] md:mt-[96px] lg:mt-[128px]">
<div class="container m-auto">
<div class="top flex justify-between">
<div class="text-greyscale_1">
<div class="title text-[20px] md:text-[32px]">特色功能</div>
<div class="subtitle text-[14px] md:text-[16px]">
这是一句简介这是一句简介这是一句简介
</div>
</div>
<div class="hint text-greyscale_4">
Product <br />
Features
</div>
</div>
<div class="cards grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
<div v-for="(feature, index) in features" :key="index" class="card">
<div class="mr-6">
<span class="icon">
<img :src="feature.icon" />
</span>
</div>
<div>
<div class="title">{{ feature.title }}</div>
<div class="subtitle">{{ feature.subtitle }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "Features",
};
</script>
<script lang="ts" setup>
const features = ref([
{
icon: "/images/head-phone.png",
title: "音频跟读",
subtitle: "支持下载/导入各类外语音频,进行解析跟读",
},
{
icon: "/images/camera.png",
title: "视频跟读",
subtitle: "支持下载/导入各类外语视频,进行解析跟读",
},
{
icon: "/images/lang.png",
title: "外语阅读",
subtitle: "支持下载/导入各类外语文章,进行解析跟读",
},
{
icon: "/images/robot.png",
title: "AI自然对话",
subtitle: "说你想说的跟AI一起复读学习",
},
{
icon: "/images/note.png",
title: "重温笔记",
subtitle: "学习遇到难点?先添加笔记,再巩固复习",
},
{
icon: "/images/book.png",
title: "词典助记",
subtitle: "学习遇到生词?添加到生词本,再巩固复习",
},
{
icon: "/images/cham.png",
title: "社区竞赛",
subtitle: "每日、每周、每月都有人跟你一起学习进步",
},
]);
</script>
<style lang="scss" scoped>
.features {
.top {
.title {
font-weight: 600;
}
.subtitle {
margin-top: 4px;
font-weight: 400;
}
.hint {
font-family: "New York";
font-size: 14px;
font-style: italic;
font-weight: 400;
text-align: right;
}
}
.cards {
display: grid;
gap: 48px;
margin-top: 32px;
.card {
display: flex;
.icon {
display: inline-block;
width: 64px;
height: 64px;
display: flex;
align-items: center;
justify-content: center;
}
.title {
margin-bottom: 16px;
font-size: 20px;
font-weight: 500;
}
.subtitle {
color: #979797;
font-size: 14px;
font-weight: 400;
}
}
.compitition {
color: #fff;
background-image: url("/images/bg-features.png");
background-repeat: no-repeat;
background-size: cover;
position: relative;
overflow: hidden;
&::after {
content: "";
position: absolute;
top: 48px;
right: -20px;
width: 824px;
height: 448px;
background-image: url("/images/rank.png");
}
}
}
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<div class="introduction mt-[64px] md:mt-[96px] lg:mt-[128px]">
<div class="container m-auto">
<div class="top flex justify-between">
<div class="text-greyscale_1">
<div class="title text-[20px] md:text-[32px]">产品介绍</div>
<div class="subtitle text-[14px] md:text-[16px]">
这是一句简介这是一句简介
</div>
</div>
<div class="hint text-[13px] md:text-[14px] text-greyscale_4">
About <br />
Product
</div>
</div>
<div class="cards grid-cols-1 md:grid-cols-2 lg:grid-cols-4 mt-8">
<div
v-for="(card, index) in cards"
:key="index"
class="card"
:style="{ backgroundImage: `url(${card.bg})` }"
>
<div class="lable" :style="{ color: card.colors[0] }">
{{ card.label }}
</div>
<div class="title my-4" :style="{ color: card.colors[0] }">
{{ card.title }}
</div>
<div class="subtitle" :style="{ color: card.colors[1] }">
{{ card.subtitle }}
</div>
<div class="arrow">
<img src="/icon/arrow-upright.svg" />
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "Introduction",
};
</script>
<script lang="ts" setup>
const cards = ref([
{
label: "Self Taught.",
title: "帮助自主学习",
subtitle: "我们提倡直接上手学习,不用理会方法论",
bg: "/images/bg-intro-1.png",
colors: ["#384C6B", "#7B93AF"],
},
{
label: "Pronunciation Correction.",
title: "纠正口语发音",
subtitle: "我们提倡直接上手学习,不用理会方法论",
bg: "/images/bg-intro-2.png",
colors: ["#4A6760", "#7C978F"],
},
{
label: "AI Companion.",
title: "AI辅助学习",
subtitle: "我们提倡直接上手学习,不用理会方法论",
bg: "/images/bg-intro-3.png",
colors: ["#384C6B", "#7B93AF"],
},
{
label: "Memory Enhancement.",
title: "拓展记忆力",
subtitle: "我们提倡直接上手学习,不用理会方法论",
bg: "/images/bg-intro-4.png",
colors: ["#70584A", "#A4978D"],
},
]);
</script>
<style lang="scss" scoped>
.introduction {
.top {
.title {
font-weight: 600;
}
.subtitle {
margin-top: 4px;
font-weight: 400;
}
.hint {
font-family: "New York";
font-style: italic;
font-weight: 400;
text-align: right;
}
}
.cards {
display: grid;
gap: 16px;
.card {
cursor: pointer;
position: relative;
color: #fff;
height: 350px;
padding: 24px;
border-radius: 4px;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
&::before {
content: "";
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
opacity: 0;
border-radius: 4px;
background: #e5eff8;
transition: ease-in-out 0.2s;
}
&:hover {
&::before {
opacity: 0.3;
}
.arrow {
opacity: 1;
}
}
.lable {
font-family: "New York";
font-size: 12px;
font-style: italic;
}
.title {
font-size: 20px;
font-weight: 500;
line-height: 1.5;
}
.subtitle {
font-size: 14px;
font-weight: 400;
line-height: 1.5;
}
.arrow {
position: absolute;
opacity: 0;
bottom: 24px;
left: 24px;
transition: ease-in-out 0.2s;
}
}
}
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<svg
:width="width"
:height="height"
viewBox="0 0 63 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.40122 5.818H0.329219V5.118C1.40255 4.978 2.69055 4.21267 4.19322 2.822H4.64122V14.778H2.40122V5.818ZM11.6893 15.03C9.08534 14.806 7.71801 13.4807 7.58734 11.054V6.14C7.93268 3.97467 9.25801 2.78467 11.5633 2.57C13.99 2.61667 15.3993 3.9 15.7913 6.42V10.998C15.698 12.2673 15.3107 13.238 14.6293 13.91C13.9573 14.582 12.9773 14.9553 11.6893 15.03ZM11.6893 13.854C12.212 13.798 12.5947 13.574 12.8373 13.182C13.08 12.7993 13.2013 12.2393 13.2013 11.502V6.042C13.2013 5.36067 13.08 4.84267 12.8373 4.488C12.5947 4.124 12.212 3.914 11.6893 3.858C11.1573 3.914 10.7513 4.12867 10.4713 4.502C10.1913 4.866 10.0513 5.37933 10.0513 6.042V11.502C10.0513 12.2207 10.182 12.7713 10.4433 13.154C10.714 13.546 11.1293 13.7793 11.6893 13.854ZM21.0136 15.03C18.4096 14.806 17.0422 13.4807 16.9116 11.054V6.14C17.2569 3.97467 18.5822 2.78467 20.8876 2.57C23.3142 2.61667 24.7236 3.9 25.1156 6.42V10.998C25.0222 12.2673 24.6349 13.238 23.9536 13.91C23.2816 14.582 22.3016 14.9553 21.0136 15.03ZM21.0136 13.854C21.5362 13.798 21.9189 13.574 22.1616 13.182C22.4042 12.7993 22.5256 12.2393 22.5256 11.502V6.042C22.5256 5.36067 22.4042 4.84267 22.1616 4.488C21.9189 4.124 21.5362 3.914 21.0136 3.858C20.4816 3.914 20.0756 4.12867 19.7956 4.502C19.5156 4.866 19.3756 5.37933 19.3756 6.042V11.502C19.3756 12.2207 19.5062 12.7713 19.7676 13.154C20.0382 13.546 20.4536 13.7793 21.0136 13.854ZM30.3378 15.03C27.7338 14.806 26.3664 13.4807 26.2358 11.054V6.14C26.5811 3.97467 27.9064 2.78467 30.2118 2.57C32.6384 2.61667 34.0478 3.9 34.4398 6.42V10.998C34.3464 12.2673 33.9591 13.238 33.2778 13.91C32.6058 14.582 31.6258 14.9553 30.3378 15.03ZM30.3378 13.854C30.8604 13.798 31.2431 13.574 31.4858 13.182C31.7284 12.7993 31.8498 12.2393 31.8498 11.502V6.042C31.8498 5.36067 31.7284 4.84267 31.4858 4.488C31.2431 4.124 30.8604 3.914 30.3378 3.858C29.8058 3.914 29.3998 4.12867 29.1198 4.502C28.8398 4.866 28.6998 5.37933 28.6998 6.042V11.502C28.6998 12.2207 28.8304 12.7713 29.0918 13.154C29.3624 13.546 29.7778 13.7793 30.3378 13.854ZM39.396 15.45C39.4333 14.4047 39.0227 13.7327 38.164 13.434V13.322H41.132V2.164H43.372V13.602C43.3253 14.6567 42.8587 15.2727 41.972 15.45H39.396ZM46.452 14.05C45.7707 10.3073 44.968 6.96133 44.044 4.012L44.1 3.9C44.772 4.236 45.5 5.00133 46.284 6.196C47.1893 7.484 47.9547 9.63067 48.58 12.636L46.452 14.05ZM35.42 13.658C36.54 10.5033 37.268 7.30667 37.604 4.068H39.9C39.4053 9.26667 37.9307 12.5007 35.476 13.77L35.42 13.658ZM61.796 5.356H61.124V13.994C61.012 14.862 60.4333 15.3287 59.388 15.394H57.372C57.372 14.4233 56.9987 13.7327 56.252 13.322V13.21H58.996V5.356H55.692V3.956H58.996V2.22H61.124V3.956H61.796V5.356ZM50.204 2.948H55.412V13.938H52.22V14.89H50.204V2.948ZM52.22 7.54H53.452V4.516H52.22V7.54ZM56.868 12.244C56.4947 9.88267 56.1027 7.82933 55.692 6.084C55.7107 5.98133 55.7667 5.93 55.86 5.93C56.0747 5.93 56.3827 6.14 56.784 6.56C57.1947 6.98 57.582 7.57267 57.946 8.338C58.3193 9.094 58.576 9.96667 58.716 10.956L56.868 12.244ZM52.22 12.412H53.452V9.108H52.22V12.412Z"
fill="currentColor"
/>
</svg>
</template>
<script lang="ts">
export default {
name: "Logo",
};
</script>
<script lang="ts" setup>
defineProps({
width: {
type: String,
default: "63",
},
height: {
type: String,
default: "18",
},
});
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,89 @@
<template>
<div class="price">
<div class="container m-auto">
<div class="gap-[48px] grid grid-cols-1 md:grid-cols-2">
<div class="intro">
<div class="title text-[20px] md:text-[32px]">增值体验</div>
<div class="subtitle text-[14px] md:text-[16px]">
Enjoy App
会根据使用的功能按量计费新用户消耗完初期余额后你可以通过充值来享受更多服务
</div>
<div class="items">
<div v-for="(item, index) in items" :key="index" class="item">
<span>
<img src="/icon/check.svg" />
</span>
<span class="text-[14px] md:text-[16px]">{{ item }}</span>
</div>
</div>
</div>
<div class="self-center">
<img
class="h-[100%] md:h-[356px] lg:hidden"
src="/images/payment.png"
/>
<img class="hidden lg:block" src="/images/payment-2.png" />
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "Price",
};
</script>
<script lang="ts" setup>
const items = ref([
"跟读更多的音频",
"跟读更多的视频",
"跟读更多的文章",
"增加与智能助手的对话次数",
"更多增值体验开发中",
]);
</script>
<style lang="scss" scoped>
.price {
margin-top: 126px;
color: #212121;
.intro {
flex-basis: 400px;
.title {
line-height: 32px;
font-weight: 600;
}
.subtitle {
font-weight: 400;
margin-top: 20px;
}
}
.items {
margin-top: 32px;
.item {
display: flex;
align-items: center;
line-height: 1;
gap: 12px;
font-size: 18px;
font-weight: 500;
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
color: #979797;
}
}
}
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div class="text-center slogan-section mt-[52px] lg:mt-[82px]">
<div class="container m-auto mb-2">
<Logo width="112" height="29" class="inline-block text-primary" />
</div>
<div class="mb-3">
<div
class="slogan inline-block text-greyscale_2 text-[24px] md:text-[48px] px-6 md:px-12 lg:max-w-[800px]"
>
用你的注意力填满一千小时就能练成任何你所需要的技能......
</div>
</div>
<div
class="hint flex justify-center text-greyscale_4 text-[14px] md:text-[18px]"
>
<span class="flex text-greyscale_1 mr-1">
<img src="/icon/enjoy-app.svg" width="24" height="24" class="mx-1" />
Enjoy App
</span>
一起享受这1000小时
</div>
<div class="mt-6">
<button
class="action hover:bg-primary bg-greyscale_2 text-white px-5 py-4 inline-flex rounded-full items-center text-[14px] md:text-[16px]"
>
<span class="mr-1">体验 Enjoy App</span>
<img src="/icon/arrow-right.svg" width="24" />
</button>
</div>
</div>
<div class="demo mt-[80px] md:mt-[120px] h-[160px] md:h-[300px] lg:h-[600px]">
<div class="bg h-[160px] md:h-[340px] lg:h-[620px]"></div>
<div class="container m-auto text-center">
<img
class="demo-screen inline w-[312px] md:w-[682px] lg:w-[924px] xl:w-[1024px] mt-[-40px] md:mt-[-60px]"
src="/images/demo.png"
/>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "Slogan",
};
</script>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.slogan {
font-family: "Noto Serif SC", sans-serif;
font-weight: 700;
line-height: 1.5;
}
.container {
position: relative;
}
.action {
transition: ease-in-out 0.2s;
}
.demo {
position: relative;
.bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
background-image: url("/images/bg-demo.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
z-index: -1;
}
.demo-screen {
border-radius: 12px;
}
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<div class="footer">
<div>做到教育 Inc.</div>
<div class="opacity-50 mt-1">All rights reserved.</div>
</div>
</template>
<script lang="ts">
export default {
name: "PageFooter",
};
</script>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.footer {
color: #0d0d0d;
padding: 65px 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 12px;
font-weight: 400;
}
</style>

View File

@@ -0,0 +1,47 @@
<template>
<div class="page-header h-11 font-medium text-greyscale_3">
<div class="flex justify-between px-6 py-3">
<Logo />
<div class="flex gap-4">
<span class="action">Github</span>
<span class="divider"></span>
<span class="action">享受 APP</span>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
name: "PageHeader",
};
</script>
<script lang="ts" setup>
import Logo from "../Logo.vue";
</script>
<style lang="scss" scoped>
.divider {
border-right: 1px solid #d7d7d7;
margin: 4px 0;
}
.page-header {
position: fixed;
top: 0;
left: 0;
width: 100%;
background: #fff;
z-index: 111;
.action {
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<div class="default-layout">
<PageHeader />
<slot />
<PageFooter />
</div>
</template>
<script lang="ts">
export default {
name: "DefaultLayout",
};
</script>
<script lang="ts" setup>
import PageFooter from "@/components/layout/PageFooter.vue";
import PageHeader from "@/components/layout/PageHeader.vue";
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,48 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
css: ["~/styles/main.css"],
site: {
url: "https://example.com",
name: "Enjoy App",
description: "Welcome to Enjoy App!",
tagline: "",
defaultLocale: "zh", // not needed if you have @nuxtjs/i18n installed
},
app: {
head: {
viewport:
"width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, viewport-fit=cover",
link: [
{
rel: "apple-touch-icon",
sizes: "180x180",
href: "/apple-touch-icon.png",
},
{ rel: "mask-icon", href: "/mask-icon.svg" },
{
rel: "icon",
type: "image/png",
sizes: "24x24",
href: "/favicon-24x24.png",
},
{
rel: "icon",
type: "image/png",
sizes: "12x12",
href: "favicon-12x12.png",
},
],
script: [],
},
},
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
modules: ["nuxt-og-image", "@nuxtjs/seo"],
});

24
1000h-portal/package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxtjs/seo": "^2.0.0-rc.11",
"nuxt": "^3.12.2",
"nuxt-og-image": "^2.2.6",
"vue": "^3.4.29",
"vue-router": "^4.3.3"
},
"devDependencies": {
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.4"
}
}

View File

@@ -0,0 +1,25 @@
<template>
<div class="home-page md:home-page--md">
<Slogan />
<Introduction />
<Features />
<Price />
<Comments />
</div>
</template>
<script lang="ts">
export default {
name: "IndexPage",
};
</script>
<script lang="ts" setup>
defineOgImage({ url: "/images/og-image.png" });
</script>
<style lang="scss" scoped></style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,6 @@
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path id="Vector 1" d="M5.5 12H17.5" stroke="white" />
<path id="Rectangle 2" d="M13.5 7L18.5 12L13.5 17" stroke="white" stroke-width="1.5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#212121" />
<path d="M11.6108 19.8892L18.8705 12.6295" stroke="white" stroke-width="1.5" />
<path d="M13.1665 12.111H19.389V18.3335" stroke="white" stroke-width="1.5" />
</svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@@ -0,0 +1,8 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M12 2.5C6.47715 2.5 2 6.97715 2 12.5C2 18.0228 6.47715 22.5 12 22.5C17.5228 22.5 22 18.0228 22 12.5C22 6.97715 17.5228 2.5 12 2.5Z"
fill="#E4F2FE" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M15.774 10.6333C16.1237 10.2058 16.0607 9.5758 15.6332 9.22607C15.2058 8.87635 14.5758 8.93935 14.226 9.36679L10.4258 14.0116L9.20711 12.7929C8.81658 12.4024 8.18342 12.4024 7.79289 12.7929C7.40237 13.1834 7.40237 13.8166 7.79289 14.2071L9.79289 16.2071C9.99267 16.4069 10.2676 16.5129 10.5498 16.4988C10.832 16.4847 11.095 16.3519 11.274 16.1333L15.774 10.6333Z"
fill="#2F8EFF" />
</svg>

After

Width:  |  Height:  |  Size: 752 B

View File

@@ -0,0 +1,7 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame 2053139512">
<path id="&#226;&#128;&#156;" opacity="0.4"
d="M5.576 2L6.12 2.896C3.912 3.952 2.952 5.648 2.856 6.736C4.36 7.152 5.768 7.856 5.768 9.616C5.768 10.8 4.84 11.696 3.496 11.696C2.024 11.696 1 10.64 1 8.688C1 6.384 2.152 3.44 5.576 2ZM12.488 2L13.064 2.896C10.856 3.952 9.896 5.648 9.768 6.736C11.304 7.152 12.712 7.856 12.712 9.616C12.712 10.8 11.784 11.696 10.408 11.696C8.968 11.696 7.944 10.64 7.944 8.688C7.944 6.384 9.096 3.44 12.488 2Z"
fill="#212121" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 611 B

View File

@@ -0,0 +1,5 @@
<svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M6.04792 9.90479C6.38107 9.90479 6.70624 9.87058 7.02009 9.80547C6.85026 10.0891 6.66523 10.3643 6.46706 10.6284C5.8679 11.427 5.18291 12.0759 4.51148 12.5155C3.82775 12.9632 3.23901 13.1429 2.80982 13.1429V15.8096C3.92555 15.8096 5.01984 15.3702 5.97233 14.7464C6.93713 14.1147 7.84156 13.2398 8.60009 12.2288C10.0999 10.2299 11.167 7.50477 10.7978 4.7738L10.7957 4.77407C10.6075 2.31649 8.55374 0.380981 6.04792 0.380981C3.41799 0.380981 1.28601 2.51296 1.28601 5.14289C1.28601 7.77281 3.41799 9.90479 6.04792 9.90479ZM18.2384 9.90479C18.5715 9.90479 18.8967 9.87058 19.2106 9.80547C19.0407 10.0891 18.8557 10.3643 18.6575 10.6284C18.0584 11.427 17.3734 12.0759 16.702 12.5155C16.0182 12.9632 15.4295 13.1429 15.0003 13.1429V15.8096C16.116 15.8096 17.2103 15.3702 18.1628 14.7464C19.1276 14.1147 20.032 13.2398 20.7906 12.2288C22.2904 10.2299 23.3575 7.50477 22.9883 4.7738L22.9862 4.77407C22.798 2.31649 20.7442 0.380981 18.2384 0.380981C15.6085 0.380981 13.4765 2.51296 13.4765 5.14289C13.4765 7.77281 15.6085 9.90479 18.2384 9.90479Z"
fill="#CEDDEA" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,51 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="24" height="24" rx="6" fill="#2F8EFF" />
<path
d="M11.8847 10.0644C12.5484 10.3172 13.1795 10.6895 13.7462 11.1833C16.0574 13.1973 16.512 16.5496 14.9615 19.0788C14.2977 18.826 13.6666 18.4537 13.0999 17.9599C10.7887 15.9458 10.3341 12.5936 11.8847 10.0644Z"
stroke="white" stroke-width="0.843215" stroke-linejoin="round" />
<path
d="M11.0572 14.2908C12.8344 14.3335 14.6166 13.6143 15.8758 12.1693C17.135 10.7243 17.6037 8.86044 17.3183 7.10579C15.5411 7.06303 13.7589 7.78228 12.4997 9.22725C11.2405 10.6722 10.7718 12.5361 11.0572 14.2908Z"
stroke="white" stroke-width="0.843215" stroke-linejoin="round" />
<g opacity="0.3" filter="url(#filter0_f_35_308)">
<path d="M7.14312 12.9801C8.65516 14.2977 10.6259 14.7498 12.4502 14.378" stroke="#2F8EFF" stroke-width="1.72156"
stroke-linejoin="round" />
</g>
<path
d="M15.123 12.8996C12.8297 14.7751 9.45182 14.781 7.14327 12.7692C6.56894 12.2688 6.10927 11.6856 5.76652 11.0531C8.05981 9.17758 11.4377 9.17172 13.7462 11.1834C14.3205 11.6839 14.7802 12.267 15.123 12.8996Z"
stroke="white" stroke-width="0.843215" stroke-linejoin="round" />
<g opacity="0.3" filter="url(#filter1_f_35_308)">
<path d="M13.676 11.3945C15.357 12.8594 16.0558 15.0323 15.7153 17.0814" stroke="#2F8EFF" stroke-width="1.72156"
stroke-linejoin="round" />
</g>
<path
d="M11.8847 10.0644C12.5484 10.3172 13.1795 10.6895 13.7462 11.1833C16.0574 13.1973 16.512 16.5496 14.9615 19.0788"
stroke="white" stroke-width="0.843215" stroke-linejoin="round" />
<g opacity="0.3" filter="url(#filter2_f_35_308)">
<path
d="M11.4753 11.3895C11.7281 10.6801 12.1156 10.0051 12.6404 9.40294C12.8456 9.16747 13.0647 8.95127 13.2954 8.75449"
stroke="#2F8EFF" stroke-width="1.72156" stroke-linejoin="round" />
</g>
<path
d="M17.3183 7.10579C15.5411 7.06303 13.7589 7.78228 12.4997 9.22725C11.7142 10.1286 11.2363 11.1931 11.0572 12.2923"
stroke="white" stroke-width="0.843215" stroke-linejoin="round" />
<defs>
<filter id="filter0_f_35_308" x="6.01543" y="11.7689" width="7.1689" height="4.15749" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.281072" result="effect1_foregroundBlur_35_308" />
</filter>
<filter id="filter1_f_35_308" x="12.5483" y="10.1835" width="4.67372" height="7.60134" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.281072" result="effect1_foregroundBlur_35_308" />
</filter>
<filter id="filter2_f_35_308" x="10.1023" y="7.53747" width="4.31374" height="4.70339" filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="0.281072" result="effect1_foregroundBlur_35_308" />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

View File

@@ -0,0 +1,17 @@
@font-face {
font-family: "Noto Serif SC";
font-display: swap;
src: url("/fonts/NotoSerifSC-Bold.ttf") format("truetype");
}
@font-face {
font-family: "New York";
font-display: swap;
src: url("/fonts/NewYorkItalic.ttf") format("truetype");
}
body {
font-family: PingFang SC, -apple-system, BlinkMacSystemFont, sans-serif, Arial, Helvetica;
font-weight: 400;
}

View File

@@ -0,0 +1,5 @@
@import "./font.css";
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,27 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./app.vue",
"./error.vue",
],
theme: {
container: {
padding: "1rem",
},
colors: {
white: "#ffffff",
primary: "#4797F5",
greyscale_1: "#0D0D0D",
greyscale_2: "#212121",
greyscale_3: "#252525",
greyscale_4: "#8D8D8D",
greyscale_5: "#d7d7d7",
},
extend: {},
},
plugins: [],
};

View File

@@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

View File

2
1000h-portal/workers-site/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
worker

View File

@@ -0,0 +1,142 @@
import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'
/**
* The DEBUG flag will do two things that help during development:
* 1. we will skip caching on the edge, which makes it easier to
* debug.
* 2. we will return an error message on exception in your Response rather
* than the default 404.html page.
*/
const DEBUG = true
addEventListener('fetch', event => {
try {
event.respondWith(handleEvent(event))
} catch (e) {
if (DEBUG) {
return event.respondWith(
new Response(e.message || e.toString(), {
status: 500,
}),
)
}
event.respondWith(new Response('Internal Error', { status: 500 }))
}
})
async function handleEvent(event) {
// const url = new URL(event.request.url)
let options = {}
// const key = url.pathname.slice(1);
// if (event.request.method === 'GET' && key.startsWith("storage/")) {
// const object = await STORAGE_BUCKET.get(key);
// if (object === null) {
// event.respondWith(Response('Object Not Found', { status: 404 }));
// return
// }
// const headers = new Headers();
// object.writeHttpMetadata(headers);
// headers.set('etag', object.httpEtag);
// event.respondWith(new Response(object.body, {
// headers,
// }));
// return ;
// }
/**
* You can add custom logic to how we fetch your assets
* by configuring the function `mapRequestToAsset`
*/
// options.mapRequestToAsset = handlePrefix(/^\/docs/)
try {
if (DEBUG) {
// customize caching
options.cacheControl = {
bypassCache: true,
};
}
// no extension and not end with '/', redirect to the same path with '/'
if (!event.request.url.endsWith("/")) {
if (!event.request.url.split("/").pop().includes(".")) {
return Response.redirect(`${event.request.url}/`, 301);
}
}
const page = await getAssetFromKV(event, options);
// allow headers to be altered
const response = new Response(page.body, page);
response.headers.set("X-XSS-Protection", "1; mode=block");
response.headers.set("X-Content-Type-Options", "nosniff");
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("Referrer-Policy", "unsafe-url");
response.headers.set("Feature-Policy", "none");
return response;
} catch (e) {
// if an error is thrown try to serve the asset at 404.html
// if (!DEBUG) {
// try {
// let notFoundResponse = await getAssetFromKV(event, {
// mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req),
// })
// return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 })
// } catch (e) {}
// }
return getAssetFromKV(event, {
mapRequestToAsset: (req) => {
const url = new URL(req.url);
let pathname = url.pathname;
if (pathname.startsWith("/tools/")) {
// remove the '/tools' prefix
pathname = pathname.substring(6);
}
// If the pathname ends with '/', assume it's a directory and append 'index.html'
if (pathname.endsWith('/')) {
pathname += 'index.html';
}
// If it doesn't have an extension, assume it's a directory and append '/index.html'
else if (!pathname.split("/").pop().includes(".")) {
pathname += '/index.html';
}
return new Request(`${url.origin}${pathname}`, req);
},
})
// return new Response(e.message || e.toString(), { status: 500 })
}
}
/**
* Here's one example of how to modify a request to
* remove a specific prefix, in this case `/docs` from
* the url. This can be useful if you are deploying to a
* route on a zone, or if you only want your static content
* to exist at a specific path.
*/
function handlePrefix(prefix) {
return request => {
// compute the default (e.g. / -> index.html)
let defaultAssetKey = mapRequestToAsset(request)
let url = new URL(defaultAssetKey.url)
// strip the prefix from the path for lookup
url.pathname = url.pathname.replace(prefix, '/')
// inherit all other props from the default request
return new Request(url.toString(), defaultAssetKey)
}
}

View File

@@ -0,0 +1,12 @@
{
"private": true,
"name": "worker",
"version": "1.0.0",
"description": "A template for kick starting a Cloudflare Workers project",
"main": "index.js",
"author": "Ashley Lewis <ashleymichal@gmail.com>",
"license": "MIT",
"dependencies": {
"@cloudflare/kv-asset-handler": "~0.1.2"
}
}

View File

@@ -0,0 +1,5 @@
name = "1000h-portal"
main = "index.js"
workers_dev = false
compatibility_date = "2023-03-23"