feat: initial commit!

This commit is contained in:
rzmk 2023-12-04 22:02:40 -05:00
commit 3c04acba10
No known key found for this signature in database
53 changed files with 21596 additions and 0 deletions

39
.github/workflows/deploy.yml vendored Normal file
View file

@ -0,0 +1,39 @@
name: Deploy to GitHub Pages
on:
# Trigger the workflow every time you push to the `main` branch
# Using a different branch name? Replace `main` with your branchs name
push:
branches: [main]
# Allows you to run this workflow manually from the Actions tab on GitHub.
workflow_dispatch:
# Allow this job to clone the repo and create a page deployment
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout your repository using git
uses: actions/checkout@v3
- name: Install, build, and upload your site
uses: withastro/action@v1
# with:
# path: . # The root location of your Astro project inside the repository. (optional)
# node-version: 18 # The specific version of Node that should be used to build your site. Defaults to 18. (optional)
# package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

20
.gitignore vendored Normal file
View file

@ -0,0 +1,20 @@
# build output
dist/
.output/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

22
README.md Normal file
View file

@ -0,0 +1,22 @@
# qsv.pro
Promotional website for qsv pro. Based on the [Tailcast template](https://github.com/matt765/Tailcast) using Astro.
## Tech stack:
Astro, React, Tailwind, Framer Motion
## Live link
[https://dathere.github.io/qsv.pro](https://dathere.github.io/qsv.pro)
## How to run
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :---------------- | :------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |

7
astro.config.mjs Normal file
View file

@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from "@astrojs/tailwind";
export default defineConfig({
integrations: [react(), tailwind()]
});

21
license Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 matt765
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

14508
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

26
package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "@example/basics",
"type": "module",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/tailwind": "^3.1.1",
"@fontsource/inter": "^4.5.15",
"astro": "^2.2.0",
"framer-motion": "^10.11.2",
"npm-check-updates": "^16.10.7",
"tailwindcss": "^3.3.1"
},
"devDependencies": {
"@astrojs/react": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

5407
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
public/sm-preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View file

@ -0,0 +1,13 @@
export const CheckArrowIcon = () => (
<div className="rounded-full bg-transparent w-5 h-5 flex justify-center items-center mr-4">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
width="20px"
height="20px"
className="fill-customSecondary"
>
<path d="M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7 425.4 105.4c12.5-12.5 32.8-12.5 45.3 0z" />
</svg>
</div>
);

View file

@ -0,0 +1,5 @@
export const CloseIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="fill-[rgb(255,255,255,0.7)]">
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z" />
</svg>
);

View file

@ -0,0 +1,10 @@
export const FacebookIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
fill="white"
className="h-5 w-5"
>
<path d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z" />
</svg>
);

View file

@ -0,0 +1,9 @@
export const GithubIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512"
className="w-6 h-6 mr-3 fill-gray-400"
>
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
</svg>
);

View file

@ -0,0 +1,10 @@
export const InstagramIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
fill="white"
className="h-5 w-5"
>
<path d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z" />
</svg>
);

View file

@ -0,0 +1,10 @@
export const QuoteIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
className="fill-customContentSubtitle"
width="35px"
>
<path d="M0 216C0 149.7 53.7 96 120 96h8c17.7 0 32 14.3 32 32s-14.3 32-32 32h-8c-30.9 0-56 25.1-56 56v8h64c35.3 0 64 28.7 64 64v64c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V320 288 216zm256 0c0-66.3 53.7-120 120-120h8c17.7 0 32 14.3 32 32s-14.3 32-32 32h-8c-30.9 0-56 25.1-56 56v8h64c35.3 0 64 28.7 64 64v64c0 35.3-28.7 64-64 64H320c-35.3 0-64-28.7-64-64V320 288 216z" />
</svg>
);

View file

@ -0,0 +1,10 @@
export const TwitterIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
fill="white"
className="h-5 w-5"
>
<path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z" />
</svg>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

View file

@ -0,0 +1,11 @@
export const NextLogo = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 128 128"
className="fill-[rgb(215,215,215)]"
>
<path d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z" />
</svg>
);

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,25 @@
export const TauriLogo = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 128 128"
>
<path
fill="#ffc131"
d="M86.242 46.547a12.19 12.19 0 0 1-24.379 0c0-6.734 5.457-12.191 12.191-12.191a12.19 12.19 0 0 1 12.188 12.191zm0 0"
/>
<path
fill="#24c8db"
d="M41.359 81.453a12.19 12.19 0 1 1 24.383 0c0 6.734-5.457 12.191-12.191 12.191S41.36 88.187 41.36 81.453zm0 0"
/>
<path
fill="#ffc131"
d="M99.316 85.637a46.5 46.5 0 0 1-16.059 6.535a32.675 32.675 0 0 0 1.797-10.719a33.3 33.3 0 0 0-.242-4.02a32.69 32.69 0 0 0 6.996-3.418a32.7 32.7 0 0 0 12.066-14.035a32.71 32.71 0 0 0-21.011-44.934a32.72 32.72 0 0 0-33.91 10.527a32.85 32.85 0 0 0-1.48 1.91a54.32 54.32 0 0 0-17.848 5.184A46.536 46.536 0 0 1 60.25 2.094a46.53 46.53 0 0 1 26.34-.375c8.633 2.418 16.387 7.273 22.324 13.984s9.813 15 11.16 23.863a46.537 46.537 0 0 1-20.758 46.071zM30.18 41.156l11.41 1.402a32.44 32.44 0 0 1 1.473-6.469a46.44 46.44 0 0 0-12.883 5.066zm0 0"
/>
<path
fill="#24c8db"
d="M28.207 42.363a46.49 46.49 0 0 1 16.188-6.559a32.603 32.603 0 0 0-2.004 11.297a32.56 32.56 0 0 0 .188 3.512a32.738 32.738 0 0 0-6.859 3.371A32.7 32.7 0 0 0 23.652 68.02c-2.59 5.742-3.461 12.113-2.52 18.34s3.668 12.051 7.844 16.77s9.617 8.129 15.684 9.824s12.496 1.605 18.512-.262a32.72 32.72 0 0 0 15.402-10.266a34.9 34.9 0 0 0 1.484-1.918a54.283 54.283 0 0 0 17.855-5.223a46.528 46.528 0 0 1-8.723 16.012a46.511 46.511 0 0 1-21.918 14.609a46.53 46.53 0 0 1-26.34.375a46.6 46.6 0 0 1-22.324-13.984A46.56 46.56 0 0 1 7.453 88.434a46.53 46.53 0 0 1 3.582-26.098a46.533 46.533 0 0 1 17.172-19.973zm69.074 44.473c-.059.035-.121.066-.18.102c.059-.035.121-.066.18-.102zm0 0"
/>
</svg>
);

