add portal
47
.github/workflows/deploy-1000h-portal.yml
vendored
Normal 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
@@ -0,0 +1,2 @@
|
||||
.output/
|
||||
dist/
|
||||
75
1000h-portal/README.md
Normal 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
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
167
1000h-portal/components/Comments.vue
Normal 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>
|
||||
153
1000h-portal/components/Features.vue
Normal 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>
|
||||
169
1000h-portal/components/Introduction.vue
Normal 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>
|
||||
35
1000h-portal/components/Logo.vue
Normal 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>
|
||||
89
1000h-portal/components/Price.vue
Normal 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>
|
||||
91
1000h-portal/components/Slogan.vue
Normal 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>
|
||||
27
1000h-portal/components/layout/PageFooter.vue
Normal 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>
|
||||
47
1000h-portal/components/layout/PageHeader.vue
Normal 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>
|
||||
20
1000h-portal/layouts/default.vue
Normal 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>
|
||||
48
1000h-portal/nuxt.config.ts
Normal 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
@@ -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"
|
||||
}
|
||||
}
|
||||
25
1000h-portal/pages/index.vue
Normal 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>
|
||||
BIN
1000h-portal/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
1000h-portal/public/favicon-12x12.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
1000h-portal/public/favicon-24x24.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
1000h-portal/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
1000h-portal/public/fonts/NewYorkItalic.ttf
Normal file
BIN
1000h-portal/public/fonts/NotoSerifSC-Bold.ttf
Normal file
6
1000h-portal/public/icon/arrow-right.svg
Normal 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 |
5
1000h-portal/public/icon/arrow-upright.svg
Normal 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 |
8
1000h-portal/public/icon/check.svg
Normal 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 |
7
1000h-portal/public/icon/double-quote-2.svg
Normal 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="“" 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 |
5
1000h-portal/public/icon/double-quote.svg
Normal 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 |
51
1000h-portal/public/icon/enjoy-app.svg
Normal 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 |
BIN
1000h-portal/public/images/app-logo.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
1000h-portal/public/images/avatar.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
1000h-portal/public/images/bg-demo.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
1000h-portal/public/images/bg-intro-1.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
1000h-portal/public/images/bg-intro-2.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
1000h-portal/public/images/bg-intro-3.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
1000h-portal/public/images/bg-intro-4.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
1000h-portal/public/images/book.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
1000h-portal/public/images/camera.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
1000h-portal/public/images/cham.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
1000h-portal/public/images/demo.png
Normal file
|
After Width: | Height: | Size: 744 KiB |
BIN
1000h-portal/public/images/head-phone.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
1000h-portal/public/images/lang.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
1000h-portal/public/images/note.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
1000h-portal/public/images/og-image.png
Normal file
|
After Width: | Height: | Size: 744 KiB |
BIN
1000h-portal/public/images/payment-2.png
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
1000h-portal/public/images/payment.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
1000h-portal/public/images/robot.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
3
1000h-portal/server/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
||||
17
1000h-portal/styles/font.css
Normal 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;
|
||||
}
|
||||
5
1000h-portal/styles/main.css
Normal file
@@ -0,0 +1,5 @@
|
||||
@import "./font.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
27
1000h-portal/tailwind.config.js
Normal 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: [],
|
||||
};
|
||||
4
1000h-portal/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
||||
0
1000h-portal/workers-site/.cargo-ok
Normal file
2
1000h-portal/workers-site/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
worker
|
||||
142
1000h-portal/workers-site/index.js
Normal 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)
|
||||
}
|
||||
}
|
||||
12
1000h-portal/workers-site/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
5
1000h-portal/wrangler.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
name = "1000h-portal"
|
||||
main = "index.js"
|
||||
workers_dev = false
|
||||
compatibility_date = "2023-03-23"
|
||||
|
||||