Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add landing page #104

Merged
merged 9 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ useSession().refresh()

<template>
<Html>
<Body class="selection:bg-primary relative font-geist text-black selection:text-inverted dark:bg-neutral-950 dark:text-white">
<Body class="selection:bg-primary relative overflow-x-hidden font-geist text-black selection:text-inverted dark:text-white">
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
Expand Down
1 change: 0 additions & 1 deletion apps/app/assets/style/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,3 @@ html.dark {

--border-color: rgba(255,255,255,0.1);
}

5 changes: 0 additions & 5 deletions apps/app/components/Noise.vue

This file was deleted.

46 changes: 46 additions & 0 deletions apps/app/components/Release.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import type { ParsedContent } from '@nuxt/content/dist/runtime/types'
import type { PropType } from 'vue'

defineProps({
content: {
type: Object as PropType<ParsedContent>,
required: true,
},
})
</script>

<template>
<article
:id="content.title"
class="border-gray mx-auto flex max-w-3xl flex-col border-t-DEFAULT px-4 py-20 sm:px-6 lg:px-8"
>
<img
:src="content.image"
:alt="content.title"
class="w-full"
>
<div class="flex items-center gap-3">
<h1 class="font-main text-primary text-xl font-bold sm:text-3xl">
{{ content.title }}
</h1>
</div>
<span class="text-muted block text-sm">
{{ new Date(content.date).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
}) }}
</span>
<ContentRenderer
:value="content"
class="post"
/>
</article>
</template>

<style scoped>
img {
@apply mx-auto rounded-xl;
}
</style>
55 changes: 55 additions & 0 deletions apps/app/components/landing/Faq.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script setup lang="ts">
const items = [
{
label: 'Is Shelve free?',
icon: 'i-lucide-wallet',
defaultOpen: true,
content: 'Yes, Shelve is under heavy development and is currently free to use. We may introduce a paid plan in the future, but the free plan will always be available.',
},
{
label: 'Is Shelve secure?',
icon: 'i-lucide-lock',
content: 'Yes, Shelve is secure. We use industry-standard encryption (AES-256) to protect your data. We never store your secrets in plain text, and we never share your data with third parties. You can also check the source code on GitHub to verify our security practices.',
},
{
label: 'How can I contribute to Shelve?',
icon: 'i-lucide-github',
content: 'You can contribute to Shelve by submitting a pull request to the GitHub repository. You can also contribute by reporting bugs, suggesting features, or sharing Shelve with your friends.',
},
{
label: 'Is Shelve open source?',
icon: 'i-lucide-code',
content: 'Yes, Shelve is open source. You can view the source code on GitHub and contribute to the project if you wish.',
},
{
label: 'How can I contact the Shelve team?',
icon: 'i-lucide-mail',
content: 'For the moment you can send me an email at contact@hrcd.fr',
}
]
</script>

<template>
<div>
<div class="mb-10 flex flex-col items-center justify-center gap-2">
<h3 class="from-primary-300 to-primary-400 bg-gradient-to-tr bg-clip-text text-3xl font-bold text-transparent sm:text-4xl">
<LandingScrambleText label="FAQ" />
</h3>
<p class="max-w-lg text-center text-sm text-gray-500 sm:text-base">
Frequently asked questions about Shelve.
</p>
</div>
<div class="mx-auto max-w-3xl">
<UAccordion
color="primary"
variant="ghost"
size="sm"
:items="items"
/>
</div>
</div>
</template>

<style scoped>

</style>
77 changes: 77 additions & 0 deletions apps/app/components/landing/Features.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<script setup lang="ts">
const features = [
{
title: 'Manage your projects secrets',
description: 'End Slack messages like "Can you send me .env file?", keep your secrets in one place and share them with your team.',
icon: 'i-lucide-lock',
},
{
title: 'Collaborate with your team',
description: 'Create, invite and manage your team members to work together on your projects in seconds.',
icon: 'i-lucide-users',
},
{
title: 'Powerful CLI',
description: 'Manage your projects secrets, files and more withour leaving your terminal.',
icon: 'i-lucide-terminal',
},
{
title: 'Shared env variables',
description: 'Create shared env variables that can be used across all your projects.',
icon: 'i-lucide-share',
soon: true,
},
{
title: 'Sync with your favorite tools',
description: 'Integrate with your favorite tools like GitHub, Vercel, etc...',
icon: 'i-custom-github',
soon: true,
},
{
title: 'Talk to project',
description: 'Talk to your project with built in trained AI on your project data.',
icon: 'i-lucide-message-square',
soon: true,
}
]
</script>