View file

@ -0,0 +1,14 @@
export const TypescriptLogo = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
viewBox="0 0 128 128"
>
<path fill="#fff" d="M22.67 47h99.67v73.67H22.67z" />
<path
fill="#007acc"
d="M1.5 63.91v62.5h125v-125H1.5zm100.73-5a15.56 15.56 0 0 1 7.82 4.5a20.58 20.58 0 0 1 3 4c0 .16-5.4 3.81-8.69 5.85c-.12.08-.6-.44-1.13-1.23a7.09 7.09 0 0 0-5.87-3.53c-3.79-.26-6.23 1.73-6.21 5a4.58 4.58 0 0 0 .54 2.34c.83 1.73 2.38 2.76 7.24 4.86c8.95 3.85 12.78 6.39 15.16 10c2.66 4 3.25 10.46 1.45 15.24c-2 5.2-6.9 8.73-13.83 9.9a38.32 38.32 0 0 1-9.52-.1a23 23 0 0 1-12.72-6.63c-1.15-1.27-3.39-4.58-3.25-4.82a9.34 9.34 0 0 1 1.15-.73L82 101l3.59-2.08l.75 1.11a16.78 16.78 0 0 0 4.74 4.54c4 2.1 9.46 1.81 12.16-.62a5.43 5.43 0 0 0 .69-6.92c-1-1.39-3-2.56-8.59-5c-6.45-2.78-9.23-4.5-11.77-7.24a16.48 16.48 0 0 1-3.43-6.25a25 25 0 0 1-.22-8c1.33-6.23 6-10.58 12.82-11.87a31.66 31.66 0 0 1 9.49.26zm-29.34 5.24v5.12H56.66v46.23H45.15V69.26H28.88v-5a49.19 49.19 0 0 1 .12-5.17C29.08 59 39 59 51 59h21.83z"
/>
</svg>
);

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

57
src/components/Brands.jsx Normal file
View file

@ -0,0 +1,57 @@
import { motion } from "framer-motion";
import { AmazonLogo } from "../assets/logos/AmazonLogo";
import { DropboxLogo } from "../assets/logos/DropboxLogo";
import { NetflixLogo } from "../assets/logos/NetflixLogo";
import { SlackLogo } from "../assets/logos/SlackLogo";
import { SpotifyLogo } from "../assets/logos/SpotifyLogo";
import { StripeLogo } from "../assets/logos/StripeLogo";
export const Brands = () => (
<section className="py-12 sm:py-24 bg-customDarkBg1 w-full mt-16 mb-16">
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className="container px-4 mx-auto 2xl:w-[1200px] xl:w-[1100px] lg:w-[1000px] md:w-4/5">
<div className="flex lg:flex-row flex-col items-center -mx-4 justify-center lg:text-left text-center">
<div className="w-full lg:w-1/2 px-4 mb-12 lg:mb-0">
<div className="flex flex-col">
<h2 className="mb-2 text-4xl sm:text-5xl 2xl:text-6xl font-bold tracking-normal text-white">
Trusted by brands
</h2>
<h2 className=" text-4xl sm:text-5xl 2xl:text-6xl font-bold tracking-normal text-customSecondary">
all over the world
</h2>
</div>
</div>
<div className="w-2/3 sm:w-[620px] lg:w-1/2 mx-auto lg:mx-0 lg:pl-10">
<div className="flex flex-wrap -m-4">
<div className="w-1/2 sm:w-1/3 py-6 flex justify-center">
<AmazonLogo />
</div>
<div className="w-1/2 sm:w-1/3 py-6 flex justify-center">
<DropboxLogo />
</div>
<div className="w-1/2 sm:w-1/3 py-6 flex justify-center">
<NetflixLogo />
</div>
<div className="w-1/2 sm:w-1/3 py-6 flex justify-center">
<StripeLogo />
</div>
<div className="w-1/2 sm:w-1/3 py-6 flex justify-center">
<SpotifyLogo />
</div>
<div className="w-1/2 sm:w-1/3 py-6 flex justify-center">
<SlackLogo />
</div>
</div>
</div>
</div>
</div>
</motion.div>
</section>
);

View file

@ -0,0 +1,5 @@
export const Divider = () => (
<div className="w-full lg:w-3/5 mx-auto">
<div className="border-t border-customGrayBorder"></div>
</div>
);

96
src/components/FAQ.jsx Normal file
View file

@ -0,0 +1,96 @@
import { useState } from "react";
import { motion } from "framer-motion";
const FAQData = [
{
question: "What is qsv pro?",
answer: "qsv pro is a desktop application that allows you to run qsv commands with a graphical user interface based on the qsv CLI tool. It also features a suite of recipes (scripts) for common data wrangling tasks to perform on your spreadsheet, including sorting rows, removing duplicate rows, and removing Personally Identifiable Information (PII).",
},
{
question: "How do I get qsv pro?",
answer: "qsv pro is currently in beta. You can get beta access by purchasing a subscription to qsv pro on our store. You will receive an email with a download link to qsv pro after purchasing a subscription, or you may click the Download button on this page's navigation bar to download qsv pro (license key still required).",
},
{
question: "Where can I provide feedback?",
answer: "There is a feedback button in the top right corner of qsv pro. You may also email us at dathere.com/contact.",
},
];
export const FAQ = () => (
<section className="relative pt-16 pb-16 bg-blueGray-50 overflow-hidden">
<div className="absolute -top-10" id="FAQ" />
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className="relative z-10 container px-2 sm:px-8 lg:px-4 mx-auto w-11/12 sm:w-full">
<div className="md:max-w-4xl mx-auto">
<p className="mb-7 custom-block-subtitle text-center">
Have any questions?
</p>
<h2 className="mb-16 custom-block-big-title text-center">
Frequently Asked Questions
</h2>
<div className="mb-11 flex flex-wrap -m-1">
{FAQData.map((item, index) => (
<div className="w-full p-1">
<FAQBox
title={item.question}
content={item.answer}
key={`${item.question}-${item.answer}`}
defaultOpen={index === 0}
/>
</div>
))}
</div>
</div>
</div>
</motion.div>
</section>
);
const FAQBox = ({ defaultOpen, title, content }) => {
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
<div
className="pt-2 sm:pt-6 pb-2 px-3 sm:px-8 rounded-3xl bg-customDarkBg3 custom-border-gray-darker mb-4 relative hover:bg-customDarkBg3Hover cursor-pointer"
onClick={() => setIsOpen(!isOpen)}
>
<div className="flex flex-col p-2 justify-center items-start">
<h3 className=" custom-content-title pt-3 sm:pt-0 pr-8 sm:pr-0">
{title}
</h3>
<p
className={`text-customGrayText pt-4 transition-all duration-300 overflow-hidden ${
isOpen ? "max-h-96" : "max-h-0"
}`}
>
{content}
</p>
</div>
<div className="absolute top-6 right-4 sm:top-8 sm:right-8">
<svg
width="28px"
height="30px"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`transition-all duration-500 ${
isOpen ? "rotate-[180deg]" : "rotate-[270deg]"
}`}
>
<path
d="M4.16732 12.5L10.0007 6.66667L15.834 12.5"
stroke="#007AFF"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
></path>
</svg>
</div>
</div>
);
};

View file

@ -0,0 +1,75 @@
import { motion } from "framer-motion";
import draganddrop from "../assets/images/drag-and-drop.gif";
import { CheckArrowIcon } from "../assets/icons/CheckArrowIcon";
export const Features0 = () => {
return (
<section
className="w-full bg-customDarkBg2 mt-20 mb-8 sm:mt-16 sm:mb-16 xl:mt-0 pt-[2rem] md:pt-[12vw] lg:pt-16"
id="features"
>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className="flex flex-wrap items-center 2xl:w-[1450px] xl:w-[1300px] w-11/12 mx-auto md:pl-4 xl:pr-16 xl:pl-16">
<div className="w-full md:w-1/2 mb-8 md:mb-0 lg:pl-16 flex justify-center mx-auto pt-16 lg:pt-0">
<img
src={draganddrop}
alt="Drag and Drop Demo"
className="rounded-xl custom-border-gray"
/>
</div>
<div className="w-full lg:w-1/2 mb-12 lg:mb-0">
<div className="mx-auto lg:mx-auto w-11/12 sm:w-4/5 md:w-3/4 lg:w-unset">
<span className="custom-block-subtitle">
Import Your Data
</span>
<h2 className="mt-6 mb-8 text-4xl lg:text-5xl custom-block-big-title">
<u>Drag & drop</u> your data into qsv pro
</h2>
<p className="mb-10 text-customGrayText leading-loose">
Drag and drop a spreadsheet file anywhere within
the Workflow section to import your data and
begin exploring your data in a data table.
</p>
<ul className="mb-6 text-white">
<li className="mb-4 flex">
<CheckArrowIcon />
<span>
File formats: .csv, .tsv, .tab, .xlsx,
.xls, .ods, .xlsm, .xlsb
</span>
</li>
<li className="mb-4 flex">
<CheckArrowIcon />
<span>
Supports large spreadsheets with
millions of rows
</span>
</li>
<li className="mb-4 flex">
<CheckArrowIcon />
<span>
Begins scanning and analyzing your data
on import
</span>
</li>
<li className="mb-4 flex">
<CheckArrowIcon />
<span>
Generates stats, frequency, and file
metadata
</span>
</li>
</ul>
</div>
</div>
</div>
</motion.div>
</section>
);
};

View file

@ -0,0 +1,59 @@
import { motion } from "framer-motion";
import recipedemo from "../assets/images/recipe-demo.gif";
import { CheckArrowIcon } from "../assets/icons/CheckArrowIcon";
export const Features1 = () => {
return (
<section className="w-full bg-customDarkBg2 mt-20 mb-8 sm:mt-16 sm:mb-16 xl:mt-4 pt-16">
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className="flex flex-wrap items-center 2xl:w-[1450px] xl:w-[1300px] w-11/12 mx-auto md:pl-4 xl:pr-16 xl:pl-16">
<div className="w-full lg:w-1/2 mb-12 lg:mb-0">
<div className="mx-auto lg:mx-auto w-11/12 sm:w-4/5 md:w-3/4 lg:w-unset">
<span className="custom-block-subtitle">
Transform Your Data
</span>
<h2 className="mt-6 mb-8 text-4xl lg:text-5xl custom-block-big-title">
Transform your data with <u>recipes</u>
</h2>
<p className="mb-10 text-customGrayText leading-loose">
qsv pro features a suite of recipes (scripts)
for common data wrangling tasks to perform on
your spreadsheet, including:
</p>
<ul className="mb-6 text-white">
<li className="mb-4 flex">
<CheckArrowIcon />
<span>Sort rows</span>
</li>
<li className="mb-4 flex">
<CheckArrowIcon />
<span>Remove duplicate rows</span>
</li>
<li className="mb-4 flex">
<CheckArrowIcon />
<span>
Remove Personally Identifiable
Information (PII)
</span>
</li>
</ul>
</div>
</div>
<div className="w-full md:w-1/2 mb-8 md:mb-0 mx-auto lg:w-1/2 flex flex-wrap lg:-mx-4 sm:pr-8 justify-center">
<img
src={recipedemo}
alt="Recipe Demo"
className="rounded-xl custom-border-gray"
/>
</div>
</div>
</motion.div>
</section>
);
};

View file

@ -0,0 +1,55 @@
import { motion } from "framer-motion";
import uploadtockan from "../assets/images/upload-to-ckan.gif";
import { CheckArrowIcon } from "../assets/icons/CheckArrowIcon";
export const Features2 = () => (
<section className="w-full bg-customDarkBg2 mt-12 sm:mt-20 mb-10 lg:my-20 pt-4">
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className="flex flex-wrap items-center 2xl:w-[1450px] xl:w-[1300px] w-11/12 mx-auto md:pl-4 xl:pr-16 xl:pl-16">
<div className="w-full md:w-1/2 mb-8 md:mb-0 lg:pl-16 flex justify-center mx-auto pt-16 lg:pt-0">
<img
src={uploadtockan}
alt="CKAN Upload Demo"
className="rounded-xl custom-border-gray"
/>
</div>
<div className="w-full lg:w-1/2 mb-12 lg:mb-0 xl:pl-8">
<div className="mx-auto lg:mx-auto w-11/12 sm:w-4/5 md:w-3/4 lg:w-unset">
<span className="custom-block-subtitle">
Load to CKAN with ease
</span>
<h2 className="mt-6 mb-8 text-4xl lg:text-5xl custom-block-big-title">
Upload your data to a <u>CKAN</u> datastore
</h2>
<p className="mb-12 text-customGrayText leading-loose">
qsv pro allows you to upload your transformed data
to a CKAN datastore & a data dictionary to specify
file metadata.
</p>
<ul className="mb-6 text-white">
<li className="mb-4 flex">
<CheckArrowIcon />
<span>Integrated with the CKAN API</span>
</li>
<li className="mb-4 flex">
<CheckArrowIcon />
<span>In-app data dictionary form</span>
</li>
<li className="mb-4 flex">
<CheckArrowIcon />
<span>Automatically inferred data types</span>
</li>
</ul>
</div>
</div>
</div>
</motion.div>
</section>
);