<template>
<div class="mx-auto max-w-5xl">
<div class="mb-10 flex flex-col items-center justify-center gap-2">
<h3 class="from-primary-300 to-primary-400 bg-gradient-to-tr bg-clip-text text-3xl font-bold text-transparent sm:text-4xl">
<LandingScrambleText label="Features" />
</h3>
<p class="max-w-lg text-pretty text-center text-sm text-gray-500 sm:text-base">
No more tedious tasks, Shelve has everything you need to manage your projects.
</p>
</div>
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3 lg:gap-y-16">
<div
v-for="(feature, index) in features"
:key="index"
class="flex flex-col gap-1"
:style="{ '--stagger': index + 1 }"
data-animate
>
<div class="flex items-center">
<div class="flex items-center justify-center rounded border border-white/5 bg-white/5 p-1">
<span :class="feature.icon" class="size-5 text-gray-300" />
</div>

<div class="ml-4 font-semibold text-gray-300">
{{ feature.title }} <span v-if="feature.soon" class="ml-1 text-xs text-gray-400">(soon)</span>
</div>
</div>

<div class="ml-11 pl-0.5 text-sm text-gray-500">
{{ feature.description }}
</div>
</div>
</div>
</div>
</template>

<style scoped>

</style>
36 changes: 36 additions & 0 deletions apps/app/components/landing/GithubStar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script setup lang="ts">
type repoType = {
name: string
stars: number
watchers: number
forks: number
repo: string
}

const githubStars = useCookie('githubStars')
if (!githubStars.value) {
githubStars.value = '0'
}

async function fetchRepo() {
try {
const res = await $fetch('https://ungh.cc/repos/hugorcd/shelve') as { repo: repoType }
githubStars.value = res.repo.stars.toString()
} catch (e) { /* empty */ }
}
fetchRepo()
</script>

<template>
<NuxtLink
class="flex items-center gap-2 text-gray-200"
to="https://github.com/HugoRCD/shelve"
>
<span class="i-custom-github text-xl" />
<LandingScrambleText :label="githubStars" class="text-sm" />
</NuxtLink>
</template>

<style scoped>

</style>
38 changes: 38 additions & 0 deletions apps/app/components/landing/Hero.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script setup lang="ts">
const { title } = useAppConfig()

defineShortcuts({
s: {
usingInput: true,
handler: () => {
useRouter().push('/login')
}
}
})
</script>

<template>
<div class="h-96 w-full overflow-hidden">
<div class="mx-auto mt-32 w-full max-w-2xl">
<div class="text-balance text-center text-3xl font-extralight sm:text-4xl">
Meet <span class="font-newsreader font-light italic">{{ title }}</span> a cosy home for all your <span class="font-newsreader font-light italic">projects</span>
</div>
<p class="mt-4 text-center text-gray-400">
Press <UKbd>S</UKbd> to start your journey
</p>
</div>

<div
class="pointer-events-none relative -mt-32 h-96 w-screen overflow-hidden [mask-image:radial-gradient(50%_50%,white,transparent)] before:absolute before:inset-0 before:bg-[radial-gradient(circle_at_bottom_center,#4C7EFF,transparent_70%)] before:opacity-40 after:absolute after:-left-1/2 after:top-1/2 after:aspect-[1/0.7] after:w-[200%] after:rounded-[100%] after:border-t after:border-[#7876c566] after:bg-[#121212]"
>
<LandingSparkles
:density="1200"
class="absolute inset-x-0 bottom-0 size-full [mask-image:radial-gradient(50%_50%,white,transparent_85%)]"
/>
</div>
</div>
</template>

<style scoped>

</style>
37 changes: 37 additions & 0 deletions apps/app/components/landing/ScrambleText.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script setup>
const props = defineProps({
label: String,
})

const displayText = ref(props.label)
const charset = 'abcdefghijklmnopqrstuvwxyz'

function randomChars(length) {
return Array.from(
{ length },
() => charset[Math.floor(Math.random() * charset.length)]
).join('')
}

async function scramble(input) {
let prefix = ''
for (let index = 0; index < input.length; index++) {
await new Promise((resolve) => setTimeout(resolve, 50))
prefix += input.charAt(index)
displayText.value = prefix + randomChars(input.length - prefix.length)
}
}

const startScrambling = () => {
scramble(props.label)
setTimeout(() => props.label.length * 50)
}
startScrambling()
</script>

<template>
<span @mouseover="startScrambling">
{{ displayText }}
</span>
</template>

Loading