View file

@ -0,0 +1,76 @@
import { motion } from "framer-motion";
import { useState } from "react";
import configurator from "../assets/images/configurator.gif";
export const FeaturesDiagonal = () => {
return (
<section className="lg:mb-16 w-full flex flex-col justify-center items-center bg-customDarkBg1">
<div className="custom-shape-divider-bottom-1665696614">
<svg
data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1200 120"
preserveAspectRatio="none"
className="custom-bg-dark2"
>
<path
d="M1200 120L0 16.48 0 0 1200 0 1200 120z"
className="custom-bg-dark1"
></path>
</svg>
</div>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className=" 2xl:w-[1150px] xl:w-[1050px] md:w-4/5 flex justify-center bg-customDarkBg1 pt-12 lg:pt-24 pb-8 lg:pb-20 mx-auto lg:flex-row flex-col">
<div className="w-3/4 lg:w-1/2 flex flex-col lg:mx-unset mx-auto">
<span className="custom-block-subtitle">
Early Access
</span>
<h2 className="mt-10 mb-8 text-4xl lg:text-5xl custom-block-big-title">
Run qsv commands with our qsv Configurator GUI
</h2>
<p className="mb-16 text-customGrayText leading-loose">
No need to run qsv commands in your terminal. Try
out our early access qsv Configurator GUI to use qsv
commands with a graphical user interface.
</p>
<a
href="https://store.dathere.com/checkout/buy/41f919fd-2b68-40ea-a5ed-0f531b2efba5"
target="_blank"
>
<div className="w-[210px] h-12 custom-button-colored mr-10 ">
Start Your Free Trial
</div>
</a>
</div>
<div className="w-4/5 lg:w-2/3 lg:pl-16 flex justify-center mx-auto pt-16 lg:pt-0">
<img
src={configurator}
alt="Configurator Demo"
className="rounded-xl custom-border-gray"
/>
</div>
</div>
</motion.div>
<div className="custom-shape-divider-top-1665696661 w-full">
<svg
data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1200 120"
preserveAspectRatio="none"
className="custom-bg-dark2"
>
<path
d="M1200 120L0 16.48 0 0 1200 0 1200 120z"
className="custom-bg-dark1"
></path>
</svg>
</div>
</section>
);
};

126
src/components/Footer.jsx Normal file
View file

@ -0,0 +1,126 @@
import { FacebookIcon } from "../assets/icons/FacebookIcon";
import { InstagramIcon } from "../assets/icons/InstagramIcon";
import datHereLogoFull from "../assets/logos/datHere-logo-light.png";
import { TwitterIcon } from "../assets/icons/TwitterIcon";
const footerData = [
{
title: "Products",
items: ["Services", "About Us", "News and Stories", "Roadmap"],
},
{
title: "Important Links",
items: [
"Organization Team",
"Our Journeys",
"Pricing Plans",
"Roadmap",
"Terms & Conditions",
"Privacy Policy",
],
},
{
title: "Company",
items: ["About Us", "Jobs", "Press", "Contact Us"],
},
];
export const Footer = () => {
return (
<footer>
<div className="pt-10 lg:pt-20 lg:pb-12 bg-customDarkBg1 radius-for-skewed">
<div className="container mx-auto px-4 w-4/5 md:w-11/12 lg:w-10/12 xl:w-4/5 2xl:w-2/3">
<div className="flex flex-wrap">
<div className="w-full lg:w-1/3 mb-16 lg:mb-0">
<div className="flex justify-center lg:justify-start items-center grow basis-0">
<div className="text-white mr-2 text-6xl">
<img
src={datHereLogoFull}
alt="datHere Logo"
className="w-48"
/>
</div>
</div>
<p className="mb-10 mt-4 sm:w-[22rem] lg:w-[20rem] xl:w-[24rem] text-gray-400 leading-loose text-center lg:text-left mx-auto lg:mx-0">
Making data Useful, Usable, and Used.
</p>
{/* <div className="w-36 mx-auto lg:mx-0">
<a
className="inline-block w-10 h-10 mr-2 p-2 bg-customDarkBg2 custom-border-gray hover:bg-gray-700 rounded-xl"
href="#"
>
<TwitterIcon />
</a>
<a
className="inline-block w-10 h-10 mr-2 p-2 bg-customDarkBg2 custom-border-gray hover:bg-gray-700 rounded-xl"
href="#"
>
<InstagramIcon />
</a>
</div> */}
</div>
<div className="w-full lg:w-2/3 lg:pl-16 hidden lg:flex flex-wrap justify-between">
{/* <div className="w-full md:w-1/3 lg:w-auto mb-16 md:mb-0">
<h3 className="mb-6 text-2xl font-bold text-white">
Products
</h3>
<ul>
{footerData[0].items.map((item, i) => (
<li key={i} className="mb-4">
<a
className="text-gray-400 hover:text-gray-300"
href="#"
aria-label=""
>
{item}
</a>
</li>
))}
</ul>
</div> */}
{/* <div className="w-full md:w-1/3 lg:w-auto mb-16 md:mb-0">
<h3 className="mb-6 text-2xl font-bold text-white">
Important Links
</h3>
<ul>
{footerData[1].items.map((item, i) => (
<li key={i} className="mb-4">
<a
className="text-gray-400 hover:text-gray-300"
href="#"
aria-label=""
>
{item}
</a>
</li>
))}
</ul>
</div> */}
{/* <div className="w-full md:w-1/3 lg:w-auto">
<h3 className="mb-6 text-2xl font-bold text-white">
Company
</h3>
<ul>
{footerData[2].items.map((item, i) => (
<li key={i} className="mb-4">
<a
className="text-gray-400 hover:text-gray-300"
href="#"
aria-label=""
>
{item}
</a>
</li>
))}
</ul>
</div> */}
</div>
</div>
<p className="lg:text-center text-sm text-gray-400 border-t border-[rgb(255,255,255,0.2)] pt-4 hidden lg:block">
&copy; 2023 datHere Inc.
</p>
</div>
</div>
</footer>
);
};

110
src/components/Hero.jsx Normal file
View file

@ -0,0 +1,110 @@
import { useState } from "react";
import { motion } from "framer-motion";
import dashboard from "../assets/images/dashboard.png";
export const Hero = () => {
return (
<section
className="w-screen flex justify-center items-center bg-customDarkBg1 mb-[28vw] md:mb-[18vw] lg:mb-[10vw] xl:mb-[13vw] 2xl:mb-60 hero-bg-gradient pb-24 sm:pb-32 md:pb-44 lg:pb-0"
id="home"
>
<div className="w-full w-[900px] md:w-[1100px] flex flex-col justify-center items-center pt-16 md:pt-16 lg:pt-20 text-center">
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<div className="text-customSecondary text-sm sm:text-base mb-6 sm:mt-32 mt-16 font-bold">
Spreadsheet Data Wrangling Desktop App
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.05 }}
>
<div className="text-5xl sm:text-6xl lg:text-7xl xl:text-7xl font-bold tracking-wide text-white px-8 sm:px-8 md:px-20 lg:px-4">
<span>qsv pro</span>
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<div className="text-customGrayText text-sm lg:text-base xl:text-lg sm:text-base mt-10 px-12 sm:px-48 ">
Transform and upload spreadsheet data to{" "}
<a
href="https://ckan.org"
target="_blank"
className="text-blue-300"
>
CKAN
</a>{" "}
with our streamlined desktop app, featuring "recipes"
for common data wrangling tasks. Based on the{" "}
<a
href="https://github.com/jqnatividad/qsv"
target="_blank"
className="text-blue-300"
>
qsv
</a>{" "}
CLI tool.
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.15 }}
>
<div className="flex flex-col gap-2 sm:flex-row mt-14 mb-24 sm:mb-40 justify-center">
<a
href="https://store.dathere.com/checkout/buy/41f919fd-2b68-40ea-a5ed-0f531b2efba5"
target="_blank"
>
<div className="custom-button-colored w-64 sm:w-52 h-12 mr-0 sm:mr-4 lg:mr-6 mb-2 sm:mb-0">
Start Your Free Trial
</div>
</a>
{/* <div
className="w-64 sm:w-52 h-12 rounded-xl font-bold text-white border border-solid flex justify-center items-center cursor-pointer bg-customDarkBg2 hover:bg-customDarkBg3 border-customPrimary transition"
>
Live demo
</div> */}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 10, zIndex: 20 }}
animate={{ opacity: 1, y: 0, zIndex: 20 }}
transition={{ duration: 0.5, delay: 0.15 }}
>
<div className="relative w-screen flex justify-center ">
<img
src={dashboard}
alt="123"
className="w-4/5 2xl:w-[1200px] mx-auto absolute z-10 rounded-xl custom-border-gray hero-dashboard-border-gradient lg:top-6 xl:top-0"
/>
</div>
</motion.div>
<div className="relative w-screen flex justify-center ">
<div className="custom-shape-divider-bottom-1665343298 mt-4 sm:mt-16 md:mt-52 hidden lg:block">
<svg
data-name="Layer 1"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1200 120"
preserveAspectRatio="none"
className=" bg-customDarkBg2"
>
<path
d="M1200 0L0 0 598.97 114.72 1200 0z"
className="shape-fill custom-bg-dark1"
></path>
</svg>
</div>
</div>
</div>
</section>
);
};

129
src/components/Navbar.jsx Normal file
View file

@ -0,0 +1,129 @@
import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import datHereLogo from "../assets/logos/datHereLogo.png";
import { GithubIcon } from "../assets/icons/GithubIcon";
const navbarLinks = [
{ label: "Home", href: "#home", ariaLabel: "Home" },
{ label: "Features", href: "#features", ariaLabel: "Features" },
// { label: "Feedback", href: "#feedback", ariaLabel: "Feedback" },
{ label: "Pricing", href: "#pricing", ariaLabel: "Pricing" },
// { label: "FAQ", href: "#FAQ", ariaLabel: "FAQ" },
];
export const Navbar = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<nav className="w-full h-20 flex flex-col justify-center items-center fixed bg-customDarkBg1 lg:bg-customDarkBgTransparent z-40 lg:backdrop-blur-xl">
<div className="2xl:w-[1280px] xl:w-10/12 w-11/12 flex justify-between items-center relative">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
exit={{ opacity: 0 }}
>
<a className="navbar-link" href="#home" aria-label="Home">
<div className="flex justify-start items-center grow basis-0">
<div className="text-white mr-2 text-6xl">
<img
src={datHereLogo}
alt="datHere logo"
className="w-6 h-6"
/>
</div>
<div className="text-white font-['Inter'] font-bold text-xl">
qsv pro
</div>
</div>
</a>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
exit={{ opacity: 0 }}
>
<div className="hidden lg:flex h-full pl-12 pb-2">
{navbarLinks.map(({ href, label, ariaLabel }) => (
<a
className="navbar-link"
href={href}
aria-label={ariaLabel}
key={label}
>
{label}
</a>
))}
</div>
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
exit={{ opacity: 0 }}
>
<div className="grow basis-0 justify-end hidden lg:flex">
<a
className="text-white custom-border-gray rounded-xl
bg-customDarkBg2 hover:bg-customDarkBg3 border-gray-700 pl-6 pr-8 pt-2 pb-2 text-sm flex"
href="https://github.com/dathere/qsv-pro-releases/releases/latest"
target="_blank"
aria-label="source code"
>
<GithubIcon />
<span className="pt-px">Download</span>
</a>
</div>
</motion.div>
<div
className="lg:hidden flex flex-col px-2 py-3 border-solid border border-gray-600 rounded-md cursor-pointer hover:bg-customDarkBg2"
onClick={() => setIsOpen(!isOpen)}
>
<div className="w-5 h-0.5 bg-gray-500 mb-1"></div>
<div className="w-5 h-0.5 bg-gray-500 mb-1"></div>
<div className="w-5 h-0.5 bg-gray-500 "></div>
</div>
</div>
{/* Mobile navbar */}
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
exit={{ opacity: 0 }}
>
<div
className="flex flex-col mt-16 lg:hidden absolute top-4 left-0 bg-customDarkBg1 z-50 w-full
items-center gap-10 pb-10 border-y border-solid border-customDarkBg3 pt-10
"
>
{navbarLinks.map(({ label, href, ariaLabel }) => (
<a
key={href}
className="navbar-link"
href={href}
onClick={() => setIsOpen(false)}
aria-label={ariaLabel}
>
{label}
</a>
))}
<a
className="text-white custom-border-gray rounded-xl
bg-customDarkBg2 hover:bg-customDarkBg3 border-gray-700 pl-6 pr-8 pt-2 pb-2 text-sm flex"
href="https://github.com/dathere/qsv-pro-releases/releases/latest"
target="_blank"
>
<GithubIcon />
Download
</a>
</div>
</motion.div>
)}
</AnimatePresence>
</nav>
);
};

194
src/components/Pricing.jsx Normal file
View file

@ -0,0 +1,194 @@
import { useState } from "react";
import { motion } from "framer-motion";
import { CheckArrowIcon } from "../assets/icons/CheckArrowIcon";
const pricingData = [
"Import local spreadsheet data",
"Transform data with recipes",
"Upload to CKAN datastores",
"Interactive data table view",
"Early access to Configurator",
];
export const Pricing = () => {
const [isMonthly, setIsMonthly] = useState(true);
const handleChange = () => {
setIsMonthly(!isMonthly);
};
return (
<section className="w-screen flex justify-center bg-customDarkBg2 relative">
<div className="absolute -top-16" id="pricing" />
<div className="pb-20 pt-12 bg-customDarkBg2 2xl:w-[1150px] lg:w-[1050px] md:w-4/5 ">
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className="container mx-auto px-4">
<div className="max-w-2xl mx-auto text-center mb-16">
<span className="custom-block-subtitle">
Pick a Plan
</span>
<h2 className="mt-6 mb-6 text-4xl lg:text-5xl font-bold font-heading text-white">
Choose your plan
</h2>
<p className="mb-6 text-customGrayText">
Select the subscription plan that suits your
needs and benefit from qsv pro.
</p>
<label className="mx-auto bg-customDarkBg3 relative flex justify-between items-center group text-xl w-44 h-12 rounded-lg pr-36 pl-1 cursor-pointer">
<input
type="checkbox"
className="peer appearance-none"
checked={!isMonthly}
onChange={handleChange}
/>
<span className="h-8 w-[5.5rem] flex items-center pr-2 bg-customDarkBg3 after:rounded-lg duration-300 ease-in-out after:w-[30rem] after:h-10 after:bg-customPrimary after:shadow-md after:duration-300 peer-checked:after:translate-x-[5.5rem] cursor-pointer"></span>
<div className="flex absolute text-white text-sm font-bold">
<div
className={
isMonthly
? "mr-9 ml-3"
: "mr-9 ml-3 text-gray-400"
}
>
Monthly
</div>
<div
className={isMonthly && "text-gray-400"}
>
Yearly
</div>
</div>
</label>
</div>
<div className="flex flex-wrap flex-col lg:flex-row -mx-4 items-center mt-20">
<div className="w-[350px] sm:w-[380px] lg:w-1/3 px-4 mb-8 lg:mb-0">
{/* <div className="p-8 bg-customDarkBg3 rounded-3xl">
<h4 className="mb-2 text-xl font-bold font-heading text-white text-left">
Beginner
</h4>
<div className="flex justify-start items-end">
<div className="text-4xl sm:text-5xl font-bold text-white text-left mt-4 mr-2">
$0
</div>
<div className="text-gray-500">
{isMonthly ? "/ month" : "/ year"}
</div>
</div>
<p className="mt-4 mb-6 2xl:mb-10 text-gray-500 leading-loose text-left">
The perfect way to get started and get
used to our tools.
</p>
<ul className="mb-2 2xl:mb-6 text-white">
{pricingData.map((text, index) => (
<li
className="mb-4 flex"
key={`${text}-${index}`}
>
<CheckArrowIcon />
<span>{text}</span>
</li>
))}
</ul>
<div
className="inline-block text-center py-2 px-4 w-full rounded-xl rounded-t-xl custom-button-colored font-bold leading-loose mt-16"
>
Get Started
</div>
</div> */}
</div>
<div className="w-[350px] sm:w-[380px] lg:w-1/3 px-4 mb-8 lg:mb-0">
<div className="px-8 py-8 bg-customDarkBg3 rounded-3xl">
<h4 className="mb-2 2xl:mb-4 text-2xl font-bold font-heading text-white text-left">
qsv pro
</h4>
<div className="flex justify-start items-end">
<div className="text-4xl sm:text-5xl font-bold text-white text-left mt-4 mr-2">
{isMonthly ? "$99.99" : "$999.00"}
</div>
<div className="text-gray-500">
{isMonthly ? "/ month" : "/ year"}
</div>
</div>
<p className="mt-8 mb-8 2xl:mb-12 text-gray-500 leading-loose text-left">
1 license key + 7 days free trial
</p>
<ul className="mb-14 text-white">
{pricingData
.concat(
!isMonthly
? ["~$200 saved per year!"]
: []
)
.map((text, index) => (
<li
className="mb-4 flex"
key={`${text}-${index}`}
>
<CheckArrowIcon />
<span>{text}</span>
</li>
))}
</ul>
<a
href={
isMonthly
? "https://store.dathere.com/checkout/buy/41f919fd-2b68-40ea-a5ed-0f531b2efba5"
: "https://store.dathere.com/checkout/buy/88fed582-ffd4-41e0-a94e-457fdd038130"
}
target="_blank"
>
<div className="inline-block text-center py-2 px-4 w-full custom-button-colored leading-loose transition duration-200 mt-20">
Start Your 7-Day Free Trial
</div>
</a>
</div>
</div>
<div className="w-[350px] sm:w-[380px] lg:w-1/3 px-4 mb-8 lg:mb-0">
{/* <div className="p-8 bg-customDarkBg3 rounded-3xl">
<h4 className="mb-2 text-xl font-bold font-heading text-white text-left">
Premium
</h4>
<div className="flex justify-start items-end">
<div className="text-4xl sm:text-5xl font-bold text-white text-left mt-4 mr-2">
{isMonthly ? "$36" : "$390"}
</div>
<div className="text-gray-500">
{isMonthly ? "/ month" : "/ year"}
</div>
</div>
<p className="mt-4 mb-6 2xl:mb-10 text-gray-500 leading-loose text-left">
Experience the full power of our
analytic platform
</p>
<ul className="mb-2 2xl:mb-6 text-white">
{pricingData.map((text, index) => (
<li
className="mb-4 flex"
key={`${text}-${index}`}
>
<CheckArrowIcon />
<span>{text}</span>
</li>
))}
</ul>
<div
className="inline-block text-center py-2 px-4 w-full rounded-xl rounded-t-xl custom-button-colored font-bold leading-loose mt-16"
>
Get Started
</div>
</div> */}
</div>
</div>
</div>
</motion.div>
</div>
</section>
);
};

View file

@ -0,0 +1,52 @@
import { useEffect, useState } from "react";
export const ScrollUpButton = () => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
window.addEventListener("scroll", toggleVisible);
}, []);
const toggleVisible = () => {
const scrolled = document.documentElement.scrollTop;
if (scrolled > 300) {
setIsVisible(true);
} else if (scrolled <= 300) {
setIsVisible(false);
}
};
const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
};
return (
<>
{isVisible && (
<div
className="w-12 h-12 fixed bottom-6 right-6 custom-border-gray rounded-xl bg-customDarkBg2 hover:bg-customDarkBg3 cursor-pointer flex justify-center items-center transition z-50"
onClick={scrollToTop}
>
<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
width="35px"
height="35px"
viewBox="0 0 20 20"
>
<path
d="M4.16732 12.5L10.0007 6.66667L15.834 12.5"
stroke="#007AFF"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
></path>
</svg>
</div>
)}
</>
);
};

62
src/components/Tech.jsx Normal file
View file

@ -0,0 +1,62 @@
import { motion } from "framer-motion";
import { TauriLogo } from "../assets/logos/TauriLogo";
import { RustLogo } from "../assets/logos/RustLogo";
import { TypescriptLogo } from "../assets/logos/TypescriptLogo";
import { NextLogo } from "../assets/logos/NextLogo";
export const Tech = () => (
<section className="py-12 sm:py-24 bg-customDarkBg1 w-full mt-16 mb-16">
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
>
<div className="container px-4 mx-auto 2xl:w-[1200px] xl:w-[1100px] lg:w-[1000px] md:w-4/5">
<div className="flex lg:flex-row flex-col items-center -mx-4 justify-center lg:text-left text-center">
<div className="w-full lg:w-1/2 px-4 mb-12 lg:mb-0">
<div className="flex flex-col">
<h2 className="mb-2 text-4xl sm:text-5xl 2xl:text-6xl font-bold tracking-normal text-white">
Built with
</h2>
<h2 className=" text-4xl sm:text-5xl 2xl:text-6xl font-bold tracking-normal text-customSecondary">
modern tech
</h2>
</div>
</div>
<div className="w-2/3 sm:w-[620px] lg:w-1/2 mx-auto lg:mx-0 lg:pl-10">
<div className="flex flex-wrap -m-4">
<div className="w-1/2 sm:w-1/4 py-6 flex justify-center">
<a href="https://tauri.app" target="_blank">
<TauriLogo />
</a>
</div>
<div className="w-1/2 sm:w-1/4 py-6 flex justify-center">
<a
href="https://www.typescriptlang.org/"
target="_blank"
>
<TypescriptLogo />
</a>
</div>
<div className="w-1/2 sm:w-1/4 py-6 flex justify-center">
<a
href="https://www.rust-lang.org/"
target="_blank"
>
<RustLogo />
</a>
</div>
<div className="w-1/2 sm:w-1/4 py-6 flex justify-center">
<a href="https://nextjs.org/" target="_blank">
<NextLogo />
</a>
</div>
</div>
</div>
</div>
</div>
</motion.div>
</section>
);

View file

@ -0,0 +1,82 @@
import { motion } from "framer-motion";
import { QuoteIcon } from "../assets/icons/QuoteIcon";
import testimonial1 from "../assets/images/testimonial1.png";
import testimonial2 from "../assets/images/testimonial2.png";
import testimonial3 from "../assets/images/testimonial3.png";
const testimonialsData = [
{
customerName: "John Watkins",
customerTitle: "Founder of Dashflow",
content:
"The powerful analytic tools have helped us streamline our processes and make data-driven decisions that positively impact our efficiency. Tailcast has been a game-changer for our business. The platform is easy to use, and the insights we've gained have driven significant improvements.",
image: testimonial1,
},
{
customerName: "John Watkins",
customerTitle: "Founder of Dashflow",
content:
"The powerful analytic tools have helped us streamline our processes and make data-driven decisions that positively impact our efficiency. Tailcast has been a game-changer for our business. The platform is easy to use, and the insights we've gained have driven significant improvements.",
image: testimonial2,
},
{
customerName: "John Watkins",
customerTitle: "Founder of Dashflow",
content:
"The powerful analytic tools have helped us streamline our processes and make data-driven decisions that positively impact our efficiency. Tailcast has been a game-changer for our business. The platform is easy to use, and the insights we've gained have driven significant improvements.",
image: testimonial3,
},
];
export const Testimonials = () => (
<section className="w-full flex justify-center pt-10 mb-16 lg:mb-32 bg-customDarkBg2 relative">
<div className="absolute -top-16" id="feedback" />
<div className="flex flex-col w-full lg:w-[1150px] justify-center">
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.4, delay: 0.3 }}
>
{/* <div className="custom-block-subtitle text-center mb-6">
Testimonials
</div> */}
<div className="custom-block-big-title text-center mb-16 px-8 sm:px-24 md:px-48">
Testimonials
</div>
<div className="flex flex-col lg:flex-row gap-8 lg:gap-5 xl:gap-10 px-6 xl:px-0 items-center">
{testimonialsData.map((testimonial, index) => (
<div
className="w-11/12 sm:w-4/5 md:w-[560px] lg:w-1/3 custom-border-gray-darker rounded-xl bg-customDarkBg3 flex flex-col px-6 py-4"
key={`${testimonial.customerName}-${index}`}
>
<div className="flex mb-2">
<QuoteIcon />
</div>
<div className="custom-content-text-white">
"{testimonial.content}"
</div>
<div className="flex mt-4 mb-2 xl:mt-8 xl:mb-4">
<img
src={testimonial.image}
alt=""
width="45px"
/>
<div className="flex flex-col ml-4">
<div className="custom-content-text-white font-medium">
{testimonial.customerName}
</div>
<div className="custom-content-text-gray">
{testimonial.customerTitle}
</div>
</div>
</div>
</div>
))}
</div>
</motion.div>
</div>
</section>
);

1
src/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="astro/client" />

54
src/layouts/Layout.astro Normal file
View file

@ -0,0 +1,54 @@
---
import "@fontsource/inter";
import "@fontsource/inter/500.css";
import "@fontsource/inter/600.css";
import "@fontsource/inter/700.css";
import "@fontsource/inter/800.css";
import "@fontsource/inter/900.css";
export interface Props {
title: string;
}
const { title } = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" href="/favicon.ico" />
<meta name="generator" content={Astro.generator} />
<!-- Primary Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content="Transform and upload spreadsheet data to CKAN with our streamlined desktop app, featuring 'recipes' for common data wrangling tasks. Based on the qsv CLI tool." />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://dathere.github.io/qsv.pro" />
<meta property="og:title" content={title} />
<meta property="og:description" content="Transform and upload spreadsheet data to CKAN with our streamlined desktop app, featuring 'recipes' for common data wrangling tasks. Based on the qsv CLI tool." />
<meta property="og:image" content="/sm-preview.png" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://dathere.github.io/qsv.pro" />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content="Transform and upload spreadsheet data to CKAN with our streamlined desktop app, featuring 'recipes' for common data wrangling tasks. Based on the qsv CLI tool." />
<meta property="twitter:image" content="/sm-preview.png" />
</head>
<body>
<slot />
<style is:global>
html {
font-family: Inter;
background-color: #26272b;
overflow-x: hidden;
scroll-behavior: smooth;
}
</style>
</body>
</html>

38
src/pages/index.astro Normal file
View file

@ -0,0 +1,38 @@
---
import Layout from "../layouts/Layout.astro";
import { Hero } from "../components/Hero.jsx";
import { Navbar } from "../components/Navbar.jsx";
import { Features1 } from "../components/Features1.jsx";
import { Features2 } from "../components/Features2.jsx";
import { Testimonials } from "../components/Testimonials.jsx";
import { FeaturesDiagonal } from "../components/FeaturesDiagonal.jsx";
import { Pricing } from "../components/Pricing.jsx";
import { FAQ } from "../components/FAQ.jsx";
// import { Brands } from "../components/Brands.jsx";
import { Tech } from "../components/Tech.jsx";
import { Divider } from "../components/Divider";
import { Footer } from "../components/Footer.jsx";
import { ScrollUpButton } from "../components/ScrollUpButton";
import "../styles/Theme.css";
import "../styles/Diagonals.css";
import { Features0 } from "../components/Features0";
---
<Layout title="qsv pro">
<Navbar client:load />
<Hero client:load />
<Features0 client:load />
<Divider />
<Features1 client:load />
<Divider />
<Features2 client:load />
<!-- <Testimonials client:load /> -->
<FeaturesDiagonal client:load />
<Pricing client:load />
<!-- <Brands client:load /> -->
<Tech client:load />
<FAQ client:load />
<Footer />
<ScrollUpButton client:load />
</Layout>

48
src/styles/Diagonals.css Normal file
View file

@ -0,0 +1,48 @@
/* Hero */
.custom-shape-divider-bottom-1665343298 {
width: 100%;
overflow: hidden;
line-height: 0;
}
.custom-shape-divider-bottom-1665343298 svg {
position: relative;
display: block;
width: calc(100% + 1.3px);
height: 200px;
}
.hero-title-gradient {
background: -webkit-linear-gradient(white, #b4add1);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Features Top */
.custom-shape-divider-bottom-1665696614 {
width: 100%;
overflow: hidden;
line-height: 0;
transform: rotate(180deg);
}
.custom-shape-divider-bottom-1665696614 svg {
position: relative;
display: block;
width: calc(100% + 1.3px);
height: 127px;
transform: rotateY(180deg);
}
/* Features Bottom */
.custom-shape-divider-top-1665696661 {
overflow: hidden;
line-height: 0;
}
.custom-shape-divider-top-1665696661 svg {
position: relative;
display: block;
width: calc(100% + 1.3px);
height: 127px;
transform: rotateY(180deg);
}

62
src/styles/Theme.css Normal file
View file

@ -0,0 +1,62 @@
/* Typography */
.custom-block-title {
@apply text-white text-3xl font-bold tracking-normal;
}
.custom-block-big-title {
@apply text-white text-4xl xl:text-5xl font-bold tracking-normal;
}
.custom-block-subtitle {
@apply text-xs text-customSecondary tracking-wider font-bold uppercase;
}
.custom-content-title {
@apply text-white text-lg font-bold tracking-normal;
}
.custom-content-text-white {
@apply text-white text-base leading-relaxed;
}
.custom-content-text-gray {
@apply text-gray-400 text-base;
}
/* Backgrounds */
.custom-bg-dark1 {
@apply bg-customDarkBg1 fill-customDarkBg1;
}
.custom-bg-dark2 {
@apply bg-customDarkBg2 fill-customDarkBg2;
}
.custom-bg-dark3 {
@apply bg-customDarkBg3 fill-customDarkBg3;
}
body,
html {
@apply bg-customDarkBg2;
}
/* Borders */
.custom-border-gray {
@apply border border-solid border-[rgb(255,255,255,0.15)];
}
.custom-border-gray-darker {
@apply border border-solid border-[rgb(255,255,255,0.07)];
}
/* Buttons */
.custom-button-colored {
@apply rounded-lg font-bold bg-customPrimary text-white flex justify-center items-center hover:bg-[rgb(0,142,200)] cursor-pointer transition;
}
/* Navbar */
.navbar-link {
@apply text-white lg:text-base text-2xl leading-6 mr-4 ml-4 2xl:mr-6 2xl:ml-6 cursor-pointer font-normal lg:font-medium hover:scale-110 transition duration-300 h-full pt-2;
}
/* Preventing FOUC */
@font-face {
font-display: var(--fontsource-Inter-font-display, optional);
}

34
tailwind.config.cjs Normal file
View file

@ -0,0 +1,34 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
theme: {
extend: {
colors: {
customPrimary: "rgb(0,122,255)",
customSecondary: "rgb(0,142,200)",
customDarkBg1: "rgb(31, 32, 35)",
customDarkBg2: "rgb(38, 39, 43)",
customDarkBg3: "rgb(48, 49, 54)",
customDarkBg3Hover: "rgb(55, 56, 62)",
customContentSubtitle: "rgb(178, 184, 205)",
customGrayBorder: "rgb(255,255,255,0.1)",
customGrayText: "rgb(174, 178, 183)",
customDarkBgTransparent: "rgb(31, 32, 35, 0.7)",
customDarkBgTransparentDarker: "rgb(0,0,0,0.5)",
customDarkBgTransparentLighter: "rgb(48, 49, 54, 0.7)",
},
fontFamily: {
Inter: "Inter",
},
screens: {
xs: "530px",
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
xll: "1400px",
"2xl": "1536px",
},
},
},
};

3
tsconfig.json Normal file
View file

@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/base"
}