mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-06 04:22:07 +00:00
fix
This commit is contained in:
@@ -1,30 +1,30 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:svelte/recommended",
|
||||
"prettier",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["@typescript-eslint"],
|
||||
parserOptions: {
|
||||
sourceType: "module",
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: [".svelte"],
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true,
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.svelte"],
|
||||
parser: "svelte-eslint-parser",
|
||||
parserOptions: {
|
||||
parser: "@typescript-eslint/parser",
|
||||
},
|
||||
root: true,
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:svelte/recommended',
|
||||
'prettier'
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaVersion: 2020,
|
||||
extraFileExtensions: ['.svelte']
|
||||
},
|
||||
],
|
||||
env: {
|
||||
browser: true,
|
||||
es2017: true,
|
||||
node: true
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.svelte'],
|
||||
parser: 'svelte-eslint-parser',
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
36
.github/workflows/index.yml
vendored
36
.github/workflows/index.yml
vendored
@@ -1,23 +1,23 @@
|
||||
name: Search Index
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
indexing:
|
||||
runs-on: ubuntu-latest
|
||||
name: Indexing
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "meilisearch/scrapix"
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20.x"
|
||||
- run: yarn
|
||||
- run: yarn start -c "$SCRAPIX_CONFIG"
|
||||
env:
|
||||
SCRAPIX_CONFIG: ${{ secrets.SCRAPIX_CONFIG }}
|
||||
indexing:
|
||||
runs-on: ubuntu-latest
|
||||
name: Indexing
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'meilisearch/scrapix'
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20.x'
|
||||
- run: yarn
|
||||
- run: yarn start -c "$SCRAPIX_CONFIG"
|
||||
env:
|
||||
SCRAPIX_CONFIG: ${{ secrets.SCRAPIX_CONFIG }}
|
||||
|
||||
132
.github/workflows/production.yml
vendored
132
.github/workflows/production.yml
vendored
@@ -1,79 +1,79 @@
|
||||
name: Production deployment
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
TAG: ${{ github.event.release.tag_name }}
|
||||
STACK_FILE: docker/production.yml
|
||||
REPOSITORY: website
|
||||
REGISTRY_USERNAME: christyjacob4
|
||||
TAG: ${{ github.event.release.tag_name }}
|
||||
STACK_FILE: docker/production.yml
|
||||
REPOSITORY: website
|
||||
REGISTRY_USERNAME: christyjacob4
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/appwrite/website:${{ env.TAG }}
|
||||
build-args: |
|
||||
"PUBLIC_APPWRITE_ENDPOINT=${{ secrets.PUBLIC_APPWRITE_ENDPOINT }}"
|
||||
"PUBLIC_APPWRITE_DASHBOARD=${{ secrets.PUBLIC_APPWRITE_DASHBOARD }}"
|
||||
"PUBLIC_APPWRITE_PROJECT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_ID }}"
|
||||
"PUBLIC_APPWRITE_DB_MAIN_ID=${{ vars.PUBLIC_APPWRITE_DB_MAIN_ID }}"
|
||||
"PUBLIC_APPWRITE_COL_THREADS_ID=${{ vars.PUBLIC_APPWRITE_COL_THREADS_ID }}"
|
||||
"PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ vars.PUBLIC_APPWRITE_COL_MESSAGES_ID }}"
|
||||
"PUBLIC_APPWRITE_FN_TLDR_ID=${{ vars.PUBLIC_APPWRITE_FN_TLDR_ID }}"
|
||||
"PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_INIT_ID }}"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ vars.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
"APPWRITE_DB_INIT_ID=${{ secrets.APPWRITE_DB_INIT_ID }}"
|
||||
"APPWRITE_COL_INIT_ID=${{ secrets.APPWRITE_COL_INIT_ID }}"
|
||||
"APPWRITE_API_KEY_INIT=${{ secrets.APPWRITE_API_KEY_INIT }}"
|
||||
"GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
|
||||
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/appwrite/website:${{ env.TAG }}
|
||||
build-args: |
|
||||
"PUBLIC_APPWRITE_ENDPOINT=${{ secrets.PUBLIC_APPWRITE_ENDPOINT }}"
|
||||
"PUBLIC_APPWRITE_DASHBOARD=${{ secrets.PUBLIC_APPWRITE_DASHBOARD }}"
|
||||
"PUBLIC_APPWRITE_PROJECT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_ID }}"
|
||||
"PUBLIC_APPWRITE_DB_MAIN_ID=${{ vars.PUBLIC_APPWRITE_DB_MAIN_ID }}"
|
||||
"PUBLIC_APPWRITE_COL_THREADS_ID=${{ vars.PUBLIC_APPWRITE_COL_THREADS_ID }}"
|
||||
"PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ vars.PUBLIC_APPWRITE_COL_MESSAGES_ID }}"
|
||||
"PUBLIC_APPWRITE_FN_TLDR_ID=${{ vars.PUBLIC_APPWRITE_FN_TLDR_ID }}"
|
||||
"PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_INIT_ID }}"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ vars.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
"APPWRITE_DB_INIT_ID=${{ secrets.APPWRITE_DB_INIT_ID }}"
|
||||
"APPWRITE_COL_INIT_ID=${{ secrets.APPWRITE_COL_INIT_ID }}"
|
||||
"APPWRITE_API_KEY_INIT=${{ secrets.APPWRITE_API_KEY_INIT }}"
|
||||
"GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
|
||||
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Execute SSH commands
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRD_SSH_HOST }}
|
||||
username: ${{ secrets.PRD_SSH_USERNAME }}
|
||||
key: ${{ secrets.PRD_SSH_KEY }}
|
||||
script: |
|
||||
url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git"
|
||||
if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then
|
||||
echo "Clone failed because the folder ${{ env.REPOSITORY }} exists"
|
||||
fi
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Execute SSH commands
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.PRD_SSH_HOST }}
|
||||
username: ${{ secrets.PRD_SSH_USERNAME }}
|
||||
key: ${{ secrets.PRD_SSH_KEY }}
|
||||
script: |
|
||||
url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git"
|
||||
if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then
|
||||
echo "Clone failed because the folder ${{ env.REPOSITORY }} exists"
|
||||
fi
|
||||
|
||||
cd ${{ env.REPOSITORY }}
|
||||
git reset --hard HEAD
|
||||
git remote set-url origin $url
|
||||
git fetch origin
|
||||
git checkout ${{ env.TAG }}
|
||||
cd ${{ env.REPOSITORY }}
|
||||
git reset --hard HEAD
|
||||
git remote set-url origin $url
|
||||
git fetch origin
|
||||
git checkout ${{ env.TAG }}
|
||||
|
||||
rm -f .env
|
||||
echo "_APP_VERSION=${{ env.TAG }}" >> .env
|
||||
echo "_APP_DOMAIN=${{ secrets.PRD_APP_DOMAIN }}" >> .env
|
||||
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env
|
||||
echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env
|
||||
rm -f .env
|
||||
echo "_APP_VERSION=${{ env.TAG }}" >> .env
|
||||
echo "_APP_DOMAIN=${{ secrets.PRD_APP_DOMAIN }}" >> .env
|
||||
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env
|
||||
echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env
|
||||
|
||||
echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ env.REGISTRY_USERNAME }} --password-stdin
|
||||
docker-compose -f ${{ env.STACK_FILE }} config
|
||||
env $(cat .env | xargs) docker stack deploy --prune --resolve-image always --with-registry-auth -c ${{ env.STACK_FILE }} ${{ env.REPOSITORY }}
|
||||
echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ env.REGISTRY_USERNAME }} --password-stdin
|
||||
docker-compose -f ${{ env.STACK_FILE }} config
|
||||
env $(cat .env | xargs) docker stack deploy --prune --resolve-image always --with-registry-auth -c ${{ env.STACK_FILE }} ${{ env.REPOSITORY }}
|
||||
|
||||
136
.github/workflows/staging.yml
vendored
136
.github/workflows/staging.yml
vendored
@@ -1,81 +1,81 @@
|
||||
name: Staging deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
env:
|
||||
TAG: ${{ github.sha }}
|
||||
STACK_FILE: docker/stage.yml
|
||||
REPOSITORY: website
|
||||
REGISTRY_USERNAME: christyjacob4
|
||||
TAG: ${{ github.sha }}
|
||||
STACK_FILE: docker/stage.yml
|
||||
REPOSITORY: website
|
||||
REGISTRY_USERNAME: christyjacob4
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/appwrite/website:${{ env.TAG }}
|
||||
build-args: |
|
||||
"PUBLIC_APPWRITE_ENDPOINT=${{ secrets.PUBLIC_APPWRITE_ENDPOINT }}"
|
||||
"PUBLIC_APPWRITE_DASHBOARD=${{ secrets.PUBLIC_APPWRITE_DASHBOARD }}"
|
||||
"PUBLIC_APPWRITE_PROJECT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_ID }}"
|
||||
"PUBLIC_APPWRITE_DB_MAIN_ID=${{ vars.PUBLIC_APPWRITE_DB_MAIN_ID }}"
|
||||
"PUBLIC_APPWRITE_COL_THREADS_ID=${{ vars.PUBLIC_APPWRITE_COL_THREADS_ID }}"
|
||||
"PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ vars.PUBLIC_APPWRITE_COL_MESSAGES_ID }}"
|
||||
"PUBLIC_APPWRITE_FN_TLDR_ID=${{ vars.PUBLIC_APPWRITE_FN_TLDR_ID }}"
|
||||
"PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_INIT_ID }}"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ vars.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
"APPWRITE_DB_INIT_ID=${{ secrets.APPWRITE_DB_INIT_ID }}"
|
||||
"APPWRITE_COL_INIT_ID=${{ secrets.APPWRITE_COL_INIT_ID }}"
|
||||
"APPWRITE_API_KEY_INIT=${{ secrets.APPWRITE_API_KEY_INIT }}"
|
||||
"GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
|
||||
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/appwrite/website:${{ env.TAG }}
|
||||
build-args: |
|
||||
"PUBLIC_APPWRITE_ENDPOINT=${{ secrets.PUBLIC_APPWRITE_ENDPOINT }}"
|
||||
"PUBLIC_APPWRITE_DASHBOARD=${{ secrets.PUBLIC_APPWRITE_DASHBOARD }}"
|
||||
"PUBLIC_APPWRITE_PROJECT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_ID }}"
|
||||
"PUBLIC_APPWRITE_DB_MAIN_ID=${{ vars.PUBLIC_APPWRITE_DB_MAIN_ID }}"
|
||||
"PUBLIC_APPWRITE_COL_THREADS_ID=${{ vars.PUBLIC_APPWRITE_COL_THREADS_ID }}"
|
||||
"PUBLIC_APPWRITE_COL_MESSAGES_ID=${{ vars.PUBLIC_APPWRITE_COL_MESSAGES_ID }}"
|
||||
"PUBLIC_APPWRITE_FN_TLDR_ID=${{ vars.PUBLIC_APPWRITE_FN_TLDR_ID }}"
|
||||
"PUBLIC_APPWRITE_PROJECT_INIT_ID=${{ vars.PUBLIC_APPWRITE_PROJECT_INIT_ID }}"
|
||||
"PUBLIC_GROWTH_ENDPOINT=${{ vars.PUBLIC_GROWTH_ENDPOINT }}"
|
||||
"APPWRITE_DB_INIT_ID=${{ secrets.APPWRITE_DB_INIT_ID }}"
|
||||
"APPWRITE_COL_INIT_ID=${{ secrets.APPWRITE_COL_INIT_ID }}"
|
||||
"APPWRITE_API_KEY_INIT=${{ secrets.APPWRITE_API_KEY_INIT }}"
|
||||
"GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}"
|
||||
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Execute SSH commands
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.STG_SSH_HOST }}
|
||||
username: ${{ secrets.STG_SSH_USERNAME }}
|
||||
key: ${{ secrets.STG_SSH_KEY }}
|
||||
script: |
|
||||
url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git"
|
||||
if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then
|
||||
echo "Clone failed because the folder ${{ env.REPOSITORY }} exists"
|
||||
fi
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Execute SSH commands
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.STG_SSH_HOST }}
|
||||
username: ${{ secrets.STG_SSH_USERNAME }}
|
||||
key: ${{ secrets.STG_SSH_KEY }}
|
||||
script: |
|
||||
url="https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/appwrite/${{ env.REPOSITORY }}.git"
|
||||
if ! git clone "${url}" "${{ env.REPOSITORY }}" 2>/dev/null && [ -d "${{ env.REPOSITORY }}" ] ; then
|
||||
echo "Clone failed because the folder ${{ env.REPOSITORY }} exists"
|
||||
fi
|
||||
|
||||
cd ${{ env.REPOSITORY }}
|
||||
git reset --hard HEAD
|
||||
git remote set-url origin $url
|
||||
git fetch origin
|
||||
git checkout ${{ env.TAG }}
|
||||
cd ${{ env.REPOSITORY }}
|
||||
git reset --hard HEAD
|
||||
git remote set-url origin $url
|
||||
git fetch origin
|
||||
git checkout ${{ env.TAG }}
|
||||
|
||||
rm -f .env
|
||||
echo "_APP_VERSION=${{ env.TAG }}" >> .env
|
||||
echo "_APP_DOMAIN=${{ secrets.STG_APP_DOMAIN }}" >> .env
|
||||
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env
|
||||
echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env
|
||||
rm -f .env
|
||||
echo "_APP_VERSION=${{ env.TAG }}" >> .env
|
||||
echo "_APP_DOMAIN=${{ secrets.STG_APP_DOMAIN }}" >> .env
|
||||
echo "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=${{ secrets.APP_SYSTEM_SECURITY_EMAIL_ADDRESS }}" >> .env
|
||||
echo "SEMATEXT_TOKEN=${{ secrets.SEMATEXT_TOKEN }}" >> .env
|
||||
|
||||
echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ env.REGISTRY_USERNAME }} --password-stdin
|
||||
docker-compose -f ${{ env.STACK_FILE }} config
|
||||
env $(cat .env | xargs) docker stack deploy --prune --resolve-image always --with-registry-auth -c ${{ env.STACK_FILE }} ${{ env.REPOSITORY }}
|
||||
echo ${{ secrets.GH_REGISTRY_TOKEN }} | docker login ghcr.io --username ${{ env.REGISTRY_USERNAME }} --password-stdin
|
||||
docker-compose -f ${{ env.STACK_FILE }} config
|
||||
env $(cat .env | xargs) docker stack deploy --prune --resolve-image always --with-registry-auth -c ${{ env.STACK_FILE }} ${{ env.REPOSITORY }}
|
||||
|
||||
34
.github/workflows/stale.yml
vendored
34
.github/workflows/stale.yml
vendored
@@ -1,23 +1,23 @@
|
||||
name: Mark stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Midnight Runtime
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Midnight Runtime
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue has been labeled as a 'question', indicating that it requires additional information from the requestor. It has been inactive for 7 days. If no further activity occurs, this issue will be closed in 14 days."
|
||||
stale-issue-label: "stale"
|
||||
days-before-stale: 7
|
||||
days-before-close: 14
|
||||
remove-stale-when-updated: true
|
||||
close-issue-message: "This issue has been closed due to inactivity. If you still require assistance, please provide the requested information."
|
||||
close-issue-reason: "not_planned"
|
||||
operations-per-run: 100
|
||||
only-labels: "question"
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue has been labeled as a 'question', indicating that it requires additional information from the requestor. It has been inactive for 7 days. If no further activity occurs, this issue will be closed in 14 days."
|
||||
stale-issue-label: 'stale'
|
||||
days-before-stale: 7
|
||||
days-before-close: 14
|
||||
remove-stale-when-updated: true
|
||||
close-issue-message: 'This issue has been closed due to inactivity. If you still require assistance, please provide the requested information.'
|
||||
close-issue-reason: 'not_planned'
|
||||
operations-per-run: 100
|
||||
only-labels: 'question'
|
||||
|
||||
84
.github/workflows/tests.yml
vendored
84
.github/workflows/tests.yml
vendored
@@ -1,50 +1,50 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: ["**"]
|
||||
pull_request_target:
|
||||
branches: ['**']
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install pnpm
|
||||
run: corepack enable
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Install pnpm
|
||||
run: corepack enable
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build Website
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=8192"
|
||||
PUBLIC_APPWRITE_PROJECT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_ID }}
|
||||
PUBLIC_APPWRITE_DB_MAIN_ID: ${{ secrets.PUBLIC_APPWRITE_DB_MAIN_ID }}
|
||||
PUBLIC_APPWRITE_COL_THREADS_ID: ${{ secrets.PUBLIC_APPWRITE_COL_THREADS_ID }}
|
||||
PUBLIC_APPWRITE_COL_MESSAGES_ID: ${{ secrets.PUBLIC_APPWRITE_COL_MESSAGES_ID }}
|
||||
PUBLIC_APPWRITE_FN_TLDR_ID: ${{ secrets.PUBLIC_APPWRITE_FN_TLDR_ID }}
|
||||
PUBLIC_APPWRITE_PROJECT_INIT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_INIT_ID }}
|
||||
PUBLIC_GROWTH_ENDPOINT: ${{ secrets.PUBLIC_GROWTH_ENDPOINT }}
|
||||
APPWRITE_DB_INIT_ID: ${{ secrets.APPWRITE_DB_INIT_ID }}
|
||||
APPWRITE_COL_INIT_ID: ${{ secrets.APPWRITE_COL_INIT_ID }}
|
||||
APPWRITE_API_KEY_INIT: ${{ secrets.APPWRITE_API_KEY_INIT }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run build
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build Website
|
||||
env:
|
||||
NODE_OPTIONS: '--max_old_space_size=8192'
|
||||
PUBLIC_APPWRITE_PROJECT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_ID }}
|
||||
PUBLIC_APPWRITE_DB_MAIN_ID: ${{ secrets.PUBLIC_APPWRITE_DB_MAIN_ID }}
|
||||
PUBLIC_APPWRITE_COL_THREADS_ID: ${{ secrets.PUBLIC_APPWRITE_COL_THREADS_ID }}
|
||||
PUBLIC_APPWRITE_COL_MESSAGES_ID: ${{ secrets.PUBLIC_APPWRITE_COL_MESSAGES_ID }}
|
||||
PUBLIC_APPWRITE_FN_TLDR_ID: ${{ secrets.PUBLIC_APPWRITE_FN_TLDR_ID }}
|
||||
PUBLIC_APPWRITE_PROJECT_INIT_ID: ${{ secrets.PUBLIC_APPWRITE_PROJECT_INIT_ID }}
|
||||
PUBLIC_GROWTH_ENDPOINT: ${{ secrets.PUBLIC_GROWTH_ENDPOINT }}
|
||||
APPWRITE_DB_INIT_ID: ${{ secrets.APPWRITE_DB_INIT_ID }}
|
||||
APPWRITE_COL_INIT_ID: ${{ secrets.APPWRITE_COL_INIT_ID }}
|
||||
APPWRITE_API_KEY_INIT: ${{ secrets.APPWRITE_API_KEY_INIT }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run build
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
# Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
|
||||
|
||||
tasks:
|
||||
- init: pnpm install && pnpm run build
|
||||
command: pnpm run dev
|
||||
- init: pnpm install && pnpm run build
|
||||
command: pnpm run dev
|
||||
|
||||
16
.prettierrc
16
.prettierrc
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"useTabs": false,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
"useTabs": false,
|
||||
"tabWidth": 4,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-svelte"],
|
||||
"pluginSearchDirs": ["."],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
|
||||
20
CONTENT.md
20
CONTENT.md
@@ -49,9 +49,9 @@ Create ordered (numbered) and unordered (bulleted) lists using 1., \*, or -.
|
||||
**Unordered List**:
|
||||
|
||||
```md
|
||||
- Apple
|
||||
- Banana
|
||||
- Cherry
|
||||
- Apple
|
||||
- Banana
|
||||
- Cherry
|
||||
```
|
||||
|
||||
#### Links
|
||||
@@ -130,19 +130,19 @@ Alternatively, use markdoc tables.
|
||||
```md
|
||||
{% table %}
|
||||
|
||||
- Heading 1
|
||||
- Heading 2
|
||||
- Heading 1
|
||||
- Heading 2
|
||||
|
||||
---
|
||||
|
||||
- Row 1 Cell 1
|
||||
- Row 1 Cell 2
|
||||
- Row 1 Cell 1
|
||||
- Row 1 Cell 2
|
||||
|
||||
---
|
||||
|
||||
- Row 2 Cell 1
|
||||
- Row 2 cell 2
|
||||
{% /table %}
|
||||
- Row 2 Cell 1
|
||||
- Row 2 cell 2
|
||||
{% /table %}
|
||||
```
|
||||
|
||||
#### Block Quotes
|
||||
|
||||
@@ -60,11 +60,11 @@ doc-548-submit-a-pull-request-section-to-contribution-guide
|
||||
|
||||
When `TYPE` can be:
|
||||
|
||||
- **feat** - is a new feature
|
||||
- **doc** - documentation only changes
|
||||
- **cicd** - changes related to CI/CD system
|
||||
- **fix** - a bug fix
|
||||
- **refactor** - code change that neither fixes a bug nor adds a feature
|
||||
- **feat** - is a new feature
|
||||
- **doc** - documentation only changes
|
||||
- **cicd** - changes related to CI/CD system
|
||||
- **fix** - a bug fix
|
||||
- **refactor** - code change that neither fixes a bug nor adds a feature
|
||||
|
||||
**All PRs must include a commit message with a description of the changes made!**
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ The Appwrite Website repo features the main Appwrite website, including our [hom
|
||||
|
||||
The Appwrite Website has been built with the following frameworks:
|
||||
|
||||
- [Svelte](https://svelte.dev/)
|
||||
- [SvelteKit](https://kit.svelte.dev/)
|
||||
- [Svelte](https://svelte.dev/)
|
||||
- [SvelteKit](https://kit.svelte.dev/)
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
192
STYLE.md
192
STYLE.md
@@ -6,17 +6,17 @@ Read this document carefully before making PRs to the Appwrite Website repo.
|
||||
|
||||
The Appwrite documentation is meant to provide general guidance that's:
|
||||
|
||||
- Unopinionated
|
||||
- Focused on the correct use of Appwrite product
|
||||
- Includes examples for all relevant and applicable SDKs
|
||||
- Agnostic to the user's implementation and stack.
|
||||
- Unopinionated
|
||||
- Focused on the correct use of Appwrite product
|
||||
- Includes examples for all relevant and applicable SDKs
|
||||
- Agnostic to the user's implementation and stack.
|
||||
|
||||
Examples of things not fit for docs, and better as a blog or video:
|
||||
|
||||
- General programming advice
|
||||
- Opinionated implementation patterns like MVVM, factory methods, etc.
|
||||
- Examples that only include a select subset of Appwrite SDKs.
|
||||
- Examples that do not work for all developers using Appwrite, but specific to Appwrite + technology.
|
||||
- General programming advice
|
||||
- Opinionated implementation patterns like MVVM, factory methods, etc.
|
||||
- Examples that only include a select subset of Appwrite SDKs.
|
||||
- Examples that do not work for all developers using Appwrite, but specific to Appwrite + technology.
|
||||
|
||||
Note that the tutorials and blogs available on the Appwrite blog and docs are meant for these types of information.
|
||||
|
||||
@@ -28,39 +28,39 @@ Appwrite's navigation increases in complexity from top down. We expect users to
|
||||
|
||||
Introduction Section:
|
||||
|
||||
- [Homes](https://appwrite.io/docs)
|
||||
- [Quick start](https://appwrite.io/docs/quick-start)
|
||||
- [Tutorial](https://appwrite.io/docs/tutorial)
|
||||
- [SDKs](https://appwrite.io/docs/sdks)
|
||||
- [API references](https://appwrite.io/docs/references)
|
||||
- [Homes](https://appwrite.io/docs)
|
||||
- [Quick start](https://appwrite.io/docs/quick-start)
|
||||
- [Tutorial](https://appwrite.io/docs/tutorial)
|
||||
- [SDKs](https://appwrite.io/docs/sdks)
|
||||
- [API references](https://appwrite.io/docs/references)
|
||||
|
||||
Products section:
|
||||
|
||||
- [Auth](https://appwrite.io/docs/products/auth)
|
||||
- [Databases](https://appwrite.io/docs/products/databases)
|
||||
- [Functions](https://appwrite.io/docs/products/functions)
|
||||
- [Storage](https://appwrite.io/docs/products/storage)
|
||||
- [Messaging](https://appwrite.io/docs/products/messaging)
|
||||
- [AI](https://appwrite.io/docs/products/ai)
|
||||
- [Auth](https://appwrite.io/docs/products/auth)
|
||||
- [Databases](https://appwrite.io/docs/products/databases)
|
||||
- [Functions](https://appwrite.io/docs/products/functions)
|
||||
- [Storage](https://appwrite.io/docs/products/storage)
|
||||
- [Messaging](https://appwrite.io/docs/products/messaging)
|
||||
- [AI](https://appwrite.io/docs/products/ai)
|
||||
|
||||
APIs section:
|
||||
|
||||
- [GraphQL](https://appwrite.io/docs/apis/graphql)
|
||||
- [REST](https://appwrite.io/docs/apis/rest)
|
||||
- [Realtime](https://appwrite.io/docs/apis/realtime)
|
||||
- [GraphQL](https://appwrite.io/docs/apis/graphql)
|
||||
- [REST](https://appwrite.io/docs/apis/rest)
|
||||
- [Realtime](https://appwrite.io/docs/apis/realtime)
|
||||
|
||||
Tooling section:
|
||||
|
||||
- [CLI](https://appwrite.io/docs/command-line)
|
||||
- [Command center](https://appwrite.io/docs/tooling/command-center)
|
||||
- [Assistant](https://appwrite.io/docs/tooling/assistant)
|
||||
- [CLI](https://appwrite.io/docs/command-line)
|
||||
- [Command center](https://appwrite.io/docs/tooling/command-center)
|
||||
- [Assistant](https://appwrite.io/docs/tooling/assistant)
|
||||
|
||||
Advanced section:
|
||||
|
||||
- [Platform](https://appwrite.io/docs/advanced/platform)
|
||||
- [Migrations](https://appwrite.io/docs/advanced/migrations)
|
||||
- [Self-hosting](https://appwrite.io/docs/advanced/self-hosting)
|
||||
- [Security](https://appwrite.io/docs/advanced/security)
|
||||
- [Platform](https://appwrite.io/docs/advanced/platform)
|
||||
- [Migrations](https://appwrite.io/docs/advanced/migrations)
|
||||
- [Self-hosting](https://appwrite.io/docs/advanced/self-hosting)
|
||||
- [Security](https://appwrite.io/docs/advanced/security)
|
||||
|
||||
Here's the intended purpose and structure of each section.
|
||||
|
||||
@@ -70,10 +70,10 @@ This section is focused on introducing what Appwrite is and giving examples to t
|
||||
Documentation here is focused on a **single flow** which means a single platform/framework + Appwrite.
|
||||
Content here is not specific to a specific product, but usually covers multiple Appwrite products.
|
||||
|
||||
- If your tutorial can be followed in about 15 minutes and fits on one page, write it under quick start
|
||||
- If you're writing a long piece of documentation that integrates Appwrite with another technology, with lots of details that's opinionated or isn't relevant for all use cases, write it under tutorial. This is similar to "cook book" at other organizations.
|
||||
- If you have information like helpers and methods that are only on SDKs but not the API, they go under SDK
|
||||
- API references are generated from source from the appwrite/appwrite repo
|
||||
- If your tutorial can be followed in about 15 minutes and fits on one page, write it under quick start
|
||||
- If you're writing a long piece of documentation that integrates Appwrite with another technology, with lots of details that's opinionated or isn't relevant for all use cases, write it under tutorial. This is similar to "cook book" at other organizations.
|
||||
- If you have information like helpers and methods that are only on SDKs but not the API, they go under SDK
|
||||
- API references are generated from source from the appwrite/appwrite repo
|
||||
|
||||
### Products
|
||||
|
||||
@@ -82,17 +82,17 @@ Code examples should cover **all available SDKs**.
|
||||
|
||||
Each product page has three main sections
|
||||
|
||||
- Introduction
|
||||
- Overview - Describes at a high level, why you might need this product
|
||||
- Quick start - Shows the most basic and quickest example to make something happen with a product. Keep it really short.
|
||||
- Concept
|
||||
- These pages usually align with sections shown in the product in the Appwrite Console.
|
||||
- Focused on describing concepts a user should know, but not actions you might take.
|
||||
- Cover all the details
|
||||
- Journeys
|
||||
- These pages focus on common actions and work flows
|
||||
- Detailed examples that span many concepts
|
||||
- Like cookbook at other organizations' documentation.
|
||||
- Introduction
|
||||
- Overview - Describes at a high level, why you might need this product
|
||||
- Quick start - Shows the most basic and quickest example to make something happen with a product. Keep it really short.
|
||||
- Concept
|
||||
- These pages usually align with sections shown in the product in the Appwrite Console.
|
||||
- Focused on describing concepts a user should know, but not actions you might take.
|
||||
- Cover all the details
|
||||
- Journeys
|
||||
- These pages focus on common actions and work flows
|
||||
- Detailed examples that span many concepts
|
||||
- Like cookbook at other organizations' documentation.
|
||||
|
||||
### APIs section
|
||||
|
||||
@@ -106,10 +106,10 @@ Describes tools that help you work with Appwrite, but are usually non-essential
|
||||
|
||||
For information that's not used commonly during the development cycle.
|
||||
|
||||
- Platform: covers concepts that apply to the entire Appwrite Cloud platform, like API keys, rate limits, etc.
|
||||
- Migrations: covers migrations feature of Appwrite that helps you move data around.
|
||||
- Security: purely information about measures Appwrite use to ensure security of the platform and data.
|
||||
- Self-hosting: The Appwrite self-hosted platform is meant to behave identically to Cloud after being configured corrrectly. This section focuses on how to configure Appwrite self-hosted such that it behaves like Cloud.
|
||||
- Platform: covers concepts that apply to the entire Appwrite Cloud platform, like API keys, rate limits, etc.
|
||||
- Migrations: covers migrations feature of Appwrite that helps you move data around.
|
||||
- Security: purely information about measures Appwrite use to ensure security of the platform and data.
|
||||
- Self-hosting: The Appwrite self-hosted platform is meant to behave identically to Cloud after being configured corrrectly. This section focuses on how to configure Appwrite self-hosted such that it behaves like Cloud.
|
||||
|
||||
## Documentation sources
|
||||
|
||||
@@ -117,22 +117,22 @@ The Appwrite docs are compiled from different repositories. Here are the signifi
|
||||
|
||||
[appwrite/website](https://github.com/appwrite/website):
|
||||
|
||||
- Tutorials
|
||||
- Quick starts
|
||||
- Product, API, Tooling and Advanced sections
|
||||
- Tutorials
|
||||
- Quick starts
|
||||
- Product, API, Tooling and Advanced sections
|
||||
|
||||
[appwrite/appwrite](https://github.com/appwrite/appwrite):
|
||||
|
||||
- [API Reference](https://appwrite.io/docs/references) pages
|
||||
- API specification
|
||||
- API description
|
||||
- API endpoint description
|
||||
- API request parameters
|
||||
- API response model
|
||||
- [API Reference](https://appwrite.io/docs/references) pages
|
||||
- API specification
|
||||
- API description
|
||||
- API endpoint description
|
||||
- API request parameters
|
||||
- API response model
|
||||
|
||||
[appwrite/sdk-generator](https://github.com/appwrite/sdk-generator):
|
||||
|
||||
- Generated examples
|
||||
- Generated examples
|
||||
|
||||
## Markdown Style guidelines
|
||||
|
||||
@@ -141,10 +141,10 @@ the tone and voice remains consistent.
|
||||
|
||||
### Headings
|
||||
|
||||
- All titles, headings, buttons, and labels should be written in **sentence case**. If you're not sure what sentence case should look like, check [APA's style guide](https://apastyle.apa.org/style-grammar-guidelines/capitalization/sentence-case) or check with ChatGPT and other LLMs which reliably converts titles to sentence case.
|
||||
- All headings in a docs page begin with `# Heading` then `## Heading` and `### Heading`. Internally, they're converted to H2 to H4 tags.
|
||||
- All headings should have an ID label, for example `# Cool heading {% #cool-heading %}` the `#cool-heading` ID will be used to generate the table of contents and add links to the heading.
|
||||
- Prefer verbs over gerunds, for example, say "Create documents" not "Creating documents".
|
||||
- All titles, headings, buttons, and labels should be written in **sentence case**. If you're not sure what sentence case should look like, check [APA's style guide](https://apastyle.apa.org/style-grammar-guidelines/capitalization/sentence-case) or check with ChatGPT and other LLMs which reliably converts titles to sentence case.
|
||||
- All headings in a docs page begin with `# Heading` then `## Heading` and `### Heading`. Internally, they're converted to H2 to H4 tags.
|
||||
- All headings should have an ID label, for example `# Cool heading {% #cool-heading %}` the `#cool-heading` ID will be used to generate the table of contents and add links to the heading.
|
||||
- Prefer verbs over gerunds, for example, say "Create documents" not "Creating documents".
|
||||
|
||||
### Extended Markdoc components
|
||||
|
||||
@@ -152,14 +152,14 @@ Appwrite's documentation uses extended markdown syntax. You can find all of the
|
||||
|
||||
### Screenshots
|
||||
|
||||
- When contributing upload original screenshots. The Appwrite design team will edit the screenshot to be consistent with other screenshots in the docs.
|
||||
- Screenshots must be 16:9
|
||||
- Screnshots should be taken in a 1400 x 900 view port on 3x DPR in browser developer tools.
|
||||
- Use generic and sensible organization, project, and resource names. Avoid names like `test`, `demo`, or `sdlkfj`.
|
||||
- All screenshot should be take from a user named Walter O'Brien. You can change the name of your current user by going to your Appwrite Console and clicking the **top right profile icon** > **Your Account** > **Name**.
|
||||
- Screenshots are stored in the `/images/docs/` folder, in a parent folder that is consistent with the path of the docs that reference the image.
|
||||
- All screenshots must be both dark and light mode, with `/path/` holding the lightmode version and `/path/dark/` holding the dark mode version.
|
||||
- Screenshots should be uploaded as un-edited original. Request help from the Appwrite design team to help you edit and refine your photos according to our guidelines.
|
||||
- When contributing upload original screenshots. The Appwrite design team will edit the screenshot to be consistent with other screenshots in the docs.
|
||||
- Screenshots must be 16:9
|
||||
- Screnshots should be taken in a 1400 x 900 view port on 3x DPR in browser developer tools.
|
||||
- Use generic and sensible organization, project, and resource names. Avoid names like `test`, `demo`, or `sdlkfj`.
|
||||
- All screenshot should be take from a user named Walter O'Brien. You can change the name of your current user by going to your Appwrite Console and clicking the **top right profile icon** > **Your Account** > **Name**.
|
||||
- Screenshots are stored in the `/images/docs/` folder, in a parent folder that is consistent with the path of the docs that reference the image.
|
||||
- All screenshots must be both dark and light mode, with `/path/` holding the lightmode version and `/path/dark/` holding the dark mode version.
|
||||
- Screenshots should be uploaded as un-edited original. Request help from the Appwrite design team to help you edit and refine your photos according to our guidelines.
|
||||
|
||||
```md
|
||||
{% only_dark %}
|
||||
@@ -210,41 +210,41 @@ Split content such that each piece makes sense without reading dependents or exp
|
||||
|
||||
### Release prep
|
||||
|
||||
- [ ] Add new version to [src/lib/utils/references.ts](src/lib/utils/references.ts)
|
||||
- [ ] Point Cloud to new version in [src/routes/docs/references/[version]/[platform]/[service]/+page.server.ts](src/routes/docs/references/[version]/[platform]/[service]/+page.server.ts)
|
||||
- [ ] Update install command in [/workspaces/website/src/routes/docs/advanced/self-hosting/+page.markdoc](/workspaces/website/src/routes/docs/advanced/self-hosting/+page.markdoc)
|
||||
- [ ] Update events [src/partials/[product]-events.md](src/partials/)
|
||||
- [ ] Update response code [src/routes/docs/advanced/platform/response-codes/+page.markdoc](src/routes/docs/advanced/platform/response-codes/+page.markdoc)
|
||||
- [ ] Bump latest SDK versions in SDKs page, quick start, and tutorials
|
||||
- [ ] Create new sections for new products
|
||||
- [ ] Create new concept and journey pages for new features
|
||||
- [ ] Update docs for breaking changes
|
||||
- [ ] Add new version to [src/lib/utils/references.ts](src/lib/utils/references.ts)
|
||||
- [ ] Point Cloud to new version in [src/routes/docs/references/[version]/[platform]/[service]/+page.server.ts](src/routes/docs/references/[version]/[platform]/[service]/+page.server.ts)
|
||||
- [ ] Update install command in [/workspaces/website/src/routes/docs/advanced/self-hosting/+page.markdoc](/workspaces/website/src/routes/docs/advanced/self-hosting/+page.markdoc)
|
||||
- [ ] Update events [src/partials/[product]-events.md](src/partials/)
|
||||
- [ ] Update response code [src/routes/docs/advanced/platform/response-codes/+page.markdoc](src/routes/docs/advanced/platform/response-codes/+page.markdoc)
|
||||
- [ ] Bump latest SDK versions in SDKs page, quick start, and tutorials
|
||||
- [ ] Create new sections for new products
|
||||
- [ ] Create new concept and journey pages for new features
|
||||
- [ ] Update docs for breaking changes
|
||||
|
||||
### Documenting a new API
|
||||
|
||||
- Add a new .md file describing the new API here: <https://github.com/appwrite/appwrite/tree/main/docs/references>
|
||||
- Add descriptions for methods and parameters in the controller code: <https://github.com/appwrite/appwrite/tree/main/app/controllers/api>
|
||||
- Check new response models have meaningful descriptions
|
||||
- Add a new .md file describing the new API here: <https://github.com/appwrite/appwrite/tree/main/docs/references>
|
||||
- Add descriptions for methods and parameters in the controller code: <https://github.com/appwrite/appwrite/tree/main/app/controllers/api>
|
||||
- Check new response models have meaningful descriptions
|
||||
|
||||
### Adding a new quickstart
|
||||
|
||||
- Copy a quick start from the [src/routes/docs/quick-starts](src/routes/docs/quick-starts) folder.
|
||||
- Add a new entry and logo to [src/routes/docs/quick-starts/+page.svelte](src/routes/docs/quick-starts/+page.svelte)
|
||||
- If you need a new logo, contact the Appwrite team to add one to Pink design.
|
||||
- Update the content of your tutorial. Remember to update the front matter!
|
||||
- Try to be consistent in both the quickstart's content and format when compared to existing quick starts
|
||||
- Add the quick start to the footer and front page of Appwrite
|
||||
- Use sections for steps on your page
|
||||
- Copy a quick start from the [src/routes/docs/quick-starts](src/routes/docs/quick-starts) folder.
|
||||
- Add a new entry and logo to [src/routes/docs/quick-starts/+page.svelte](src/routes/docs/quick-starts/+page.svelte)
|
||||
- If you need a new logo, contact the Appwrite team to add one to Pink design.
|
||||
- Update the content of your tutorial. Remember to update the front matter!
|
||||
- Try to be consistent in both the quickstart's content and format when compared to existing quick starts
|
||||
- Add the quick start to the footer and front page of Appwrite
|
||||
- Use sections for steps on your page
|
||||
|
||||
### Adding a new tutorial
|
||||
|
||||
- Copy a tutorial from the [src/routes/docs/tutorials](src/routes/docs/tutorials) folder.
|
||||
- Update the `+page.ts`'s redirect, for example, the Android tutorial has this: [src/routes/docs/tutorials/android/+page.ts](src/routes/docs/tutorials/android/+page.ts)
|
||||
- Update [src/routes/docs/tutorials/+page.svelte](src/routes/docs/tutorials/+page.svelte) and add your new tutorial
|
||||
- Update [src/routes/docs/tutorials/android/+layout.ts](src/routes/docs/tutorials/android/+layout.ts) and add your new tutorial
|
||||
- Add the content of your tutorial. Keep pages short, separated by a different distinct feature for each step.
|
||||
- If you need a new logo, contact the Appwrite team to add one to Pink design.
|
||||
- Add the tutorial to the footer and front page of Appwrite
|
||||
- Copy a tutorial from the [src/routes/docs/tutorials](src/routes/docs/tutorials) folder.
|
||||
- Update the `+page.ts`'s redirect, for example, the Android tutorial has this: [src/routes/docs/tutorials/android/+page.ts](src/routes/docs/tutorials/android/+page.ts)
|
||||
- Update [src/routes/docs/tutorials/+page.svelte](src/routes/docs/tutorials/+page.svelte) and add your new tutorial
|
||||
- Update [src/routes/docs/tutorials/android/+layout.ts](src/routes/docs/tutorials/android/+layout.ts) and add your new tutorial
|
||||
- Add the content of your tutorial. Keep pages short, separated by a different distinct feature for each step.
|
||||
- If you need a new logo, contact the Appwrite team to add one to Pink design.
|
||||
- Add the tutorial to the footer and front page of Appwrite
|
||||
|
||||
## Language and diction
|
||||
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:2.9
|
||||
command:
|
||||
- --log.level=DEBUG
|
||||
- --api.insecure=true
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedByDefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`homepage`)
|
||||
- --accesslog=true
|
||||
labels:
|
||||
- traefik.http.routers.traefik.middlewares=traefik-compress
|
||||
- traefik.http.middlewares.traefik-compress.compress=true
|
||||
ports:
|
||||
- 80:80
|
||||
- 8080:8080
|
||||
volumes:
|
||||
# - /letsencrypt:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- homepage
|
||||
traefik:
|
||||
image: traefik:2.9
|
||||
command:
|
||||
- --log.level=DEBUG
|
||||
- --api.insecure=true
|
||||
- --providers.docker=true
|
||||
- --providers.docker.exposedByDefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`homepage`)
|
||||
- --accesslog=true
|
||||
labels:
|
||||
- traefik.http.routers.traefik.middlewares=traefik-compress
|
||||
- traefik.http.middlewares.traefik-compress.compress=true
|
||||
ports:
|
||||
- 80:80
|
||||
- 8080:8080
|
||||
volumes:
|
||||
# - /letsencrypt:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- homepage
|
||||
|
||||
homepage:
|
||||
image: homepage-dev
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
- PUBLIC_APPWRITE_ENDPOINT=$PUBLIC_APPWRITE_ENDPOINT
|
||||
- PUBLIC_APPWRITE_DASHBOARD=$PUBLIC_APPWRITE_DASHBOARD
|
||||
- PUBLIC_APPWRITE_PROJECT_INIT_ID=$PUBLIC_APPWRITE_PROJECT_INIT_ID
|
||||
- PUBLIC_APPWRITE_PROJECT_ID=$PUBLIC_APPWRITE_PROJECT_ID
|
||||
- PUBLIC_APPWRITE_DB_MAIN_ID=$PUBLIC_APPWRITE_DB_MAIN_ID
|
||||
- PUBLIC_APPWRITE_COL_THREADS_ID=$PUBLIC_APPWRITE_COL_THREADS_ID
|
||||
- PUBLIC_APPWRITE_COL_MESSAGES_ID=$PUBLIC_APPWRITE_COL_MESSAGES_ID
|
||||
- PUBLIC_APPWRITE_FN_TLDR_ID=$PUBLIC_APPWRITE_FN_TLDR_ID
|
||||
restart: always
|
||||
networks:
|
||||
- homepage
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.constraint-label-stack=homepage
|
||||
- traefik.docker.network=appwrite
|
||||
- traefik.http.middlewares.appwrite_middlewares.compress=true
|
||||
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
|
||||
#http
|
||||
- traefik.http.routers.appwrite.entrypoints=web
|
||||
- traefik.http.routers.appwrite.rule=PathPrefix(`/`)
|
||||
- traefik.http.routers.appwrite.service=appwrite_service
|
||||
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
|
||||
# https
|
||||
- traefik.http.routers.appwrite_secure.entrypoints=websecure
|
||||
- traefik.http.routers.appwrite_secure.rule=PathPrefix(`/`)
|
||||
- traefik.http.routers.appwrite_secure.service=appwrite_service
|
||||
- traefik.http.routers.appwrite_secure.tls=true
|
||||
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
|
||||
homepage:
|
||||
image: homepage-dev
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
- PUBLIC_APPWRITE_ENDPOINT=$PUBLIC_APPWRITE_ENDPOINT
|
||||
- PUBLIC_APPWRITE_DASHBOARD=$PUBLIC_APPWRITE_DASHBOARD
|
||||
- PUBLIC_APPWRITE_PROJECT_INIT_ID=$PUBLIC_APPWRITE_PROJECT_INIT_ID
|
||||
- PUBLIC_APPWRITE_PROJECT_ID=$PUBLIC_APPWRITE_PROJECT_ID
|
||||
- PUBLIC_APPWRITE_DB_MAIN_ID=$PUBLIC_APPWRITE_DB_MAIN_ID
|
||||
- PUBLIC_APPWRITE_COL_THREADS_ID=$PUBLIC_APPWRITE_COL_THREADS_ID
|
||||
- PUBLIC_APPWRITE_COL_MESSAGES_ID=$PUBLIC_APPWRITE_COL_MESSAGES_ID
|
||||
- PUBLIC_APPWRITE_FN_TLDR_ID=$PUBLIC_APPWRITE_FN_TLDR_ID
|
||||
restart: always
|
||||
networks:
|
||||
- homepage
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.constraint-label-stack=homepage
|
||||
- traefik.docker.network=appwrite
|
||||
- traefik.http.middlewares.appwrite_middlewares.compress=true
|
||||
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
|
||||
#http
|
||||
- traefik.http.routers.appwrite.entrypoints=web
|
||||
- traefik.http.routers.appwrite.rule=PathPrefix(`/`)
|
||||
- traefik.http.routers.appwrite.service=appwrite_service
|
||||
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
|
||||
# https
|
||||
- traefik.http.routers.appwrite_secure.entrypoints=websecure
|
||||
- traefik.http.routers.appwrite_secure.rule=PathPrefix(`/`)
|
||||
- traefik.http.routers.appwrite_secure.service=appwrite_service
|
||||
- traefik.http.routers.appwrite_secure.tls=true
|
||||
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
|
||||
|
||||
networks:
|
||||
homepage:
|
||||
homepage:
|
||||
|
||||
@@ -1,135 +1,135 @@
|
||||
x-logging: &x-logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-file: "5"
|
||||
max-size: "20m"
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-file: '5'
|
||||
max-size: '20m'
|
||||
|
||||
x-update-config: &x-update-config
|
||||
update_config:
|
||||
order: stop-first
|
||||
failure_action: rollback
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
rollback_config:
|
||||
failure_action: pause
|
||||
monitor: 5s
|
||||
parallelism: 2
|
||||
order: stop-first
|
||||
update_config:
|
||||
order: stop-first
|
||||
failure_action: rollback
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
rollback_config:
|
||||
failure_action: pause
|
||||
monitor: 5s
|
||||
parallelism: 2
|
||||
order: stop-first
|
||||
|
||||
version: "3.8"
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:2.9
|
||||
<<: *x-logging
|
||||
command:
|
||||
- --log.level=DEBUG
|
||||
- --api.insecure=false
|
||||
- --providers.docker=true
|
||||
- --providers.docker.watch=true
|
||||
- --providers.docker.swarmMode=true
|
||||
- --providers.docker.exposedByDefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge=true
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
|
||||
- --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json
|
||||
- --accesslog=true
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- /letsencrypt:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- cloud
|
||||
deploy:
|
||||
replicas: 3
|
||||
<<: *x-update-config
|
||||
placement:
|
||||
max_replicas_per_node: 1
|
||||
constraints:
|
||||
- node.role == manager
|
||||
preferences:
|
||||
- spread: node.role == worker
|
||||
labels:
|
||||
- traefik.http.routers.traefik.middlewares=traefik-compress
|
||||
- traefik.http.middlewares.traefik-compress.compress=true
|
||||
traefik:
|
||||
image: traefik:2.9
|
||||
<<: *x-logging
|
||||
command:
|
||||
- --log.level=DEBUG
|
||||
- --api.insecure=false
|
||||
- --providers.docker=true
|
||||
- --providers.docker.watch=true
|
||||
- --providers.docker.swarmMode=true
|
||||
- --providers.docker.exposedByDefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge=true
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
|
||||
- --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json
|
||||
- --accesslog=true
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- /letsencrypt:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- cloud
|
||||
deploy:
|
||||
replicas: 3
|
||||
<<: *x-update-config
|
||||
placement:
|
||||
max_replicas_per_node: 1
|
||||
constraints:
|
||||
- node.role == manager
|
||||
preferences:
|
||||
- spread: node.role == worker
|
||||
labels:
|
||||
- traefik.http.routers.traefik.middlewares=traefik-compress
|
||||
- traefik.http.middlewares.traefik-compress.compress=true
|
||||
|
||||
server:
|
||||
image: ghcr.io/appwrite/website:$_APP_VERSION
|
||||
<<: *x-logging
|
||||
networks:
|
||||
- cloud
|
||||
environment:
|
||||
- PUBLIC_APPWRITE_PROJECT_INIT_ID
|
||||
- PUBLIC_APPWRITE_PROJECT_ID
|
||||
- PUBLIC_APPWRITE_DB_MAIN_ID
|
||||
- PUBLIC_APPWRITE_COL_THREADS_ID
|
||||
- PUBLIC_APPWRITE_COL_MESSAGES_ID
|
||||
- PUBLIC_APPWRITE_FN_TLDR_ID
|
||||
deploy:
|
||||
<<: *x-update-config
|
||||
mode: replicated
|
||||
replicas: 8
|
||||
placement:
|
||||
max_replicas_per_node: 2
|
||||
constraints:
|
||||
- node.role == worker
|
||||
preferences:
|
||||
- spread: node.role == worker
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.lbswarm=true
|
||||
- traefik.constraint-label-stack=appwrite
|
||||
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
|
||||
- traefik.http.middlewares.appwrite_middlewares.compress=true
|
||||
#http
|
||||
- traefik.http.routers.appwrite.entrypoints=web
|
||||
- traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
|
||||
- traefik.http.routers.appwrite.service=appwrite_service
|
||||
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
|
||||
# https
|
||||
- traefik.http.routers.appwrite_secure.entrypoints=websecure
|
||||
- traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
|
||||
- traefik.http.routers.appwrite_secure.service=appwrite_service
|
||||
- traefik.http.routers.appwrite_secure.tls=true
|
||||
- traefik.http.routers.appwrite_secure.tls.certresolver=myresolver
|
||||
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
|
||||
server:
|
||||
image: ghcr.io/appwrite/website:$_APP_VERSION
|
||||
<<: *x-logging
|
||||
networks:
|
||||
- cloud
|
||||
environment:
|
||||
- PUBLIC_APPWRITE_PROJECT_INIT_ID
|
||||
- PUBLIC_APPWRITE_PROJECT_ID
|
||||
- PUBLIC_APPWRITE_DB_MAIN_ID
|
||||
- PUBLIC_APPWRITE_COL_THREADS_ID
|
||||
- PUBLIC_APPWRITE_COL_MESSAGES_ID
|
||||
- PUBLIC_APPWRITE_FN_TLDR_ID
|
||||
deploy:
|
||||
<<: *x-update-config
|
||||
mode: replicated
|
||||
replicas: 8
|
||||
placement:
|
||||
max_replicas_per_node: 2
|
||||
constraints:
|
||||
- node.role == worker
|
||||
preferences:
|
||||
- spread: node.role == worker
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.lbswarm=true
|
||||
- traefik.constraint-label-stack=appwrite
|
||||
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
|
||||
- traefik.http.middlewares.appwrite_middlewares.compress=true
|
||||
#http
|
||||
- traefik.http.routers.appwrite.entrypoints=web
|
||||
- traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
|
||||
- traefik.http.routers.appwrite.service=appwrite_service
|
||||
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
|
||||
# https
|
||||
- traefik.http.routers.appwrite_secure.entrypoints=websecure
|
||||
- traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
|
||||
- traefik.http.routers.appwrite_secure.service=appwrite_service
|
||||
- traefik.http.routers.appwrite_secure.tls=true
|
||||
- traefik.http.routers.appwrite_secure.tls.certresolver=myresolver
|
||||
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
|
||||
|
||||
janitor:
|
||||
image: appwrite/docker-janitor
|
||||
deploy:
|
||||
mode: global
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- TIME_BETWEEN_RUNS=3600
|
||||
- UNUSED_TIME=6h
|
||||
janitor:
|
||||
image: appwrite/docker-janitor
|
||||
deploy:
|
||||
mode: global
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- TIME_BETWEEN_RUNS=3600
|
||||
- UNUSED_TIME=6h
|
||||
|
||||
sematext-agent:
|
||||
image: sematext/agent:latest
|
||||
environment:
|
||||
REGION: EU
|
||||
INFRA_TOKEN: $SEMATEXT_TOKEN
|
||||
deploy:
|
||||
mode: global
|
||||
restart_policy:
|
||||
condition: any
|
||||
volumes:
|
||||
- /:/hostfs:ro
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /dev:/hostfs/dev:ro
|
||||
- /var/run:/var/run
|
||||
- /sys/kernel/debug:/sys/kernel/debug
|
||||
sematext-agent:
|
||||
image: sematext/agent:latest
|
||||
environment:
|
||||
REGION: EU
|
||||
INFRA_TOKEN: $SEMATEXT_TOKEN
|
||||
deploy:
|
||||
mode: global
|
||||
restart_policy:
|
||||
condition: any
|
||||
volumes:
|
||||
- /:/hostfs:ro
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /dev:/hostfs/dev:ro
|
||||
- /var/run:/var/run
|
||||
- /sys/kernel/debug:/sys/kernel/debug
|
||||
|
||||
networks:
|
||||
cloud:
|
||||
driver: overlay
|
||||
cloud:
|
||||
driver: overlay
|
||||
|
||||
252
docker/stage.yml
252
docker/stage.yml
@@ -1,137 +1,137 @@
|
||||
x-logging: &x-logging
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-file: "5"
|
||||
max-size: "20m"
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-file: '5'
|
||||
max-size: '20m'
|
||||
|
||||
x-update-config: &x-update-config
|
||||
update_config:
|
||||
order: stop-first
|
||||
failure_action: rollback
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
rollback_config:
|
||||
failure_action: pause
|
||||
monitor: 5s
|
||||
parallelism: 2
|
||||
order: stop-first
|
||||
update_config:
|
||||
order: stop-first
|
||||
failure_action: rollback
|
||||
parallelism: 1
|
||||
delay: 10s
|
||||
rollback_config:
|
||||
failure_action: pause
|
||||
monitor: 5s
|
||||
parallelism: 2
|
||||
order: stop-first
|
||||
|
||||
version: "3.8"
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:2.9
|
||||
<<: *x-logging
|
||||
command:
|
||||
- --log.level=DEBUG
|
||||
- --api.insecure=true
|
||||
- --providers.docker=true
|
||||
- --providers.docker.watch=true
|
||||
- --providers.docker.swarmMode=true
|
||||
- --providers.docker.exposedByDefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge=true
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
|
||||
- --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json
|
||||
- --accesslog=true
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- /letsencrypt:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- cloud
|
||||
deploy:
|
||||
replicas: 3
|
||||
<<: *x-update-config
|
||||
placement:
|
||||
max_replicas_per_node: 1
|
||||
constraints:
|
||||
- node.role == manager
|
||||
preferences:
|
||||
- spread: node.role == worker
|
||||
labels:
|
||||
- traefik.http.routers.traefik.middlewares=traefik-compress
|
||||
- traefik.http.middlewares.traefik-compress.compress=true
|
||||
traefik:
|
||||
image: traefik:2.9
|
||||
<<: *x-logging
|
||||
command:
|
||||
- --log.level=DEBUG
|
||||
- --api.insecure=true
|
||||
- --providers.docker=true
|
||||
- --providers.docker.watch=true
|
||||
- --providers.docker.swarmMode=true
|
||||
- --providers.docker.exposedByDefault=false
|
||||
- --entrypoints.web.address=:80
|
||||
- --entrypoints.websecure.address=:443
|
||||
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entrypoint.scheme=https
|
||||
- --providers.docker.constraints=Label(`traefik.constraint-label-stack`,`appwrite`)
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge=true
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
|
||||
- --certificatesresolvers.myresolver.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/${_APP_DOMAIN}.json
|
||||
- --accesslog=true
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- /letsencrypt:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
networks:
|
||||
- cloud
|
||||
deploy:
|
||||
replicas: 3
|
||||
<<: *x-update-config
|
||||
placement:
|
||||
max_replicas_per_node: 1
|
||||
constraints:
|
||||
- node.role == manager
|
||||
preferences:
|
||||
- spread: node.role == worker
|
||||
labels:
|
||||
- traefik.http.routers.traefik.middlewares=traefik-compress
|
||||
- traefik.http.middlewares.traefik-compress.compress=true
|
||||
|
||||
server:
|
||||
image: ghcr.io/appwrite/website:$_APP_VERSION
|
||||
<<: *x-logging
|
||||
networks:
|
||||
- cloud
|
||||
environment:
|
||||
- PUBLIC_APPWRITE_PROJECT_INIT_ID
|
||||
- PUBLIC_APPWRITE_PROJECT_ID
|
||||
- PUBLIC_APPWRITE_DB_MAIN_ID
|
||||
- PUBLIC_APPWRITE_COL_THREADS_ID
|
||||
- PUBLIC_APPWRITE_COL_MESSAGES_ID
|
||||
- PUBLIC_APPWRITE_FN_TLDR_ID
|
||||
deploy:
|
||||
<<: *x-update-config
|
||||
mode: replicated
|
||||
replicas: 8
|
||||
placement:
|
||||
max_replicas_per_node: 2
|
||||
constraints:
|
||||
- node.role == worker
|
||||
preferences:
|
||||
- spread: node.role == worker
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.lbswarm=true
|
||||
- traefik.constraint-label-stack=appwrite
|
||||
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
|
||||
- traefik.http.middlewares.appwrite_middlewares.compress=true
|
||||
#http
|
||||
- traefik.http.routers.appwrite.entrypoints=web
|
||||
- traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
|
||||
- traefik.http.routers.appwrite.service=appwrite_service
|
||||
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
|
||||
# https
|
||||
- traefik.http.routers.appwrite_secure.entrypoints=websecure
|
||||
- traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
|
||||
- traefik.http.routers.appwrite_secure.service=appwrite_service
|
||||
- traefik.http.routers.appwrite_secure.tls=true
|
||||
- traefik.http.routers.appwrite_secure.tls.certresolver=myresolver
|
||||
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
|
||||
server:
|
||||
image: ghcr.io/appwrite/website:$_APP_VERSION
|
||||
<<: *x-logging
|
||||
networks:
|
||||
- cloud
|
||||
environment:
|
||||
- PUBLIC_APPWRITE_PROJECT_INIT_ID
|
||||
- PUBLIC_APPWRITE_PROJECT_ID
|
||||
- PUBLIC_APPWRITE_DB_MAIN_ID
|
||||
- PUBLIC_APPWRITE_COL_THREADS_ID
|
||||
- PUBLIC_APPWRITE_COL_MESSAGES_ID
|
||||
- PUBLIC_APPWRITE_FN_TLDR_ID
|
||||
deploy:
|
||||
<<: *x-update-config
|
||||
mode: replicated
|
||||
replicas: 8
|
||||
placement:
|
||||
max_replicas_per_node: 2
|
||||
constraints:
|
||||
- node.role == worker
|
||||
preferences:
|
||||
- spread: node.role == worker
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.docker.lbswarm=true
|
||||
- traefik.constraint-label-stack=appwrite
|
||||
- traefik.http.services.appwrite_service.loadbalancer.server.port=3000
|
||||
- traefik.http.middlewares.appwrite_middlewares.compress=true
|
||||
#http
|
||||
- traefik.http.routers.appwrite.entrypoints=web
|
||||
- traefik.http.routers.appwrite.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
|
||||
- traefik.http.routers.appwrite.service=appwrite_service
|
||||
- traefik.http.routers.appwrite.middlewares=appwrite_middlewares
|
||||
# https
|
||||
- traefik.http.routers.appwrite_secure.entrypoints=websecure
|
||||
- traefik.http.routers.appwrite_secure.rule=Host(`$_APP_DOMAIN`) || Host(`www.$_APP_DOMAIN`)
|
||||
- traefik.http.routers.appwrite_secure.service=appwrite_service
|
||||
- traefik.http.routers.appwrite_secure.tls=true
|
||||
- traefik.http.routers.appwrite_secure.tls.certresolver=myresolver
|
||||
- traefik.http.routers.appwrite_secure.middlewares=appwrite_middlewares
|
||||
|
||||
janitor:
|
||||
image: appwrite/docker-janitor
|
||||
deploy:
|
||||
mode: global
|
||||
environment:
|
||||
- TIME_BETWEEN_RUNS=600
|
||||
- UNUSED_TIME=10m
|
||||
- RUN_ON_STARTUP=true
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
janitor:
|
||||
image: appwrite/docker-janitor
|
||||
deploy:
|
||||
mode: global
|
||||
environment:
|
||||
- TIME_BETWEEN_RUNS=600
|
||||
- UNUSED_TIME=10m
|
||||
- RUN_ON_STARTUP=true
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
sematext-agent:
|
||||
image: sematext/agent:latest
|
||||
environment:
|
||||
REGION: EU
|
||||
INFRA_TOKEN: $SEMATEXT_TOKEN
|
||||
deploy:
|
||||
mode: global
|
||||
restart_policy:
|
||||
condition: any
|
||||
volumes:
|
||||
- /:/hostfs:ro
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /dev:/hostfs/dev:ro
|
||||
- /var/run:/var/run
|
||||
- /sys/kernel/debug:/sys/kernel/debug
|
||||
sematext-agent:
|
||||
image: sematext/agent:latest
|
||||
environment:
|
||||
REGION: EU
|
||||
INFRA_TOKEN: $SEMATEXT_TOKEN
|
||||
deploy:
|
||||
mode: global
|
||||
restart_policy:
|
||||
condition: any
|
||||
volumes:
|
||||
- /:/hostfs:ro
|
||||
- /etc/passwd:/etc/passwd:ro
|
||||
- /etc/group:/etc/group:ro
|
||||
- /sys:/host/sys:ro
|
||||
- /dev:/hostfs/dev:ro
|
||||
- /var/run:/var/run
|
||||
- /sys/kernel/debug:/sys/kernel/debug
|
||||
|
||||
networks:
|
||||
cloud:
|
||||
driver: overlay
|
||||
cloud:
|
||||
driver: overlay
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[
|
||||
{
|
||||
"id": "appwrite",
|
||||
"path": "src/",
|
||||
"schema": {
|
||||
"path": ".svelte-kit/markdoc_schema.js",
|
||||
"type": "esm",
|
||||
"property": "default",
|
||||
"watch": true
|
||||
{
|
||||
"id": "appwrite",
|
||||
"path": "src/",
|
||||
"schema": {
|
||||
"path": ".svelte-kit/markdoc_schema.js",
|
||||
"type": "esm",
|
||||
"property": "default",
|
||||
"watch": true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
178
package.json
178
package.json
@@ -1,94 +1,88 @@
|
||||
{
|
||||
"name": "appwrite-website",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node ./scripts/build.js",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i",
|
||||
"dev": "vite dev",
|
||||
"download-contributors": "node ./scripts/download-contributor-data.js",
|
||||
"format": "prettier --plugin-search-dir . --write .",
|
||||
"icons:build": "node ./src/icons/build.js",
|
||||
"icons:generate": "node ./src/icons/optimize.js && node ./src/icons/build.js",
|
||||
"icons:optimize": "node ./src/icons/optimize.js",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"preview": "vite preview",
|
||||
"test": "npm run test:integration && npm run test:unit",
|
||||
"test:integration": "playwright test",
|
||||
"test:unit": "vitest",
|
||||
"optimize": "node ./scripts/optimize-assets.js"
|
||||
},
|
||||
"packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a",
|
||||
"dependencies": {
|
||||
"@sentry/sveltekit": "^8.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cva": "npm:class-variance-authority@^0.7.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"h3": "^1.12.0",
|
||||
"sharp": "^0.33.4",
|
||||
"tailwind-merge": "^2.4.0"
|
||||
},
|
||||
"prettier": {
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte",
|
||||
"prettier-plugin-tailwindcss"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appwrite.io/console": "^0.6.2",
|
||||
"@appwrite.io/pink": "~0.26.0",
|
||||
"@appwrite.io/pink-icons": "~0.26.0",
|
||||
"@appwrite.io/repo": "github:appwrite/appwrite#1.6.x",
|
||||
"@internationalized/date": "3.5.0",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.74.4",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@sveltejs/adapter-node": "^4.0.1",
|
||||
"@sveltejs/enhanced-img": "^0.1.9",
|
||||
"@sveltejs/kit": "^2.5.17",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@tailwindcss/postcss": "4.0.0-alpha.17",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/markdown-it": "^13.0.8",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
||||
"@typescript-eslint/parser": "^7.13.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"dequal": "^2.0.3",
|
||||
"embla-carousel": "^8.1.5",
|
||||
"embla-carousel-svelte": "^8.1.5",
|
||||
"embla-carousel-wheel-gestures": "^8.0.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-svelte": "^2.40.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"meilisearch": "^0.37.0",
|
||||
"motion": "^10.18.0",
|
||||
"node-html-parser": "^6.1.13",
|
||||
"openapi-types": "^12.1.3",
|
||||
"oslllo-svg-fixer": "^3.0.0",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"remeda": "^2.10.0",
|
||||
"sass": "^1.77.6",
|
||||
"svelte": "^4.2.18",
|
||||
"svelte-check": "^3.8.1",
|
||||
"svelte-markdoc-preprocess": "^2.0.0",
|
||||
"svelte-markdown": "^0.4.1",
|
||||
"svgtofont": "^4.2.1",
|
||||
"tailwindcss": "4.0.0-alpha.17",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^5.5.2",
|
||||
"vite": "^5.3.1",
|
||||
"vite-plugin-dynamic-import": "^1.5.0",
|
||||
"vite-plugin-image-optimizer": "^1.1.8",
|
||||
"vitest": "^1.6.0"
|
||||
}
|
||||
"name": "appwrite-website",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "node ./scripts/build.js",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"clean": "rm -rf node_modules && rm -rf .svelte_kit && pnpm i",
|
||||
"dev": "vite dev",
|
||||
"download-contributors": "node ./scripts/download-contributor-data.js",
|
||||
"format": "prettier --plugin-search-dir . --write .",
|
||||
"icons:build": "node ./src/icons/build.js",
|
||||
"icons:generate": "node ./src/icons/optimize.js && node ./src/icons/build.js",
|
||||
"icons:optimize": "node ./src/icons/optimize.js",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"preview": "vite preview",
|
||||
"test": "npm run test:integration && npm run test:unit",
|
||||
"test:integration": "playwright test",
|
||||
"test:unit": "vitest",
|
||||
"optimize": "node ./scripts/optimize-assets.js"
|
||||
},
|
||||
"packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a",
|
||||
"dependencies": {
|
||||
"@sentry/sveltekit": "^8.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cva": "npm:class-variance-authority@^0.7.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"h3": "^1.12.0",
|
||||
"sharp": "^0.33.4",
|
||||
"tailwind-merge": "^2.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@appwrite.io/console": "^0.6.2",
|
||||
"@appwrite.io/pink": "~0.26.0",
|
||||
"@appwrite.io/pink-icons": "~0.26.0",
|
||||
"@appwrite.io/repo": "github:appwrite/appwrite#1.6.x",
|
||||
"@internationalized/date": "3.5.0",
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.74.4",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@sveltejs/adapter-node": "^4.0.1",
|
||||
"@sveltejs/enhanced-img": "^0.1.9",
|
||||
"@sveltejs/kit": "^2.5.17",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@tailwindcss/postcss": "4.0.0-alpha.17",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/markdown-it": "^13.0.8",
|
||||
"@types/morgan": "^1.9.9",
|
||||
"@typescript-eslint/eslint-plugin": "^7.13.1",
|
||||
"@typescript-eslint/parser": "^7.13.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"dequal": "^2.0.3",
|
||||
"embla-carousel": "^8.1.5",
|
||||
"embla-carousel-svelte": "^8.1.5",
|
||||
"embla-carousel-wheel-gestures": "^8.0.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-svelte": "^2.40.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"meilisearch": "^0.37.0",
|
||||
"motion": "^10.18.0",
|
||||
"node-html-parser": "^6.1.13",
|
||||
"openapi-types": "^12.1.3",
|
||||
"oslllo-svg-fixer": "^3.0.0",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-svelte": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"remeda": "^2.10.0",
|
||||
"sass": "^1.77.6",
|
||||
"svelte": "^4.2.18",
|
||||
"svelte-check": "^3.8.1",
|
||||
"svelte-markdoc-preprocess": "^2.0.0",
|
||||
"svelte-markdown": "^0.4.1",
|
||||
"svgtofont": "^4.2.1",
|
||||
"tailwindcss": "4.0.0-alpha.17",
|
||||
"tslib": "^2.6.3",
|
||||
"typescript": "^5.5.2",
|
||||
"vite": "^5.3.1",
|
||||
"vite-plugin-dynamic-import": "^1.5.0",
|
||||
"vite-plugin-image-optimizer": "^1.1.8",
|
||||
"vitest": "^1.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { PlaywrightTestConfig } from "@playwright/test";
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
webServer: {
|
||||
command: "npm run build && npm run preview",
|
||||
port: 4173,
|
||||
},
|
||||
testDir: "tests",
|
||||
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
|
||||
webServer: {
|
||||
command: 'npm run build && npm run preview',
|
||||
port: 4173
|
||||
},
|
||||
testDir: 'tests',
|
||||
testMatch: /(.+\.)?(test|spec)\.[jt]s/
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { build } from "vite";
|
||||
import { downloadContributors } from "./download-contributor-data.js";
|
||||
import { build } from 'vite';
|
||||
import { downloadContributors } from './download-contributor-data.js';
|
||||
|
||||
async function main() {
|
||||
await downloadContributors();
|
||||
await build();
|
||||
await downloadContributors();
|
||||
await build();
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -1,108 +1,100 @@
|
||||
import fs from "fs";
|
||||
import fs from 'fs';
|
||||
|
||||
const perPage = 100;
|
||||
|
||||
const outputFile = `./src/lib/contributors.ts`;
|
||||
|
||||
const headers = process.env.GITHUB_TOKEN
|
||||
? {
|
||||
Authorization: `token ${process.env.GITHUB_TOKEN}`,
|
||||
}
|
||||
: {};
|
||||
? {
|
||||
Authorization: `token ${process.env.GITHUB_TOKEN}`
|
||||
}
|
||||
: {};
|
||||
|
||||
console.log(`using github token: ${!!process.env.GITHUB_TOKEN}`);
|
||||
|
||||
async function fetchRepositories() {
|
||||
let page = 1;
|
||||
let repositoriesData = [];
|
||||
let hasMoreData = true;
|
||||
let page = 1;
|
||||
let repositoriesData = [];
|
||||
let hasMoreData = true;
|
||||
|
||||
while (hasMoreData) {
|
||||
console.log(`Fetching page ${page} of repositories...`);
|
||||
const url = `https://api.github.com/orgs/appwrite/repos?page=${page}&per_page=${perPage}`;
|
||||
while (hasMoreData) {
|
||||
console.log(`Fetching page ${page} of repositories...`);
|
||||
const url = `https://api.github.com/orgs/appwrite/repos?page=${page}&per_page=${perPage}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers,
|
||||
});
|
||||
const response = await fetch(url, {
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch data from ${url}`);
|
||||
break;
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch data from ${url}`);
|
||||
break;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.length === 0) {
|
||||
hasMoreData = false;
|
||||
} else {
|
||||
repositoriesData = repositoriesData.concat(data);
|
||||
page++;
|
||||
}
|
||||
console.log(`Fetched ${data.length} repositories. Total: ${repositoriesData.length}...\n`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.length === 0) {
|
||||
hasMoreData = false;
|
||||
} else {
|
||||
repositoriesData = repositoriesData.concat(data);
|
||||
page++;
|
||||
}
|
||||
console.log(
|
||||
`Fetched ${data.length} repositories. Total: ${repositoriesData.length}...\n`,
|
||||
);
|
||||
}
|
||||
|
||||
return repositoriesData.map((repo) => repo.full_name);
|
||||
return repositoriesData.map((repo) => repo.full_name);
|
||||
}
|
||||
|
||||
async function fetchContributors(apiUrl) {
|
||||
let page = 1;
|
||||
let contributorsData = [];
|
||||
let hasMoreData = true;
|
||||
let page = 1;
|
||||
let contributorsData = [];
|
||||
let hasMoreData = true;
|
||||
|
||||
while (hasMoreData) {
|
||||
console.log(`Fetching page ${page} of contributors...`);
|
||||
const url = `${apiUrl}?page=${page}&per_page=${perPage}`;
|
||||
const response = await fetch(url, {
|
||||
headers,
|
||||
});
|
||||
while (hasMoreData) {
|
||||
console.log(`Fetching page ${page} of contributors...`);
|
||||
const url = `${apiUrl}?page=${page}&per_page=${perPage}`;
|
||||
const response = await fetch(url, {
|
||||
headers
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch data from ${url}`, await response.text());
|
||||
break;
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch data from ${url}`, await response.text());
|
||||
break;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.length === 0) {
|
||||
hasMoreData = false;
|
||||
} else {
|
||||
contributorsData = contributorsData.concat(data.map((c) => c.login));
|
||||
page++;
|
||||
}
|
||||
|
||||
console.log(`Fetched ${data.length} contributors. Total: ${contributorsData.length}...\n`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.length === 0) {
|
||||
hasMoreData = false;
|
||||
} else {
|
||||
contributorsData = contributorsData.concat(data.map((c) => c.login));
|
||||
page++;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Fetched ${data.length} contributors. Total: ${contributorsData.length}...\n`,
|
||||
);
|
||||
}
|
||||
|
||||
return contributorsData;
|
||||
return contributorsData;
|
||||
}
|
||||
|
||||
export async function downloadContributors() {
|
||||
const contributors = new Set();
|
||||
const contributors = new Set();
|
||||
|
||||
for (const repo of [
|
||||
"appwrite/appwrite",
|
||||
"appwrite/console",
|
||||
"appwrite/sdk-generator",
|
||||
]) {
|
||||
console.log(`Fetching contributors for ${repo}...`);
|
||||
const url = `https://api.github.com/repos/${repo}/contributors`;
|
||||
const data = await fetchContributors(url);
|
||||
for (const repo of ['appwrite/appwrite', 'appwrite/console', 'appwrite/sdk-generator']) {
|
||||
console.log(`Fetching contributors for ${repo}...`);
|
||||
const url = `https://api.github.com/repos/${repo}/contributors`;
|
||||
const data = await fetchContributors(url);
|
||||
|
||||
data.forEach((contributor) => contributors.add(contributor));
|
||||
}
|
||||
data.forEach((contributor) => contributors.add(contributor));
|
||||
}
|
||||
|
||||
const contributorsArray = Array.from(contributors);
|
||||
const contributorsString = JSON.stringify(contributorsArray, null, 4);
|
||||
const contributorsFile = `export const contributors = ${contributorsString};`;
|
||||
const contributorsArray = Array.from(contributors);
|
||||
const contributorsString = JSON.stringify(contributorsArray, null, 4);
|
||||
const contributorsFile = `export const contributors = ${contributorsString};`;
|
||||
|
||||
const currentContributors = fs.readFileSync(outputFile, "utf8");
|
||||
if (currentContributors.length >= contributorsFile.length) {
|
||||
console.log("No new contributors found. Exiting...");
|
||||
return;
|
||||
}
|
||||
const currentContributors = fs.readFileSync(outputFile, 'utf8');
|
||||
if (currentContributors.length >= contributorsFile.length) {
|
||||
console.log('No new contributors found. Exiting...');
|
||||
return;
|
||||
}
|
||||
|
||||
fs.writeFileSync(outputFile, contributorsFile);
|
||||
fs.writeFileSync(outputFile, contributorsFile);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { readdirSync, statSync } from "fs";
|
||||
import { join, relative } from "path";
|
||||
import sharp from "sharp";
|
||||
import { fileURLToPath } from "url";
|
||||
import { readdirSync, statSync } from 'fs';
|
||||
import { join, relative } from 'path';
|
||||
import sharp from 'sharp';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
||||
const root_dir = join(__dirname, "../static");
|
||||
const exceptions = ["assets/"];
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const root_dir = join(__dirname, '../static');
|
||||
const exceptions = ['assets/'];
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
@@ -17,79 +17,78 @@ const exceptions = ["assets/"];
|
||||
* }}
|
||||
*/
|
||||
const config = {
|
||||
jpeg: {
|
||||
quality: 100,
|
||||
},
|
||||
webp: {
|
||||
lossless: true,
|
||||
},
|
||||
png: {
|
||||
quality: 100,
|
||||
},
|
||||
gif: {
|
||||
quality: 100,
|
||||
},
|
||||
avif: {
|
||||
lossless: true,
|
||||
},
|
||||
jpeg: {
|
||||
quality: 100
|
||||
},
|
||||
webp: {
|
||||
lossless: true
|
||||
},
|
||||
png: {
|
||||
quality: 100
|
||||
},
|
||||
gif: {
|
||||
quality: 100
|
||||
},
|
||||
avif: {
|
||||
lossless: true
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {sharp.ResizeOptions} */
|
||||
const resize_config = {
|
||||
width: 1280,
|
||||
height: 1280,
|
||||
fit: sharp.fit.inside,
|
||||
withoutEnlargement: true,
|
||||
width: 1280,
|
||||
height: 1280,
|
||||
fit: sharp.fit.inside,
|
||||
withoutEnlargement: true
|
||||
};
|
||||
|
||||
function* walk_directory(dir) {
|
||||
const files = readdirSync(dir);
|
||||
const files = readdirSync(dir);
|
||||
|
||||
for (const file of files) {
|
||||
const pathToFile = join(dir, file);
|
||||
const isDirectory = statSync(pathToFile).isDirectory();
|
||||
if (isDirectory) {
|
||||
yield* walk_directory(pathToFile);
|
||||
} else {
|
||||
yield pathToFile;
|
||||
for (const file of files) {
|
||||
const pathToFile = join(dir, file);
|
||||
const isDirectory = statSync(pathToFile).isDirectory();
|
||||
if (isDirectory) {
|
||||
yield* walk_directory(pathToFile);
|
||||
} else {
|
||||
yield pathToFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function is_image(file) {
|
||||
const extension = file.split(".").pop().toLowerCase();
|
||||
const extension = file.split('.').pop().toLowerCase();
|
||||
|
||||
return Object.keys(config).includes(extension);
|
||||
return Object.keys(config).includes(extension);
|
||||
}
|
||||
|
||||
function get_relative_path(file) {
|
||||
return relative(root_dir, file);
|
||||
return relative(root_dir, file);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
for (const file of walk_directory(join(__dirname, "../static"))) {
|
||||
const relative_path = get_relative_path(file);
|
||||
const is_animated = file.endsWith(".gif");
|
||||
if (!is_image(file)) continue;
|
||||
if (exceptions.some((exception) => relative_path.startsWith(exception)))
|
||||
continue;
|
||||
for (const file of walk_directory(join(__dirname, '../static'))) {
|
||||
const relative_path = get_relative_path(file);
|
||||
const is_animated = file.endsWith('.gif');
|
||||
if (!is_image(file)) continue;
|
||||
if (exceptions.some((exception) => relative_path.startsWith(exception))) continue;
|
||||
|
||||
const image = sharp(file, {
|
||||
animated: is_animated,
|
||||
});
|
||||
const size_before = (await image.toBuffer()).length;
|
||||
const meta = await image.metadata();
|
||||
const buffer = await image[meta.format](config[meta.format])
|
||||
.resize(resize_config)
|
||||
.toBuffer();
|
||||
const size_after = buffer.length;
|
||||
const image = sharp(file, {
|
||||
animated: is_animated
|
||||
});
|
||||
const size_before = (await image.toBuffer()).length;
|
||||
const meta = await image.metadata();
|
||||
const buffer = await image[meta.format](config[meta.format])
|
||||
.resize(resize_config)
|
||||
.toBuffer();
|
||||
const size_after = buffer.length;
|
||||
|
||||
if (size_after >= size_before) continue;
|
||||
if (size_after >= size_before) continue;
|
||||
|
||||
console.log(relative_path);
|
||||
console.log(relative_path);
|
||||
|
||||
await sharp(buffer).toFile(file);
|
||||
}
|
||||
await sharp(buffer).toFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { createApp, fromNodeMiddleware, toNodeListener } from "h3";
|
||||
import { createServer } from "node:http";
|
||||
import { handler } from "../build/handler.js";
|
||||
import { sitemap } from "./sitemap.js";
|
||||
import { createApp, fromNodeMiddleware, toNodeListener } from 'h3';
|
||||
import { createServer } from 'node:http';
|
||||
import { handler } from '../build/handler.js';
|
||||
import { sitemap } from './sitemap.js';
|
||||
|
||||
async function main() {
|
||||
const port = process.env.PORT || 3000;
|
||||
const app = createApp();
|
||||
app.use("/sitemap.xml", await sitemap());
|
||||
app.use(fromNodeMiddleware(handler));
|
||||
const server = createServer(toNodeListener(app)).listen(port);
|
||||
server.addListener("listening", () => {
|
||||
console.log(`Listening on http://0.0.0.0:${port}`);
|
||||
});
|
||||
const port = process.env.PORT || 3000;
|
||||
const app = createApp();
|
||||
app.use('/sitemap.xml', await sitemap());
|
||||
app.use(fromNodeMiddleware(handler));
|
||||
const server = createServer(toNodeListener(app)).listen(port);
|
||||
server.addListener('listening', () => {
|
||||
console.log(`Listening on http://0.0.0.0:${port}`);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -1,45 +1,43 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { defineEventHandler, setResponseHeader } from "h3";
|
||||
import { createRequire } from 'node:module';
|
||||
import { defineEventHandler, setResponseHeader } from 'h3';
|
||||
|
||||
/**
|
||||
* @returns {Promise<import('h3').EventHandler>}
|
||||
*/
|
||||
export async function sitemap() {
|
||||
console.info("Preparing Sitemap...");
|
||||
const manifest = await import("../build/server/manifest.js");
|
||||
const prerendered = manifest.prerendered;
|
||||
const file_route_extensions = [".json", ".xml"];
|
||||
const routes = [...prerendered, ...collectThreads()].filter(
|
||||
(route) => !file_route_extensions.some((ext) => route.endsWith(ext)),
|
||||
);
|
||||
console.info(`Sitemap loaded with ${routes.length} routes!`);
|
||||
console.info('Preparing Sitemap...');
|
||||
const manifest = await import('../build/server/manifest.js');
|
||||
const prerendered = manifest.prerendered;
|
||||
const file_route_extensions = ['.json', '.xml'];
|
||||
const routes = [...prerendered, ...collectThreads()].filter(
|
||||
(route) => !file_route_extensions.some((ext) => route.endsWith(ext))
|
||||
);
|
||||
console.info(`Sitemap loaded with ${routes.length} routes!`);
|
||||
|
||||
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${routes
|
||||
.map(
|
||||
(route) => `<url>
|
||||
.map(
|
||||
(route) => `<url>
|
||||
<loc>https://appwrite.io${route}</loc>
|
||||
</url>
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</urlset>`;
|
||||
|
||||
return defineEventHandler((event) => {
|
||||
setResponseHeader(event, "Content-Type", "application/xml");
|
||||
return defineEventHandler((event) => {
|
||||
setResponseHeader(event, 'Content-Type', 'application/xml');
|
||||
|
||||
return sitemap;
|
||||
});
|
||||
return sitemap;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function collectThreads() {
|
||||
const threads = createRequire(import.meta.url)(
|
||||
"../build/prerendered/threads/data.json",
|
||||
);
|
||||
const threads = createRequire(import.meta.url)('../build/prerendered/threads/data.json');
|
||||
|
||||
return threads.map((id) => `/threads/${id}`);
|
||||
return threads.map((id) => `/threads/${id}`);
|
||||
}
|
||||
|
||||
184
src/app.css
184
src/app.css
@@ -1,124 +1,124 @@
|
||||
@import "tailwindcss";
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
/* Colors */
|
||||
--color-*: initial;
|
||||
/* Colors */
|
||||
--color-*: initial;
|
||||
|
||||
/* base */
|
||||
--color-primary: hsl(var(--color-primary));
|
||||
--color-secondary: hsl(var(--color-secondary));
|
||||
--color-accent: var(--color-secondary);
|
||||
--color-border-primary: hsl(var(--color-greyscale-hue) 5.7% 10.4% / 0.04);
|
||||
/* base */
|
||||
--color-primary: hsl(var(--color-primary));
|
||||
--color-secondary: hsl(var(--color-secondary));
|
||||
--color-accent: var(--color-secondary);
|
||||
--color-border-primary: hsl(var(--color-greyscale-hue) 5.7% 10.4% / 0.04);
|
||||
|
||||
/* pink */
|
||||
--color-pink-200: hsl(var(--color-pink-hue) 98% 84%);
|
||||
--color-pink-500: hsl(var(--color-pink-hue) 98% 60%);
|
||||
--color-pink-600: hsl(var(--color-pink-hue) 65% 48%);
|
||||
--color-pink-700: hsl(var(--color-pink-hue) 65% 36%);
|
||||
/* pink */
|
||||
--color-pink-200: hsl(var(--color-pink-hue) 98% 84%);
|
||||
--color-pink-500: hsl(var(--color-pink-hue) 98% 60%);
|
||||
--color-pink-600: hsl(var(--color-pink-hue) 65% 48%);
|
||||
--color-pink-700: hsl(var(--color-pink-hue) 65% 36%);
|
||||
|
||||
/* red */
|
||||
--color-red-200: calc(hsl(var(--color-red-hue) - 2) 100% 92%);
|
||||
--color-red-500: hsl(var(--color-red-hue) 100% 61%);
|
||||
--color-red-700: calc(hsl(var(--color-red-hue) - 3) 82% 39%);
|
||||
/* red */
|
||||
--color-red-200: calc(hsl(var(--color-red-hue) - 2) 100% 92%);
|
||||
--color-red-500: hsl(var(--color-red-hue) 100% 61%);
|
||||
--color-red-700: calc(hsl(var(--color-red-hue) - 3) 82% 39%);
|
||||
|
||||
/* orange */
|
||||
--color-orange-200: hsl(var(--color-orange-hue) 100% 88%);
|
||||
--color-orange-500: hsl(var(--color-orange-hue) 99% 70%);
|
||||
--color-orange-700: hsl(var(--color-orange-hue) 42% 42%);
|
||||
/* orange */
|
||||
--color-orange-200: hsl(var(--color-orange-hue) 100% 88%);
|
||||
--color-orange-500: hsl(var(--color-orange-hue) 99% 70%);
|
||||
--color-orange-700: hsl(var(--color-orange-hue) 42% 42%);
|
||||
|
||||
/* mint */
|
||||
--color-mint-200: hsl(var(--color-mint-hue) 56% 88%);
|
||||
--color-mint-500: calc(hsl(var(--color-mint-hue) + 1) 54% 69%);
|
||||
--color-mint-700: calc(hsl(var(--color-mint-hue) + 2) 24% 41%);
|
||||
/* mint */
|
||||
--color-mint-200: hsl(var(--color-mint-hue) 56% 88%);
|
||||
--color-mint-500: calc(hsl(var(--color-mint-hue) + 1) 54% 69%);
|
||||
--color-mint-700: calc(hsl(var(--color-mint-hue) + 2) 24% 41%);
|
||||
|
||||
/* purple */
|
||||
--color-purple-200: hsl(var(--color-purple-hue) 100% 88%);
|
||||
--color-purple-500: calc(hsl(var(--color-purple-hue) - 1) 99% 70%);
|
||||
--color-purple-700: calc(hsl(var(--color-purple-hue) - 1) 42% 42%);
|
||||
/* purple */
|
||||
--color-purple-200: hsl(var(--color-purple-hue) 100% 88%);
|
||||
--color-purple-500: calc(hsl(var(--color-purple-hue) - 1) 99% 70%);
|
||||
--color-purple-700: calc(hsl(var(--color-purple-hue) - 1) 42% 42%);
|
||||
|
||||
/* yellow */
|
||||
--color-yellow-200: hsl(var(--color-yellow-hue) 100% 88%);
|
||||
--color-yellow-500: hsl(var(--color-yellow-hue) 99% 70%);
|
||||
--color-yellow-700: calc(hsl(var(--color-yellow-hue) + 1) 42% 42%);
|
||||
/* yellow */
|
||||
--color-yellow-200: hsl(var(--color-yellow-hue) 100% 88%);
|
||||
--color-yellow-500: hsl(var(--color-yellow-hue) 99% 70%);
|
||||
--color-yellow-700: calc(hsl(var(--color-yellow-hue) + 1) 42% 42%);
|
||||
|
||||
/* blue */
|
||||
--color-blue-200: hsl(var(--color-blue-hue) 100% 88%);
|
||||
--color-blue-500: calc(hsl(var(--color-blue-hue) - 1) 99% 70%);
|
||||
--color-blue-700: calc(hsl(var(--color-blue-hue) - 1) 42% 42%);
|
||||
/* blue */
|
||||
--color-blue-200: hsl(var(--color-blue-hue) 100% 88%);
|
||||
--color-blue-500: calc(hsl(var(--color-blue-hue) - 1) 99% 70%);
|
||||
--color-blue-700: calc(hsl(var(--color-blue-hue) - 1) 42% 42%);
|
||||
|
||||
/* secondary */
|
||||
--color-secondary-100: hsl(var(--color-secondary-hue) 99% 66%);
|
||||
/* secondary */
|
||||
--color-secondary-100: hsl(var(--color-secondary-hue) 99% 66%);
|
||||
|
||||
/* greyscale */
|
||||
--color-white: hsl(0 0% 100%);
|
||||
--color-black: hsl(0 0% 0%);
|
||||
--color-greyscale-25: hsl(var(--color-greyscale-hue) 11% 98%);
|
||||
--color-greyscale-50: hsl(var(--color-greyscale-hue) 11% 94%);
|
||||
--color-greyscale-100: hsl(var(--color-greyscale-hue) 6% 90%);
|
||||
--color-greyscale-200: hsl(var(--color-greyscale-hue) 4% 85%);
|
||||
--color-greyscale-250: hsl(var(--color-greyscale-hue) 3% 77%);
|
||||
--color-greyscale-300: hsl(var(--color-greyscale-hue) 2% 68%);
|
||||
--color-greyscale-400: hsl(var(--color-greyscale-hue) 2% 60%);
|
||||
--color-greyscale-500: hsl(var(--color-greyscale-hue) 2% 52%);
|
||||
--color-greyscale-600: hsl(var(--color-greyscale-hue) 2% 43%);
|
||||
--color-greyscale-700: hsl(var(--color-greyscale-hue) 3% 35%);
|
||||
--color-greyscale-750: hsl(var(--color-greyscale-hue) 4% 26%);
|
||||
--color-greyscale-800: hsl(var(--color-greyscale-hue) 4% 18%);
|
||||
--color-greyscale-850: hsl(var(--color-greyscale-hue) 3% 14%);
|
||||
--color-greyscale-900: hsl(var(--color-greyscale-hue) 5.7% 10.4%);
|
||||
/* greyscale */
|
||||
--color-white: hsl(0 0% 100%);
|
||||
--color-black: hsl(0 0% 0%);
|
||||
--color-greyscale-25: hsl(var(--color-greyscale-hue) 11% 98%);
|
||||
--color-greyscale-50: hsl(var(--color-greyscale-hue) 11% 94%);
|
||||
--color-greyscale-100: hsl(var(--color-greyscale-hue) 6% 90%);
|
||||
--color-greyscale-200: hsl(var(--color-greyscale-hue) 4% 85%);
|
||||
--color-greyscale-250: hsl(var(--color-greyscale-hue) 3% 77%);
|
||||
--color-greyscale-300: hsl(var(--color-greyscale-hue) 2% 68%);
|
||||
--color-greyscale-400: hsl(var(--color-greyscale-hue) 2% 60%);
|
||||
--color-greyscale-500: hsl(var(--color-greyscale-hue) 2% 52%);
|
||||
--color-greyscale-600: hsl(var(--color-greyscale-hue) 2% 43%);
|
||||
--color-greyscale-700: hsl(var(--color-greyscale-hue) 3% 35%);
|
||||
--color-greyscale-750: hsl(var(--color-greyscale-hue) 4% 26%);
|
||||
--color-greyscale-800: hsl(var(--color-greyscale-hue) 4% 18%);
|
||||
--color-greyscale-850: hsl(var(--color-greyscale-hue) 3% 14%);
|
||||
--color-greyscale-900: hsl(var(--color-greyscale-hue) 5.7% 10.4%);
|
||||
|
||||
/* Animations */
|
||||
--animate-scale-in: scale-in 200ms ease-out forwards;
|
||||
/* Animations */
|
||||
--animate-scale-in: scale-in 200ms ease-out forwards;
|
||||
|
||||
/* Pink polyfills */
|
||||
--transition: 0.2s;
|
||||
/* Pink polyfills */
|
||||
--transition: 0.2s;
|
||||
|
||||
/* Keyframes */
|
||||
@keyframes scale-in {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
/* Keyframes */
|
||||
@keyframes scale-in {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Fonts */
|
||||
--font-family-sans: "Inter", arial, sans-serif;
|
||||
--font-family-mono: "Fira Code", monospace;
|
||||
--font-family-aeonik-fono: "Aenoik Fono", monospace;
|
||||
--font-family-aeonik-pro: "Aeonik Pro", var(--font-family-sans);
|
||||
--font-family-archia: "Archia", arial, sans-serif;
|
||||
/* Fonts */
|
||||
--font-family-sans: 'Inter', arial, sans-serif;
|
||||
--font-family-mono: 'Fira Code', monospace;
|
||||
--font-family-aeonik-fono: 'Aenoik Fono', monospace;
|
||||
--font-family-aeonik-pro: 'Aeonik Pro', var(--font-family-sans);
|
||||
--font-family-archia: 'Archia', arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Themes */
|
||||
:root,
|
||||
.light {
|
||||
--color-pink-hue: 343;
|
||||
--color-secondary-hue: 351;
|
||||
--color-red-hue: 3;
|
||||
--color-orange-hue: 18;
|
||||
--color-mint-hue: 177;
|
||||
--color-purple-hue: 249;
|
||||
--color-yellow-hue: 42;
|
||||
--color-blue-hue: 217;
|
||||
--color-greyscale-hue: 240;
|
||||
--color-pink-hue: 343;
|
||||
--color-secondary-hue: 351;
|
||||
--color-red-hue: 3;
|
||||
--color-orange-hue: 18;
|
||||
--color-mint-hue: 177;
|
||||
--color-purple-hue: 249;
|
||||
--color-yellow-hue: 42;
|
||||
--color-blue-hue: 217;
|
||||
--color-greyscale-hue: 240;
|
||||
|
||||
/* base */
|
||||
--color-primary: var(--color-greyscale-900);
|
||||
--color-secondary: var(--color-greyscale-700);
|
||||
--color-accent: var(--color-pink-600);
|
||||
/* base */
|
||||
--color-primary: var(--color-greyscale-900);
|
||||
--color-secondary: var(--color-greyscale-700);
|
||||
--color-accent: var(--color-pink-600);
|
||||
}
|
||||
|
||||
/* dark theme */
|
||||
.dark {
|
||||
--color-primary: var(--color-greyscale-100);
|
||||
--color-secondary: var(--color-greyscale-300);
|
||||
--color-primary: var(--color-greyscale-100);
|
||||
--color-secondary: var(--color-greyscale-300);
|
||||
}
|
||||
|
||||
/* Container */
|
||||
@layer components {
|
||||
.container {
|
||||
@apply mx-auto box-content max-w-[75rem] px-8;
|
||||
}
|
||||
.container {
|
||||
@apply mx-auto box-content max-w-[75rem] px-8;
|
||||
}
|
||||
}
|
||||
|
||||
14
src/app.d.ts
vendored
14
src/app.d.ts
vendored
@@ -1,14 +1,14 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
interface PageData {
|
||||
changelogEntries: number;
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
interface PageData {
|
||||
changelogEntries: number;
|
||||
}
|
||||
// interface Platform {}
|
||||
}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
|
||||
105
src/app.html
105
src/app.html
@@ -1,60 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="twitter:site" content="@appwrite" />
|
||||
<link rel="icon" type="image/svg+xml" href="/images/logos/logo.svg" />
|
||||
<!-- preload font for headlines -->
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/aeonik-pro/AeonikPro-Regular.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin
|
||||
/>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="twitter:site" content="@appwrite" />
|
||||
<link rel="icon" type="image/svg+xml" href="/images/logos/logo.svg" />
|
||||
<!-- preload font for headlines -->
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/aeonik-pro/AeonikPro-Regular.woff2"
|
||||
as="font"
|
||||
type="font/woff2"
|
||||
crossorigin
|
||||
/>
|
||||
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
|
||||
<body class="dark" data-sveltekit-preload-data="hover">
|
||||
<script>
|
||||
// Theme
|
||||
const isDocs = window.location.pathname.startsWith("/docs");
|
||||
if (isDocs) {
|
||||
const theme = localStorage.getItem("theme");
|
||||
if (theme) {
|
||||
document.body.classList.remove("dark", "light");
|
||||
if (theme === "system") {
|
||||
const systemTheme = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
).matches
|
||||
? "dark"
|
||||
: "light";
|
||||
document.body.classList.add(`${systemTheme}`);
|
||||
} else {
|
||||
document.body.classList.add(`${theme}`);
|
||||
// Color scheme in html
|
||||
document.documentElement.style.setProperty("color-scheme", theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
<body class="dark" data-sveltekit-preload-data="hover">
|
||||
<script>
|
||||
// Theme
|
||||
const isDocs = window.location.pathname.startsWith('/docs');
|
||||
if (isDocs) {
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
document.body.classList.remove('dark', 'light');
|
||||
if (theme === 'system') {
|
||||
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
.matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
document.body.classList.add(`${systemTheme}`);
|
||||
} else {
|
||||
document.body.classList.add(`${theme}`);
|
||||
// Color scheme in html
|
||||
document.documentElement.style.setProperty('color-scheme', theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Progressive enhancement
|
||||
document.body.dataset.jsEnabled = "";
|
||||
// Progressive enhancement
|
||||
document.body.dataset.jsEnabled = '';
|
||||
|
||||
// Banner
|
||||
const BANNER_KEY = "%aw_banner_key%";
|
||||
const hideBanner = localStorage.getItem(BANNER_KEY);
|
||||
if (hideBanner === "true") {
|
||||
document.body.dataset.bannerHidden = "";
|
||||
}
|
||||
// Is logged in
|
||||
const isLoggedIn = localStorage.getItem("appwrite:user");
|
||||
if (isLoggedIn) {
|
||||
document.body.dataset.loggedIn = "";
|
||||
}
|
||||
</script>
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
// Banner
|
||||
const BANNER_KEY = '%aw_banner_key%';
|
||||
const hideBanner = localStorage.getItem(BANNER_KEY);
|
||||
if (hideBanner === 'true') {
|
||||
document.body.dataset.bannerHidden = '';
|
||||
}
|
||||
// Is logged in
|
||||
const isLoggedIn = localStorage.getItem('appwrite:user');
|
||||
if (isLoggedIn) {
|
||||
document.body.dataset.loggedIn = '';
|
||||
}
|
||||
</script>
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { dev } from "$app/environment";
|
||||
import { SENTRY_DSN } from "$lib/constants";
|
||||
import { handleErrorWithSentry, replayIntegration } from "@sentry/sveltekit";
|
||||
import * as Sentry from "@sentry/sveltekit";
|
||||
import { dev } from '$app/environment';
|
||||
import { SENTRY_DSN } from '$lib/constants';
|
||||
import { handleErrorWithSentry, replayIntegration } from '@sentry/sveltekit';
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
|
||||
Sentry.init({
|
||||
enabled: !dev,
|
||||
dsn: SENTRY_DSN,
|
||||
allowUrls: [/appwrite\.io/],
|
||||
tracesSampleRate: 1.0,
|
||||
enabled: !dev,
|
||||
dsn: SENTRY_DSN,
|
||||
allowUrls: [/appwrite\.io/],
|
||||
tracesSampleRate: 1.0,
|
||||
|
||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||
// in development and sample at a lower rate in production
|
||||
replaysSessionSampleRate: 0,
|
||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||
// in development and sample at a lower rate in production
|
||||
replaysSessionSampleRate: 0,
|
||||
|
||||
// If the entire session is not sampled, use the below sample rate to sample
|
||||
// sessions when an error occurs.
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
// If the entire session is not sampled, use the below sample rate to sample
|
||||
// sessions when an error occurs.
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
// If you don't want to use Session Replay, just remove the line below:
|
||||
integrations: [
|
||||
replayIntegration({
|
||||
maskAllInputs: true,
|
||||
maskAllText: false,
|
||||
blockAllMedia: false,
|
||||
}),
|
||||
],
|
||||
// If you don't want to use Session Replay, just remove the line below:
|
||||
integrations: [
|
||||
replayIntegration({
|
||||
maskAllInputs: true,
|
||||
maskAllText: false,
|
||||
blockAllMedia: false
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// If you have a custom error handler, pass it to `handleErrorWithSentry`
|
||||
|
||||
@@ -1,46 +1,39 @@
|
||||
import * as Sentry from "@sentry/sveltekit";
|
||||
import type { Handle } from "@sveltejs/kit";
|
||||
import redirects from "./redirects.json";
|
||||
import { sequence } from "@sveltejs/kit/hooks";
|
||||
import { BANNER_KEY, SENTRY_DSN } from "$lib/constants";
|
||||
import { dev } from "$app/environment";
|
||||
import * as Sentry from '@sentry/sveltekit';
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import redirects from './redirects.json';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import { BANNER_KEY, SENTRY_DSN } from '$lib/constants';
|
||||
import { dev } from '$app/environment';
|
||||
|
||||
Sentry.init({
|
||||
enabled: !dev,
|
||||
dsn: SENTRY_DSN,
|
||||
tracesSampleRate: 1,
|
||||
allowUrls: [/appwrite\.io/],
|
||||
enabled: !dev,
|
||||
dsn: SENTRY_DSN,
|
||||
tracesSampleRate: 1,
|
||||
allowUrls: [/appwrite\.io/]
|
||||
});
|
||||
|
||||
const redirectMap = new Map(
|
||||
redirects.map(({ link, redirect }) => [link, redirect]),
|
||||
);
|
||||
const redirectMap = new Map(redirects.map(({ link, redirect }) => [link, redirect]));
|
||||
|
||||
const redirecter: Handle = async ({ event, resolve }) => {
|
||||
const currentPath = event.url.pathname;
|
||||
if (redirectMap.has(currentPath)) {
|
||||
return new Response(null, {
|
||||
status: 308,
|
||||
headers: {
|
||||
location: redirectMap.get(currentPath) ?? "",
|
||||
},
|
||||
});
|
||||
}
|
||||
const currentPath = event.url.pathname;
|
||||
if (redirectMap.has(currentPath)) {
|
||||
return new Response(null, {
|
||||
status: 308,
|
||||
headers: {
|
||||
location: redirectMap.get(currentPath) ?? ''
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return await resolve(event);
|
||||
return await resolve(event);
|
||||
};
|
||||
|
||||
const bannerRewriter: Handle = async ({ event, resolve }) => {
|
||||
const response = await resolve(event, {
|
||||
transformPageChunk: ({ html }) =>
|
||||
html.replace("%aw_banner_key%", BANNER_KEY),
|
||||
});
|
||||
return response;
|
||||
const response = await resolve(event, {
|
||||
transformPageChunk: ({ html }) => html.replace('%aw_banner_key%', BANNER_KEY)
|
||||
});
|
||||
return response;
|
||||
};
|
||||
|
||||
export const handle = sequence(
|
||||
Sentry.sentryHandle(),
|
||||
redirecter,
|
||||
bannerRewriter,
|
||||
);
|
||||
export const handle = sequence(Sentry.sentryHandle(), redirecter, bannerRewriter);
|
||||
export const handleError = Sentry.handleErrorWithSentry();
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { generateIcons } from "./scripts.js";
|
||||
import { generateIcons } from './scripts.js';
|
||||
|
||||
generateIcons();
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { optimizeSVG } from "./scripts.js";
|
||||
import { optimizeSVG } from './scripts.js';
|
||||
|
||||
optimizeSVG();
|
||||
|
||||
@@ -1,314 +1,314 @@
|
||||
{
|
||||
"apple": {
|
||||
"encodedCode": "\\ea01",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-apple",
|
||||
"unicode": ""
|
||||
},
|
||||
"appwrite": {
|
||||
"encodedCode": "\\ea02",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-appwrite",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-down": {
|
||||
"encodedCode": "\\ea03",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-down",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-ext-link": {
|
||||
"encodedCode": "\\ea04",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-ext-link",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-left": {
|
||||
"encodedCode": "\\ea05",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-left",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-right": {
|
||||
"encodedCode": "\\ea06",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-right",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-up": {
|
||||
"encodedCode": "\\ea07",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-up",
|
||||
"unicode": ""
|
||||
},
|
||||
"calendar": {
|
||||
"encodedCode": "\\ea08",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-calendar",
|
||||
"unicode": ""
|
||||
},
|
||||
"check": {
|
||||
"encodedCode": "\\ea09",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-check",
|
||||
"unicode": ""
|
||||
},
|
||||
"chevron-down": {
|
||||
"encodedCode": "\\ea0a",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-chevron-down",
|
||||
"unicode": ""
|
||||
},
|
||||
"chevron-left": {
|
||||
"encodedCode": "\\ea0b",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-chevron-left",
|
||||
"unicode": ""
|
||||
},
|
||||
"chevron-right": {
|
||||
"encodedCode": "\\ea0c",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-chevron-right",
|
||||
"unicode": ""
|
||||
},
|
||||
"chevron-up": {
|
||||
"encodedCode": "\\ea0d",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-chevron-up",
|
||||
"unicode": ""
|
||||
},
|
||||
"close": {
|
||||
"encodedCode": "\\ea0e",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-close",
|
||||
"unicode": ""
|
||||
},
|
||||
"command": {
|
||||
"encodedCode": "\\ea0f",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-command",
|
||||
"unicode": ""
|
||||
},
|
||||
"copy": {
|
||||
"encodedCode": "\\ea10",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-copy",
|
||||
"unicode": ""
|
||||
},
|
||||
"daily-dev": {
|
||||
"encodedCode": "\\ea11",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-daily-dev",
|
||||
"unicode": ""
|
||||
},
|
||||
"dark": {
|
||||
"encodedCode": "\\ea12",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-dark",
|
||||
"unicode": ""
|
||||
},
|
||||
"discord": {
|
||||
"encodedCode": "\\ea13",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-discord",
|
||||
"unicode": ""
|
||||
},
|
||||
"divider-vertical": {
|
||||
"encodedCode": "\\ea14",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-divider-vertical",
|
||||
"unicode": ""
|
||||
},
|
||||
"download": {
|
||||
"encodedCode": "\\ea15",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-download",
|
||||
"unicode": ""
|
||||
},
|
||||
"ext-link": {
|
||||
"encodedCode": "\\ea16",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-ext-link",
|
||||
"unicode": ""
|
||||
},
|
||||
"firebase": {
|
||||
"encodedCode": "\\ea17",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-firebase",
|
||||
"unicode": ""
|
||||
},
|
||||
"github": {
|
||||
"encodedCode": "\\ea18",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-github",
|
||||
"unicode": ""
|
||||
},
|
||||
"google": {
|
||||
"encodedCode": "\\ea19",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-google",
|
||||
"unicode": ""
|
||||
},
|
||||
"hamburger-menu": {
|
||||
"encodedCode": "\\ea1a",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-hamburger-menu",
|
||||
"unicode": ""
|
||||
},
|
||||
"light": {
|
||||
"encodedCode": "\\ea1b",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-light",
|
||||
"unicode": ""
|
||||
},
|
||||
"linkedin": {
|
||||
"encodedCode": "\\ea1c",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-linkedin",
|
||||
"unicode": ""
|
||||
},
|
||||
"location": {
|
||||
"encodedCode": "\\ea1d",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-location",
|
||||
"unicode": ""
|
||||
},
|
||||
"logout-left": {
|
||||
"encodedCode": "\\ea1e",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-logout-left",
|
||||
"unicode": ""
|
||||
},
|
||||
"logout-right": {
|
||||
"encodedCode": "\\ea1f",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-logout-right",
|
||||
"unicode": ""
|
||||
},
|
||||
"mailgun": {
|
||||
"encodedCode": "\\ea20",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-mailgun",
|
||||
"unicode": ""
|
||||
},
|
||||
"message": {
|
||||
"encodedCode": "\\ea21",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-message",
|
||||
"unicode": ""
|
||||
},
|
||||
"microsoft": {
|
||||
"encodedCode": "\\ea22",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-microsoft",
|
||||
"unicode": ""
|
||||
},
|
||||
"minus": {
|
||||
"encodedCode": "\\ea23",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-minus",
|
||||
"unicode": ""
|
||||
},
|
||||
"nuxt": {
|
||||
"encodedCode": "\\ea24",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-nuxt",
|
||||
"unicode": ""
|
||||
},
|
||||
"platform": {
|
||||
"encodedCode": "\\ea25",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-platform",
|
||||
"unicode": ""
|
||||
},
|
||||
"play": {
|
||||
"encodedCode": "\\ea26",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-play",
|
||||
"unicode": ""
|
||||
},
|
||||
"plus": {
|
||||
"encodedCode": "\\ea27",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-plus",
|
||||
"unicode": ""
|
||||
},
|
||||
"product-hunt": {
|
||||
"encodedCode": "\\ea28",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-product-hunt",
|
||||
"unicode": ""
|
||||
},
|
||||
"refine": {
|
||||
"encodedCode": "\\ea29",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-refine",
|
||||
"unicode": ""
|
||||
},
|
||||
"rest": {
|
||||
"encodedCode": "\\ea2a",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-rest",
|
||||
"unicode": ""
|
||||
},
|
||||
"search": {
|
||||
"encodedCode": "\\ea2b",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-search",
|
||||
"unicode": ""
|
||||
},
|
||||
"sendgrid": {
|
||||
"encodedCode": "\\ea2c",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-sendgrid",
|
||||
"unicode": ""
|
||||
},
|
||||
"star": {
|
||||
"encodedCode": "\\ea2d",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-star",
|
||||
"unicode": ""
|
||||
},
|
||||
"system": {
|
||||
"encodedCode": "\\ea2e",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-system",
|
||||
"unicode": ""
|
||||
},
|
||||
"textmagic": {
|
||||
"encodedCode": "\\ea2f",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-textmagic",
|
||||
"unicode": ""
|
||||
},
|
||||
"twitter": {
|
||||
"encodedCode": "\\ea30",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-twitter",
|
||||
"unicode": ""
|
||||
},
|
||||
"vue": {
|
||||
"encodedCode": "\\ea31",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-vue",
|
||||
"unicode": ""
|
||||
},
|
||||
"x": {
|
||||
"encodedCode": "\\ea32",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-x",
|
||||
"unicode": ""
|
||||
},
|
||||
"ycombinator": {
|
||||
"encodedCode": "\\ea33",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-ycombinator",
|
||||
"unicode": ""
|
||||
},
|
||||
"youtube": {
|
||||
"encodedCode": "\\ea34",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-youtube",
|
||||
"unicode": ""
|
||||
}
|
||||
"apple": {
|
||||
"encodedCode": "\\ea01",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-apple",
|
||||
"unicode": ""
|
||||
},
|
||||
"appwrite": {
|
||||
"encodedCode": "\\ea02",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-appwrite",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-down": {
|
||||
"encodedCode": "\\ea03",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-down",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-ext-link": {
|
||||
"encodedCode": "\\ea04",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-ext-link",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-left": {
|
||||
"encodedCode": "\\ea05",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-left",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-right": {
|
||||
"encodedCode": "\\ea06",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-right",
|
||||
"unicode": ""
|
||||
},
|
||||
"arrow-up": {
|
||||
"encodedCode": "\\ea07",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-arrow-up",
|
||||
"unicode": ""
|
||||
},
|
||||
"calendar": {
|
||||
"encodedCode": "\\ea08",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-calendar",
|
||||
"unicode": ""
|
||||
},
|
||||
"check": {
|
||||
"encodedCode": "\\ea09",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-check",
|
||||
"unicode": ""
|
||||
},
|
||||
"chevron-down": {
|
||||
"encodedCode": "\\ea0a",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-chevron-down",
|
||||
"unicode": ""
|
||||
},
|
||||
"chevron-left": {
|
||||
"encodedCode": "\\ea0b",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-chevron-left",
|
||||
"unicode": ""
|
||||
},
|
||||
"chevron-right": {
|
||||
"encodedCode": "\\ea0c",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-chevron-right",
|
||||
"unicode": ""
|
||||
},
|
||||
"chevron-up": {
|
||||
"encodedCode": "\\ea0d",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-chevron-up",
|
||||
"unicode": ""
|
||||
},
|
||||
"close": {
|
||||
"encodedCode": "\\ea0e",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-close",
|
||||
"unicode": ""
|
||||
},
|
||||
"command": {
|
||||
"encodedCode": "\\ea0f",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-command",
|
||||
"unicode": ""
|
||||
},
|
||||
"copy": {
|
||||
"encodedCode": "\\ea10",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-copy",
|
||||
"unicode": ""
|
||||
},
|
||||
"daily-dev": {
|
||||
"encodedCode": "\\ea11",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-daily-dev",
|
||||
"unicode": ""
|
||||
},
|
||||
"dark": {
|
||||
"encodedCode": "\\ea12",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-dark",
|
||||
"unicode": ""
|
||||
},
|
||||
"discord": {
|
||||
"encodedCode": "\\ea13",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-discord",
|
||||
"unicode": ""
|
||||
},
|
||||
"divider-vertical": {
|
||||
"encodedCode": "\\ea14",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-divider-vertical",
|
||||
"unicode": ""
|
||||
},
|
||||
"download": {
|
||||
"encodedCode": "\\ea15",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-download",
|
||||
"unicode": ""
|
||||
},
|
||||
"ext-link": {
|
||||
"encodedCode": "\\ea16",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-ext-link",
|
||||
"unicode": ""
|
||||
},
|
||||
"firebase": {
|
||||
"encodedCode": "\\ea17",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-firebase",
|
||||
"unicode": ""
|
||||
},
|
||||
"github": {
|
||||
"encodedCode": "\\ea18",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-github",
|
||||
"unicode": ""
|
||||
},
|
||||
"google": {
|
||||
"encodedCode": "\\ea19",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-google",
|
||||
"unicode": ""
|
||||
},
|
||||
"hamburger-menu": {
|
||||
"encodedCode": "\\ea1a",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-hamburger-menu",
|
||||
"unicode": ""
|
||||
},
|
||||
"light": {
|
||||
"encodedCode": "\\ea1b",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-light",
|
||||
"unicode": ""
|
||||
},
|
||||
"linkedin": {
|
||||
"encodedCode": "\\ea1c",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-linkedin",
|
||||
"unicode": ""
|
||||
},
|
||||
"location": {
|
||||
"encodedCode": "\\ea1d",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-location",
|
||||
"unicode": ""
|
||||
},
|
||||
"logout-left": {
|
||||
"encodedCode": "\\ea1e",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-logout-left",
|
||||
"unicode": ""
|
||||
},
|
||||
"logout-right": {
|
||||
"encodedCode": "\\ea1f",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-logout-right",
|
||||
"unicode": ""
|
||||
},
|
||||
"mailgun": {
|
||||
"encodedCode": "\\ea20",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-mailgun",
|
||||
"unicode": ""
|
||||
},
|
||||
"message": {
|
||||
"encodedCode": "\\ea21",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-message",
|
||||
"unicode": ""
|
||||
},
|
||||
"microsoft": {
|
||||
"encodedCode": "\\ea22",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-microsoft",
|
||||
"unicode": ""
|
||||
},
|
||||
"minus": {
|
||||
"encodedCode": "\\ea23",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-minus",
|
||||
"unicode": ""
|
||||
},
|
||||
"nuxt": {
|
||||
"encodedCode": "\\ea24",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-nuxt",
|
||||
"unicode": ""
|
||||
},
|
||||
"platform": {
|
||||
"encodedCode": "\\ea25",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-platform",
|
||||
"unicode": ""
|
||||
},
|
||||
"play": {
|
||||
"encodedCode": "\\ea26",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-play",
|
||||
"unicode": ""
|
||||
},
|
||||
"plus": {
|
||||
"encodedCode": "\\ea27",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-plus",
|
||||
"unicode": ""
|
||||
},
|
||||
"product-hunt": {
|
||||
"encodedCode": "\\ea28",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-product-hunt",
|
||||
"unicode": ""
|
||||
},
|
||||
"refine": {
|
||||
"encodedCode": "\\ea29",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-refine",
|
||||
"unicode": ""
|
||||
},
|
||||
"rest": {
|
||||
"encodedCode": "\\ea2a",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-rest",
|
||||
"unicode": ""
|
||||
},
|
||||
"search": {
|
||||
"encodedCode": "\\ea2b",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-search",
|
||||
"unicode": ""
|
||||
},
|
||||
"sendgrid": {
|
||||
"encodedCode": "\\ea2c",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-sendgrid",
|
||||
"unicode": ""
|
||||
},
|
||||
"star": {
|
||||
"encodedCode": "\\ea2d",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-star",
|
||||
"unicode": ""
|
||||
},
|
||||
"system": {
|
||||
"encodedCode": "\\ea2e",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-system",
|
||||
"unicode": ""
|
||||
},
|
||||
"textmagic": {
|
||||
"encodedCode": "\\ea2f",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-textmagic",
|
||||
"unicode": ""
|
||||
},
|
||||
"twitter": {
|
||||
"encodedCode": "\\ea30",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-twitter",
|
||||
"unicode": ""
|
||||
},
|
||||
"vue": {
|
||||
"encodedCode": "\\ea31",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-vue",
|
||||
"unicode": ""
|
||||
},
|
||||
"x": {
|
||||
"encodedCode": "\\ea32",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-x",
|
||||
"unicode": ""
|
||||
},
|
||||
"ycombinator": {
|
||||
"encodedCode": "\\ea33",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-ycombinator",
|
||||
"unicode": ""
|
||||
},
|
||||
"youtube": {
|
||||
"encodedCode": "\\ea34",
|
||||
"prefix": "web-icon",
|
||||
"className": "web-icon-youtube",
|
||||
"unicode": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,178 +1,178 @@
|
||||
@font-face {
|
||||
font-family: "web-icon";
|
||||
font-display: swap;
|
||||
src: url("web-icon.eot"); /* IE9*/
|
||||
src:
|
||||
url("web-icon.eot#iefix") format("embedded-opentype"),
|
||||
/* IE6-IE8 */ url("web-icon.woff2") format("woff2"),
|
||||
url("web-icon.woff") format("woff"),
|
||||
url("web-icon.ttf") format("truetype"),
|
||||
/* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
|
||||
url("web-icon.svg#web-icon") format("svg"); /* iOS 4.1- */
|
||||
font-family: 'web-icon';
|
||||
font-display: swap;
|
||||
src: url('web-icon.eot'); /* IE9*/
|
||||
src:
|
||||
url('web-icon.eot#iefix') format('embedded-opentype'),
|
||||
/* IE6-IE8 */ url('web-icon.woff2') format('woff2'),
|
||||
url('web-icon.woff') format('woff'),
|
||||
url('web-icon.ttf') format('truetype'),
|
||||
/* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ url('web-icon.svg#web-icon')
|
||||
format('svg'); /* iOS 4.1- */
|
||||
}
|
||||
|
||||
[class^="web-icon-"],
|
||||
[class*=" web-icon-"] {
|
||||
font-family: "web-icon" !important;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
[class^='web-icon-'],
|
||||
[class*=' web-icon-'] {
|
||||
font-family: 'web-icon' !important;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.web-icon-apple:before {
|
||||
content: "\ea01";
|
||||
content: '\ea01';
|
||||
}
|
||||
.web-icon-appwrite:before {
|
||||
content: "\ea02";
|
||||
content: '\ea02';
|
||||
}
|
||||
.web-icon-arrow-down:before {
|
||||
content: "\ea03";
|
||||
content: '\ea03';
|
||||
}
|
||||
.web-icon-arrow-ext-link:before {
|
||||
content: "\ea04";
|
||||
content: '\ea04';
|
||||
}
|
||||
.web-icon-arrow-left:before {
|
||||
content: "\ea05";
|
||||
content: '\ea05';
|
||||
}
|
||||
.web-icon-arrow-right:before {
|
||||
content: "\ea06";
|
||||
content: '\ea06';
|
||||
}
|
||||
.web-icon-arrow-up:before {
|
||||
content: "\ea07";
|
||||
content: '\ea07';
|
||||
}
|
||||
.web-icon-calendar:before {
|
||||
content: "\ea08";
|
||||
content: '\ea08';
|
||||
}
|
||||
.web-icon-check:before {
|
||||
content: "\ea09";
|
||||
content: '\ea09';
|
||||
}
|
||||
.web-icon-chevron-down:before {
|
||||
content: "\ea0a";
|
||||
content: '\ea0a';
|
||||
}
|
||||
.web-icon-chevron-left:before {
|
||||
content: "\ea0b";
|
||||
content: '\ea0b';
|
||||
}
|
||||
.web-icon-chevron-right:before {
|
||||
content: "\ea0c";
|
||||
content: '\ea0c';
|
||||
}
|
||||
.web-icon-chevron-up:before {
|
||||
content: "\ea0d";
|
||||
content: '\ea0d';
|
||||
}
|
||||
.web-icon-close:before {
|
||||
content: "\ea0e";
|
||||
content: '\ea0e';
|
||||
}
|
||||
.web-icon-command:before {
|
||||
content: "\ea0f";
|
||||
content: '\ea0f';
|
||||
}
|
||||
.web-icon-copy:before {
|
||||
content: "\ea10";
|
||||
content: '\ea10';
|
||||
}
|
||||
.web-icon-daily-dev:before {
|
||||
content: "\ea11";
|
||||
content: '\ea11';
|
||||
}
|
||||
.web-icon-dark:before {
|
||||
content: "\ea12";
|
||||
content: '\ea12';
|
||||
}
|
||||
.web-icon-discord:before {
|
||||
content: "\ea13";
|
||||
content: '\ea13';
|
||||
}
|
||||
.web-icon-divider-vertical:before {
|
||||
content: "\ea14";
|
||||
content: '\ea14';
|
||||
}
|
||||
.web-icon-download:before {
|
||||
content: "\ea15";
|
||||
content: '\ea15';
|
||||
}
|
||||
.web-icon-ext-link:before {
|
||||
content: "\ea16";
|
||||
content: '\ea16';
|
||||
}
|
||||
.web-icon-firebase:before {
|
||||
content: "\ea17";
|
||||
content: '\ea17';
|
||||
}
|
||||
.web-icon-github:before {
|
||||
content: "\ea18";
|
||||
content: '\ea18';
|
||||
}
|
||||
.web-icon-google:before {
|
||||
content: "\ea19";
|
||||
content: '\ea19';
|
||||
}
|
||||
.web-icon-hamburger-menu:before {
|
||||
content: "\ea1a";
|
||||
content: '\ea1a';
|
||||
}
|
||||
.web-icon-light:before {
|
||||
content: "\ea1b";
|
||||
content: '\ea1b';
|
||||
}
|
||||
.web-icon-linkedin:before {
|
||||
content: "\ea1c";
|
||||
content: '\ea1c';
|
||||
}
|
||||
.web-icon-location:before {
|
||||
content: "\ea1d";
|
||||
content: '\ea1d';
|
||||
}
|
||||
.web-icon-logout-left:before {
|
||||
content: "\ea1e";
|
||||
content: '\ea1e';
|
||||
}
|
||||
.web-icon-logout-right:before {
|
||||
content: "\ea1f";
|
||||
content: '\ea1f';
|
||||
}
|
||||
.web-icon-mailgun:before {
|
||||
content: "\ea20";
|
||||
content: '\ea20';
|
||||
}
|
||||
.web-icon-message:before {
|
||||
content: "\ea21";
|
||||
content: '\ea21';
|
||||
}
|
||||
.web-icon-microsoft:before {
|
||||
content: "\ea22";
|
||||
content: '\ea22';
|
||||
}
|
||||
.web-icon-minus:before {
|
||||
content: "\ea23";
|
||||
content: '\ea23';
|
||||
}
|
||||
.web-icon-nuxt:before {
|
||||
content: "\ea24";
|
||||
content: '\ea24';
|
||||
}
|
||||
.web-icon-platform:before {
|
||||
content: "\ea25";
|
||||
content: '\ea25';
|
||||
}
|
||||
.web-icon-play:before {
|
||||
content: "\ea26";
|
||||
content: '\ea26';
|
||||
}
|
||||
.web-icon-plus:before {
|
||||
content: "\ea27";
|
||||
content: '\ea27';
|
||||
}
|
||||
.web-icon-product-hunt:before {
|
||||
content: "\ea28";
|
||||
content: '\ea28';
|
||||
}
|
||||
.web-icon-refine:before {
|
||||
content: "\ea29";
|
||||
content: '\ea29';
|
||||
}
|
||||
.web-icon-rest:before {
|
||||
content: "\ea2a";
|
||||
content: '\ea2a';
|
||||
}
|
||||
.web-icon-search:before {
|
||||
content: "\ea2b";
|
||||
content: '\ea2b';
|
||||
}
|
||||
.web-icon-sendgrid:before {
|
||||
content: "\ea2c";
|
||||
content: '\ea2c';
|
||||
}
|
||||
.web-icon-star:before {
|
||||
content: "\ea2d";
|
||||
content: '\ea2d';
|
||||
}
|
||||
.web-icon-system:before {
|
||||
content: "\ea2e";
|
||||
content: '\ea2e';
|
||||
}
|
||||
.web-icon-textmagic:before {
|
||||
content: "\ea2f";
|
||||
content: '\ea2f';
|
||||
}
|
||||
.web-icon-twitter:before {
|
||||
content: "\ea30";
|
||||
content: '\ea30';
|
||||
}
|
||||
.web-icon-vue:before {
|
||||
content: "\ea31";
|
||||
content: '\ea31';
|
||||
}
|
||||
.web-icon-x:before {
|
||||
content: "\ea32";
|
||||
content: '\ea32';
|
||||
}
|
||||
.web-icon-ycombinator:before {
|
||||
content: "\ea33";
|
||||
content: '\ea33';
|
||||
}
|
||||
.web-icon-youtube:before {
|
||||
content: "\ea34";
|
||||
content: '\ea34';
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
// @ts-expect-error missing types
|
||||
import SVGFixer from "oslllo-svg-fixer";
|
||||
import svgtofont from "svgtofont";
|
||||
import { resolve } from "path";
|
||||
import SVGFixer from 'oslllo-svg-fixer';
|
||||
import svgtofont from 'svgtofont';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const src = resolve(process.cwd(), "src/icons/svg");
|
||||
const optimized = resolve(process.cwd(), "src/icons/optimized");
|
||||
const dist = resolve(process.cwd(), "src/icons/output");
|
||||
const src = resolve(process.cwd(), 'src/icons/svg');
|
||||
const optimized = resolve(process.cwd(), 'src/icons/optimized');
|
||||
const dist = resolve(process.cwd(), 'src/icons/output');
|
||||
|
||||
export const optimizeSVG = async () => {
|
||||
const fixer = new SVGFixer(src, optimized, {
|
||||
showProgressBar: true,
|
||||
});
|
||||
const fixer = new SVGFixer(src, optimized, {
|
||||
showProgressBar: true
|
||||
});
|
||||
|
||||
await fixer.fix();
|
||||
await fixer.fix();
|
||||
};
|
||||
|
||||
export const generateIcons = async () => {
|
||||
await svgtofont({
|
||||
classNamePrefix: "web-icon",
|
||||
src: optimized,
|
||||
dist: dist,
|
||||
fontName: "web-icon",
|
||||
styleTemplates: resolve(process.cwd(), "src/icons/templates"),
|
||||
css: {
|
||||
fontSize: "20px",
|
||||
},
|
||||
outSVGReact: false,
|
||||
svgicons2svgfont: {
|
||||
centerHorizontally: true,
|
||||
centerVertically: true,
|
||||
fixedWidth: true,
|
||||
fontHeight: 1000,
|
||||
normalize: true,
|
||||
descent: 200,
|
||||
},
|
||||
emptyDist: true,
|
||||
generateInfoData: true,
|
||||
});
|
||||
await svgtofont({
|
||||
classNamePrefix: 'web-icon',
|
||||
src: optimized,
|
||||
dist: dist,
|
||||
fontName: 'web-icon',
|
||||
styleTemplates: resolve(process.cwd(), 'src/icons/templates'),
|
||||
css: {
|
||||
fontSize: '20px'
|
||||
},
|
||||
outSVGReact: false,
|
||||
svgicons2svgfont: {
|
||||
centerHorizontally: true,
|
||||
centerVertically: true,
|
||||
fixedWidth: true,
|
||||
fontHeight: 1000,
|
||||
normalize: true,
|
||||
descent: 200
|
||||
},
|
||||
emptyDist: true,
|
||||
generateInfoData: true
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
export let src: string;
|
||||
export let alt = "";
|
||||
export let controls = true;
|
||||
export let autoplay = false;
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let src: string;
|
||||
export let alt = '';
|
||||
export let controls = true;
|
||||
export let autoplay = false;
|
||||
let className = '';
|
||||
export { className as class };
|
||||
|
||||
const videoExtensions = ["mp4", "webm", "ogg"] as const;
|
||||
$: isVideo = videoExtensions.some((ext) => src.endsWith(ext));
|
||||
const videoExtensions = ['mp4', 'webm', 'ogg'] as const;
|
||||
$: isVideo = videoExtensions.some((ext) => src.endsWith(ext));
|
||||
</script>
|
||||
|
||||
{#if isVideo}
|
||||
<!-- svelte-ignore a11y-media-has-caption-->
|
||||
<video {src} class={className} {controls} {autoplay}>
|
||||
<slot />
|
||||
</video>
|
||||
<!-- svelte-ignore a11y-media-has-caption-->
|
||||
<video {src} class={className} {controls} {autoplay}>
|
||||
<slot />
|
||||
</video>
|
||||
{:else}
|
||||
<img loading="lazy" {src} {alt} class={className} />
|
||||
<img loading="lazy" {src} {alt} class={className} />
|
||||
{/if}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { melt } from "@melt-ui/svelte";
|
||||
import { getTabsContext } from "./index.svelte";
|
||||
import { melt } from '@melt-ui/svelte';
|
||||
import { getTabsContext } from './index.svelte';
|
||||
|
||||
export let tab: string;
|
||||
export let tab: string;
|
||||
|
||||
const {
|
||||
elements: { content },
|
||||
} = getTabsContext();
|
||||
const {
|
||||
elements: { content }
|
||||
} = getTabsContext();
|
||||
</script>
|
||||
|
||||
<div use:melt={$content(tab)}>
|
||||
<slot />
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { melt } from "@melt-ui/svelte";
|
||||
import { getTabsContext } from "./index.svelte";
|
||||
import { melt } from '@melt-ui/svelte';
|
||||
import { getTabsContext } from './index.svelte';
|
||||
|
||||
const {
|
||||
tabs,
|
||||
elements: { list, trigger },
|
||||
states: { value },
|
||||
} = getTabsContext();
|
||||
const {
|
||||
tabs,
|
||||
elements: { list, trigger },
|
||||
states: { value }
|
||||
} = getTabsContext();
|
||||
|
||||
export let stretch = false;
|
||||
export let style = "";
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let stretch = false;
|
||||
export let style = '';
|
||||
let className = '';
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<ul class="web-secondary-tabs {className}" use:melt={$list} {style}>
|
||||
{#each tabs as tab}
|
||||
<li class="web-secondary-tabs-item" class:flex-1={stretch}>
|
||||
<button
|
||||
class="web-secondary-tabs-button w-full"
|
||||
class:is-selected={$value === tab}
|
||||
use:melt={$trigger(tab)}
|
||||
>
|
||||
<slot {tab}>
|
||||
<span class="web-main-body-500">{tab}</span>
|
||||
</slot>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
{#each tabs as tab}
|
||||
<li class="web-secondary-tabs-item" class:flex-1={stretch}>
|
||||
<button
|
||||
class="web-secondary-tabs-button w-full"
|
||||
class:is-selected={$value === tab}
|
||||
use:melt={$trigger(tab)}
|
||||
>
|
||||
<slot {tab}>
|
||||
<span class="web-main-body-500">{tab}</span>
|
||||
</slot>
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
<script lang="ts" context="module">
|
||||
const CTX_KEY = "tabs";
|
||||
const CTX_KEY = 'tabs';
|
||||
|
||||
type Context = Tabs & {
|
||||
tabs: readonly string[];
|
||||
};
|
||||
type Context = Tabs & {
|
||||
tabs: readonly string[];
|
||||
};
|
||||
|
||||
const setTabsContext = (ctx: Context) => {
|
||||
setContext(CTX_KEY, ctx);
|
||||
};
|
||||
const setTabsContext = (ctx: Context) => {
|
||||
setContext(CTX_KEY, ctx);
|
||||
};
|
||||
|
||||
export const getTabsContext = () => {
|
||||
return getContext<Context>(CTX_KEY);
|
||||
};
|
||||
export const getTabsContext = () => {
|
||||
return getContext<Context>(CTX_KEY);
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createTabs, melt, type Tabs } from "@melt-ui/svelte";
|
||||
import { getContext, setContext } from "svelte";
|
||||
import List from "./List.svelte";
|
||||
import Content from "./Content.svelte";
|
||||
import { createTabs, melt, type Tabs } from '@melt-ui/svelte';
|
||||
import { getContext, setContext } from 'svelte';
|
||||
import List from './List.svelte';
|
||||
import Content from './Content.svelte';
|
||||
|
||||
export let tabs: Context["tabs"];
|
||||
export let tab = tabs[0];
|
||||
export let tabs: Context['tabs'];
|
||||
export let tab = tabs[0];
|
||||
|
||||
const ctx = createTabs({
|
||||
defaultValue: tab,
|
||||
onValueChange({ next }) {
|
||||
tab = next;
|
||||
return next;
|
||||
},
|
||||
});
|
||||
$: value.set(tab);
|
||||
const ctx = createTabs({
|
||||
defaultValue: tab,
|
||||
onValueChange({ next }) {
|
||||
tab = next;
|
||||
return next;
|
||||
}
|
||||
});
|
||||
$: value.set(tab);
|
||||
|
||||
setTabsContext({ ...ctx, tabs });
|
||||
setTabsContext({ ...ctx, tabs });
|
||||
|
||||
const {
|
||||
elements: { root },
|
||||
states: { value },
|
||||
} = ctx;
|
||||
const {
|
||||
elements: { root },
|
||||
states: { value }
|
||||
} = ctx;
|
||||
</script>
|
||||
|
||||
<div use:melt={$root}>
|
||||
<slot TabsList={List} TabContent={Content} />
|
||||
<slot TabsList={List} TabContent={Content} />
|
||||
</div>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as Tabs } from "./Tabs/index.svelte";
|
||||
export { default as Media } from "./Media.svelte";
|
||||
export { default as Tabs } from './Tabs/index.svelte';
|
||||
export { default as Media } from './Media.svelte';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
export const autoHash = (
|
||||
node: Element,
|
||||
callback: (entries: IntersectionObserverEntry[]) => void,
|
||||
node: Element,
|
||||
callback: (entries: IntersectionObserverEntry[]) => void
|
||||
) => {
|
||||
const observer = new IntersectionObserver(callback, {
|
||||
threshold: 1,
|
||||
});
|
||||
const observer = new IntersectionObserver(callback, {
|
||||
threshold: 1
|
||||
});
|
||||
|
||||
observer.observe(node);
|
||||
observer.observe(node);
|
||||
|
||||
return { destroy: () => observer.disconnect() };
|
||||
return { destroy: () => observer.disconnect() };
|
||||
};
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
export function clickOutside(
|
||||
node: HTMLElement,
|
||||
callback: (e: MouseEvent) => void,
|
||||
) {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (node && !node.contains(event.target as Node)) {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
export function clickOutside(node: HTMLElement, callback: (e: MouseEvent) => void) {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (node && !node.contains(event.target as Node)) {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClick, true);
|
||||
document.addEventListener('click', handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener("click", handleClick, true);
|
||||
},
|
||||
};
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const highlight = (node: HTMLElement, text: string[]) => {
|
||||
text.forEach((word) => {
|
||||
const regex = new RegExp(`(${word})`, "gi");
|
||||
node.innerHTML = node.innerHTML.replace(regex, "<mark>$1</mark>");
|
||||
});
|
||||
text.forEach((word) => {
|
||||
const regex = new RegExp(`(${word})`, 'gi');
|
||||
node.innerHTML = node.innerHTML.replace(regex, '<mark>$1</mark>');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from "./portal";
|
||||
export * from "./rect";
|
||||
export * from "./visible";
|
||||
export * from './portal';
|
||||
export * from './rect';
|
||||
export * from './visible';
|
||||
|
||||
@@ -1,58 +1,55 @@
|
||||
import { tick } from "svelte";
|
||||
import type { Action } from "svelte/action";
|
||||
import { tick } from 'svelte';
|
||||
import type { Action } from 'svelte/action';
|
||||
|
||||
export type PortalConfig =
|
||||
| { target?: string | HTMLElement | undefined; prepend?: boolean }
|
||||
| undefined;
|
||||
| { target?: string | HTMLElement | undefined; prepend?: boolean }
|
||||
| undefined;
|
||||
|
||||
const defaults: PortalConfig = {
|
||||
target: "body",
|
||||
prepend: false,
|
||||
target: 'body',
|
||||
prepend: false
|
||||
};
|
||||
|
||||
export const portal: Action<HTMLElement, PortalConfig> = (
|
||||
el,
|
||||
config?: PortalConfig,
|
||||
) => {
|
||||
let targetEl;
|
||||
export const portal: Action<HTMLElement, PortalConfig> = (el, config?: PortalConfig) => {
|
||||
let targetEl;
|
||||
|
||||
async function update(newConfig: PortalConfig) {
|
||||
const { target, prepend } = { ...defaults, ...newConfig };
|
||||
async function update(newConfig: PortalConfig) {
|
||||
const { target, prepend } = { ...defaults, ...newConfig };
|
||||
|
||||
if (typeof target === "string") {
|
||||
targetEl = document.querySelector(target);
|
||||
if (targetEl === null) {
|
||||
await tick();
|
||||
targetEl = document.querySelector(target);
|
||||
}
|
||||
if (targetEl === null) {
|
||||
throw new Error(`No element found matching css selector: "${target}"`);
|
||||
}
|
||||
} else if (target instanceof HTMLElement) {
|
||||
targetEl = target;
|
||||
} else {
|
||||
throw new TypeError(
|
||||
`Unknown portal target type: ${
|
||||
target === null ? "null" : typeof target
|
||||
}. Allowed types: string (CSS selector) or HTMLElement.`,
|
||||
);
|
||||
if (typeof target === 'string') {
|
||||
targetEl = document.querySelector(target);
|
||||
if (targetEl === null) {
|
||||
await tick();
|
||||
targetEl = document.querySelector(target);
|
||||
}
|
||||
if (targetEl === null) {
|
||||
throw new Error(`No element found matching css selector: "${target}"`);
|
||||
}
|
||||
} else if (target instanceof HTMLElement) {
|
||||
targetEl = target;
|
||||
} else {
|
||||
throw new TypeError(
|
||||
`Unknown portal target type: ${
|
||||
target === null ? 'null' : typeof target
|
||||
}. Allowed types: string (CSS selector) or HTMLElement.`
|
||||
);
|
||||
}
|
||||
el.dataset.portal = '';
|
||||
if (prepend) {
|
||||
targetEl.prepend(el);
|
||||
} else {
|
||||
targetEl.appendChild(el);
|
||||
}
|
||||
el.hidden = false;
|
||||
}
|
||||
el.dataset.portal = "";
|
||||
if (prepend) {
|
||||
targetEl.prepend(el);
|
||||
} else {
|
||||
targetEl.appendChild(el);
|
||||
|
||||
function destroy() {
|
||||
el.remove();
|
||||
}
|
||||
el.hidden = false;
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
el.remove();
|
||||
}
|
||||
|
||||
update(config ?? {});
|
||||
return {
|
||||
update,
|
||||
destroy,
|
||||
};
|
||||
update(config ?? {});
|
||||
return {
|
||||
update,
|
||||
destroy
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import type { Action } from "svelte/action";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { Action } from 'svelte/action';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
type Args = Writable<DOMRect | null>;
|
||||
|
||||
export const rect: Action<HTMLElement, Args> = (node, store) => {
|
||||
let observer: ResizeObserver | null = null;
|
||||
const update = (store: Args) => {
|
||||
observer?.disconnect();
|
||||
let observer: ResizeObserver | null = null;
|
||||
const update = (store: Args) => {
|
||||
observer?.disconnect();
|
||||
|
||||
store.set(node.getBoundingClientRect());
|
||||
store.set(node.getBoundingClientRect());
|
||||
|
||||
// Observe resize
|
||||
observer = new ResizeObserver(() => {
|
||||
store.set(node.getBoundingClientRect());
|
||||
});
|
||||
observer.observe(node);
|
||||
};
|
||||
// Observe resize
|
||||
observer = new ResizeObserver(() => {
|
||||
store.set(node.getBoundingClientRect());
|
||||
});
|
||||
observer.observe(node);
|
||||
};
|
||||
|
||||
update(store);
|
||||
update(store);
|
||||
|
||||
return {
|
||||
update,
|
||||
destroy() {
|
||||
// no-op
|
||||
},
|
||||
};
|
||||
return {
|
||||
update,
|
||||
destroy() {
|
||||
// no-op
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export const scrollToTop = (node: HTMLElement) => {
|
||||
const handleClick = () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
const handleClick = () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
};
|
||||
|
||||
node.addEventListener("click", handleClick);
|
||||
node.addEventListener('click', handleClick);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener("click", handleClick);
|
||||
},
|
||||
};
|
||||
return {
|
||||
destroy() {
|
||||
node.removeEventListener('click', handleClick);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import { isVisible } from "$lib/utils/isVisible";
|
||||
import type { Action } from "svelte/action";
|
||||
import { isVisible } from '$lib/utils/isVisible';
|
||||
import type { Action } from 'svelte/action';
|
||||
|
||||
type Args =
|
||||
| {
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
right?: number;
|
||||
}
|
||||
| undefined;
|
||||
| {
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
right?: number;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
export const visible: Action<
|
||||
HTMLElement,
|
||||
Args,
|
||||
{ "on:visible": (e: CustomEvent<boolean>) => void }
|
||||
HTMLElement,
|
||||
Args,
|
||||
{ 'on:visible': (e: CustomEvent<boolean>) => void }
|
||||
> = (node, args) => {
|
||||
let visible = false;
|
||||
let visible = false;
|
||||
|
||||
const createVisibilityHandler = (newArgs: Args) => {
|
||||
const argsWithDefaults = {
|
||||
top: 0,
|
||||
bottom: window.innerHeight,
|
||||
left: 0,
|
||||
right: window.innerWidth,
|
||||
...newArgs,
|
||||
const createVisibilityHandler = (newArgs: Args) => {
|
||||
const argsWithDefaults = {
|
||||
top: 0,
|
||||
bottom: window.innerHeight,
|
||||
left: 0,
|
||||
right: window.innerWidth,
|
||||
...newArgs
|
||||
};
|
||||
return () => {
|
||||
visible = isVisible(node, argsWithDefaults);
|
||||
node.dispatchEvent(new CustomEvent('visible', { detail: visible }));
|
||||
};
|
||||
};
|
||||
return () => {
|
||||
visible = isVisible(node, argsWithDefaults);
|
||||
node.dispatchEvent(new CustomEvent("visible", { detail: visible }));
|
||||
|
||||
let handleVisibility = createVisibilityHandler(args);
|
||||
handleVisibility();
|
||||
|
||||
function destroy() {
|
||||
window.removeEventListener('scroll', handleVisibility);
|
||||
window.removeEventListener('resize', handleVisibility);
|
||||
}
|
||||
|
||||
function update(args: Args) {
|
||||
destroy();
|
||||
handleVisibility = createVisibilityHandler(args);
|
||||
window.addEventListener('scroll', handleVisibility);
|
||||
window.addEventListener('resize', handleVisibility);
|
||||
}
|
||||
|
||||
update(args);
|
||||
|
||||
return {
|
||||
update,
|
||||
destroy() {
|
||||
window.removeEventListener('scroll', handleVisibility);
|
||||
window.removeEventListener('resize', handleVisibility);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let handleVisibility = createVisibilityHandler(args);
|
||||
handleVisibility();
|
||||
|
||||
function destroy() {
|
||||
window.removeEventListener("scroll", handleVisibility);
|
||||
window.removeEventListener("resize", handleVisibility);
|
||||
}
|
||||
|
||||
function update(args: Args) {
|
||||
destroy();
|
||||
handleVisibility = createVisibilityHandler(args);
|
||||
window.addEventListener("scroll", handleVisibility);
|
||||
window.addEventListener("resize", handleVisibility);
|
||||
}
|
||||
|
||||
update(args);
|
||||
|
||||
return {
|
||||
update,
|
||||
destroy() {
|
||||
window.removeEventListener("scroll", handleVisibility);
|
||||
window.removeEventListener("resize", handleVisibility);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { rect } from "$lib/actions";
|
||||
import { writable } from "svelte/store";
|
||||
import { rect } from '$lib/actions';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const bodyRect = writable<DOMRect | null>(null);
|
||||
const bodyRect = writable<DOMRect | null>(null);
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
<div
|
||||
class="true-body"
|
||||
style:width={`${$bodyRect?.width ?? 0}px`}
|
||||
style:height={`${$bodyRect?.height ?? 0}px`}
|
||||
/>
|
||||
<div class="body" use:rect={bodyRect}>
|
||||
<slot />
|
||||
</div>
|
||||
<div
|
||||
class="true-body"
|
||||
style:width={`${$bodyRect?.width ?? 0}px`}
|
||||
style:height={`${$bodyRect?.height ?? 0}px`}
|
||||
/>
|
||||
<div class="body" use:rect={bodyRect}>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.relative {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.relative {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.body {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.body {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.true-body {
|
||||
transition: 0.2s ease;
|
||||
}
|
||||
.true-body {
|
||||
transition: 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import "$scss/hljs.css";
|
||||
import '$scss/hljs.css';
|
||||
|
||||
import { getCodeHtml } from "$lib/utils/code";
|
||||
import { getCodeHtml } from '$lib/utils/code';
|
||||
|
||||
export let content: string;
|
||||
$: codeHtml = getCodeHtml({ content, language: "js" });
|
||||
export let content: string;
|
||||
$: codeHtml = getCodeHtml({ content, language: 'js' });
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||
|
||||
@@ -1,100 +1,96 @@
|
||||
<script>
|
||||
import AutoBox from "../AutoBox.svelte";
|
||||
import Code from "./Code.svelte";
|
||||
import AutoBox from '../AutoBox.svelte';
|
||||
import Code from './Code.svelte';
|
||||
</script>
|
||||
|
||||
<div class="code-console">
|
||||
<div class="header">
|
||||
<div class="ellipse" />
|
||||
<div class="ellipse-2" />
|
||||
<div class="ellipse-3" />
|
||||
</div>
|
||||
<div class="block">
|
||||
<AutoBox>
|
||||
<slot {Code} />
|
||||
</AutoBox>
|
||||
</div>
|
||||
<div id="code-bottom" />
|
||||
<div class="header">
|
||||
<div class="ellipse" />
|
||||
<div class="ellipse-2" />
|
||||
<div class="ellipse-3" />
|
||||
</div>
|
||||
<div class="block">
|
||||
<AutoBox>
|
||||
<slot {Code} />
|
||||
</AutoBox>
|
||||
</div>
|
||||
<div id="code-bottom" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "$scss/abstract/mixins/border-gradient" as gradients;
|
||||
@use '$scss/abstract/mixins/border-gradient' as gradients;
|
||||
|
||||
.code-console {
|
||||
@include gradients.border-gradient;
|
||||
--p-radius: 16px;
|
||||
.code-console {
|
||||
@include gradients.border-gradient;
|
||||
--p-radius: 16px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: hsl(var(--web-color-card));
|
||||
border-radius: var(--p-radius);
|
||||
--m-border-radius: var(--p-radius);
|
||||
--m-border-gradient-before: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.12) 0%,
|
||||
rgba(255, 255, 255, 0) 125.11%
|
||||
);
|
||||
backdrop-filter: blur(8px);
|
||||
background-color: hsl(var(--web-color-card));
|
||||
border-radius: var(--p-radius);
|
||||
--m-border-radius: var(--p-radius);
|
||||
--m-border-gradient-before: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.12) 0%,
|
||||
rgba(255, 255, 255, 0) 125.11%
|
||||
);
|
||||
backdrop-filter: blur(8px);
|
||||
|
||||
min-width: 330px;
|
||||
width: fit-content;
|
||||
padding: 0 0.25rem 0.25rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(
|
||||
129deg,
|
||||
rgba(0, 0, 0, 0.48) 22.38%,
|
||||
rgba(0, 0, 0, 0) 136.5%
|
||||
);
|
||||
padding: 20px;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.75rem;
|
||||
|
||||
.ellipse {
|
||||
background-color: #ec6a5e;
|
||||
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 12px;
|
||||
border-radius: 4px;
|
||||
min-width: 330px;
|
||||
width: fit-content;
|
||||
padding: 0 0.25rem 0.25rem;
|
||||
}
|
||||
|
||||
.ellipse-2 {
|
||||
background-color: #f5bf4f;
|
||||
.block {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 12px;
|
||||
border-radius: 4px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(129deg, rgba(0, 0, 0, 0.48) 22.38%, rgba(0, 0, 0, 0) 136.5%);
|
||||
padding: 20px;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ellipse-3 {
|
||||
background-color: #61c554;
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.75rem;
|
||||
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 12px;
|
||||
border-radius: 4px;
|
||||
.ellipse {
|
||||
background-color: #ec6a5e;
|
||||
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ellipse-2 {
|
||||
background-color: #f5bf4f;
|
||||
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ellipse-3 {
|
||||
background-color: #61c554;
|
||||
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code-console :global(code) {
|
||||
white-space: pre;
|
||||
tab-size: 4;
|
||||
}
|
||||
.code-console :global(code) {
|
||||
white-space: pre;
|
||||
tab-size: 4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,407 +1,387 @@
|
||||
<script lang="ts">
|
||||
import { toScale, type Scale } from "$lib/utils/toScale";
|
||||
import {
|
||||
spring,
|
||||
type AnimationListOptions,
|
||||
type SpringOptions,
|
||||
} from "motion";
|
||||
import { animation, createScrollHandler, scroll, type Animation } from ".";
|
||||
import { GITHUB_STARS } from "$lib/constants";
|
||||
import { toScale, type Scale } from '$lib/utils/toScale';
|
||||
import { spring, type AnimationListOptions, type SpringOptions } from 'motion';
|
||||
import { animation, createScrollHandler, scroll, type Animation } from '.';
|
||||
import { GITHUB_STARS } from '$lib/constants';
|
||||
|
||||
const springOptions: SpringOptions = {
|
||||
stiffness: 58.78,
|
||||
mass: 1,
|
||||
damping: 17.14,
|
||||
};
|
||||
const animationOptions: AnimationListOptions = {
|
||||
x: { easing: spring(springOptions) },
|
||||
y: { easing: spring(springOptions) },
|
||||
};
|
||||
const animations: {
|
||||
mobile: {
|
||||
main: Animation;
|
||||
reversed: Animation;
|
||||
const springOptions: SpringOptions = {
|
||||
stiffness: 58.78,
|
||||
mass: 1,
|
||||
damping: 17.14
|
||||
};
|
||||
desktop: {
|
||||
main: Animation;
|
||||
reversed: Animation;
|
||||
const animationOptions: AnimationListOptions = {
|
||||
x: { easing: spring(springOptions) },
|
||||
y: { easing: spring(springOptions) }
|
||||
};
|
||||
}[] = [
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
"#oss-discord",
|
||||
{ x: 0, y: [1200, 0], rotate: 1 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-discord",
|
||||
{ y: 1200, x: 0, rotate: 1 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
"#oss-discord",
|
||||
{ x: 20, y: "-75vh", rotate: 15 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-discord",
|
||||
{ x: -100, y: "0vh", rotate: 15 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
"#oss-github",
|
||||
{ x: 0, y: [1200, -10], rotate: -2 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-github",
|
||||
{ y: 1200, x: 10, rotate: -2 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
"#oss-github",
|
||||
{ x: -100, y: "-50vh", rotate: 6.26 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-github",
|
||||
{ x: 0, y: "0vh", rotate: 6.26 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
"#oss-twitter",
|
||||
{ x: 0, y: [1200, 10], rotate: -3 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-twitter",
|
||||
{ y: 1200, x: -10, rotate: -3 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
"#oss-twitter",
|
||||
{ x: 100, y: "-65vh", rotate: -15 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-twitter",
|
||||
{ x: 0, y: "0vh", rotate: -15 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
"#oss-youtube",
|
||||
{ x: 0, y: [1200, 5], rotate: 2 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-youtube",
|
||||
{ y: 1200, x: -5, rotate: 2 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
"#oss-youtube",
|
||||
{ x: -100, y: "-50vh", rotate: -3.77 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-youtube",
|
||||
{ x: 0, y: "0vh", rotate: -3.77 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
"#oss-commits",
|
||||
{ x: 0, y: [1200, -4], rotate: -1 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-commits",
|
||||
{ y: 1200, x: 4, rotate: -1 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
"#oss-commits",
|
||||
{ x: 100, y: "-70vh", rotate: -10.2 },
|
||||
animationOptions,
|
||||
),
|
||||
reversed: animation(
|
||||
"#oss-commits",
|
||||
{ x: 0, y: "0vh", rotate: -10.2 },
|
||||
animationOptions,
|
||||
),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
function isMobile(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
return window?.innerWidth < 1024;
|
||||
}
|
||||
|
||||
const animScale: Scale = [0, animations.length - 1];
|
||||
const percentScale: Scale = [0.1, 0.8];
|
||||
const scrollHandler = createScrollHandler(
|
||||
animations.map(({ mobile, desktop }, i) => {
|
||||
return {
|
||||
percentage: isMobile() ? toScale(i, animScale, percentScale) : 0.1,
|
||||
whenAfter() {
|
||||
const { main, reversed } = isMobile() ? mobile : desktop;
|
||||
|
||||
main.play();
|
||||
return reversed.play;
|
||||
const animations: {
|
||||
mobile: {
|
||||
main: Animation;
|
||||
reversed: Animation;
|
||||
};
|
||||
desktop: {
|
||||
main: Animation;
|
||||
reversed: Animation;
|
||||
};
|
||||
}[] = [
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
'#oss-discord',
|
||||
{ x: 0, y: [1200, 0], rotate: 1 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation('#oss-discord', { y: 1200, x: 0, rotate: 1 }, animationOptions)
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
'#oss-discord',
|
||||
{ x: 20, y: '-75vh', rotate: 15 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation(
|
||||
'#oss-discord',
|
||||
{ x: -100, y: '0vh', rotate: 15 },
|
||||
animationOptions
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
}),
|
||||
);
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
'#oss-github',
|
||||
{ x: 0, y: [1200, -10], rotate: -2 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation('#oss-github', { y: 1200, x: 10, rotate: -2 }, animationOptions)
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
'#oss-github',
|
||||
{ x: -100, y: '-50vh', rotate: 6.26 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation(
|
||||
'#oss-github',
|
||||
{ x: 0, y: '0vh', rotate: 6.26 },
|
||||
animationOptions
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
'#oss-twitter',
|
||||
{ x: 0, y: [1200, 10], rotate: -3 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation(
|
||||
'#oss-twitter',
|
||||
{ y: 1200, x: -10, rotate: -3 },
|
||||
animationOptions
|
||||
)
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
'#oss-twitter',
|
||||
{ x: 100, y: '-65vh', rotate: -15 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation(
|
||||
'#oss-twitter',
|
||||
{ x: 0, y: '0vh', rotate: -15 },
|
||||
animationOptions
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
'#oss-youtube',
|
||||
{ x: 0, y: [1200, 5], rotate: 2 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation('#oss-youtube', { y: 1200, x: -5, rotate: 2 }, animationOptions)
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
'#oss-youtube',
|
||||
{ x: -100, y: '-50vh', rotate: -3.77 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation(
|
||||
'#oss-youtube',
|
||||
{ x: 0, y: '0vh', rotate: -3.77 },
|
||||
animationOptions
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
mobile: {
|
||||
main: animation(
|
||||
'#oss-commits',
|
||||
{ x: 0, y: [1200, -4], rotate: -1 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation('#oss-commits', { y: 1200, x: 4, rotate: -1 }, animationOptions)
|
||||
},
|
||||
desktop: {
|
||||
main: animation(
|
||||
'#oss-commits',
|
||||
{ x: 100, y: '-70vh', rotate: -10.2 },
|
||||
animationOptions
|
||||
),
|
||||
reversed: animation(
|
||||
'#oss-commits',
|
||||
{ x: 0, y: '0vh', rotate: -10.2 },
|
||||
animationOptions
|
||||
)
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
function isMobile(): boolean {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return window?.innerWidth < 1024;
|
||||
}
|
||||
|
||||
const animScale: Scale = [0, animations.length - 1];
|
||||
const percentScale: Scale = [0.1, 0.8];
|
||||
const scrollHandler = createScrollHandler(
|
||||
animations.map(({ mobile, desktop }, i) => {
|
||||
return {
|
||||
percentage: isMobile() ? toScale(i, animScale, percentScale) : 0.1,
|
||||
whenAfter() {
|
||||
const { main, reversed } = isMobile() ? mobile : desktop;
|
||||
|
||||
main.play();
|
||||
return reversed.play;
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
</script>
|
||||
|
||||
<div
|
||||
id="open-source"
|
||||
use:scroll
|
||||
on:web-scroll={({ detail }) => {
|
||||
const { percentage } = detail;
|
||||
scrollHandler(percentage);
|
||||
}}
|
||||
on:web-resize={({ detail }) => {
|
||||
scrollHandler.reset();
|
||||
const { percentage } = detail;
|
||||
id="open-source"
|
||||
use:scroll
|
||||
on:web-scroll={({ detail }) => {
|
||||
const { percentage } = detail;
|
||||
scrollHandler(percentage);
|
||||
}}
|
||||
on:web-resize={({ detail }) => {
|
||||
scrollHandler.reset();
|
||||
const { percentage } = detail;
|
||||
|
||||
scrollHandler(percentage);
|
||||
}}
|
||||
scrollHandler(percentage);
|
||||
}}
|
||||
>
|
||||
<div class="sticky-wrapper">
|
||||
<h3 class="web-display !text-primary">Powered by Open Source</h3>
|
||||
<div class="sticky-wrapper">
|
||||
<h3 class="web-display !text-primary">Powered by Open Source</h3>
|
||||
|
||||
<div class="cards-wrapper">
|
||||
<a
|
||||
href="/discord"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-discord"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-discord web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="Discord"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">17k+ Discord Members</div>
|
||||
</a>
|
||||
<div class="cards-wrapper">
|
||||
<a
|
||||
href="/discord"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-discord"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-discord web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="Discord"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">17k+ Discord Members</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-github"
|
||||
href="https://github.com/appwrite/appwrite"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-github web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="GitHub"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">
|
||||
{GITHUB_STARS}+ GitHub Stars
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-github"
|
||||
href="https://github.com/appwrite/appwrite"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-github web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="GitHub"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">
|
||||
{GITHUB_STARS}+ GitHub Stars
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://twitter.com/appwrite"
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-twitter"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-x web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="Twitter"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">128k+ Twitter Followers</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com/appwrite"
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-twitter"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-x web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="Twitter"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">128k+ Twitter Followers</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://www.youtube.com/@Appwrite"
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-youtube"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-youtube web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="YouTube"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">7k+ Youtube Subscribers</div>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.youtube.com/@Appwrite"
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-youtube"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-youtube web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="YouTube"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">7k+ Youtube Subscribers</div>
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-commits"
|
||||
href="https://github.com/appwrite/appwrite"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-github web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="GitHub"
|
||||
/>
|
||||
<a
|
||||
class="web-card is-white web-u-min-block-size-320 oss-card flex flex-col"
|
||||
id="oss-commits"
|
||||
href="https://github.com/appwrite/appwrite"
|
||||
>
|
||||
<div class="flex flex-col justify-between gap-8">
|
||||
<span
|
||||
class="web-icon-github web-u-font-size-40"
|
||||
aria-hidden="true"
|
||||
aria-label="GitHub"
|
||||
/>
|
||||
</div>
|
||||
<div class="web-title mt-auto">21k+ Code Commits</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="web-title mt-auto">21k+ Code Commits</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
#open-source {
|
||||
min-height: 150vh;
|
||||
height: 3500px;
|
||||
position: relative;
|
||||
#open-source {
|
||||
min-height: 150vh;
|
||||
height: 3500px;
|
||||
position: relative;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
height: 1500px;
|
||||
}
|
||||
}
|
||||
|
||||
.sticky-wrapper {
|
||||
position: sticky;
|
||||
top: -15vh;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1.25rem;
|
||||
padding-inline: 1.25rem;
|
||||
|
||||
width: 100%;
|
||||
height: 130vh;
|
||||
|
||||
text-align: center;
|
||||
|
||||
&::after {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
hsl(var(--web-color-background)) 0%,
|
||||
hsl(var(--web-color-background) / 0) 5%
|
||||
);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@media (min-width: 1024px) {
|
||||
height: 1500px;
|
||||
}
|
||||
}
|
||||
|
||||
.cards-wrapper {
|
||||
position: relative;
|
||||
height: 22.5rem;
|
||||
margin-top: 80px;
|
||||
.sticky-wrapper {
|
||||
position: sticky;
|
||||
top: -15vh;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1.25rem;
|
||||
padding-inline: 1.25rem;
|
||||
|
||||
width: 100%;
|
||||
height: 130vh;
|
||||
|
||||
text-align: center;
|
||||
|
||||
&::after {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
hsl(var(--web-color-background)) 0%,
|
||||
hsl(var(--web-color-background) / 0) 5%
|
||||
);
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.cards-wrapper {
|
||||
position: relative;
|
||||
height: 22.5rem;
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
h3 {
|
||||
max-width: 61.375rem;
|
||||
}
|
||||
|
||||
.cards-wrapper {
|
||||
position: absolute;
|
||||
height: 100vh;
|
||||
width: clamp(1024px, 90vw, 1440px);
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
h3 {
|
||||
max-width: 61.375rem;
|
||||
}
|
||||
|
||||
.cards-wrapper {
|
||||
position: absolute;
|
||||
height: 100vh;
|
||||
width: clamp(1024px, 90vw, 1440px);
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.oss-card {
|
||||
background: linear-gradient(
|
||||
106deg,
|
||||
rgba(255, 255, 255, 0.72) 0%,
|
||||
rgba(255, 255, 255, 0.8) 41.9%,
|
||||
rgba(255, 255, 255, 0.6) 100%
|
||||
);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
--card-padding: 2rem;
|
||||
|
||||
--w: clamp(306px, 50vw, 22.125rem);
|
||||
--h: 20.125rem;
|
||||
width: var(--w);
|
||||
height: var(--h);
|
||||
text-align: left;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
position: absolute;
|
||||
left: calc(50% - calc(var(--w) / 2));
|
||||
transform: translateX(-1200px);
|
||||
|
||||
[class*="icon"] {
|
||||
opacity: 48%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.oss-card {
|
||||
left: unset;
|
||||
transform: unset;
|
||||
background: linear-gradient(
|
||||
106deg,
|
||||
rgba(255, 255, 255, 0.72) 0%,
|
||||
rgba(255, 255, 255, 0.8) 41.9%,
|
||||
rgba(255, 255, 255, 0.6) 100%
|
||||
);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
--card-padding: 2rem;
|
||||
|
||||
--w: clamp(306px, 50vw, 22.125rem);
|
||||
--h: 20.125rem;
|
||||
width: var(--w);
|
||||
height: var(--h);
|
||||
text-align: left;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
position: absolute;
|
||||
left: calc(50% - calc(var(--w) / 2));
|
||||
transform: translateX(-1200px);
|
||||
|
||||
[class*='icon'] {
|
||||
opacity: 48%;
|
||||
}
|
||||
}
|
||||
|
||||
#oss-discord {
|
||||
bottom: -400px;
|
||||
left: 1%;
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.oss-card {
|
||||
left: unset;
|
||||
transform: unset;
|
||||
}
|
||||
|
||||
#oss-github {
|
||||
bottom: -400px;
|
||||
left: 19%;
|
||||
}
|
||||
#oss-discord {
|
||||
bottom: -400px;
|
||||
left: 1%;
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
|
||||
#oss-twitter {
|
||||
bottom: -400px;
|
||||
left: clamp(20%, 22vw, 29%);
|
||||
}
|
||||
#oss-github {
|
||||
bottom: -400px;
|
||||
left: 19%;
|
||||
}
|
||||
|
||||
#oss-youtube {
|
||||
bottom: -400px;
|
||||
left: clamp(64%, calc(1024px - 10vw), 70%);
|
||||
}
|
||||
#oss-twitter {
|
||||
bottom: -400px;
|
||||
left: clamp(20%, 22vw, 29%);
|
||||
}
|
||||
|
||||
#oss-commits {
|
||||
bottom: -400px;
|
||||
right: 10%;
|
||||
#oss-youtube {
|
||||
bottom: -400px;
|
||||
left: clamp(64%, calc(1024px - 10vw), 70%);
|
||||
}
|
||||
|
||||
#oss-commits {
|
||||
bottom: -400px;
|
||||
right: 10%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
export let id: string | undefined = undefined;
|
||||
export let id: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<div class="phone" {id}>
|
||||
<div class="inner">
|
||||
<slot />
|
||||
</div>
|
||||
<div class="inner">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
<script lang="ts">
|
||||
import AutoHeight from "../AutoBox.svelte";
|
||||
import AutoHeight from '../AutoBox.svelte';
|
||||
</script>
|
||||
|
||||
<div class="anim-box">
|
||||
<div class="top"><slot name="top" /></div>
|
||||
<div class="content">
|
||||
<AutoHeight>
|
||||
<slot />
|
||||
</AutoHeight>
|
||||
</div>
|
||||
<div class="top"><slot name="top" /></div>
|
||||
<div class="content">
|
||||
<AutoHeight>
|
||||
<slot />
|
||||
</AutoHeight>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "$scss/abstract/mixins/border-gradient" as gradients;
|
||||
@use '$scss/abstract/mixins/border-gradient' as gradients;
|
||||
|
||||
.anim-box {
|
||||
@include gradients.border-gradient;
|
||||
--m-border-radius: 1rem;
|
||||
--m-border-gradient-before: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.12) 0%,
|
||||
rgba(255, 255, 255, 0) 125.11%
|
||||
);
|
||||
border-radius: var(--m-border-radius);
|
||||
background: hsl(var(--web-color-card));
|
||||
backdrop-filter: blur(8px);
|
||||
.anim-box {
|
||||
@include gradients.border-gradient;
|
||||
--m-border-radius: 1rem;
|
||||
--m-border-gradient-before: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.12) 0%,
|
||||
rgba(255, 255, 255, 0) 125.11%
|
||||
);
|
||||
border-radius: var(--m-border-radius);
|
||||
background: hsl(var(--web-color-card));
|
||||
backdrop-filter: blur(8px);
|
||||
|
||||
padding: 0.5rem;
|
||||
padding-block-start: 0;
|
||||
padding: 0.5rem;
|
||||
padding-block-start: 0;
|
||||
|
||||
text-align: left;
|
||||
text-align: left;
|
||||
|
||||
.top {
|
||||
color: var(--greyscale-50, #ededf0);
|
||||
font-family: Aeonik Pro;
|
||||
font-size: 1.25rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 2rem; /* 160% */
|
||||
letter-spacing: -0.0125rem;
|
||||
.top {
|
||||
color: var(--greyscale-50, #ededf0);
|
||||
font-family: Aeonik Pro;
|
||||
font-size: 1.25rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 2rem; /* 160% */
|
||||
letter-spacing: -0.0125rem;
|
||||
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.content {
|
||||
border-radius: 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(30px);
|
||||
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
border-radius: 0.75rem;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(30px);
|
||||
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,224 +1,220 @@
|
||||
<script>
|
||||
import { objectKeys } from "$lib/utils/object";
|
||||
import { infos } from "./Products.svelte";
|
||||
import { objectKeys } from '$lib/utils/object';
|
||||
import { infos } from './Products.svelte';
|
||||
</script>
|
||||
|
||||
<div class="outside">
|
||||
<div class="wrapper">
|
||||
<span class="web-badges web-eyebrow !text-white">Products_</span>
|
||||
<div class="wrapper">
|
||||
<span class="web-badges web-eyebrow !text-white">Products_</span>
|
||||
|
||||
<h2 class="web-display web-u-color-text-primary mt-4">
|
||||
Your backend, minus the hassle
|
||||
</h2>
|
||||
<h2 class="web-display web-u-color-text-primary mt-4">Your backend, minus the hassle</h2>
|
||||
|
||||
<p class="web-description mt-4">
|
||||
Build secure and scalable applications with less code. Add authentication,
|
||||
databases, storage, and more using Appwrite's development platform.
|
||||
</p>
|
||||
<p class="web-description mt-4">
|
||||
Build secure and scalable applications with less code. Add authentication, databases,
|
||||
storage, and more using Appwrite's development platform.
|
||||
</p>
|
||||
|
||||
<div class="infos">
|
||||
{#each objectKeys(infos) as prod, i}
|
||||
{@const info = infos[prod]}
|
||||
{@const isLast = i === objectKeys(infos).length - 1}
|
||||
<div class="infos">
|
||||
{#each objectKeys(infos) as prod, i}
|
||||
{@const info = infos[prod]}
|
||||
{@const isLast = i === objectKeys(infos).length - 1}
|
||||
|
||||
{#if info}
|
||||
<div class="info">
|
||||
<h3>
|
||||
<img src={info.icon.active} alt="" />
|
||||
<span class="web-label web-u-color-text-primary"
|
||||
>{info.title}</span
|
||||
>
|
||||
</h3>
|
||||
{#if info}
|
||||
<div class="info">
|
||||
<h3>
|
||||
<img src={info.icon.active} alt="" />
|
||||
<span class="web-label web-u-color-text-primary">{info.title}</span>
|
||||
</h3>
|
||||
|
||||
<h4 class="web-title">{info.subtitle}</h4>
|
||||
<h4 class="web-title">{info.subtitle}</h4>
|
||||
<p>
|
||||
{info.description}
|
||||
</p>
|
||||
<ul class="features">
|
||||
{#each info.features as feature}
|
||||
<li>{feature}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if info.shot}
|
||||
<enhanced:img class="img" src={info.shot} alt="" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !isLast}
|
||||
<hr />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="post-wrapper">
|
||||
<img src="/images/products/post.png" alt="" />
|
||||
|
||||
<h2>See your products grow</h2>
|
||||
<p>
|
||||
{info.description}
|
||||
Keep track of your projects progress on the Appwrite Console and see them grow into
|
||||
products users love and use every day.
|
||||
</p>
|
||||
<ul class="features">
|
||||
{#each info.features as feature}
|
||||
<li>{feature}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if info.shot}
|
||||
<enhanced:img class="img" src={info.shot} alt="" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !isLast}
|
||||
<hr />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="post-wrapper">
|
||||
<img src="/images/products/post.png" alt="" />
|
||||
|
||||
<h2>See your products grow</h2>
|
||||
<p>
|
||||
Keep track of your projects progress on the Appwrite Console and see
|
||||
them grow into products users love and use every day.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="img-overlay" />
|
||||
<div class="img-overlay" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.outside {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
|
||||
.img-overlay {
|
||||
content: "";
|
||||
background: linear-gradient(to bottom, transparent 0%, black 40%);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
height: 30rem;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1399px) {
|
||||
.outside {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
|
||||
.wrapper {
|
||||
--padding-inline: 1.25rem;
|
||||
|
||||
padding-block-start: 5rem;
|
||||
padding-inline: var(--padding-inline);
|
||||
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.infos {
|
||||
margin-block-start: 3rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3rem;
|
||||
|
||||
.info {
|
||||
h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
|
||||
.web-label {
|
||||
margin-block-start: 0.25rem;
|
||||
.img-overlay {
|
||||
content: '';
|
||||
background: linear-gradient(to bottom, transparent 0%, black 40%);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
height: 30rem;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: hsl(var(--web-color-primary));
|
||||
margin-block-start: 0.75rem;
|
||||
}
|
||||
@media (max-width: 1399px) {
|
||||
.outside {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
.wrapper {
|
||||
--padding-inline: 1.25rem;
|
||||
|
||||
padding-block-start: 5rem;
|
||||
padding-inline: var(--padding-inline);
|
||||
|
||||
max-width: 600px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.infos {
|
||||
margin-block-start: 3rem;
|
||||
|
||||
.features {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-block-start: 2rem;
|
||||
gap: 3rem;
|
||||
|
||||
li {
|
||||
--marker-size: 1.25rem;
|
||||
--margin-left: calc(var(--marker-size) + 0.75rem);
|
||||
position: relative;
|
||||
margin-inline-start: var(--margin-left);
|
||||
.info {
|
||||
h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
.web-label {
|
||||
margin-block-start: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
left: calc(var(--margin-left) * -1);
|
||||
top: 50%;
|
||||
width: var(--marker-size);
|
||||
height: var(--marker-size);
|
||||
h4 {
|
||||
color: hsl(var(--web-color-primary));
|
||||
margin-block-start: 0.75rem;
|
||||
}
|
||||
|
||||
transform: translateY(-50%);
|
||||
p {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
background: url("/images/icons/colored/check.svg") no-repeat;
|
||||
}
|
||||
.features {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-block-start: 2rem;
|
||||
|
||||
li {
|
||||
--marker-size: 1.25rem;
|
||||
--margin-left: calc(var(--marker-size) + 0.75rem);
|
||||
position: relative;
|
||||
margin-inline-start: var(--margin-left);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
left: calc(var(--margin-left) * -1);
|
||||
top: 50%;
|
||||
width: var(--marker-size);
|
||||
height: var(--marker-size);
|
||||
|
||||
transform: translateY(-50%);
|
||||
|
||||
background: url('/images/icons/colored/check.svg') no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
inline-size: 100%;
|
||||
block-size: auto;
|
||||
margin-block-start: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.img {
|
||||
inline-size: 100%;
|
||||
block-size: auto;
|
||||
margin-block-start: 2.5rem;
|
||||
}
|
||||
hr {
|
||||
border: 1px solid hsl(var(--web-color-smooth));
|
||||
margin-inline: calc(var(--padding-inline) * -1);
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid hsl(var(--web-color-smooth));
|
||||
margin-inline: calc(var(--padding-inline) * -1);
|
||||
.post-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
|
||||
position: relative;
|
||||
width: 100%;
|
||||
/* overflow: hidden; */
|
||||
|
||||
padding-block-start: 35rem;
|
||||
padding-block-end: 5rem;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-block-size: unset;
|
||||
max-inline-size: unset;
|
||||
top: 5rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 37.5rem;
|
||||
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--greyscale-50, #ededf0);
|
||||
text-align: center;
|
||||
|
||||
/* Responsive/Display */
|
||||
font-family: Aeonik Pro;
|
||||
font-size: 48px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 50px; /* 104.167% */
|
||||
letter-spacing: -0.48px;
|
||||
max-width: 20rem;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--greyscale-400, #97979b);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 22px; /* 137.5% */
|
||||
letter-spacing: -0.072px;
|
||||
margin-block-start: 1rem;
|
||||
max-width: 20rem;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.post-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
|
||||
position: relative;
|
||||
width: 100%;
|
||||
/* overflow: hidden; */
|
||||
|
||||
padding-block-start: 35rem;
|
||||
padding-block-end: 5rem;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-block-size: unset;
|
||||
max-inline-size: unset;
|
||||
top: 5rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 37.5rem;
|
||||
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--greyscale-50, #ededf0);
|
||||
text-align: center;
|
||||
|
||||
/* Responsive/Display */
|
||||
font-family: Aeonik Pro;
|
||||
font-size: 48px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 50px; /* 104.167% */
|
||||
letter-spacing: -0.48px;
|
||||
max-width: 20rem;
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--greyscale-400, #97979b);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 22px; /* 137.5% */
|
||||
letter-spacing: -0.072px;
|
||||
margin-block-start: 1rem;
|
||||
max-width: 20rem;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { createCheckbox, melt } from "@melt-ui/svelte";
|
||||
import { createCheckbox, melt } from '@melt-ui/svelte';
|
||||
|
||||
export let checked = false;
|
||||
export let checked = false;
|
||||
|
||||
const {
|
||||
elements: { root },
|
||||
states: { checked: localChecked },
|
||||
helpers: { isChecked },
|
||||
} = createCheckbox({
|
||||
onCheckedChange({ next }) {
|
||||
if (typeof next === "boolean") {
|
||||
checked = next;
|
||||
}
|
||||
return next;
|
||||
},
|
||||
});
|
||||
const {
|
||||
elements: { root },
|
||||
states: { checked: localChecked },
|
||||
helpers: { isChecked }
|
||||
} = createCheckbox({
|
||||
onCheckedChange({ next }) {
|
||||
if (typeof next === 'boolean') {
|
||||
checked = next;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
});
|
||||
|
||||
$: localChecked.set(checked);
|
||||
$: localChecked.set(checked);
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<button use:melt={$root} class="anim-checkbox">
|
||||
{#if $isChecked}
|
||||
<span class="web-icon-check" />
|
||||
{/if}
|
||||
</button>
|
||||
<button use:melt={$root} class="anim-checkbox">
|
||||
{#if $isChecked}
|
||||
<span class="web-icon-check" />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.wrapper {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.anim-checkbox {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
border-radius: 0.125rem;
|
||||
border: 1.5px solid var(--greyscale-500, #818186);
|
||||
|
||||
position: relative;
|
||||
|
||||
&:global(.anim-checkbox[data-state="checked"]) {
|
||||
background-color: #7c67fe;
|
||||
border-color: #7c67fe;
|
||||
.wrapper {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
[class*="icon-"] {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
.anim-checkbox {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
}
|
||||
border-radius: 0.125rem;
|
||||
border: 1.5px solid var(--greyscale-500, #818186);
|
||||
|
||||
position: relative;
|
||||
|
||||
&:global(.anim-checkbox[data-state='checked']) {
|
||||
background-color: #7c67fe;
|
||||
border-color: #7c67fe;
|
||||
}
|
||||
}
|
||||
|
||||
[class*='icon-'] {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { getInitials } from "$lib/animations";
|
||||
import { fly } from "svelte/transition";
|
||||
import { authController } from ".";
|
||||
import { flip } from "$lib/utils/flip";
|
||||
import { getInitials } from '$lib/animations';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { authController } from '.';
|
||||
import { flip } from '$lib/utils/flip';
|
||||
|
||||
const { state } = authController;
|
||||
const { state } = authController;
|
||||
|
||||
type AuthEntry = {
|
||||
avatar: string;
|
||||
name: string;
|
||||
email: string;
|
||||
id: number;
|
||||
};
|
||||
$: authData = [
|
||||
$state.submitted
|
||||
? {
|
||||
avatar: getInitials($state.name),
|
||||
name: $state.name,
|
||||
email: $state.email,
|
||||
id: 0,
|
||||
type AuthEntry = {
|
||||
avatar: string;
|
||||
name: string;
|
||||
email: string;
|
||||
id: number;
|
||||
};
|
||||
$: authData = [
|
||||
$state.submitted
|
||||
? {
|
||||
avatar: getInitials($state.name),
|
||||
name: $state.name,
|
||||
email: $state.email,
|
||||
id: 0
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
avatar: 'BD',
|
||||
name: 'Benjamin Davis',
|
||||
email: 'benjamin.davis@example.com',
|
||||
id: 1
|
||||
},
|
||||
{
|
||||
avatar: 'OS',
|
||||
name: 'Olivia Smith',
|
||||
email: 'olivia.smith@example.com',
|
||||
id: 2
|
||||
},
|
||||
{
|
||||
avatar: 'EW',
|
||||
name: 'Ethan Wilson',
|
||||
email: 'ethan.wilson@example.com',
|
||||
id: 3
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
avatar: "BD",
|
||||
name: "Benjamin Davis",
|
||||
email: "benjamin.davis@example.com",
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
avatar: "OS",
|
||||
name: "Olivia Smith",
|
||||
email: "olivia.smith@example.com",
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
avatar: "EW",
|
||||
name: "Ethan Wilson",
|
||||
email: "ethan.wilson@example.com",
|
||||
id: 3,
|
||||
},
|
||||
].filter(Boolean) as AuthEntry[];
|
||||
].filter(Boolean) as AuthEntry[];
|
||||
</script>
|
||||
|
||||
<div class="pseudo-table">
|
||||
<div class="header">
|
||||
<span class="web-eyebrow">Name</span>
|
||||
<span class="web-eyebrow">Identifier</span>
|
||||
</div>
|
||||
{#each authData as user (user.id)}
|
||||
<div
|
||||
class="row"
|
||||
in:fly={{ duration: 100, x: -16, delay: 100 }}
|
||||
out:fly={{ duration: 100, x: -16 }}
|
||||
animate:flip={{ duration: 150 }}
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="avatar is-size-small">{user.avatar}</div>
|
||||
<span class="truncated">{user.name}</span>
|
||||
</div>
|
||||
<span class="truncated">{user.email}</span>
|
||||
<div class="header">
|
||||
<span class="web-eyebrow">Name</span>
|
||||
<span class="web-eyebrow">Identifier</span>
|
||||
</div>
|
||||
{/each}
|
||||
{#each authData as user (user.id)}
|
||||
<div
|
||||
class="row"
|
||||
in:fly={{ duration: 100, x: -16, delay: 100 }}
|
||||
out:fly={{ duration: 100, x: -16 }}
|
||||
animate:flip={{ duration: 150 }}
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="avatar is-size-small">{user.avatar}</div>
|
||||
<span class="truncated">{user.name}</span>
|
||||
</div>
|
||||
<span class="truncated">{user.email}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import Code from "$lib/animations/CodeWindow/Code.svelte";
|
||||
import { authController } from ".";
|
||||
import Code from '$lib/animations/CodeWindow/Code.svelte';
|
||||
import { authController } from '.';
|
||||
|
||||
const { state } = authController;
|
||||
const { state } = authController;
|
||||
|
||||
$: content = `
|
||||
$: content = `
|
||||
const result = account.create(
|
||||
ID.unique(),
|
||||
'${$state.email}',
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
<script lang="ts">
|
||||
import { Switch } from "$lib/components";
|
||||
import { objectKeys } from "$lib/utils/object";
|
||||
import { authController } from ".";
|
||||
import { Switch } from '$lib/components';
|
||||
import { objectKeys } from '$lib/utils/object';
|
||||
import { authController } from '.';
|
||||
|
||||
const { state } = authController;
|
||||
const { state } = authController;
|
||||
|
||||
const getIcon = (provider: string) => {
|
||||
return `web-icon-${provider.toLowerCase()}`;
|
||||
};
|
||||
const getIcon = (provider: string) => {
|
||||
return `web-icon-${provider.toLowerCase()}`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="auth-controls">
|
||||
{#each objectKeys($state.controls) as provider, i}
|
||||
{@const isLast = i === objectKeys($state.controls).length - 1}
|
||||
<div>
|
||||
<span class={getIcon(provider)} />
|
||||
<span>{provider}</span>
|
||||
<Switch bind:checked={$state.controls[provider]} />
|
||||
</div>
|
||||
{#if !isLast}
|
||||
<div class="sep" />
|
||||
{/if}
|
||||
{/each}
|
||||
{#each objectKeys($state.controls) as provider, i}
|
||||
{@const isLast = i === objectKeys($state.controls).length - 1}
|
||||
<div>
|
||||
<span class={getIcon(provider)} />
|
||||
<span>{provider}</span>
|
||||
<Switch bind:checked={$state.controls[provider]} />
|
||||
</div>
|
||||
{#if !isLast}
|
||||
<div class="sep" />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.auth-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.auth-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
padding: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
|
||||
width: 12.5rem;
|
||||
width: 12.5rem;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> :nth-child(2) {
|
||||
margin-left: 0.75rem;
|
||||
color: hsl(var(--web-color-white));
|
||||
> :nth-child(2) {
|
||||
margin-left: 0.75rem;
|
||||
color: hsl(var(--web-color-white));
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.00394rem;
|
||||
}
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.00394rem;
|
||||
}
|
||||
|
||||
> :global(:nth-child(3)) {
|
||||
margin-left: auto;
|
||||
}
|
||||
> :global(:nth-child(3)) {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.sep {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
margin-block: 0.5rem;
|
||||
}
|
||||
|
||||
[class*='icon-'] {
|
||||
--size: 2rem;
|
||||
font-size: var(--size);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
color: hsl(var(--web-color-greayscale-50));
|
||||
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sep {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
margin-block: 0.5rem;
|
||||
}
|
||||
|
||||
[class*="icon-"] {
|
||||
--size: 2rem;
|
||||
font-size: var(--size);
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
color: hsl(var(--web-color-greayscale-50));
|
||||
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,110 +1,95 @@
|
||||
import Box from "./box.svelte";
|
||||
import Code from "./code.svelte";
|
||||
import Controls from "./controls.svelte";
|
||||
import Phone from "./phone.svelte";
|
||||
import Box from './box.svelte';
|
||||
import Code from './code.svelte';
|
||||
import Controls from './controls.svelte';
|
||||
import Phone from './phone.svelte';
|
||||
|
||||
export const Auth = {
|
||||
Phone,
|
||||
Box,
|
||||
Code,
|
||||
Controls,
|
||||
Phone,
|
||||
Box,
|
||||
Code,
|
||||
Controls
|
||||
};
|
||||
|
||||
import { safeAnimate, sleep, write } from "$lib/animations";
|
||||
import { createResettable } from "$lib/utils/resettable";
|
||||
import { getElSelector } from "../Products.svelte";
|
||||
import { safeAnimate, sleep, write } from '$lib/animations';
|
||||
import { createResettable } from '$lib/utils/resettable';
|
||||
import { getElSelector } from '../Products.svelte';
|
||||
|
||||
type State = {
|
||||
email: string;
|
||||
password: string;
|
||||
name: string;
|
||||
email: string;
|
||||
password: string;
|
||||
name: string;
|
||||
|
||||
showControls: boolean;
|
||||
submitted: boolean;
|
||||
controls: {
|
||||
GitHub: boolean;
|
||||
Google: boolean;
|
||||
Apple: boolean;
|
||||
Microsoft: boolean;
|
||||
};
|
||||
showControls: boolean;
|
||||
submitted: boolean;
|
||||
controls: {
|
||||
GitHub: boolean;
|
||||
Google: boolean;
|
||||
Apple: boolean;
|
||||
Microsoft: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const state = createResettable<State>({
|
||||
email: "",
|
||||
password: "",
|
||||
name: "Walter O'Brian",
|
||||
showControls: false,
|
||||
submitted: false,
|
||||
controls: {
|
||||
GitHub: true,
|
||||
Google: false,
|
||||
Apple: false,
|
||||
Microsoft: false,
|
||||
},
|
||||
email: '',
|
||||
password: '',
|
||||
name: "Walter O'Brian",
|
||||
showControls: false,
|
||||
submitted: false,
|
||||
controls: {
|
||||
GitHub: true,
|
||||
Google: false,
|
||||
Apple: false,
|
||||
Microsoft: false
|
||||
}
|
||||
});
|
||||
|
||||
const emailToSet = "walterobrian@example.com";
|
||||
const passwordToSet = "password";
|
||||
const emailToSet = 'walterobrian@example.com';
|
||||
const passwordToSet = 'password';
|
||||
|
||||
const execute = async () => {
|
||||
const phone = getElSelector("phone");
|
||||
const box = getElSelector("box");
|
||||
const code = getElSelector("code");
|
||||
const controls = getElSelector("controls");
|
||||
const phone = getElSelector('phone');
|
||||
const box = getElSelector('box');
|
||||
const code = getElSelector('code');
|
||||
const controls = getElSelector('controls');
|
||||
|
||||
// Reset
|
||||
const { update } = state.reset();
|
||||
// Reset
|
||||
const { update } = state.reset();
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(box, { x: 310, y: 140, opacity: 0 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
safeAnimate(code, { x: 200, y: 460, opacity: 0 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
safeAnimate(phone, { x: 0, y: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(controls, { x: 420, y: 0, opacity: 0 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(box, { x: 310, y: 140, opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(code, { x: 200, y: 460, opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(phone, { x: 0, y: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(controls, { x: 420, y: 0, opacity: 0 }, { duration: 0.5 })?.finished
|
||||
]);
|
||||
|
||||
// Start
|
||||
await safeAnimate(
|
||||
box,
|
||||
{ y: [48, 140], opacity: 1 },
|
||||
{ duration: 0.25, delay: 0.25 },
|
||||
)?.finished;
|
||||
// Start
|
||||
await safeAnimate(box, { y: [48, 140], opacity: 1 }, { duration: 0.25, delay: 0.25 })?.finished;
|
||||
|
||||
await sleep(50);
|
||||
await sleep(50);
|
||||
|
||||
await write(emailToSet, (v) => update((p) => ({ ...p, email: v })), 300);
|
||||
await sleep(50);
|
||||
await write(emailToSet, (v) => update((p) => ({ ...p, email: v })), 300);
|
||||
await sleep(50);
|
||||
|
||||
await write(
|
||||
passwordToSet,
|
||||
(v) => update((p) => ({ ...p, password: v })),
|
||||
300,
|
||||
);
|
||||
await sleep(50);
|
||||
await write(passwordToSet, (v) => update((p) => ({ ...p, password: v })), 300);
|
||||
await sleep(50);
|
||||
|
||||
await safeAnimate(
|
||||
code,
|
||||
{ x: [200, 200], y: [460 + 16, 460], opacity: [0, 1] },
|
||||
{ duration: 0.25 },
|
||||
)?.finished;
|
||||
await safeAnimate(
|
||||
code,
|
||||
{ x: [200, 200], y: [460 + 16, 460], opacity: [0, 1] },
|
||||
{ duration: 0.25 }
|
||||
)?.finished;
|
||||
|
||||
await sleep(350);
|
||||
await sleep(350);
|
||||
|
||||
update((p) => ({ ...p, submitted: true }));
|
||||
update((p) => ({ ...p, submitted: true }));
|
||||
|
||||
await sleep(1000);
|
||||
await sleep(1000);
|
||||
|
||||
update((p) => ({ ...p, showControls: true }));
|
||||
safeAnimate(
|
||||
controls,
|
||||
{ x: [420, 420], y: [16, 0], opacity: 1 },
|
||||
{ duration: 0.5 },
|
||||
);
|
||||
update((p) => ({ ...p, showControls: true }));
|
||||
safeAnimate(controls, { x: [420, 420], y: [16, 0], opacity: 1 }, { duration: 0.5 });
|
||||
};
|
||||
|
||||
export const authController = {
|
||||
execute,
|
||||
state,
|
||||
execute,
|
||||
state
|
||||
};
|
||||
|
||||
@@ -1,229 +1,220 @@
|
||||
<script lang="ts">
|
||||
import { fade } from "svelte/transition";
|
||||
import { authController } from ".";
|
||||
import { objectKeys } from "$lib/utils/object";
|
||||
import { flip } from "$lib/utils/flip";
|
||||
import { fade } from 'svelte/transition';
|
||||
import { authController } from '.';
|
||||
import { objectKeys } from '$lib/utils/object';
|
||||
import { flip } from '$lib/utils/flip';
|
||||
|
||||
const { state } = authController;
|
||||
const { state } = authController;
|
||||
|
||||
$: controlsEnabled =
|
||||
$state.showControls && Object.values($state.controls).some(Boolean);
|
||||
$: controlsEnabled = $state.showControls && Object.values($state.controls).some(Boolean);
|
||||
</script>
|
||||
|
||||
<div data-theme-ignore class="inner-phone light">
|
||||
<p class="title">Create an Account</p>
|
||||
<p class="subtitle">Please enter your details</p>
|
||||
<div class="inputs">
|
||||
<fieldset>
|
||||
<label for="name">Your Name</label>
|
||||
<input
|
||||
type="name"
|
||||
id="name"
|
||||
placeholder="Enter your name"
|
||||
bind:value={$state.name}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label for="email">Your Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
placeholder="Enter your email"
|
||||
bind:value={$state.email}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label for="password">Create Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
placeholder="Enter Password"
|
||||
bind:value={$state.password}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<button class="sign-up">Sign Up</button>
|
||||
{#if controlsEnabled}
|
||||
<span class="with-sep" transition:fade={{ duration: 100 }}
|
||||
>or sign up with</span
|
||||
>
|
||||
<div class="oauth-btns" transition:fade={{ duration: 100 }}>
|
||||
{#each objectKeys($state.controls).filter((p) => $state.controls[p]) as provider (provider)}
|
||||
<button
|
||||
class="oauth"
|
||||
transition:fade={{ duration: 100 }}
|
||||
animate:flip={{ duration: 250 }}
|
||||
>
|
||||
<div class="inner">
|
||||
<span class="web-icon-{provider.toLowerCase()}" />
|
||||
<span>{provider}</span>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
<p class="title">Create an Account</p>
|
||||
<p class="subtitle">Please enter your details</p>
|
||||
<div class="inputs">
|
||||
<fieldset>
|
||||
<label for="name">Your Name</label>
|
||||
<input type="name" id="name" placeholder="Enter your name" bind:value={$state.name} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label for="email">Your Email</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
placeholder="Enter your email"
|
||||
bind:value={$state.email}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label for="password">Create Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
placeholder="Enter Password"
|
||||
bind:value={$state.password}
|
||||
/>
|
||||
</fieldset>
|
||||
</div>
|
||||
{/if}
|
||||
<button class="sign-up">Sign Up</button>
|
||||
{#if controlsEnabled}
|
||||
<span class="with-sep" transition:fade={{ duration: 100 }}>or sign up with</span>
|
||||
<div class="oauth-btns" transition:fade={{ duration: 100 }}>
|
||||
{#each objectKeys($state.controls).filter((p) => $state.controls[p]) as provider (provider)}
|
||||
<button
|
||||
class="oauth"
|
||||
transition:fade={{ duration: 100 }}
|
||||
animate:flip={{ duration: 250 }}
|
||||
>
|
||||
<div class="inner">
|
||||
<span class="web-icon-{provider.toLowerCase()}" />
|
||||
<span>{provider}</span>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inner-phone {
|
||||
padding-block: 3rem;
|
||||
padding-inline: 1rem;
|
||||
.inner-phone {
|
||||
padding-block: 3rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
|
||||
.title {
|
||||
color: #434347;
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 22px; /* 137.5% */
|
||||
letter-spacing: -0.224px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.196px;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-block-start: 1.5rem;
|
||||
|
||||
fieldset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3125rem;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
color: var(--color-greyscale-700, #56565c);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px; /* 133.333% */
|
||||
letter-spacing: -0.168px;
|
||||
.title {
|
||||
color: #434347;
|
||||
font-family: Inter;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 22px; /* 137.5% */
|
||||
letter-spacing: -0.224px;
|
||||
}
|
||||
|
||||
input {
|
||||
all: unset;
|
||||
display: flex;
|
||||
padding: 8px 12px;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #d8d8db;
|
||||
|
||||
color: #434347;
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px; /* 133.333% */
|
||||
letter-spacing: -0.168px;
|
||||
.subtitle {
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.196px;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
margin-block-start: 1.5rem;
|
||||
|
||||
fieldset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3125rem;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
color: var(--color-greyscale-700, #56565c);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px; /* 133.333% */
|
||||
letter-spacing: -0.168px;
|
||||
}
|
||||
|
||||
input {
|
||||
all: unset;
|
||||
display: flex;
|
||||
padding: 8px 12px;
|
||||
align-items: flex-start;
|
||||
align-self: stretch;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #d8d8db;
|
||||
|
||||
color: #434347;
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px; /* 133.333% */
|
||||
letter-spacing: -0.168px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sign-up {
|
||||
padding: 0.375rem 0.75rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-block-start: 1.25rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
background: var(--appwrite-purple, #7c67fe);
|
||||
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.06);
|
||||
|
||||
color: var(--color-bw-white, #fff);
|
||||
text-align: center;
|
||||
|
||||
/* Responsive/SubBody-500 */
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 22px; /* 157.143% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.with-sep {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
letter-spacing: -0.0105rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
|
||||
margin-block-start: 0.75rem;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
height: 1px;
|
||||
flex-grow: 1;
|
||||
background-color: hsl(var(--web-color-greyscale-200));
|
||||
}
|
||||
}
|
||||
|
||||
.oauth-btns {
|
||||
--gap: 0.5rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap);
|
||||
margin-block-start: 0.75rem;
|
||||
}
|
||||
|
||||
.oauth {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #d9d9d9;
|
||||
color: hsl(var(--web-color-greyscale-750));
|
||||
text-align: center;
|
||||
|
||||
/* Responsive/Caption-500 */
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.01575rem;
|
||||
|
||||
flex: 1 1 calc(50% - var(--gap));
|
||||
padding-block: 0.375rem;
|
||||
position: relative;
|
||||
height: 2.125rem;
|
||||
|
||||
.inner {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transform: translate(-50%, -50%) scale(var(--inverse-sx, 1), var(--inverse-sy, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sign-up {
|
||||
padding: 0.375rem 0.75rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-block-start: 1.25rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
background: var(--appwrite-purple, #7c67fe);
|
||||
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.06);
|
||||
|
||||
color: var(--color-bw-white, #fff);
|
||||
text-align: center;
|
||||
|
||||
/* Responsive/SubBody-500 */
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 22px; /* 157.143% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.with-sep {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
letter-spacing: -0.0105rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
|
||||
margin-block-start: 0.75rem;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
height: 1px;
|
||||
flex-grow: 1;
|
||||
background-color: hsl(var(--web-color-greyscale-200));
|
||||
}
|
||||
}
|
||||
|
||||
.oauth-btns {
|
||||
--gap: 0.5rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap);
|
||||
margin-block-start: 0.75rem;
|
||||
}
|
||||
|
||||
.oauth {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #d9d9d9;
|
||||
color: hsl(var(--web-color-greyscale-750));
|
||||
text-align: center;
|
||||
|
||||
/* Responsive/Caption-500 */
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.01575rem;
|
||||
|
||||
flex: 1 1 calc(50% - var(--gap));
|
||||
padding-block: 0.375rem;
|
||||
position: relative;
|
||||
height: 2.125rem;
|
||||
|
||||
.inner {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transform: translate(-50%, -50%)
|
||||
scale(var(--inverse-sx, 1), var(--inverse-sy, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,58 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { slide } from "svelte/transition";
|
||||
import { databasesController } from ".";
|
||||
import { flip } from "$lib/utils/flip";
|
||||
import { slide } from 'svelte/transition';
|
||||
import { databasesController } from '.';
|
||||
import { flip } from '$lib/utils/flip';
|
||||
|
||||
const { state } = databasesController;
|
||||
const { state } = databasesController;
|
||||
</script>
|
||||
|
||||
<div class="pseudo-table">
|
||||
<div class="header">
|
||||
<span class="web-eyebrow">Document ID</span>
|
||||
<span class="web-eyebrow">Task</span>
|
||||
</div>
|
||||
{#each $state.tasks.slice(0, $state.tableSlice) as task (task.id)}
|
||||
<div
|
||||
class="row"
|
||||
transition:slide={{ duration: 150 }}
|
||||
animate:flip={{ duration: 150 }}
|
||||
>
|
||||
<div class="copy-button">
|
||||
<span class="web-icon-copy" />
|
||||
<span>{task.id}</span>
|
||||
</div>
|
||||
<span class="truncated">{task.title}</span>
|
||||
<div class="header">
|
||||
<span class="web-eyebrow">Document ID</span>
|
||||
<span class="web-eyebrow">Task</span>
|
||||
</div>
|
||||
{/each}
|
||||
{#each $state.tasks.slice(0, $state.tableSlice) as task (task.id)}
|
||||
<div class="row" transition:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
|
||||
<div class="copy-button">
|
||||
<span class="web-icon-copy" />
|
||||
<span>{task.id}</span>
|
||||
</div>
|
||||
<span class="truncated">{task.title}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.copy-button {
|
||||
display: flex;
|
||||
padding: 0.25rem 0.5rem;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
.copy-button {
|
||||
display: flex;
|
||||
padding: 0.25rem 0.5rem;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
|
||||
border-radius: 62.4375rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(2.6666667461395264px);
|
||||
border-radius: 62.4375rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(2.6666667461395264px);
|
||||
|
||||
[class*="icon-"] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
[class*='icon-'] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
}
|
||||
|
||||
span:not([class*='icon-']) {
|
||||
color: var(--greyscale-400, #adadb1);
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 142.857% */
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
span:not([class*="icon-"]) {
|
||||
color: var(--greyscale-400, #adadb1);
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 142.857% */
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Code from "$lib/animations/CodeWindow/Code.svelte";
|
||||
import Code from '$lib/animations/CodeWindow/Code.svelte';
|
||||
|
||||
let content = `
|
||||
let content = `
|
||||
const result = databases.createDocument(
|
||||
'Your-tasks',
|
||||
tasks,
|
||||
|
||||
@@ -1,95 +1,94 @@
|
||||
import Box from "./box.svelte";
|
||||
import Code from "./code.svelte";
|
||||
import Phone from "./phone.svelte";
|
||||
import Box from './box.svelte';
|
||||
import Code from './code.svelte';
|
||||
import Phone from './phone.svelte';
|
||||
|
||||
import { safeAnimate, sleep } from "$lib/animations";
|
||||
import { createResettable } from "$lib/utils/resettable";
|
||||
import { getElSelector } from "../Products.svelte";
|
||||
import { safeAnimate, sleep } from '$lib/animations';
|
||||
import { createResettable } from '$lib/utils/resettable';
|
||||
import { getElSelector } from '../Products.svelte';
|
||||
|
||||
type Task = {
|
||||
id: string;
|
||||
title: string;
|
||||
checked: boolean;
|
||||
id: string;
|
||||
title: string;
|
||||
checked: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
tasks: Task[];
|
||||
tableSlice: number;
|
||||
tasks: Task[];
|
||||
tableSlice: number;
|
||||
};
|
||||
|
||||
const state = createResettable<State>({
|
||||
tasks: [
|
||||
{
|
||||
id: "3397fecdedb13397fecdedb1",
|
||||
title: "Research user needs",
|
||||
checked: true,
|
||||
},
|
||||
],
|
||||
tableSlice: 1,
|
||||
tasks: [
|
||||
{
|
||||
id: '3397fecdedb13397fecdedb1',
|
||||
title: 'Research user needs',
|
||||
checked: true
|
||||
}
|
||||
],
|
||||
tableSlice: 1
|
||||
});
|
||||
|
||||
const execute = async () => {
|
||||
const phone = getElSelector("phone");
|
||||
const box = getElSelector("box");
|
||||
const code = getElSelector("code");
|
||||
const { update } = state.reset();
|
||||
const phone = getElSelector('phone');
|
||||
const box = getElSelector('box');
|
||||
const code = getElSelector('code');
|
||||
const { update } = state.reset();
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(phone, { x: 390, y: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(box, { x: 0, y: 32, opacity: 1 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(code, { x: 80, y: 320, opacity: 1 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(phone, { x: 390, y: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(box, { x: 0, y: 32, opacity: 1 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(code, { x: 80, y: 320, opacity: 1 }, { duration: 0.5 })?.finished
|
||||
]);
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: [
|
||||
...p.tasks,
|
||||
{
|
||||
id: "3397fecdedb13397fecdedb2",
|
||||
title: "Create wireframes",
|
||||
checked: false,
|
||||
},
|
||||
],
|
||||
}));
|
||||
await sleep(250);
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: [
|
||||
...p.tasks,
|
||||
{
|
||||
id: '3397fecdedb13397fecdedb2',
|
||||
title: 'Create wireframes',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
}));
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tableSlice: p.tableSlice + 1,
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
tableSlice: p.tableSlice + 1
|
||||
}));
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: [
|
||||
...p.tasks,
|
||||
{
|
||||
id: "3397fecdedb13397fecdedb3",
|
||||
title: "Create visual design",
|
||||
checked: false,
|
||||
},
|
||||
],
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: [
|
||||
...p.tasks,
|
||||
{
|
||||
id: '3397fecdedb13397fecdedb3',
|
||||
title: 'Create visual design',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tableSlice: p.tableSlice + 1,
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
tableSlice: p.tableSlice + 1
|
||||
}));
|
||||
};
|
||||
|
||||
export const databasesController = {
|
||||
execute,
|
||||
state,
|
||||
execute,
|
||||
state
|
||||
};
|
||||
|
||||
export const Databases = {
|
||||
Phone,
|
||||
Box,
|
||||
Code,
|
||||
Phone,
|
||||
Box,
|
||||
Code
|
||||
};
|
||||
|
||||
@@ -1,131 +1,127 @@
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { databasesController } from ".";
|
||||
import TaskCheckbox from "../TaskCheckbox.svelte";
|
||||
import { fly } from 'svelte/transition';
|
||||
import { databasesController } from '.';
|
||||
import TaskCheckbox from '../TaskCheckbox.svelte';
|
||||
|
||||
const { state } = databasesController;
|
||||
const { state } = databasesController;
|
||||
</script>
|
||||
|
||||
<div data-theme-ignore class="inner-phone light">
|
||||
<div class="header">
|
||||
<p class="title">Your tasks</p>
|
||||
<span class="icon-menu" aria-label="menu" />
|
||||
</div>
|
||||
<div class="header">
|
||||
<p class="title">Your tasks</p>
|
||||
<span class="icon-menu" aria-label="menu" />
|
||||
</div>
|
||||
|
||||
<div class="date">Today</div>
|
||||
<div class="tasks">
|
||||
{#each $state.tasks as task (task.id)}
|
||||
<div
|
||||
class="task"
|
||||
data-checked={task.checked ? "" : undefined}
|
||||
in:fly={{ x: -16 }}
|
||||
>
|
||||
<TaskCheckbox bind:checked={task.checked} />
|
||||
<span class="title">{task.title}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="date">Today</div>
|
||||
<div class="tasks">
|
||||
{#each $state.tasks as task (task.id)}
|
||||
<div class="task" data-checked={task.checked ? '' : undefined} in:fly={{ x: -16 }}>
|
||||
<TaskCheckbox bind:checked={task.checked} />
|
||||
<span class="title">{task.title}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<button class="add-btn">
|
||||
<span class="web-icon-plus" />
|
||||
</button>
|
||||
<button class="add-btn">
|
||||
<span class="web-icon-plus" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inner-phone {
|
||||
padding-block: 3rem;
|
||||
padding-inline: 1rem;
|
||||
.inner-phone {
|
||||
padding-block: 3rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
|
||||
position: relative;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
[class*="icon-"] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-block-start: 3rem;
|
||||
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
}
|
||||
|
||||
.tasks {
|
||||
margin-block-start: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.task {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid hsl(var(--web-color-greyscale-50));
|
||||
background: hsl(var(--web-color-white));
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
|
||||
padding-block: 0.55rem;
|
||||
padding-inline: 0.88rem;
|
||||
|
||||
/* Responsive/SubBody-400 */
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.00394rem;
|
||||
|
||||
transition: opacity 200ms ease;
|
||||
|
||||
&[data-checked] {
|
||||
opacity: 0.6;
|
||||
[class*='icon-'] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-block-start: 3rem;
|
||||
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
}
|
||||
|
||||
.tasks {
|
||||
margin-block-start: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.task {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid hsl(var(--web-color-greyscale-50));
|
||||
background: hsl(var(--web-color-white));
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
|
||||
padding-block: 0.55rem;
|
||||
padding-inline: 0.88rem;
|
||||
|
||||
/* Responsive/SubBody-400 */
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.00394rem;
|
||||
|
||||
transition: opacity 200ms ease;
|
||||
|
||||
&[data-checked] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 2.5rem;
|
||||
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
|
||||
background-color: rgba(124, 103, 254, 1);
|
||||
color: rgba(237, 237, 240, 1);
|
||||
font-size: 1.5rem;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 2.5rem;
|
||||
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
|
||||
background-color: rgba(124, 103, 254, 1);
|
||||
color: rgba(237, 237, 240, 1);
|
||||
font-size: 1.5rem;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { portal } from "$lib/actions";
|
||||
import Code from "$lib/animations/CodeWindow/Code.svelte";
|
||||
import { fade } from "svelte/transition";
|
||||
import { functionsController } from ".";
|
||||
import { portal } from '$lib/actions';
|
||||
import Code from '$lib/animations/CodeWindow/Code.svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { functionsController } from '.';
|
||||
|
||||
let content = `
|
||||
let content = `
|
||||
const userId = req.headers['user-id'];
|
||||
|
||||
if (req.path === '/subscribe') {
|
||||
@@ -18,39 +18,39 @@ if (req.path === '/webhook') {
|
||||
|
||||
return res.json({ success: true });`.trim();
|
||||
|
||||
const { state } = functionsController;
|
||||
const { state } = functionsController;
|
||||
</script>
|
||||
|
||||
<Code {content} />
|
||||
|
||||
<div use:portal={{ target: "#code-bottom" }} class="bottom">
|
||||
{#if $state.submit !== "idle"}
|
||||
<span class="web-icon-github" in:fade />
|
||||
{/if}
|
||||
{#if $state.submit === "loading"}
|
||||
<span in:fade>Pushing to GitHub...</span>
|
||||
<div class="loader is-small" in:fade />
|
||||
{:else if $state.submit === "success"}
|
||||
<span>Deployed to Appwrite Cloud</span>
|
||||
<span class="web-icon-check" />
|
||||
{/if}
|
||||
<div use:portal={{ target: '#code-bottom' }} class="bottom">
|
||||
{#if $state.submit !== 'idle'}
|
||||
<span class="web-icon-github" in:fade />
|
||||
{/if}
|
||||
{#if $state.submit === 'loading'}
|
||||
<span in:fade>Pushing to GitHub...</span>
|
||||
<div class="loader is-small" in:fade />
|
||||
{:else if $state.submit === 'success'}
|
||||
<span>Deployed to Appwrite Cloud</span>
|
||||
<span class="web-icon-check" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
.bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
height: 3rem;
|
||||
padding-inline: 1rem;
|
||||
height: 3rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
color: var(--color-bw-white, #fff);
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%; /* 1.3125rem */
|
||||
letter-spacing: -0.00875rem;
|
||||
}
|
||||
color: var(--color-bw-white, #fff);
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%; /* 1.3125rem */
|
||||
letter-spacing: -0.00875rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,61 +1,58 @@
|
||||
import Code from "./code.svelte";
|
||||
import Phone from "./phone.svelte";
|
||||
import Code from './code.svelte';
|
||||
import Phone from './phone.svelte';
|
||||
|
||||
import { safeAnimate, sleep } from "$lib/animations";
|
||||
import { createResettable } from "$lib/utils/resettable";
|
||||
import { getElSelector } from "../Products.svelte";
|
||||
import { safeAnimate, sleep } from '$lib/animations';
|
||||
import { createResettable } from '$lib/utils/resettable';
|
||||
import { getElSelector } from '../Products.svelte';
|
||||
|
||||
type State = {
|
||||
submit: "idle" | "loading" | "success";
|
||||
submit: 'idle' | 'loading' | 'success';
|
||||
};
|
||||
|
||||
const state = createResettable<State>({
|
||||
submit: "idle",
|
||||
submit: 'idle'
|
||||
});
|
||||
|
||||
const execute = async () => {
|
||||
const phone = getElSelector("phone");
|
||||
const box = getElSelector("box");
|
||||
const code = getElSelector("code");
|
||||
const phone = getElSelector('phone');
|
||||
const box = getElSelector('box');
|
||||
const code = getElSelector('code');
|
||||
|
||||
const { update } = state.reset();
|
||||
const { update } = state.reset();
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(phone, { x: 430, y: 0, width: "275px" }, { duration: 0.5 })
|
||||
?.finished,
|
||||
safeAnimate(code, { x: 0, y: 200, opacity: 0 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(phone, { x: 430, y: 0, width: '275px' }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(code, { x: 0, y: 200, opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished
|
||||
]);
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
await safeAnimate(code, { zIndex: 0 }, { duration: 0 })?.finished;
|
||||
await safeAnimate(code, { y: [200 - 16, 200], opacity: 1 }, { duration: 0.5 })
|
||||
?.finished;
|
||||
await safeAnimate(code, { zIndex: 0 }, { duration: 0 })?.finished;
|
||||
await safeAnimate(code, { y: [200 - 16, 200], opacity: 1 }, { duration: 0.5 })?.finished;
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
submit: "loading",
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
submit: 'loading'
|
||||
}));
|
||||
|
||||
await sleep(1500);
|
||||
await sleep(1500);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
submit: "success",
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
submit: 'success'
|
||||
}));
|
||||
};
|
||||
|
||||
export const functionsController = {
|
||||
execute,
|
||||
state,
|
||||
execute,
|
||||
state
|
||||
};
|
||||
|
||||
export const Functions = {
|
||||
Phone,
|
||||
Phone,
|
||||
|
||||
Code,
|
||||
Code
|
||||
};
|
||||
|
||||
@@ -1,325 +1,325 @@
|
||||
<script lang="ts">
|
||||
import { flip } from "$lib/utils/flip";
|
||||
import { crossfade, scale, slide } from "svelte/transition";
|
||||
import { functionsController } from ".";
|
||||
import { flip } from '$lib/utils/flip';
|
||||
import { crossfade, scale, slide } from 'svelte/transition';
|
||||
import { functionsController } from '.';
|
||||
|
||||
const { state } = functionsController;
|
||||
const { state } = functionsController;
|
||||
|
||||
type Method = {
|
||||
icon: string;
|
||||
label: string;
|
||||
};
|
||||
type Method = {
|
||||
icon: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
$: methods = [
|
||||
$state.submit === "success" && {
|
||||
icon: "/images/animations/stripe.png",
|
||||
label: "Stripe",
|
||||
},
|
||||
{
|
||||
icon: "/images/animations/credit-card.svg",
|
||||
label: "Card",
|
||||
},
|
||||
{
|
||||
icon: "/images/animations/paypal.svg",
|
||||
label: "PayPal",
|
||||
},
|
||||
{
|
||||
icon: "/images/animations/apple.svg",
|
||||
label: "Apple",
|
||||
},
|
||||
].filter(Boolean) as Method[];
|
||||
$: methods = [
|
||||
$state.submit === 'success' && {
|
||||
icon: '/images/animations/stripe.png',
|
||||
label: 'Stripe'
|
||||
},
|
||||
{
|
||||
icon: '/images/animations/credit-card.svg',
|
||||
label: 'Card'
|
||||
},
|
||||
{
|
||||
icon: '/images/animations/paypal.svg',
|
||||
label: 'PayPal'
|
||||
},
|
||||
{
|
||||
icon: '/images/animations/apple.svg',
|
||||
label: 'Apple'
|
||||
}
|
||||
].filter(Boolean) as Method[];
|
||||
</script>
|
||||
|
||||
<div data-theme-ignore class="inner-phone light">
|
||||
<div class="header">
|
||||
<p class="title">Upgrade plan</p>
|
||||
<span class="icon-menu" aria-label="menu" />
|
||||
</div>
|
||||
|
||||
<div class="plan">
|
||||
<p class="title">Premium plan</p>
|
||||
<div class="subscription">
|
||||
<p class="price">$20</p>
|
||||
<p class="period">/month</p>
|
||||
<div class="header">
|
||||
<p class="title">Upgrade plan</p>
|
||||
<span class="icon-menu" aria-label="menu" />
|
||||
</div>
|
||||
<ul>
|
||||
<li>Premium plan</li>
|
||||
<li>Premium plan</li>
|
||||
<li>Premium plan</li>
|
||||
|
||||
<div class="plan">
|
||||
<p class="title">Premium plan</p>
|
||||
<div class="subscription">
|
||||
<p class="price">$20</p>
|
||||
<p class="period">/month</p>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Premium plan</li>
|
||||
<li>Premium plan</li>
|
||||
<li>Premium plan</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="methods">
|
||||
{#each methods as method, i (method.label)}
|
||||
<li
|
||||
in:scale={{ delay: 150 }}
|
||||
animate:flip={{ duration: 500 }}
|
||||
data-active={i == 0 ? '' : undefined}
|
||||
>
|
||||
<img src={method.icon} alt="" />
|
||||
<p>{method.label}</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="methods">
|
||||
{#each methods as method, i (method.label)}
|
||||
<li
|
||||
in:scale={{ delay: 150 }}
|
||||
animate:flip={{ duration: 500 }}
|
||||
data-active={i == 0 ? "" : undefined}
|
||||
>
|
||||
<img src={method.icon} alt="" />
|
||||
<p>{method.label}</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
{#if $state.submit !== "success"}
|
||||
<div class="form">
|
||||
<p>Card information</p>
|
||||
<div class="bordered">
|
||||
<div>
|
||||
<p>placeholder</p>
|
||||
<img src="/images/animations/visa.png" alt="" />
|
||||
<img src="/images/animations/mastercard.png" alt="" />
|
||||
{#if $state.submit !== 'success'}
|
||||
<div class="form">
|
||||
<p>Card information</p>
|
||||
<div class="bordered">
|
||||
<div>
|
||||
<p>placeholder</p>
|
||||
<img src="/images/animations/visa.png" alt="" />
|
||||
<img src="/images/animations/mastercard.png" alt="" />
|
||||
</div>
|
||||
<div>
|
||||
<p>MM/YY</p>
|
||||
<p>CVV</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>MM/YY</p>
|
||||
<p>CVV</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<button>
|
||||
Pay $20.00
|
||||
{#if $state.submit === "success"}
|
||||
<span in:slide={{ axis: "x" }}>on Stripe</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<button>
|
||||
Pay $20.00
|
||||
{#if $state.submit === 'success'}
|
||||
<span in:slide={{ axis: 'x' }}>on Stripe</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inner-phone {
|
||||
padding-block: 3rem 1.5rem;
|
||||
padding-inline: 1rem;
|
||||
.inner-phone {
|
||||
padding-block: 3rem 1.5rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
color: #434347;
|
||||
font-family: Inter;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
[class*="icon-"] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
}
|
||||
}
|
||||
|
||||
.plan {
|
||||
display: flex;
|
||||
padding: 0.75rem 1rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
|
||||
border-radius: 0.75rem;
|
||||
background: rgba(237, 237, 240, 0.5);
|
||||
|
||||
margin-block-start: 1rem;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-700, #56565c);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
}
|
||||
|
||||
.subscription {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-block-start: 0.15rem;
|
||||
|
||||
.price {
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
font-family: Inter;
|
||||
font-size: 1.125rem;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 1.25rem; /* 111.111% */
|
||||
}
|
||||
|
||||
.period {
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
margin-block-start: 0.75rem;
|
||||
|
||||
li {
|
||||
color: var(--color-greyscale-500, #818186);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
padding-inline-start: 1.5rem;
|
||||
.title {
|
||||
color: #434347;
|
||||
font-family: Inter;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
background-image: url("/images/animations/check-circle.svg");
|
||||
}
|
||||
[class*='icon-'] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
}
|
||||
}
|
||||
|
||||
.plan {
|
||||
display: flex;
|
||||
padding: 0.75rem 1rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
|
||||
border-radius: 0.75rem;
|
||||
background: rgba(237, 237, 240, 0.5);
|
||||
|
||||
margin-block-start: 1rem;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-700, #56565c);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
}
|
||||
|
||||
.subscription {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-block-start: 0.15rem;
|
||||
|
||||
.price {
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
font-family: Inter;
|
||||
font-size: 1.125rem;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 1.25rem; /* 111.111% */
|
||||
}
|
||||
|
||||
.period {
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
margin-block-start: 0.75rem;
|
||||
|
||||
li {
|
||||
color: var(--color-greyscale-500, #818186);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
|
||||
padding-inline-start: 1.5rem;
|
||||
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
background-image: url('/images/animations/check-circle.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.methods {
|
||||
margin-block-start: 1.25rem;
|
||||
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
overflow: hidden;
|
||||
margin-inline: -1rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
li {
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
width: 5.5rem;
|
||||
padding: 0.75rem 0.75rem 0.625rem 0.75rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.125rem;
|
||||
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid var(--greyscale-50, #ededf0);
|
||||
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
|
||||
&[data-active] {
|
||||
border-color: var(--appwrite-purple, #7c67fe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-block-start: 1.25rem;
|
||||
|
||||
> p {
|
||||
color: var(--dark-neutrals-150, #373b4d);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%; /* 1.125rem */
|
||||
}
|
||||
|
||||
.bordered {
|
||||
margin-block-start: 0.25rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #ededf0;
|
||||
background: var(--color-bw-white, #fff);
|
||||
|
||||
> div:first-child {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
padding: 0.65rem 0.75rem;
|
||||
border-bottom: 1px solid #ededf0;
|
||||
|
||||
> :nth-child(2) {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> div:nth-child(2) {
|
||||
display: flex;
|
||||
|
||||
> p {
|
||||
padding: 0.65rem 0.75rem;
|
||||
}
|
||||
|
||||
> p:first-child {
|
||||
flex-grow: 3;
|
||||
}
|
||||
|
||||
> p:last-child {
|
||||
flex-grow: 1;
|
||||
border-inline-start: 1px solid #ededf0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--light-neutrals-50, #c4c6d7);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%; /* 1.125rem */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
padding: 0.5rem 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
background: var(--appwrite-purple, #7c67fe);
|
||||
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.06);
|
||||
|
||||
color: var(--color-bw-white, #fff);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.01225rem;
|
||||
|
||||
margin-block-start: auto;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.methods {
|
||||
margin-block-start: 1.25rem;
|
||||
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
overflow: hidden;
|
||||
margin-inline: -1rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
li {
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
width: 5.5rem;
|
||||
padding: 0.75rem 0.75rem 0.625rem 0.75rem;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.125rem;
|
||||
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid var(--greyscale-50, #ededf0);
|
||||
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
|
||||
&[data-active] {
|
||||
border-color: var(--appwrite-purple, #7c67fe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
margin-block-start: 1.25rem;
|
||||
|
||||
> p {
|
||||
color: var(--dark-neutrals-150, #373b4d);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%; /* 1.125rem */
|
||||
}
|
||||
|
||||
.bordered {
|
||||
margin-block-start: 0.25rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #ededf0;
|
||||
background: var(--color-bw-white, #fff);
|
||||
|
||||
> div:first-child {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
padding: 0.65rem 0.75rem;
|
||||
border-bottom: 1px solid #ededf0;
|
||||
|
||||
> :nth-child(2) {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> div:nth-child(2) {
|
||||
display: flex;
|
||||
|
||||
> p {
|
||||
padding: 0.65rem 0.75rem;
|
||||
}
|
||||
|
||||
> p:first-child {
|
||||
flex-grow: 3;
|
||||
}
|
||||
|
||||
> p:last-child {
|
||||
flex-grow: 1;
|
||||
border-inline-start: 1px solid #ededf0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--light-neutrals-50, #c4c6d7);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%; /* 1.125rem */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
padding: 0.5rem 1rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
background: var(--appwrite-purple, #7c67fe);
|
||||
box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.06);
|
||||
|
||||
color: var(--color-bw-white, #fff);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.01225rem;
|
||||
|
||||
margin-block-start: auto;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,120 +1,116 @@
|
||||
<script lang="ts">
|
||||
import { fade, slide } from "svelte/transition";
|
||||
import { messagingController } from ".";
|
||||
import { flip } from "$lib/utils/flip";
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
import { messagingController } from '.';
|
||||
import { flip } from '$lib/utils/flip';
|
||||
|
||||
const { state } = messagingController;
|
||||
const { state } = messagingController;
|
||||
</script>
|
||||
|
||||
<div class="pseudo-table">
|
||||
<div class="header">
|
||||
<span class="web-eyebrow">Message ID</span>
|
||||
<span class="web-eyebrow">Type</span>
|
||||
<span class="web-eyebrow" style:text-align="center">Status</span>
|
||||
</div>
|
||||
{#each $state.messages.slice(0, $state.tableSlice) as task (task.id)}
|
||||
<div
|
||||
class="row"
|
||||
transition:slide={{ duration: 150 }}
|
||||
animate:flip={{ duration: 150 }}
|
||||
>
|
||||
<div class="copy-button">
|
||||
<span class="web-icon-copy" />
|
||||
<span>{task.id}</span>
|
||||
</div>
|
||||
<div class="icon-button">
|
||||
<div class="icon">
|
||||
<img src={task.icon} alt="" width="16" height="16" />
|
||||
</div>
|
||||
<span class="truncated">{task.type}</span>
|
||||
</div>
|
||||
|
||||
<div class="status-indicator">
|
||||
{#if task.status === "sending"}
|
||||
<div class="loader is-small" in:fade />
|
||||
{:else}
|
||||
<span class="web-icon-check" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="header">
|
||||
<span class="web-eyebrow">Message ID</span>
|
||||
<span class="web-eyebrow">Type</span>
|
||||
<span class="web-eyebrow" style:text-align="center">Status</span>
|
||||
</div>
|
||||
{/each}
|
||||
{#each $state.messages.slice(0, $state.tableSlice) as task (task.id)}
|
||||
<div class="row" transition:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
|
||||
<div class="copy-button">
|
||||
<span class="web-icon-copy" />
|
||||
<span>{task.id}</span>
|
||||
</div>
|
||||
<div class="icon-button">
|
||||
<div class="icon">
|
||||
<img src={task.icon} alt="" width="16" height="16" />
|
||||
</div>
|
||||
<span class="truncated">{task.type}</span>
|
||||
</div>
|
||||
|
||||
<div class="status-indicator">
|
||||
{#if task.status === 'sending'}
|
||||
<div class="loader is-small" in:fade />
|
||||
{:else}
|
||||
<span class="web-icon-check" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header,
|
||||
.row {
|
||||
grid-template-columns: 7rem 1fr 1fr !important;
|
||||
gap: 1.5rem 3rem;
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
display: flex;
|
||||
padding: 0.25rem 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.375rem;
|
||||
|
||||
border-radius: 62.4375rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(2.6666667461395264px);
|
||||
|
||||
[class*="icon-"] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
.header,
|
||||
.row {
|
||||
grid-template-columns: 7rem 1fr 1fr !important;
|
||||
gap: 1.5rem 3rem;
|
||||
}
|
||||
|
||||
span:not([class*="icon-"]) {
|
||||
color: var(--greyscale-400, #adadb1);
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 142.857% */
|
||||
.copy-button {
|
||||
display: flex;
|
||||
padding: 0.25rem 0.5rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.375rem;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border-radius: 62.4375rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(2.6666667461395264px);
|
||||
|
||||
[class*='icon-'] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
}
|
||||
|
||||
span:not([class*='icon-']) {
|
||||
color: var(--greyscale-400, #adadb1);
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 142.857% */
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
.icon-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
.icon {
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(2.6666667461395264px);
|
||||
border-radius: 100%;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.icon {
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(2.6666667461395264px);
|
||||
border-radius: 100%;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
span:not([class*='icon-']) {
|
||||
color: var(--greyscale-400, #adadb1);
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 142.857% */
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
span:not([class*="icon-"]) {
|
||||
color: var(--greyscale-400, #adadb1);
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.25rem; /* 142.857% */
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
[class*='icon-'] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
[class*="icon-"] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { messagingController } from ".";
|
||||
import Code from "$lib/animations/CodeWindow/Code.svelte";
|
||||
import { messagingController } from '.';
|
||||
import Code from '$lib/animations/CodeWindow/Code.svelte';
|
||||
|
||||
const { state } = messagingController;
|
||||
const { state } = messagingController;
|
||||
|
||||
$: content = `
|
||||
$: content = `
|
||||
await messaging.createPush(
|
||||
ID.unique(),
|
||||
'${$state.heading}',
|
||||
|
||||
@@ -1,173 +1,164 @@
|
||||
import Box from "./box.svelte";
|
||||
import Code from "./code.svelte";
|
||||
import Phone from "./phone.svelte";
|
||||
import Box from './box.svelte';
|
||||
import Code from './code.svelte';
|
||||
import Phone from './phone.svelte';
|
||||
|
||||
import { safeAnimate, sleep, write } from "$lib/animations";
|
||||
import { createResettable } from "$lib/utils/resettable";
|
||||
import { getElSelector } from "../Products.svelte";
|
||||
import { safeAnimate, sleep, write } from '$lib/animations';
|
||||
import { createResettable } from '$lib/utils/resettable';
|
||||
import { getElSelector } from '../Products.svelte';
|
||||
|
||||
type Task = {
|
||||
id: string;
|
||||
title: string;
|
||||
checked: boolean;
|
||||
id: string;
|
||||
title: string;
|
||||
checked: boolean;
|
||||
};
|
||||
|
||||
type Message = {
|
||||
id: string;
|
||||
type: string;
|
||||
icon: string;
|
||||
status: "sending" | "sent";
|
||||
id: string;
|
||||
type: string;
|
||||
icon: string;
|
||||
status: 'sending' | 'sent';
|
||||
};
|
||||
|
||||
type State = {
|
||||
heading: string;
|
||||
message: string;
|
||||
tasks: Task[];
|
||||
messages: Message[];
|
||||
tableSlice: number;
|
||||
submit: "loading" | "success";
|
||||
heading: string;
|
||||
message: string;
|
||||
tasks: Task[];
|
||||
messages: Message[];
|
||||
tableSlice: number;
|
||||
submit: 'loading' | 'success';
|
||||
};
|
||||
|
||||
const state = createResettable<State>({
|
||||
heading: "",
|
||||
message: "",
|
||||
tasks: [
|
||||
{
|
||||
id: "3397fecdedb13397fecdedb1",
|
||||
title: "Research user needs",
|
||||
checked: true,
|
||||
},
|
||||
],
|
||||
messages: [
|
||||
{
|
||||
id: "...3397fecdedb1",
|
||||
type: "SMS",
|
||||
icon: "./images/icons/illustrated/dark/sms.svg",
|
||||
status: "sent",
|
||||
},
|
||||
{
|
||||
id: "...2224gabjger4",
|
||||
type: "Email",
|
||||
icon: "./images/icons/illustrated/dark/email.svg",
|
||||
status: "sent",
|
||||
},
|
||||
],
|
||||
tableSlice: 2,
|
||||
submit: "loading",
|
||||
heading: '',
|
||||
message: '',
|
||||
tasks: [
|
||||
{
|
||||
id: '3397fecdedb13397fecdedb1',
|
||||
title: 'Research user needs',
|
||||
checked: true
|
||||
}
|
||||
],
|
||||
messages: [
|
||||
{
|
||||
id: '...3397fecdedb1',
|
||||
type: 'SMS',
|
||||
icon: './images/icons/illustrated/dark/sms.svg',
|
||||
status: 'sent'
|
||||
},
|
||||
{
|
||||
id: '...2224gabjger4',
|
||||
type: 'Email',
|
||||
icon: './images/icons/illustrated/dark/email.svg',
|
||||
status: 'sent'
|
||||
}
|
||||
],
|
||||
tableSlice: 2,
|
||||
submit: 'loading'
|
||||
});
|
||||
|
||||
const execute = async () => {
|
||||
const phone = getElSelector("phone");
|
||||
const box = getElSelector("box");
|
||||
const code = getElSelector("code");
|
||||
const { update } = state.reset();
|
||||
const phone = getElSelector('phone');
|
||||
const box = getElSelector('box');
|
||||
const code = getElSelector('code');
|
||||
const { update } = state.reset();
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(phone, { x: 365, y: 0, width: "275px" }, { duration: 0.5 })
|
||||
?.finished,
|
||||
safeAnimate(
|
||||
code,
|
||||
{ x: 80, y: 325, opacity: 0, zIndex: 100 },
|
||||
{ duration: 0.5 },
|
||||
)?.finished,
|
||||
safeAnimate(box, { x: 0, y: 32, opacity: 1 }, { duration: 0.5, delay: 1 })
|
||||
?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(phone, { x: 365, y: 0, width: '275px' }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(code, { x: 80, y: 325, opacity: 0, zIndex: 100 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(box, { x: 0, y: 32, opacity: 1 }, { duration: 0.5, delay: 1 })?.finished
|
||||
]);
|
||||
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: [
|
||||
...p.tasks,
|
||||
{
|
||||
id: "3397fecdedb13397fecdedb2",
|
||||
title: "Create wireframes",
|
||||
checked: false,
|
||||
},
|
||||
],
|
||||
}));
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tableSlice: p.tableSlice + 1,
|
||||
}));
|
||||
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: [
|
||||
...p.tasks,
|
||||
{
|
||||
id: "3397fecdedb13397fecdedb3",
|
||||
title: "Create visual design",
|
||||
checked: false,
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tableSlice: p.tableSlice + 1,
|
||||
}));
|
||||
|
||||
await sleep(250);
|
||||
|
||||
safeAnimate(code, { opacity: 1 }, { duration: 0.5 })?.finished,
|
||||
await sleep(250);
|
||||
await write(
|
||||
"New task assigned to you",
|
||||
(v) => {
|
||||
state.update((n) => ({ ...n, heading: v }));
|
||||
},
|
||||
300,
|
||||
);
|
||||
|
||||
await write(
|
||||
"You were assigned a new task in your board. Tap to check it out.",
|
||||
(v) => {
|
||||
state.update((n) => ({ ...n, message: v }));
|
||||
},
|
||||
300,
|
||||
);
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: [
|
||||
...p.tasks,
|
||||
{
|
||||
id: '3397fecdedb13397fecdedb2',
|
||||
title: 'Create wireframes',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
}));
|
||||
await sleep(250);
|
||||
|
||||
await sleep(250);
|
||||
update((p) => ({
|
||||
...p,
|
||||
tableSlice: p.tableSlice + 1
|
||||
}));
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
messages: [
|
||||
...p.messages,
|
||||
{
|
||||
id: "...5689fdoerre2",
|
||||
type: "Push",
|
||||
icon: "./images/icons/illustrated/dark/push.svg",
|
||||
status: "sending",
|
||||
},
|
||||
],
|
||||
}));
|
||||
await sleep(250);
|
||||
|
||||
await sleep(1250);
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: [
|
||||
...p.tasks,
|
||||
{
|
||||
id: '3397fecdedb13397fecdedb3',
|
||||
title: 'Create visual design',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
submit: "success",
|
||||
messages: p.messages.map((m) =>
|
||||
m.id === "...5689fdoerre2" ? { ...m, status: "sent" } : m,
|
||||
),
|
||||
}));
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
tableSlice: p.tableSlice + 1
|
||||
}));
|
||||
|
||||
await sleep(250);
|
||||
|
||||
safeAnimate(code, { opacity: 1 }, { duration: 0.5 })?.finished, await sleep(250);
|
||||
await write(
|
||||
'New task assigned to you',
|
||||
(v) => {
|
||||
state.update((n) => ({ ...n, heading: v }));
|
||||
},
|
||||
300
|
||||
);
|
||||
|
||||
await write(
|
||||
'You were assigned a new task in your board. Tap to check it out.',
|
||||
(v) => {
|
||||
state.update((n) => ({ ...n, message: v }));
|
||||
},
|
||||
300
|
||||
);
|
||||
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
messages: [
|
||||
...p.messages,
|
||||
{
|
||||
id: '...5689fdoerre2',
|
||||
type: 'Push',
|
||||
icon: './images/icons/illustrated/dark/push.svg',
|
||||
status: 'sending'
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
await sleep(1250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
submit: 'success',
|
||||
messages: p.messages.map((m) => (m.id === '...5689fdoerre2' ? { ...m, status: 'sent' } : m))
|
||||
}));
|
||||
};
|
||||
|
||||
export const messagingController = {
|
||||
execute,
|
||||
state,
|
||||
execute,
|
||||
state
|
||||
};
|
||||
|
||||
export const Messaging = {
|
||||
Phone,
|
||||
Box,
|
||||
Code,
|
||||
Phone,
|
||||
Box,
|
||||
Code
|
||||
};
|
||||
|
||||
@@ -1,211 +1,205 @@
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { messagingController } from ".";
|
||||
import TaskCheckbox from "../TaskCheckbox.svelte";
|
||||
import { fly } from 'svelte/transition';
|
||||
import { messagingController } from '.';
|
||||
import TaskCheckbox from '../TaskCheckbox.svelte';
|
||||
|
||||
const { state } = messagingController;
|
||||
const { state } = messagingController;
|
||||
</script>
|
||||
|
||||
{#if $state.submit === "success"}
|
||||
<div class="push-notification" in:fly={{ y: -20 }}>
|
||||
<div class="icon" />
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<h3 class="title">New task assigned to you</h3>
|
||||
<span class="time">now</span>
|
||||
</div>
|
||||
<p class="message">
|
||||
You were assigned a new task in your board. Tap to check it out.
|
||||
</p>
|
||||
{#if $state.submit === 'success'}
|
||||
<div class="push-notification" in:fly={{ y: -20 }}>
|
||||
<div class="icon" />
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<h3 class="title">New task assigned to you</h3>
|
||||
<span class="time">now</span>
|
||||
</div>
|
||||
<p class="message">You were assigned a new task in your board. Tap to check it out.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div data-theme-ignore class="inner-phone light">
|
||||
<div class="header">
|
||||
<p class="title">Your tasks</p>
|
||||
<span class="icon-menu" aria-label="menu" />
|
||||
</div>
|
||||
<div class="header">
|
||||
<p class="title">Your tasks</p>
|
||||
<span class="icon-menu" aria-label="menu" />
|
||||
</div>
|
||||
|
||||
<div class="date">Today</div>
|
||||
<div class="tasks">
|
||||
{#each $state.tasks as task (task.id)}
|
||||
<div
|
||||
class="task"
|
||||
data-checked={task.checked ? "" : undefined}
|
||||
in:fly={{ x: -16 }}
|
||||
>
|
||||
<TaskCheckbox bind:checked={task.checked} />
|
||||
<span class="title">{task.title}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<button class="add-btn">
|
||||
<span class="web-icon-plus" />
|
||||
</button>
|
||||
<div class="date">Today</div>
|
||||
<div class="tasks">
|
||||
{#each $state.tasks as task (task.id)}
|
||||
<div class="task" data-checked={task.checked ? '' : undefined} in:fly={{ x: -16 }}>
|
||||
<TaskCheckbox bind:checked={task.checked} />
|
||||
<span class="title">{task.title}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<button class="add-btn">
|
||||
<span class="web-icon-plus" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.push-notification {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
top: 20px;
|
||||
padding: 0.5rem;
|
||||
margin: 0 auto;
|
||||
width: 125%;
|
||||
height: 60px;
|
||||
gap: 0.75rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1000;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(8px);
|
||||
box-shadow: 3px -8px 32px 0px rgba(0, 0, 0, 0.24);
|
||||
|
||||
.icon {
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 10px;
|
||||
background-image: linear-gradient(180deg, #7c67fe, #4a3e98);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: var(--color-greyscale-700, #56565c);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
}
|
||||
.message {
|
||||
color: var(--color-greyscale-700, #56565c);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
}
|
||||
|
||||
.inner-phone {
|
||||
padding-block: 3rem 1.5rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
[class*="icon-"] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-block-start: 3rem;
|
||||
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
}
|
||||
|
||||
.tasks {
|
||||
margin-block-start: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.task {
|
||||
.push-notification {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
top: 20px;
|
||||
padding: 0.5rem;
|
||||
margin: 0 auto;
|
||||
width: 125%;
|
||||
height: 60px;
|
||||
gap: 0.75rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1000;
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(8px);
|
||||
box-shadow: 3px -8px 32px 0px rgba(0, 0, 0, 0.24);
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid hsl(var(--web-color-greyscale-50));
|
||||
background: hsl(var(--web-color-white));
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
|
||||
padding-block: 0.55rem;
|
||||
padding-inline: 0.88rem;
|
||||
|
||||
/* Responsive/SubBody-400 */
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.00394rem;
|
||||
|
||||
transition: opacity 200ms ease;
|
||||
|
||||
&[data-checked] {
|
||||
opacity: 0.6;
|
||||
.icon {
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 10px;
|
||||
background-image: linear-gradient(180deg, #7c67fe, #4a3e98);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: var(--color-greyscale-700, #56565c);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
}
|
||||
.message {
|
||||
color: var(--color-greyscale-700, #56565c);
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 2.5rem;
|
||||
.inner-phone {
|
||||
padding-block: 3rem 1.5rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
|
||||
background-color: rgba(124, 103, 254, 1);
|
||||
color: rgba(237, 237, 240, 1);
|
||||
font-size: 1.5rem;
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 100%;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
[class*='icon-'] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-block-start: 3rem;
|
||||
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
}
|
||||
|
||||
.tasks {
|
||||
margin-block-start: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.task {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid hsl(var(--web-color-greyscale-50));
|
||||
background: hsl(var(--web-color-white));
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
|
||||
padding-block: 0.55rem;
|
||||
padding-inline: 0.88rem;
|
||||
|
||||
/* Responsive/SubBody-400 */
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.00394rem;
|
||||
|
||||
transition: opacity 200ms ease;
|
||||
|
||||
&[data-checked] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 2.5rem;
|
||||
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
|
||||
background-color: rgba(124, 103, 254, 1);
|
||||
color: rgba(237, 237, 240, 1);
|
||||
font-size: 1.5rem;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { safeAnimate, sleep } from "$lib/animations";
|
||||
import { createResettable } from "$lib/utils/resettable";
|
||||
import { animate } from "motion";
|
||||
import { getElSelector } from "../Products.svelte";
|
||||
import { safeAnimate, sleep } from '$lib/animations';
|
||||
import { createResettable } from '$lib/utils/resettable';
|
||||
import { animate } from 'motion';
|
||||
import { getElSelector } from '../Products.svelte';
|
||||
|
||||
const requests = createResettable(0);
|
||||
const databases = createResettable(0);
|
||||
@@ -12,54 +12,42 @@ const executions = createResettable(0);
|
||||
const realtime = createResettable(0);
|
||||
|
||||
const execute = async () => {
|
||||
const phone = getElSelector("phone");
|
||||
const pd = getElSelector("pd");
|
||||
const phone = getElSelector('phone');
|
||||
const pd = getElSelector('pd');
|
||||
|
||||
const graphBox = getElSelector("graph-box");
|
||||
const graphBox = getElSelector('graph-box');
|
||||
|
||||
const boxesAndStates = [
|
||||
{ box: getElSelector("post-auth"), state: authentication.reset() },
|
||||
{ box: getElSelector("post-storage"), state: storage.reset() },
|
||||
{ box: getElSelector("post-bandwidth"), state: bandwidth.reset() },
|
||||
{ box: getElSelector("post-functions"), state: executions.reset() },
|
||||
{ box: getElSelector("post-databases"), state: databases.reset() },
|
||||
{ box: getElSelector("post-realtime"), state: realtime.reset() },
|
||||
{ box: getElSelector("post-requests"), state: requests.reset() },
|
||||
];
|
||||
const boxesAndStates = [
|
||||
{ box: getElSelector('post-auth'), state: authentication.reset() },
|
||||
{ box: getElSelector('post-storage'), state: storage.reset() },
|
||||
{ box: getElSelector('post-bandwidth'), state: bandwidth.reset() },
|
||||
{ box: getElSelector('post-functions'), state: executions.reset() },
|
||||
{ box: getElSelector('post-databases'), state: databases.reset() },
|
||||
{ box: getElSelector('post-realtime'), state: realtime.reset() },
|
||||
{ box: getElSelector('post-requests'), state: requests.reset() }
|
||||
];
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(pd, { opacity: 0, y: -16 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(
|
||||
graphBox,
|
||||
{ opacity: 0, visibility: "hidden" },
|
||||
{ duration: 0.5 },
|
||||
)?.finished,
|
||||
safeAnimate(
|
||||
phone,
|
||||
{ x: "-50%", width: "660px" },
|
||||
{ duration: 1, delay: 0.5 },
|
||||
)?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(pd, { opacity: 0, y: -16 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(graphBox, { opacity: 0, visibility: 'hidden' }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(phone, { x: '-50%', width: '660px' }, { duration: 1, delay: 0.5 })?.finished
|
||||
]);
|
||||
|
||||
boxesAndStates.forEach(({ box, state }, i) => {
|
||||
safeAnimate(
|
||||
box,
|
||||
{ opacity: 1, y: [1200, 0] },
|
||||
{ duration: 0.5, delay: i * 0.1 },
|
||||
)?.finished;
|
||||
animate(state.set, { duration: 2, delay: (i + 1) * 0.25 });
|
||||
});
|
||||
boxesAndStates.forEach(({ box, state }, i) => {
|
||||
safeAnimate(box, { opacity: 1, y: [1200, 0] }, { duration: 0.5, delay: i * 0.1 })?.finished;
|
||||
animate(state.set, { duration: 2, delay: (i + 1) * 0.25 });
|
||||
});
|
||||
};
|
||||
|
||||
export const postController = {
|
||||
execute,
|
||||
state: {
|
||||
requests,
|
||||
databases,
|
||||
authentication,
|
||||
storage,
|
||||
bandwidth,
|
||||
executions,
|
||||
realtime,
|
||||
},
|
||||
execute,
|
||||
state: {
|
||||
requests,
|
||||
databases,
|
||||
authentication,
|
||||
storage,
|
||||
bandwidth,
|
||||
executions,
|
||||
realtime
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,316 +1,308 @@
|
||||
<script lang="ts">
|
||||
import { toScale } from "$lib/utils/toScale";
|
||||
import { postController } from ".";
|
||||
import { elId } from "../Products.svelte";
|
||||
import { toScale } from '$lib/utils/toScale';
|
||||
import { postController } from '.';
|
||||
import { elId } from '../Products.svelte';
|
||||
|
||||
const {
|
||||
state: {
|
||||
authentication,
|
||||
bandwidth,
|
||||
databases,
|
||||
executions,
|
||||
requests,
|
||||
storage,
|
||||
realtime,
|
||||
},
|
||||
} = postController;
|
||||
const {
|
||||
state: { authentication, bandwidth, databases, executions, requests, storage, realtime }
|
||||
} = postController;
|
||||
|
||||
const formatK = (num: number) => {
|
||||
if (num > 999) {
|
||||
return `${(num / 1000).toFixed(1)}K`;
|
||||
}
|
||||
return Math.floor(num);
|
||||
};
|
||||
const formatK = (num: number) => {
|
||||
if (num > 999) {
|
||||
return `${(num / 1000).toFixed(1)}K`;
|
||||
}
|
||||
return Math.floor(num);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="gradient-box auth" id="post-auth-{$elId}">
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="icon-user-group" />
|
||||
<p class="f-eyebrow">Authentication</p>
|
||||
</div>
|
||||
<p class="f-display mbs-16">
|
||||
{formatK(toScale($authentication, [0, 1], [0, 4000]))}
|
||||
</p>
|
||||
<div class="mbs-4 flex items-center justify-between">
|
||||
<p class="f-sub">Users</p>
|
||||
<p class="f-idk">Sessions: 20K</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="icon-user-group" />
|
||||
<p class="f-eyebrow">Authentication</p>
|
||||
</div>
|
||||
<p class="f-display mbs-16">
|
||||
{formatK(toScale($authentication, [0, 1], [0, 4000]))}
|
||||
</p>
|
||||
<div class="mbs-4 flex items-center justify-between">
|
||||
<p class="f-sub">Users</p>
|
||||
<p class="f-idk">Sessions: 20K</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gradient-box storage" id="post-storage-{$elId}">
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="icon-folder" />
|
||||
<p class="f-eyebrow">Storage</p>
|
||||
</div>
|
||||
<p class="f-display mbs-16">
|
||||
{toScale($storage, [0, 1], [0, 8]).toFixed(1)}
|
||||
<span class="f-tiny-display">GB</span>
|
||||
</p>
|
||||
<div class="mbs-4 flex items-center justify-between">
|
||||
<p class="f-sub">Storage</p>
|
||||
<p class="f-idk">Buckets: 44</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="icon-folder" />
|
||||
<p class="f-eyebrow">Storage</p>
|
||||
</div>
|
||||
<p class="f-display mbs-16">
|
||||
{toScale($storage, [0, 1], [0, 8]).toFixed(1)}
|
||||
<span class="f-tiny-display">GB</span>
|
||||
</p>
|
||||
<div class="mbs-4 flex items-center justify-between">
|
||||
<p class="f-sub">Storage</p>
|
||||
<p class="f-idk">Buckets: 44</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gradient-box bandwidth" id="post-bandwidth-{$elId}">
|
||||
<p class="f-display">
|
||||
{toScale($bandwidth, [0, 1], [0, 1.2]).toFixed(2)}
|
||||
<span class="f-tiny-display">GB</span>
|
||||
</p>
|
||||
<p class="f-sub">Bandwidth</p>
|
||||
<img class="mbs-16" src="./images/animations/bandwidth-graph.svg" alt="" />
|
||||
<p class="f-display">
|
||||
{toScale($bandwidth, [0, 1], [0, 1.2]).toFixed(2)}
|
||||
<span class="f-tiny-display">GB</span>
|
||||
</p>
|
||||
<p class="f-sub">Bandwidth</p>
|
||||
<img class="mbs-16" src="./images/animations/bandwidth-graph.svg" alt="" />
|
||||
</div>
|
||||
|
||||
<div class="gradient-box functions" id="post-functions-{$elId}">
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="icon-lightning-bolt" />
|
||||
<p class="f-eyebrow">Functions</p>
|
||||
</div>
|
||||
<p class="f-display mbs-16">
|
||||
{toScale($executions, [0, 1], [0, 846]).toFixed(0)}
|
||||
</p>
|
||||
<div class="mbs-4 flex items-center justify-between">
|
||||
<p class="f-sub">Executions</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="icon-lightning-bolt" />
|
||||
<p class="f-eyebrow">Functions</p>
|
||||
</div>
|
||||
<p class="f-display mbs-16">
|
||||
{toScale($executions, [0, 1], [0, 846]).toFixed(0)}
|
||||
</p>
|
||||
<div class="mbs-4 flex items-center justify-between">
|
||||
<p class="f-sub">Executions</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gradient-box databases" id="post-databases-{$elId}">
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="icon-database" />
|
||||
<p class="f-eyebrow">Databases</p>
|
||||
</div>
|
||||
<p class="f-display mbs-16">
|
||||
{toScale($databases, [0, 1], [0, 8]).toFixed(0)}
|
||||
</p>
|
||||
<div class="mbs-4 flex items-center justify-between">
|
||||
<p class="f-sub">Databases</p>
|
||||
<p class="f-idk">Documents: 20</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="icon-database" />
|
||||
<p class="f-eyebrow">Databases</p>
|
||||
</div>
|
||||
<p class="f-display mbs-16">
|
||||
{toScale($databases, [0, 1], [0, 8]).toFixed(0)}
|
||||
</p>
|
||||
<div class="mbs-4 flex items-center justify-between">
|
||||
<p class="f-sub">Databases</p>
|
||||
<p class="f-idk">Documents: 20</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gradient-box requests" id="post-requests-{$elId}">
|
||||
<p class="f-display">{formatK(toScale($requests, [0, 1], [0, 6849]))}</p>
|
||||
<p class="f-sub">Requests</p>
|
||||
<img class="mbs-16" src="./images/animations/requests-graph.svg" alt="" />
|
||||
<p class="f-display">{formatK(toScale($requests, [0, 1], [0, 6849]))}</p>
|
||||
<p class="f-sub">Requests</p>
|
||||
<img class="mbs-16" src="./images/animations/requests-graph.svg" alt="" />
|
||||
</div>
|
||||
|
||||
<div class="gradient-box realtime" id="post-realtime-{$elId}">
|
||||
<p class="f-display">{formatK(toScale($realtime, [0, 1], [0, 100000]))}</p>
|
||||
<p class="f-sub">Realtime connections</p>
|
||||
<img class="mbs-16" src="./images/animations/realtime-graph.svg" alt="" />
|
||||
<p class="f-display">{formatK(toScale($realtime, [0, 1], [0, 100000]))}</p>
|
||||
<p class="f-sub">Realtime connections</p>
|
||||
<img class="mbs-16" src="./images/animations/realtime-graph.svg" alt="" />
|
||||
</div>
|
||||
|
||||
<div class="gradient-overlay flex flex-col">
|
||||
<h3>See your products grow</h3>
|
||||
<p>
|
||||
Keep track of your projects progress on the Appwrite Console and see them
|
||||
grow into products users love and use every day.
|
||||
</p>
|
||||
<h3>See your products grow</h3>
|
||||
<p>
|
||||
Keep track of your projects progress on the Appwrite Console and see them grow into products
|
||||
users love and use every day.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "$scss/abstract/mixins/border-gradient" as gradients;
|
||||
@use '$scss/abstract/mixins/border-gradient' as gradients;
|
||||
|
||||
// Utilities
|
||||
.f-eyebrow {
|
||||
color: #adadb0;
|
||||
/* Eyebrow headings/level 3 */
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 150%; /* 1.125rem */
|
||||
letter-spacing: 0.09rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.f-display {
|
||||
color: #ededf0;
|
||||
font-family: Aeonik Pro;
|
||||
font-size: 2rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 2.25rem; /* 112.5% */
|
||||
}
|
||||
|
||||
.f-tiny-display {
|
||||
color: var(--greyscale-50, #ededf0);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1rem; /* 114.286% */
|
||||
}
|
||||
|
||||
.f-sub {
|
||||
color: #97979b;
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.125rem; /* 128.571% */
|
||||
}
|
||||
|
||||
.f-idk {
|
||||
color: var(--primary, #e4e4e7);
|
||||
text-align: right;
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%; /* 1.3125rem */
|
||||
opacity: 40%;
|
||||
}
|
||||
|
||||
.mbs-16 {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
.mbs-4 {
|
||||
margin-block-start: 0.25rem;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
// Components
|
||||
.gradient-box {
|
||||
@include gradients.border-gradient;
|
||||
--m-border-gradient-before: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.12) 0%,
|
||||
rgba(255, 255, 255, 0) 125.11%
|
||||
);
|
||||
--m-border-radius: 1rem;
|
||||
|
||||
position: absolute;
|
||||
background: var(--card, rgba(35, 35, 37, 0.9));
|
||||
box-shadow:
|
||||
0px 0px 0px 0px rgba(0, 0, 0, 0.06),
|
||||
-2px 4px 9px 0px rgba(0, 0, 0, 0.06),
|
||||
-8px 15px 17px 0px rgba(0, 0, 0, 0.05),
|
||||
-19px 34px 23px 0px rgba(0, 0, 0, 0.03),
|
||||
-33px 60px 27px 0px rgba(0, 0, 0, 0.01),
|
||||
-52px 94px 30px 0px rgba(0, 0, 0, 0);
|
||||
backdrop-filter: blur(8px);
|
||||
|
||||
padding: 1.5rem;
|
||||
|
||||
z-index: 9999;
|
||||
|
||||
min-width: 20rem;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.gradient-overlay {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
bottom: -7.5rem;
|
||||
width: 100%;
|
||||
height: 30rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.25rem;
|
||||
|
||||
padding-block-start: 15rem;
|
||||
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.75s ease-in-out 1.5s forwards;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
//background: #19191d; // old bg
|
||||
//filter: blur(125px); // break Safari
|
||||
background: #19191dcc;
|
||||
filter: blur(67px);
|
||||
// Utilities
|
||||
.f-eyebrow {
|
||||
color: #adadb0;
|
||||
/* Eyebrow headings/level 3 */
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 150%; /* 1.125rem */
|
||||
letter-spacing: 0.09rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h3 {
|
||||
position: relative;
|
||||
color: var(--primary, #e4e4e7);
|
||||
text-align: center;
|
||||
|
||||
/* Desktop/Display */
|
||||
font-family: Aeonik Pro;
|
||||
font-size: 4rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4.25rem; /* 106.25% */
|
||||
letter-spacing: -0.04rem;
|
||||
.f-display {
|
||||
color: #ededf0;
|
||||
font-family: Aeonik Pro;
|
||||
font-size: 2rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 2.25rem; /* 112.5% */
|
||||
}
|
||||
|
||||
p {
|
||||
position: relative;
|
||||
|
||||
color: var(--secondary, #adadb0);
|
||||
text-align: center;
|
||||
|
||||
/* Desktop/Description */
|
||||
font-family: Inter;
|
||||
font-size: 1.25rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.75rem; /* 140% */
|
||||
letter-spacing: -0.0175rem;
|
||||
max-width: 40rem;
|
||||
text-align: center;
|
||||
.f-tiny-display {
|
||||
color: var(--greyscale-50, #ededf0);
|
||||
text-align: center;
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1rem; /* 114.286% */
|
||||
}
|
||||
}
|
||||
|
||||
// Specifics
|
||||
.auth {
|
||||
opacity: 0;
|
||||
left: 4rem;
|
||||
top: -11rem;
|
||||
}
|
||||
.f-sub {
|
||||
color: #97979b;
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.125rem; /* 128.571% */
|
||||
}
|
||||
|
||||
.storage {
|
||||
opacity: 0;
|
||||
left: -10rem;
|
||||
top: -2rem;
|
||||
}
|
||||
.f-idk {
|
||||
color: var(--primary, #e4e4e7);
|
||||
text-align: right;
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 150%; /* 1.3125rem */
|
||||
opacity: 40%;
|
||||
}
|
||||
|
||||
.bandwidth {
|
||||
opacity: 0;
|
||||
left: -4rem;
|
||||
top: 11rem;
|
||||
}
|
||||
.mbs-16 {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
.functions {
|
||||
opacity: 0;
|
||||
left: -6rem;
|
||||
top: 35rem;
|
||||
}
|
||||
.mbs-4 {
|
||||
margin-block-start: 0.25rem;
|
||||
}
|
||||
|
||||
.databases {
|
||||
opacity: 0;
|
||||
top: -13rem;
|
||||
right: 10rem;
|
||||
}
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.requests {
|
||||
opacity: 0;
|
||||
top: 17rem;
|
||||
right: -18rem;
|
||||
}
|
||||
// Components
|
||||
.gradient-box {
|
||||
@include gradients.border-gradient;
|
||||
--m-border-gradient-before: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0.12) 0%,
|
||||
rgba(255, 255, 255, 0) 125.11%
|
||||
);
|
||||
--m-border-radius: 1rem;
|
||||
|
||||
.realtime {
|
||||
opacity: 0;
|
||||
top: -1rem;
|
||||
right: -7rem;
|
||||
}
|
||||
position: absolute;
|
||||
background: var(--card, rgba(35, 35, 37, 0.9));
|
||||
box-shadow:
|
||||
0px 0px 0px 0px rgba(0, 0, 0, 0.06),
|
||||
-2px 4px 9px 0px rgba(0, 0, 0, 0.06),
|
||||
-8px 15px 17px 0px rgba(0, 0, 0, 0.05),
|
||||
-19px 34px 23px 0px rgba(0, 0, 0, 0.03),
|
||||
-33px 60px 27px 0px rgba(0, 0, 0, 0.01),
|
||||
-52px 94px 30px 0px rgba(0, 0, 0, 0);
|
||||
backdrop-filter: blur(8px);
|
||||
|
||||
padding: 1.5rem;
|
||||
|
||||
z-index: 9999;
|
||||
|
||||
min-width: 20rem;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.gradient-overlay {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
bottom: -7.5rem;
|
||||
width: 100%;
|
||||
height: 30rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.25rem;
|
||||
|
||||
padding-block-start: 15rem;
|
||||
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.75s ease-in-out 1.5s forwards;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
inset: 0;
|
||||
position: absolute;
|
||||
//background: #19191d; // old bg
|
||||
//filter: blur(125px); // break Safari
|
||||
background: #19191dcc;
|
||||
filter: blur(67px);
|
||||
}
|
||||
|
||||
h3 {
|
||||
position: relative;
|
||||
color: var(--primary, #e4e4e7);
|
||||
text-align: center;
|
||||
|
||||
/* Desktop/Display */
|
||||
font-family: Aeonik Pro;
|
||||
font-size: 4rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 4.25rem; /* 106.25% */
|
||||
letter-spacing: -0.04rem;
|
||||
}
|
||||
|
||||
p {
|
||||
position: relative;
|
||||
|
||||
color: var(--secondary, #adadb0);
|
||||
text-align: center;
|
||||
|
||||
/* Desktop/Description */
|
||||
font-family: Inter;
|
||||
font-size: 1.25rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.75rem; /* 140% */
|
||||
letter-spacing: -0.0175rem;
|
||||
max-width: 40rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Specifics
|
||||
.auth {
|
||||
opacity: 0;
|
||||
left: 4rem;
|
||||
top: -11rem;
|
||||
}
|
||||
|
||||
.storage {
|
||||
opacity: 0;
|
||||
left: -10rem;
|
||||
top: -2rem;
|
||||
}
|
||||
|
||||
.bandwidth {
|
||||
opacity: 0;
|
||||
left: -4rem;
|
||||
top: 11rem;
|
||||
}
|
||||
|
||||
.functions {
|
||||
opacity: 0;
|
||||
left: -6rem;
|
||||
top: 35rem;
|
||||
}
|
||||
|
||||
.databases {
|
||||
opacity: 0;
|
||||
top: -13rem;
|
||||
right: 10rem;
|
||||
}
|
||||
|
||||
.requests {
|
||||
opacity: 0;
|
||||
top: 17rem;
|
||||
right: -18rem;
|
||||
}
|
||||
|
||||
.realtime {
|
||||
opacity: 0;
|
||||
top: -1rem;
|
||||
right: -7rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,286 +1,238 @@
|
||||
import Phone from "./phone.svelte";
|
||||
import Phone from './phone.svelte';
|
||||
|
||||
import { safeAnimate, sleep } from "$lib/animations";
|
||||
import { createResettable } from "$lib/utils/resettable";
|
||||
import { getElSelector } from "../Products.svelte";
|
||||
import { animate } from "motion";
|
||||
import { safeAnimate, sleep } from '$lib/animations';
|
||||
import { createResettable } from '$lib/utils/resettable';
|
||||
import { getElSelector } from '../Products.svelte';
|
||||
import { animate } from 'motion';
|
||||
|
||||
type Task = {
|
||||
title: string;
|
||||
tags: string[];
|
||||
images?: string[];
|
||||
title: string;
|
||||
tags: string[];
|
||||
images?: string[];
|
||||
};
|
||||
|
||||
type User = {
|
||||
name: string;
|
||||
color: string;
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
tasks: {
|
||||
todo: Task[];
|
||||
doing: Task[];
|
||||
done: Task[];
|
||||
};
|
||||
users: User[];
|
||||
tasks: {
|
||||
todo: Task[];
|
||||
doing: Task[];
|
||||
done: Task[];
|
||||
};
|
||||
users: User[];
|
||||
};
|
||||
|
||||
const state = createResettable<State>({
|
||||
tasks: {
|
||||
todo: [
|
||||
{
|
||||
title: "Edit images for website",
|
||||
tags: ["design", "content"],
|
||||
images: [
|
||||
"./images/animations/storage-2.png",
|
||||
"./images/animations/storage-3.png",
|
||||
tasks: {
|
||||
todo: [
|
||||
{
|
||||
title: 'Edit images for website',
|
||||
tags: ['design', 'content'],
|
||||
images: ['./images/animations/storage-2.png', './images/animations/storage-3.png']
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
doing: [
|
||||
{
|
||||
title: "Handoff meet",
|
||||
tags: ["design", "dev"],
|
||||
},
|
||||
],
|
||||
done: [],
|
||||
},
|
||||
users: [],
|
||||
doing: [
|
||||
{
|
||||
title: 'Handoff meet',
|
||||
tags: ['design', 'dev']
|
||||
}
|
||||
],
|
||||
done: []
|
||||
},
|
||||
users: []
|
||||
});
|
||||
|
||||
export const connectionsProg = createResettable(0);
|
||||
|
||||
const addUser = (update: typeof state.update, user: User) => {
|
||||
update((p) => ({
|
||||
...p,
|
||||
users: [...p.users, user],
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
users: [...p.users, user]
|
||||
}));
|
||||
};
|
||||
|
||||
const addTask = (
|
||||
update: typeof state.update,
|
||||
group: keyof State["tasks"],
|
||||
task: Task,
|
||||
) => {
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: {
|
||||
...p.tasks,
|
||||
[group]: [task, ...p.tasks[group]],
|
||||
},
|
||||
}));
|
||||
const addTask = (update: typeof state.update, group: keyof State['tasks'], task: Task) => {
|
||||
update((p) => ({
|
||||
...p,
|
||||
tasks: {
|
||||
...p.tasks,
|
||||
[group]: [task, ...p.tasks[group]]
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
const execute = async () => {
|
||||
const phone = getElSelector("phone");
|
||||
const code = getElSelector("code");
|
||||
const box = getElSelector("box");
|
||||
const phone = getElSelector('phone');
|
||||
const code = getElSelector('code');
|
||||
const box = getElSelector('box');
|
||||
|
||||
const walter = getElSelector("user-Walter");
|
||||
const aditya = getElSelector("user-Aditya");
|
||||
const sara = getElSelector("user-Sara");
|
||||
const walter = getElSelector('user-Walter');
|
||||
const aditya = getElSelector('user-Aditya');
|
||||
const sara = getElSelector('user-Sara');
|
||||
|
||||
const addTodo = getElSelector("add-todo");
|
||||
const addDoing = getElSelector("add-doing");
|
||||
const addDone = getElSelector("add-done");
|
||||
const addTodo = getElSelector('add-todo');
|
||||
const addDoing = getElSelector('add-doing');
|
||||
const addDone = getElSelector('add-done');
|
||||
|
||||
const graphBox = getElSelector("graph-box");
|
||||
const graphBox = getElSelector('graph-box');
|
||||
|
||||
const pd = getElSelector("pd");
|
||||
const pd = getElSelector('pd');
|
||||
|
||||
const { update } = state.reset();
|
||||
const { set: setConn } = connectionsProg.reset();
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(phone, { x: 0, y: 0, width: "660px" }, { duration: 0.5 })
|
||||
?.finished,
|
||||
safeAnimate(code, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(
|
||||
graphBox,
|
||||
{ opacity: 0, x: 0, y: 0, visibility: "visible" },
|
||||
{ duration: 0 },
|
||||
)?.finished,
|
||||
safeAnimate(pd, { opacity: 1, y: 0 }, { duration: 0.5 })?.finished,
|
||||
]);
|
||||
|
||||
// Graphbox
|
||||
sleep(1250).then(async () => {
|
||||
await safeAnimate(graphBox, { opacity: 1 }, { duration: 0.5 })?.finished;
|
||||
|
||||
animate(
|
||||
(y) => {
|
||||
setConn(y);
|
||||
},
|
||||
{ duration: 2.5, easing: "ease-in" },
|
||||
);
|
||||
});
|
||||
|
||||
// Walter
|
||||
sleep(500).then(async () => {
|
||||
addUser(update, { name: "Walter", color: "#fd366e" });
|
||||
await sleep(500);
|
||||
await safeAnimate(walter, { x: -200, y: -100, scale: 1 }, { duration: 0.5 })
|
||||
?.finished;
|
||||
await Promise.all([
|
||||
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addTodo, { scale: [1, 0.9, 1] }, { duration: 0.25 })
|
||||
?.finished,
|
||||
]);
|
||||
|
||||
addTask(update, "todo", {
|
||||
title: "Handoff meet",
|
||||
tags: ["design", "dev"],
|
||||
});
|
||||
|
||||
await safeAnimate(
|
||||
walter,
|
||||
{ scale: 1, x: -180, y: -160 },
|
||||
{ duration: 0.75, delay: 0.5 },
|
||||
)?.finished;
|
||||
|
||||
await sleep(500);
|
||||
|
||||
await safeAnimate(walter, { x: 210, y: -100, scale: 1 }, { duration: 0.5 })
|
||||
?.finished;
|
||||
const { update } = state.reset();
|
||||
const { set: setConn } = connectionsProg.reset();
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })
|
||||
?.finished,
|
||||
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(phone, { x: 0, y: 0, width: '660px' }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(code, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(graphBox, { opacity: 0, x: 0, y: 0, visibility: 'visible' }, { duration: 0 })
|
||||
?.finished,
|
||||
safeAnimate(pd, { opacity: 1, y: 0 }, { duration: 0.5 })?.finished
|
||||
]);
|
||||
|
||||
addTask(update, "done", {
|
||||
title: "Create migrations script",
|
||||
tags: ["Dev"],
|
||||
// Graphbox
|
||||
sleep(1250).then(async () => {
|
||||
await safeAnimate(graphBox, { opacity: 1 }, { duration: 0.5 })?.finished;
|
||||
|
||||
animate(
|
||||
(y) => {
|
||||
setConn(y);
|
||||
},
|
||||
{ duration: 2.5, easing: 'ease-in' }
|
||||
);
|
||||
});
|
||||
|
||||
safeAnimate(
|
||||
walter,
|
||||
{ scale: 1, x: 230, y: -20 },
|
||||
{ duration: 0.75, delay: 0.5 },
|
||||
);
|
||||
// Walter
|
||||
sleep(500).then(async () => {
|
||||
addUser(update, { name: 'Walter', color: '#fd366e' });
|
||||
await sleep(500);
|
||||
await safeAnimate(walter, { x: -200, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
|
||||
await Promise.all([
|
||||
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addTodo, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
|
||||
]);
|
||||
|
||||
await sleep(750);
|
||||
addTask(update, 'todo', {
|
||||
title: 'Handoff meet',
|
||||
tags: ['design', 'dev']
|
||||
});
|
||||
|
||||
await safeAnimate(walter, { x: -10, y: -100, scale: 1 }, { duration: 0.5 })
|
||||
?.finished;
|
||||
await safeAnimate(walter, { scale: 1, x: -180, y: -160 }, { duration: 0.75, delay: 0.5 })
|
||||
?.finished;
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDoing, { scale: [1, 0.9, 1] }, { duration: 0.25 })
|
||||
?.finished,
|
||||
]);
|
||||
await sleep(500);
|
||||
|
||||
addTask(update, "doing", {
|
||||
title: "Configure blog SEO",
|
||||
tags: ["dev", "content"],
|
||||
await safeAnimate(walter, { x: 210, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
|
||||
]);
|
||||
|
||||
addTask(update, 'done', {
|
||||
title: 'Create migrations script',
|
||||
tags: ['Dev']
|
||||
});
|
||||
|
||||
safeAnimate(walter, { scale: 1, x: 230, y: -20 }, { duration: 0.75, delay: 0.5 });
|
||||
|
||||
await sleep(750);
|
||||
|
||||
await safeAnimate(walter, { x: -10, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(walter, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDoing, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
|
||||
]);
|
||||
|
||||
addTask(update, 'doing', {
|
||||
title: 'Configure blog SEO',
|
||||
tags: ['dev', 'content']
|
||||
});
|
||||
|
||||
await safeAnimate(walter, { scale: 1, x: -70, y: 80 }, { duration: 0.75, delay: 0.25 });
|
||||
});
|
||||
|
||||
await safeAnimate(
|
||||
walter,
|
||||
{ scale: 1, x: -70, y: 80 },
|
||||
{ duration: 0.75, delay: 0.25 },
|
||||
);
|
||||
});
|
||||
// Aditya
|
||||
sleep(1500).then(async () => {
|
||||
addUser(update, { name: 'Aditya', color: 'rgba(124, 103, 254, 1)' });
|
||||
await sleep(500);
|
||||
await safeAnimate(aditya, { x: 200, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
|
||||
await Promise.all([
|
||||
safeAnimate(aditya, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
|
||||
]);
|
||||
|
||||
// Aditya
|
||||
sleep(1500).then(async () => {
|
||||
addUser(update, { name: "Aditya", color: "rgba(124, 103, 254, 1)" });
|
||||
await sleep(500);
|
||||
await safeAnimate(aditya, { x: 200, y: -100, scale: 1 }, { duration: 0.5 })
|
||||
?.finished;
|
||||
await Promise.all([
|
||||
safeAnimate(aditya, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })
|
||||
?.finished,
|
||||
]);
|
||||
addTask(update, 'done', {
|
||||
title: 'Write up briefing',
|
||||
tags: ['dev-rel']
|
||||
});
|
||||
|
||||
addTask(update, "done", {
|
||||
title: "Write up briefing",
|
||||
tags: ["dev-rel"],
|
||||
await safeAnimate(aditya, { scale: 1, x: 180, y: 60 }, { duration: 0.75, delay: 0.5 })
|
||||
?.finished;
|
||||
|
||||
await sleep(750);
|
||||
|
||||
await safeAnimate(aditya, { x: -210, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(aditya, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addTodo, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
|
||||
]);
|
||||
|
||||
addTask(update, 'todo', {
|
||||
title: 'Review branding blog post',
|
||||
tags: ['dev-rel']
|
||||
});
|
||||
|
||||
await safeAnimate(aditya, { scale: 1, x: 70, y: -220 }, { duration: 0.75, delay: 0.5 })
|
||||
?.finished;
|
||||
});
|
||||
|
||||
await safeAnimate(
|
||||
aditya,
|
||||
{ scale: 1, x: 180, y: 60 },
|
||||
{ duration: 0.75, delay: 0.5 },
|
||||
)?.finished;
|
||||
// Sara
|
||||
sleep(2500).then(async () => {
|
||||
addUser(update, { name: 'Sara', color: 'rgba(103, 163, 254, 1)' });
|
||||
await sleep(500);
|
||||
await safeAnimate(sara, { x: 0, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
|
||||
await Promise.all([
|
||||
safeAnimate(sara, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDoing, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
|
||||
]);
|
||||
|
||||
await sleep(750);
|
||||
addTask(update, 'doing', {
|
||||
title: 'Prepare design system presentation',
|
||||
tags: ['design']
|
||||
});
|
||||
|
||||
await safeAnimate(aditya, { x: -210, y: -100, scale: 1 }, { duration: 0.5 })
|
||||
?.finished;
|
||||
await safeAnimate(sara, { scale: 1, y: 60, x: -50 }, { duration: 0.75, delay: 0.5 })
|
||||
?.finished;
|
||||
await sleep(250);
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(aditya, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addTodo, { scale: [1, 0.9, 1] }, { duration: 0.25 })
|
||||
?.finished,
|
||||
]);
|
||||
await safeAnimate(sara, { x: 200, y: -100, scale: 1 }, { duration: 0.5 })?.finished;
|
||||
|
||||
addTask(update, "todo", {
|
||||
title: "Review branding blog post",
|
||||
tags: ["dev-rel"],
|
||||
await Promise.all([
|
||||
safeAnimate(sara, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished
|
||||
]);
|
||||
|
||||
addTask(update, 'done', {
|
||||
title: 'QA branding animations',
|
||||
tags: ['Dev']
|
||||
});
|
||||
|
||||
await safeAnimate(sara, { scale: 1, x: 180, y: 60 }, { duration: 0.75, delay: 0.5 })
|
||||
?.finished;
|
||||
});
|
||||
|
||||
await safeAnimate(
|
||||
aditya,
|
||||
{ scale: 1, x: 70, y: -220 },
|
||||
{ duration: 0.75, delay: 0.5 },
|
||||
)?.finished;
|
||||
});
|
||||
|
||||
// Sara
|
||||
sleep(2500).then(async () => {
|
||||
addUser(update, { name: "Sara", color: "rgba(103, 163, 254, 1)" });
|
||||
await sleep(500);
|
||||
await safeAnimate(sara, { x: 0, y: -100, scale: 1 }, { duration: 0.5 })
|
||||
?.finished;
|
||||
await Promise.all([
|
||||
safeAnimate(sara, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDoing, { scale: [1, 0.9, 1] }, { duration: 0.25 })
|
||||
?.finished,
|
||||
]);
|
||||
|
||||
addTask(update, "doing", {
|
||||
title: "Prepare design system presentation",
|
||||
tags: ["design"],
|
||||
});
|
||||
|
||||
await safeAnimate(
|
||||
sara,
|
||||
{ scale: 1, y: 60, x: -50 },
|
||||
{ duration: 0.75, delay: 0.5 },
|
||||
)?.finished;
|
||||
await sleep(250);
|
||||
|
||||
await safeAnimate(sara, { x: 200, y: -100, scale: 1 }, { duration: 0.5 })
|
||||
?.finished;
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(sara, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(addDone, { scale: [1, 0.9, 1] }, { duration: 0.25 })
|
||||
?.finished,
|
||||
]);
|
||||
|
||||
addTask(update, "done", {
|
||||
title: "QA branding animations",
|
||||
tags: ["Dev"],
|
||||
});
|
||||
|
||||
await safeAnimate(
|
||||
sara,
|
||||
{ scale: 1, x: 180, y: 60 },
|
||||
{ duration: 0.75, delay: 0.5 },
|
||||
)?.finished;
|
||||
});
|
||||
};
|
||||
|
||||
export const realtimeController = {
|
||||
execute,
|
||||
state,
|
||||
execute,
|
||||
state
|
||||
};
|
||||
|
||||
export const Realtime = {
|
||||
Phone,
|
||||
Phone
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,43 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { slide } from "svelte/transition";
|
||||
import { storageController } from ".";
|
||||
import { flip } from "$lib/utils/flip";
|
||||
import { slide } from 'svelte/transition';
|
||||
import { storageController } from '.';
|
||||
import { flip } from '$lib/utils/flip';
|
||||
|
||||
const { state } = storageController;
|
||||
const { state } = storageController;
|
||||
</script>
|
||||
|
||||
<div class="pseudo-table">
|
||||
<div class="header">
|
||||
<span class="web-eyebrow">Filename</span>
|
||||
<span class="web-eyebrow">Type</span>
|
||||
<span class="web-eyebrow">Size</span>
|
||||
</div>
|
||||
{#each $state.files as file (file.src)}
|
||||
<div
|
||||
class="row"
|
||||
in:slide={{ duration: 150 }}
|
||||
animate:flip={{ duration: 150 }}
|
||||
>
|
||||
<div class="img-wrapper">
|
||||
<img src={file.src} alt="" />
|
||||
<span>{file.filename}</span>
|
||||
</div>
|
||||
<span class="truncated">{file.type}</span>
|
||||
<span class="truncated">{file.size}</span>
|
||||
<div class="header">
|
||||
<span class="web-eyebrow">Filename</span>
|
||||
<span class="web-eyebrow">Type</span>
|
||||
<span class="web-eyebrow">Size</span>
|
||||
</div>
|
||||
{/each}
|
||||
{#each $state.files as file (file.src)}
|
||||
<div class="row" in:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
|
||||
<div class="img-wrapper">
|
||||
<img src={file.src} alt="" />
|
||||
<span>{file.filename}</span>
|
||||
</div>
|
||||
<span class="truncated">{file.type}</span>
|
||||
<span class="truncated">{file.size}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.header,
|
||||
.row {
|
||||
grid-template-columns: 7rem 1fr 1fr !important;
|
||||
gap: 1.5rem 3rem;
|
||||
}
|
||||
.header,
|
||||
.row {
|
||||
grid-template-columns: 7rem 1fr 1fr !important;
|
||||
gap: 1.5rem 3rem;
|
||||
}
|
||||
|
||||
.img-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.img-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Code from "$lib/animations/CodeWindow/Code.svelte";
|
||||
import Code from '$lib/animations/CodeWindow/Code.svelte';
|
||||
|
||||
let content = `
|
||||
let content = `
|
||||
const result = storage.createFile(
|
||||
'my-bucket',
|
||||
ID.unique(),
|
||||
|
||||
@@ -1,146 +1,137 @@
|
||||
import Box from "./box.svelte";
|
||||
import Code from "./code.svelte";
|
||||
import Phone from "./phone.svelte";
|
||||
import Box from './box.svelte';
|
||||
import Code from './code.svelte';
|
||||
import Phone from './phone.svelte';
|
||||
|
||||
import { safeAnimate, sleep } from "$lib/animations";
|
||||
import { createResettable } from "$lib/utils/resettable";
|
||||
import { getElSelector } from "../Products.svelte";
|
||||
import { safeAnimate, sleep } from '$lib/animations';
|
||||
import { createResettable } from '$lib/utils/resettable';
|
||||
import { getElSelector } from '../Products.svelte';
|
||||
|
||||
type File = {
|
||||
src: string;
|
||||
filename: string;
|
||||
type: string;
|
||||
size: string;
|
||||
src: string;
|
||||
filename: string;
|
||||
type: string;
|
||||
size: string;
|
||||
};
|
||||
|
||||
type State = {
|
||||
files: File[];
|
||||
files: File[];
|
||||
};
|
||||
|
||||
const state = createResettable<State>({
|
||||
files: [],
|
||||
files: []
|
||||
});
|
||||
|
||||
const execute = async () => {
|
||||
const phone = getElSelector("phone");
|
||||
const box = getElSelector("box");
|
||||
const code = getElSelector("code");
|
||||
const overlay = getElSelector("overlay");
|
||||
const drawer = getElSelector("drawer");
|
||||
const upload = getElSelector("upload");
|
||||
const uploadBtn = getElSelector("upload-btn");
|
||||
const uploadImg = getElSelector("upload-img");
|
||||
const uploadLoading = getElSelector("upload-loading");
|
||||
const uploadText = getElSelector("upload-text");
|
||||
const phone = getElSelector('phone');
|
||||
const box = getElSelector('box');
|
||||
const code = getElSelector('code');
|
||||
const overlay = getElSelector('overlay');
|
||||
const drawer = getElSelector('drawer');
|
||||
const upload = getElSelector('upload');
|
||||
const uploadBtn = getElSelector('upload-btn');
|
||||
const uploadImg = getElSelector('upload-img');
|
||||
const uploadLoading = getElSelector('upload-loading');
|
||||
const uploadText = getElSelector('upload-text');
|
||||
|
||||
const { update } = state.reset();
|
||||
const { update } = state.reset();
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(phone, { x: 0, y: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(code, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(uploadLoading, { opacity: 0 }, { duration: 0 })?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(phone, { x: 0, y: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(box, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(code, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(uploadLoading, { opacity: 0 }, { duration: 0 })?.finished
|
||||
]);
|
||||
|
||||
await safeAnimate(code, { zIndex: 20 }, { duration: 0 })?.finished;
|
||||
await safeAnimate(code, { zIndex: 20 }, { duration: 0 })?.finished;
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
files: [
|
||||
...p.files,
|
||||
{
|
||||
src: "/images/animations/storage-1.png",
|
||||
filename: "Profile.png",
|
||||
type: "image/png",
|
||||
size: "362.6 KB",
|
||||
},
|
||||
],
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
files: [
|
||||
...p.files,
|
||||
{
|
||||
src: '/images/animations/storage-1.png',
|
||||
filename: 'Profile.png',
|
||||
type: 'image/png',
|
||||
size: '362.6 KB'
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(overlay, { opacity: 1 }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(drawer, { y: [128, 0], opacity: 1 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(overlay, { opacity: 1 }, { duration: 0.25 })?.finished,
|
||||
safeAnimate(drawer, { y: [128, 0], opacity: 1 }, { duration: 0.5 })?.finished
|
||||
]);
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
await safeAnimate(uploadBtn, { scale: [1, 0.9, 1] }, { duration: 0.25 })
|
||||
?.finished;
|
||||
await safeAnimate(uploadBtn, { scale: [1, 0.9, 1] }, { duration: 0.25 })?.finished;
|
||||
|
||||
await safeAnimate(code, { x: 300, y: 32 }, { duration: 0 })?.finished;
|
||||
await safeAnimate(code, { x: 300, y: 32 }, { duration: 0 })?.finished;
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(code, { y: [32 - 16, 32], opacity: 1 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
safeAnimate(upload, { y: [-16, 0], opacity: 1 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(code, { y: [32 - 16, 32], opacity: 1 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(upload, { y: [-16, 0], opacity: 1 }, { duration: 0.5 })?.finished
|
||||
]);
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
await safeAnimate(box, { x: 300, y: 300 }, { duration: 0 })?.finished;
|
||||
await safeAnimate(box, { x: 300, y: 300 }, { duration: 0 })?.finished;
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(
|
||||
uploadImg,
|
||||
{ x: [64, 48], y: [80, 64], opacity: 1 },
|
||||
{ duration: 0.5 },
|
||||
)?.finished,
|
||||
safeAnimate(box, { y: [300 - 16, 300], opacity: 1 }, { duration: 1 })
|
||||
?.finished,
|
||||
]);
|
||||
await Promise.all([
|
||||
safeAnimate(uploadImg, { x: [64, 48], y: [80, 64], opacity: 1 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
safeAnimate(box, { y: [300 - 16, 300], opacity: 1 }, { duration: 1 })?.finished
|
||||
]);
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
await Promise.all([
|
||||
safeAnimate(uploadText, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(uploadLoading, { opacity: 1 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(uploadImg, { opacity: 0, y: 64 + 8 }, { duration: 0.5 })
|
||||
?.finished,
|
||||
]);
|
||||
await sleep(250);
|
||||
await Promise.all([
|
||||
safeAnimate(uploadText, { opacity: 0 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(uploadLoading, { opacity: 1 }, { duration: 0.5 })?.finished,
|
||||
safeAnimate(uploadImg, { opacity: 0, y: 64 + 8 }, { duration: 0.5 })?.finished
|
||||
]);
|
||||
await sleep(250);
|
||||
|
||||
await safeAnimate(upload, { opacity: 0, y: 48 }, { duration: 0.5 })?.finished;
|
||||
await safeAnimate(upload, { opacity: 0, y: 48 }, { duration: 0.5 })?.finished;
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
files: [
|
||||
...p.files,
|
||||
{
|
||||
src: "/images/animations/storage-2.png",
|
||||
filename: "Vector.svg",
|
||||
type: "vector/svg",
|
||||
size: "1.5 KB",
|
||||
},
|
||||
],
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
files: [
|
||||
...p.files,
|
||||
{
|
||||
src: '/images/animations/storage-2.png',
|
||||
filename: 'Vector.svg',
|
||||
type: 'vector/svg',
|
||||
size: '1.5 KB'
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
await sleep(250);
|
||||
await sleep(250);
|
||||
|
||||
update((p) => ({
|
||||
...p,
|
||||
files: [
|
||||
...p.files,
|
||||
{
|
||||
src: "/images/animations/storage-3.png",
|
||||
filename: "img2.webp",
|
||||
type: "image/webp",
|
||||
size: "3.2 MB",
|
||||
},
|
||||
],
|
||||
}));
|
||||
update((p) => ({
|
||||
...p,
|
||||
files: [
|
||||
...p.files,
|
||||
{
|
||||
src: '/images/animations/storage-3.png',
|
||||
filename: 'img2.webp',
|
||||
type: 'image/webp',
|
||||
size: '3.2 MB'
|
||||
}
|
||||
]
|
||||
}));
|
||||
};
|
||||
|
||||
export const storageController = {
|
||||
execute,
|
||||
state,
|
||||
execute,
|
||||
state
|
||||
};
|
||||
|
||||
export const Storage = {
|
||||
Phone,
|
||||
Box,
|
||||
Code,
|
||||
Phone,
|
||||
Box,
|
||||
Code
|
||||
};
|
||||
|
||||
@@ -1,288 +1,284 @@
|
||||
<script lang="ts">
|
||||
import { fly } from "svelte/transition";
|
||||
import { storageController } from ".";
|
||||
import { elId } from "../Products.svelte";
|
||||
import TaskCheckbox from "../TaskCheckbox.svelte";
|
||||
import { databasesController } from "../databases";
|
||||
import { fly } from 'svelte/transition';
|
||||
import { storageController } from '.';
|
||||
import { elId } from '../Products.svelte';
|
||||
import TaskCheckbox from '../TaskCheckbox.svelte';
|
||||
import { databasesController } from '../databases';
|
||||
|
||||
const { state: dbState } = databasesController;
|
||||
const fixedTasks = $dbState.tasks;
|
||||
const { state: dbState } = databasesController;
|
||||
const fixedTasks = $dbState.tasks;
|
||||
|
||||
const { state } = storageController;
|
||||
const { state } = storageController;
|
||||
</script>
|
||||
|
||||
<div data-theme-ignore class="inner-phone light">
|
||||
<div class="header">
|
||||
<p class="title">Your tasks</p>
|
||||
<span class="icon-menu" aria-label="menu" />
|
||||
</div>
|
||||
|
||||
<div class="date">Today</div>
|
||||
<div class="tasks">
|
||||
{#each fixedTasks as task (task.id)}
|
||||
<div
|
||||
class="task"
|
||||
data-checked={task.checked ? "" : undefined}
|
||||
in:fly={{ x: -16 }}
|
||||
>
|
||||
<TaskCheckbox bind:checked={task.checked} />
|
||||
<span class="title">{task.title}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<button class="add-btn">
|
||||
<span class="web-icon-plus" />
|
||||
</button>
|
||||
|
||||
<div class="overlay" id="overlay-{$elId}">
|
||||
<div class="drawer" id="drawer-{$elId}">
|
||||
<p class="title">Edit images for website</p>
|
||||
<p class="subtitle">Edit the attached images to use in the website</p>
|
||||
|
||||
<div class="upload" id="upload-btn-{$elId}">Upload media...</div>
|
||||
<div class="images">
|
||||
{#each $state.files.slice(1) as file}
|
||||
<img src={file.src} alt="" transition:fly={{ x: 16 }} />
|
||||
{/each}
|
||||
</div>
|
||||
<div class="header">
|
||||
<p class="title">Your tasks</p>
|
||||
<span class="icon-menu" aria-label="menu" />
|
||||
</div>
|
||||
|
||||
<div class="date">Today</div>
|
||||
<div class="tasks">
|
||||
{#each fixedTasks as task (task.id)}
|
||||
<div class="task" data-checked={task.checked ? '' : undefined} in:fly={{ x: -16 }}>
|
||||
<TaskCheckbox bind:checked={task.checked} />
|
||||
<span class="title">{task.title}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<button class="add-btn">
|
||||
<span class="web-icon-plus" />
|
||||
</button>
|
||||
|
||||
<div class="overlay" id="overlay-{$elId}">
|
||||
<div class="drawer" id="drawer-{$elId}">
|
||||
<p class="title">Edit images for website</p>
|
||||
<p class="subtitle">Edit the attached images to use in the website</p>
|
||||
|
||||
<div class="upload" id="upload-btn-{$elId}">Upload media...</div>
|
||||
<div class="images">
|
||||
{#each $state.files.slice(1) as file}
|
||||
<img src={file.src} alt="" transition:fly={{ x: 16 }} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upload-media" id="upload-{$elId}">
|
||||
<p class="title">Upload media</p>
|
||||
<div class="drop-zone">
|
||||
<span id="upload-text-{$elId}"> Drop media here </span>
|
||||
<div class="loading-overlay" id="upload-loading-{$elId}">
|
||||
<div class="loader" />
|
||||
<p class="title">Upload media</p>
|
||||
<div class="drop-zone">
|
||||
<span id="upload-text-{$elId}"> Drop media here </span>
|
||||
<div class="loading-overlay" id="upload-loading-{$elId}">
|
||||
<div class="loader" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img id="upload-img-{$elId}" src="/images/animations/storage-2.png" alt="" />
|
||||
<img id="upload-img-{$elId}" src="/images/animations/storage-2.png" alt="" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.inner-phone {
|
||||
padding-block: 3rem;
|
||||
padding-inline: 1rem;
|
||||
.inner-phone {
|
||||
padding-block: 3rem;
|
||||
padding-inline: 1rem;
|
||||
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
color: rgba(67, 67, 71, 1);
|
||||
text-align: left;
|
||||
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
[class*="icon-"] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-block-start: 3rem;
|
||||
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
}
|
||||
|
||||
.tasks {
|
||||
margin-block-start: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.task {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid hsl(var(--web-color-greyscale-50));
|
||||
background: hsl(var(--web-color-white));
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
|
||||
padding-block: 0.55rem;
|
||||
padding-inline: 0.88rem;
|
||||
|
||||
/* Responsive/SubBody-400 */
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.00394rem;
|
||||
|
||||
transition: opacity 200ms ease;
|
||||
|
||||
&[data-checked] {
|
||||
opacity: 0.6;
|
||||
[class*='icon-'] {
|
||||
font-size: 1.25rem;
|
||||
color: hsl(var(--web-color-greyscale-500));
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-block-start: 3rem;
|
||||
|
||||
color: hsl(var(--web-color-greyscale-600));
|
||||
font-family: Inter;
|
||||
font-size: 0.75rem;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 1.25rem; /* 166.667% */
|
||||
}
|
||||
|
||||
.tasks {
|
||||
margin-block-start: 0.5rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.task {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid hsl(var(--web-color-greyscale-50));
|
||||
background: hsl(var(--web-color-white));
|
||||
color: var(--greyscale-700, var(--color-greyscale-700, #56565c));
|
||||
|
||||
padding-block: 0.55rem;
|
||||
padding-inline: 0.88rem;
|
||||
|
||||
/* Responsive/SubBody-400 */
|
||||
font-family: Inter;
|
||||
font-size: 0.875rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.375rem; /* 157.143% */
|
||||
letter-spacing: -0.00394rem;
|
||||
|
||||
transition: opacity 200ms ease;
|
||||
|
||||
&[data-checked] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 2.5rem;
|
||||
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
|
||||
background-color: rgba(124, 103, 254, 1);
|
||||
color: rgba(237, 237, 240, 1);
|
||||
font-size: 1.5rem;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
bottom: 2.5rem;
|
||||
.overlay {
|
||||
opacity: 0;
|
||||
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.12);
|
||||
background-color: rgba(124, 103, 254, 1);
|
||||
color: rgba(237, 237, 240, 1);
|
||||
font-size: 1.5rem;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
opacity: 0;
|
||||
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
|
||||
background: rgba(0, 0, 0, 0.32);
|
||||
border-radius: 2rem;
|
||||
|
||||
.drawer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 60%;
|
||||
opacity: 0;
|
||||
|
||||
background-color: white;
|
||||
border-radius: 0.88463rem 0.88463rem 2rem 2rem;
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
.title {
|
||||
color: #434347;
|
||||
font-family: Inter;
|
||||
font-size: 0.88463rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.21638rem; /* 137.5% */
|
||||
letter-spacing: -0.01238rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
|
||||
font-family: Inter;
|
||||
font-size: 0.77406rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.10575rem; /* 142.857% */
|
||||
letter-spacing: -0.01081rem;
|
||||
|
||||
margin-block-start: 0.2rem;
|
||||
}
|
||||
|
||||
.upload {
|
||||
display: flex;
|
||||
padding: 0.44231rem 0.66344rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
|
||||
border-radius: 0.66344rem;
|
||||
border: 1px dashed #d9d9d9;
|
||||
|
||||
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
|
||||
font-family: Inter;
|
||||
font-size: 0.77406rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.10575rem; /* 142.857% */
|
||||
letter-spacing: -0.01081rem;
|
||||
|
||||
margin-block-start: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-media {
|
||||
position: absolute;
|
||||
right: calc(0% - 24px);
|
||||
bottom: 8rem;
|
||||
background-color: white;
|
||||
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 1rem;
|
||||
padding: 0.75rem;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 0.85rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
.drop-zone {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0.885px dashed #d9d9d9;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
|
||||
padding: 2rem 1.25rem;
|
||||
margin-block-start: 0.5rem;
|
||||
font-size: 0.65rem;
|
||||
font-family: Inter;
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
|
||||
background: rgba(0, 0, 0, 0.32);
|
||||
border-radius: 2rem;
|
||||
|
||||
.drawer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 60%;
|
||||
opacity: 0;
|
||||
|
||||
background-color: white;
|
||||
border-radius: 0.88463rem 0.88463rem 2rem 2rem;
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
.title {
|
||||
color: #434347;
|
||||
font-family: Inter;
|
||||
font-size: 0.88463rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.21638rem; /* 137.5% */
|
||||
letter-spacing: -0.01238rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
|
||||
font-family: Inter;
|
||||
font-size: 0.77406rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.10575rem; /* 142.857% */
|
||||
letter-spacing: -0.01081rem;
|
||||
|
||||
margin-block-start: 0.2rem;
|
||||
}
|
||||
|
||||
.upload {
|
||||
display: flex;
|
||||
padding: 0.44231rem 0.66344rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
|
||||
border-radius: 0.66344rem;
|
||||
border: 1px dashed #d9d9d9;
|
||||
|
||||
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
|
||||
font-family: Inter;
|
||||
font-size: 0.77406rem;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 1.10575rem; /* 142.857% */
|
||||
letter-spacing: -0.01081rem;
|
||||
|
||||
margin-block-start: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-media {
|
||||
position: absolute;
|
||||
right: calc(0% - 24px);
|
||||
bottom: 8rem;
|
||||
background-color: white;
|
||||
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
|
||||
border-radius: 1rem;
|
||||
padding: 0.75rem;
|
||||
|
||||
opacity: 0;
|
||||
z-index: 100;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.title {
|
||||
color: var(--color-greyscale-800, #2d2d31);
|
||||
font-family: Inter;
|
||||
font-size: 0.85rem;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 1.375rem; /* 137.5% */
|
||||
letter-spacing: -0.014rem;
|
||||
}
|
||||
|
||||
.drop-zone {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0.885px dashed #d9d9d9;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
color: var(--greyscale-500, var(--color-greyscale-500, #818186));
|
||||
padding: 2rem 1.25rem;
|
||||
margin-block-start: 0.5rem;
|
||||
font-size: 0.65rem;
|
||||
font-family: Inter;
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
z-index: 100;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
.images {
|
||||
display: flex;
|
||||
margin-block-start: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.images {
|
||||
display: flex;
|
||||
margin-block-start: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,198 +1,188 @@
|
||||
import type { Action } from "svelte/action";
|
||||
import type { Action } from 'svelte/action';
|
||||
import {
|
||||
animate as motionAnimate,
|
||||
type ElementOrSelector,
|
||||
type MotionKeyframesDefinition,
|
||||
type AnimationOptionsWithOverrides,
|
||||
animate,
|
||||
} from "motion";
|
||||
animate as motionAnimate,
|
||||
type ElementOrSelector,
|
||||
type MotionKeyframesDefinition,
|
||||
type AnimationOptionsWithOverrides,
|
||||
animate
|
||||
} from 'motion';
|
||||
|
||||
export function animation(
|
||||
elementOrSelector: ElementOrSelector,
|
||||
keyframes: MotionKeyframesDefinition,
|
||||
options?: AnimationOptionsWithOverrides,
|
||||
elementOrSelector: ElementOrSelector,
|
||||
keyframes: MotionKeyframesDefinition,
|
||||
options?: AnimationOptionsWithOverrides
|
||||
) {
|
||||
const play = () => {
|
||||
const played = motionAnimate(elementOrSelector, keyframes, options);
|
||||
return played;
|
||||
};
|
||||
const play = () => {
|
||||
const played = motionAnimate(elementOrSelector, keyframes, options);
|
||||
return played;
|
||||
};
|
||||
|
||||
const reverse = () => {
|
||||
const reversedKeyframes = Object.fromEntries(
|
||||
Object.entries(keyframes).map(([key, keyframe]) => {
|
||||
return [
|
||||
key,
|
||||
Array.isArray(keyframe) ? [...keyframe].reverse() : keyframe,
|
||||
];
|
||||
}),
|
||||
) as typeof keyframes;
|
||||
const reversed = motionAnimate(
|
||||
elementOrSelector,
|
||||
reversedKeyframes,
|
||||
options,
|
||||
);
|
||||
return reversed;
|
||||
};
|
||||
const reverse = () => {
|
||||
const reversedKeyframes = Object.fromEntries(
|
||||
Object.entries(keyframes).map(([key, keyframe]) => {
|
||||
return [key, Array.isArray(keyframe) ? [...keyframe].reverse() : keyframe];
|
||||
})
|
||||
) as typeof keyframes;
|
||||
const reversed = motionAnimate(elementOrSelector, reversedKeyframes, options);
|
||||
return reversed;
|
||||
};
|
||||
|
||||
return {
|
||||
play,
|
||||
reverse,
|
||||
};
|
||||
return {
|
||||
play,
|
||||
reverse
|
||||
};
|
||||
}
|
||||
|
||||
export type Animation = ReturnType<typeof animation>;
|
||||
|
||||
export const safeAnimate = (
|
||||
elementOrSelector: ElementOrSelector,
|
||||
keyframes: MotionKeyframesDefinition,
|
||||
options?: AnimationOptionsWithOverrides,
|
||||
elementOrSelector: ElementOrSelector,
|
||||
keyframes: MotionKeyframesDefinition,
|
||||
options?: AnimationOptionsWithOverrides
|
||||
) => {
|
||||
try {
|
||||
return animate(elementOrSelector, keyframes, options);
|
||||
} catch {
|
||||
// do nothing lol
|
||||
}
|
||||
try {
|
||||
return animate(elementOrSelector, keyframes, options);
|
||||
} catch {
|
||||
// do nothing lol
|
||||
}
|
||||
};
|
||||
|
||||
type Unsubscriber = () => void;
|
||||
|
||||
type PreviousScroll = "before" | "after" | undefined;
|
||||
type PreviousScroll = 'before' | 'after' | undefined;
|
||||
|
||||
type ScrollCallbackState = {
|
||||
previous?: PreviousScroll;
|
||||
unsubscribe?: Unsubscriber;
|
||||
executedCount: number;
|
||||
previous?: PreviousScroll;
|
||||
unsubscribe?: Unsubscriber;
|
||||
executedCount: number;
|
||||
};
|
||||
|
||||
export type ScrollCallback = {
|
||||
percentage: number;
|
||||
whenAfter?: (
|
||||
args: Omit<ScrollCallbackState, "unsubscribe">,
|
||||
) => Unsubscriber | void;
|
||||
percentage: number;
|
||||
whenAfter?: (args: Omit<ScrollCallbackState, 'unsubscribe'>) => Unsubscriber | void;
|
||||
};
|
||||
|
||||
export function createScrollHandler(callbacks: ScrollCallback[]) {
|
||||
const states: ScrollCallbackState[] = callbacks.map(() => ({
|
||||
executedCount: 0,
|
||||
}));
|
||||
const states: ScrollCallbackState[] = callbacks.map(() => ({
|
||||
executedCount: 0
|
||||
}));
|
||||
|
||||
const handler = function (scrollPercentage: number) {
|
||||
callbacks.forEach((callback, i) => {
|
||||
const { percentage, whenAfter } = callback;
|
||||
const { previous, unsubscribe, executedCount } = states[i];
|
||||
const handler = function (scrollPercentage: number) {
|
||||
callbacks.forEach((callback, i) => {
|
||||
const { percentage, whenAfter } = callback;
|
||||
const { previous, unsubscribe, executedCount } = states[i];
|
||||
|
||||
if (scrollPercentage >= percentage && previous !== "after") {
|
||||
// Execute whenAfter
|
||||
states[i].unsubscribe =
|
||||
whenAfter?.({ previous, executedCount }) ?? undefined;
|
||||
states[i].previous = "after";
|
||||
if (whenAfter) {
|
||||
states[i].executedCount++;
|
||||
}
|
||||
} else if (scrollPercentage < percentage && previous === "after") {
|
||||
unsubscribe?.();
|
||||
states[i].unsubscribe = undefined;
|
||||
states[i].previous = "before";
|
||||
}
|
||||
});
|
||||
};
|
||||
if (scrollPercentage >= percentage && previous !== 'after') {
|
||||
// Execute whenAfter
|
||||
states[i].unsubscribe = whenAfter?.({ previous, executedCount }) ?? undefined;
|
||||
states[i].previous = 'after';
|
||||
if (whenAfter) {
|
||||
states[i].executedCount++;
|
||||
}
|
||||
} else if (scrollPercentage < percentage && previous === 'after') {
|
||||
unsubscribe?.();
|
||||
states[i].unsubscribe = undefined;
|
||||
states[i].previous = 'before';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handler.reset = () => {
|
||||
states.forEach((state) => {
|
||||
// state.unsubscribe?.();
|
||||
state.unsubscribe = undefined;
|
||||
state.previous = undefined;
|
||||
state.executedCount = 0;
|
||||
});
|
||||
};
|
||||
handler.reset = () => {
|
||||
states.forEach((state) => {
|
||||
// state.unsubscribe?.();
|
||||
state.unsubscribe = undefined;
|
||||
state.previous = undefined;
|
||||
state.executedCount = 0;
|
||||
});
|
||||
};
|
||||
|
||||
return handler;
|
||||
return handler;
|
||||
}
|
||||
|
||||
export type ScrollInfo = {
|
||||
percentage: number;
|
||||
traversed: number;
|
||||
remaning: number;
|
||||
percentage: number;
|
||||
traversed: number;
|
||||
remaning: number;
|
||||
};
|
||||
|
||||
export const scroll: Action<
|
||||
HTMLElement,
|
||||
undefined,
|
||||
{
|
||||
"on:web-scroll": (e: CustomEvent<ScrollInfo>) => void;
|
||||
"on:web-resize": (e: CustomEvent<ScrollInfo>) => void;
|
||||
}
|
||||
HTMLElement,
|
||||
undefined,
|
||||
{
|
||||
'on:web-scroll': (e: CustomEvent<ScrollInfo>) => void;
|
||||
'on:web-resize': (e: CustomEvent<ScrollInfo>) => void;
|
||||
}
|
||||
> = (node) => {
|
||||
function getScrollInfo(): ScrollInfo {
|
||||
const { top, height } = node.getBoundingClientRect();
|
||||
const { innerHeight } = window;
|
||||
function getScrollInfo(): ScrollInfo {
|
||||
const { top, height } = node.getBoundingClientRect();
|
||||
const { innerHeight } = window;
|
||||
|
||||
const scrollHeight = height - innerHeight;
|
||||
const scrollPercentage = (-1 * top) / scrollHeight;
|
||||
const scrollHeight = height - innerHeight;
|
||||
const scrollPercentage = (-1 * top) / scrollHeight;
|
||||
|
||||
const traversed = scrollPercentage * scrollHeight;
|
||||
const remaning = scrollHeight - traversed;
|
||||
const traversed = scrollPercentage * scrollHeight;
|
||||
const remaning = scrollHeight - traversed;
|
||||
|
||||
return {
|
||||
percentage: scrollPercentage,
|
||||
traversed,
|
||||
remaning
|
||||
};
|
||||
}
|
||||
|
||||
const createHandler = (eventName: 'web-scroll' | 'web-resize') => {
|
||||
return () => {
|
||||
node.dispatchEvent(
|
||||
new CustomEvent<ScrollInfo>(eventName, {
|
||||
detail: getScrollInfo()
|
||||
})
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const handleScroll = createHandler('web-scroll');
|
||||
const handleResize = createHandler('web-resize');
|
||||
|
||||
handleScroll();
|
||||
handleResize();
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return {
|
||||
percentage: scrollPercentage,
|
||||
traversed,
|
||||
remaning,
|
||||
destroy() {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const createHandler = (eventName: "web-scroll" | "web-resize") => {
|
||||
return () => {
|
||||
node.dispatchEvent(
|
||||
new CustomEvent<ScrollInfo>(eventName, {
|
||||
detail: getScrollInfo(),
|
||||
}),
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const handleScroll = createHandler("web-scroll");
|
||||
const handleResize = createHandler("web-resize");
|
||||
|
||||
handleScroll();
|
||||
handleResize();
|
||||
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type TimelineEvent = {
|
||||
at: number;
|
||||
callback: () => void;
|
||||
at: number;
|
||||
callback: () => void;
|
||||
};
|
||||
|
||||
export function createTimeline(events: TimelineEvent[]) {
|
||||
let timeoutIds: NodeJS.Timeout[] = [];
|
||||
let timeoutIds: NodeJS.Timeout[] = [];
|
||||
|
||||
const play = () => {
|
||||
events.forEach((event) => {
|
||||
const timeoutId = setTimeout(event.callback, event.at);
|
||||
timeoutIds.push(timeoutId);
|
||||
});
|
||||
};
|
||||
const play = () => {
|
||||
events.forEach((event) => {
|
||||
const timeoutId = setTimeout(event.callback, event.at);
|
||||
timeoutIds.push(timeoutId);
|
||||
});
|
||||
};
|
||||
|
||||
const cancel = () => {
|
||||
timeoutIds.forEach(clearTimeout);
|
||||
timeoutIds = [];
|
||||
};
|
||||
const cancel = () => {
|
||||
timeoutIds.forEach(clearTimeout);
|
||||
timeoutIds = [];
|
||||
};
|
||||
|
||||
return { play, cancel };
|
||||
return { play, cancel };
|
||||
}
|
||||
|
||||
type ProgressEvent = {
|
||||
percentage: number;
|
||||
callback: () => void;
|
||||
percentage: number;
|
||||
callback: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -203,56 +193,54 @@ type ProgressEvent = {
|
||||
* handler(0.45) // will execute the event with percentage 0.4.
|
||||
*/
|
||||
export function createProgressSequence(events: ProgressEvent[]) {
|
||||
// Sort from highest to lowest percentage
|
||||
const sortedEvents = [...events].sort((a, b) => b.percentage - a.percentage);
|
||||
// Sort from highest to lowest percentage
|
||||
const sortedEvents = [...events].sort((a, b) => b.percentage - a.percentage);
|
||||
|
||||
let lastEventIdx = -1;
|
||||
let lastEventIdx = -1;
|
||||
|
||||
const handler = (percentage: number) => {
|
||||
const idx = sortedEvents.findIndex(
|
||||
(event) => event.percentage <= percentage,
|
||||
);
|
||||
if (idx === lastEventIdx) {
|
||||
return;
|
||||
}
|
||||
const event = sortedEvents[idx];
|
||||
event?.callback();
|
||||
lastEventIdx = idx;
|
||||
};
|
||||
const handler = (percentage: number) => {
|
||||
const idx = sortedEvents.findIndex((event) => event.percentage <= percentage);
|
||||
if (idx === lastEventIdx) {
|
||||
return;
|
||||
}
|
||||
const event = sortedEvents[idx];
|
||||
event?.callback();
|
||||
lastEventIdx = idx;
|
||||
};
|
||||
|
||||
handler.resetLastEventIdx = () => {
|
||||
lastEventIdx = -1;
|
||||
};
|
||||
handler.resetLastEventIdx = () => {
|
||||
lastEventIdx = -1;
|
||||
};
|
||||
|
||||
return handler;
|
||||
return handler;
|
||||
}
|
||||
|
||||
export type ProgressSequence = ReturnType<typeof createProgressSequence>;
|
||||
|
||||
export function write(text: string, cb: (v: string) => void, duration = 500) {
|
||||
const step = duration / text.length;
|
||||
let i = 0;
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
cb(text.slice(0, ++i));
|
||||
if (i === text.length) {
|
||||
clearInterval(interval);
|
||||
resolve(undefined);
|
||||
}
|
||||
}, step);
|
||||
});
|
||||
const step = duration / text.length;
|
||||
let i = 0;
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(() => {
|
||||
cb(text.slice(0, ++i));
|
||||
if (i === text.length) {
|
||||
clearInterval(interval);
|
||||
resolve(undefined);
|
||||
}
|
||||
}, step);
|
||||
});
|
||||
}
|
||||
|
||||
export function sleep(duration: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, duration);
|
||||
});
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, duration);
|
||||
});
|
||||
}
|
||||
|
||||
export function getInitials(name: string) {
|
||||
return name
|
||||
.split(" ")
|
||||
.map((word) => word?.[0]?.toUpperCase() ?? "")
|
||||
.join("")
|
||||
.slice(0, 2);
|
||||
return name
|
||||
.split(' ')
|
||||
.map((word) => word?.[0]?.toUpperCase() ?? '')
|
||||
.join('')
|
||||
.slice(0, 2);
|
||||
}
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { rect } from "$lib/actions";
|
||||
import { clamp } from "$lib/utils/clamp";
|
||||
import { onMount } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import { rect } from '$lib/actions';
|
||||
import { clamp } from '$lib/utils/clamp';
|
||||
import { onMount } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export let percentage = 0;
|
||||
let easedPercentage = 0;
|
||||
export let percentage = 0;
|
||||
let easedPercentage = 0;
|
||||
|
||||
const elRect = writable<DOMRect | null>(null);
|
||||
$: y = $elRect ? clamp(0, easedPercentage, 1) * $elRect.height : 0;
|
||||
const elRect = writable<DOMRect | null>(null);
|
||||
$: y = $elRect ? clamp(0, easedPercentage, 1) * $elRect.height : 0;
|
||||
|
||||
onMount(() => {
|
||||
let frame: number | null = null;
|
||||
const ease = () => {
|
||||
easedPercentage += percentage - easedPercentage;
|
||||
frame = window.requestAnimationFrame(ease);
|
||||
};
|
||||
ease();
|
||||
onMount(() => {
|
||||
let frame: number | null = null;
|
||||
const ease = () => {
|
||||
easedPercentage += percentage - easedPercentage;
|
||||
frame = window.requestAnimationFrame(ease);
|
||||
};
|
||||
ease();
|
||||
|
||||
return () => {
|
||||
frame && window.cancelAnimationFrame(frame);
|
||||
};
|
||||
});
|
||||
return () => {
|
||||
frame && window.cancelAnimationFrame(frame);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="scroll-indicator relative h-full w-px shrink-0 rounded-full"
|
||||
use:rect={elRect}
|
||||
style:--y={`${y}px`}
|
||||
style:--percentage={`${easedPercentage * 100}%`}
|
||||
class="scroll-indicator relative h-full w-px shrink-0 rounded-full"
|
||||
use:rect={elRect}
|
||||
style:--y={`${y}px`}
|
||||
style:--percentage={`${easedPercentage * 100}%`}
|
||||
>
|
||||
<div class="absolute -top-[8px] left-1/2" />
|
||||
<div class="absolute -top-[8px] left-1/2" />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.scroll-indicator {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--web-color-accent)) 0%,
|
||||
hsl(var(--web-color-greyscale-700)) var(--percentage),
|
||||
hsl(var(--web-color-greyscale-700)) 100%
|
||||
);
|
||||
}
|
||||
.scroll-indicator {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
hsl(var(--web-color-accent)) 0%,
|
||||
hsl(var(--web-color-greyscale-700)) var(--percentage),
|
||||
hsl(var(--web-color-greyscale-700)) 100%
|
||||
);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import {
|
||||
PUBLIC_APPWRITE_ENDPOINT,
|
||||
PUBLIC_APPWRITE_PROJECT_ID,
|
||||
} from "$env/static/public";
|
||||
import { Client, Databases, Functions } from "@appwrite.io/console";
|
||||
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_ID } from '$env/static/public';
|
||||
import { Client, Databases, Functions } from '@appwrite.io/console';
|
||||
|
||||
export const client = new Client();
|
||||
|
||||
client
|
||||
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(PUBLIC_APPWRITE_PROJECT_ID);
|
||||
client.setEndpoint(PUBLIC_APPWRITE_ENDPOINT).setProject(PUBLIC_APPWRITE_PROJECT_ID);
|
||||
|
||||
export const databases = new Databases(client);
|
||||
export const functions = new Functions(client);
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { APPWRITE_API_KEY_INIT } from "$env/static/private";
|
||||
import {
|
||||
PUBLIC_APPWRITE_ENDPOINT,
|
||||
PUBLIC_APPWRITE_PROJECT_INIT_ID,
|
||||
} from "$env/static/public";
|
||||
import { Account, Client, Databases } from "@appwrite.io/console";
|
||||
import { APPWRITE_API_KEY_INIT } from '$env/static/private';
|
||||
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_INIT_ID } from '$env/static/public';
|
||||
import { Account, Client, Databases } from '@appwrite.io/console';
|
||||
|
||||
const clientServer = new Client();
|
||||
clientServer
|
||||
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(PUBLIC_APPWRITE_PROJECT_INIT_ID)
|
||||
.setKey(APPWRITE_API_KEY_INIT);
|
||||
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(PUBLIC_APPWRITE_PROJECT_INIT_ID)
|
||||
.setKey(APPWRITE_API_KEY_INIT);
|
||||
|
||||
export const appwriteInitServer = {
|
||||
account: new Account(clientServer),
|
||||
databases: new Databases(clientServer),
|
||||
account: new Account(clientServer),
|
||||
databases: new Databases(clientServer)
|
||||
};
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import {
|
||||
PUBLIC_APPWRITE_ENDPOINT,
|
||||
PUBLIC_APPWRITE_PROJECT_INIT_ID,
|
||||
} from "$env/static/public";
|
||||
import { Client, Account } from "@appwrite.io/console";
|
||||
import { PUBLIC_APPWRITE_ENDPOINT, PUBLIC_APPWRITE_PROJECT_INIT_ID } from '$env/static/public';
|
||||
import { Client, Account } from '@appwrite.io/console';
|
||||
|
||||
const client = new Client();
|
||||
client
|
||||
.setEndpoint(PUBLIC_APPWRITE_ENDPOINT)
|
||||
.setProject(PUBLIC_APPWRITE_PROJECT_INIT_ID);
|
||||
client.setEndpoint(PUBLIC_APPWRITE_ENDPOINT).setProject(PUBLIC_APPWRITE_PROJECT_INIT_ID);
|
||||
|
||||
export const appwriteInit = {
|
||||
client,
|
||||
account: new Account(client),
|
||||
client,
|
||||
account: new Account(client)
|
||||
};
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<script lang="ts">
|
||||
export let open: boolean = false;
|
||||
export let title: string;
|
||||
export let open: boolean = false;
|
||||
export let title: string;
|
||||
</script>
|
||||
|
||||
<li class="collapsible-item">
|
||||
<details class="collapsible-wrapper" {open}>
|
||||
<summary class="collapsible-button">
|
||||
<span class="text">{title}</span>
|
||||
<div class="icon web-u-color-text-primary">
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
</summary>
|
||||
<div class="collapsible-content flex flex-col">
|
||||
<slot />
|
||||
</div>
|
||||
</details>
|
||||
<details class="collapsible-wrapper" {open}>
|
||||
<summary class="collapsible-button">
|
||||
<span class="text">{title}</span>
|
||||
<div class="icon web-u-color-text-primary">
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</div>
|
||||
</summary>
|
||||
<div class="collapsible-content flex flex-col">
|
||||
<slot />
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
<style>
|
||||
.collapsible-item {
|
||||
border-block-end: 0.0625rem solid hsl(var(--web-color-offset));
|
||||
}
|
||||
.collapsible-item {
|
||||
border-block-end: 0.0625rem solid hsl(var(--web-color-offset));
|
||||
}
|
||||
|
||||
.collapsible-button {
|
||||
padding-block-start: 1rem;
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
.collapsible-button {
|
||||
padding-block-start: 1rem;
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
margin-block-start: 0;
|
||||
padding-block-end: 0;
|
||||
gap: 4px;
|
||||
}
|
||||
.collapsible-content {
|
||||
margin-block-start: 0;
|
||||
padding-block-end: 0;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
<ul
|
||||
class="collapsible w-full"
|
||||
style="--p-toggle-border-color: var(--web-color-border);"
|
||||
>
|
||||
<slot />
|
||||
<ul class="collapsible w-full" style="--p-toggle-border-color: var(--web-color-border);">
|
||||
<slot />
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
.collapsible {
|
||||
padding-block-start: 0;
|
||||
padding-block-end: 2rem;
|
||||
}
|
||||
.collapsible {
|
||||
padding-block-start: 0;
|
||||
padding-block-end: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Root from "./Root.svelte";
|
||||
import Item from "./Item.svelte";
|
||||
import Root from './Root.svelte';
|
||||
import Item from './Item.svelte';
|
||||
|
||||
export { Root as Accordion, Item as AccordionItem };
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { BANNER_KEY } from "$lib/constants";
|
||||
import { browser } from '$app/environment';
|
||||
import { BANNER_KEY } from '$lib/constants';
|
||||
|
||||
const hideTopBanner = () => {
|
||||
document.body.dataset.bannerHidden = "";
|
||||
localStorage.setItem(BANNER_KEY, "true");
|
||||
};
|
||||
const hideTopBanner = () => {
|
||||
document.body.dataset.bannerHidden = '';
|
||||
localStorage.setItem(BANNER_KEY, 'true');
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="web-top-banner">
|
||||
<div class="web-top-banner-content web-u-color-text-primary">
|
||||
<slot />
|
||||
{#if browser}
|
||||
<button
|
||||
class="web-top-banner-button"
|
||||
aria-label="close discord message"
|
||||
on:click={hideTopBanner}
|
||||
>
|
||||
<span class="web-icon-close" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="web-top-banner-content web-u-color-text-primary">
|
||||
<slot />
|
||||
{#if browser}
|
||||
<button
|
||||
class="web-top-banner-button"
|
||||
aria-label="close discord message"
|
||||
on:click={hideTopBanner}
|
||||
>
|
||||
<span class="web-icon-close" aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
<script lang="ts">
|
||||
import Media from "$lib/UI/Media.svelte";
|
||||
import { formatDate } from "$lib/utils/date";
|
||||
import Media from '$lib/UI/Media.svelte';
|
||||
import { formatDate } from '$lib/utils/date';
|
||||
|
||||
export let title: string;
|
||||
export let cover: string;
|
||||
export let href: string;
|
||||
export let date: Date;
|
||||
export let timeToRead: number;
|
||||
export let author: string;
|
||||
export let avatar: string;
|
||||
export let title: string;
|
||||
export let cover: string;
|
||||
export let href: string;
|
||||
export let date: Date;
|
||||
export let timeToRead: number;
|
||||
export let author: string;
|
||||
export let avatar: string;
|
||||
</script>
|
||||
|
||||
<li>
|
||||
<a class="web-grid-articles-item is-transparent" {href}>
|
||||
<div class="web-grid-articles-item-image">
|
||||
<Media
|
||||
src={cover}
|
||||
class="web-u-media-ratio-16-9"
|
||||
alt={title}
|
||||
autoplay
|
||||
controls={false}
|
||||
/>
|
||||
</div>
|
||||
<div class="web-grid-articles-item-content">
|
||||
<h4 class="web-label web-u-color-text-primary">
|
||||
{title}
|
||||
</h4>
|
||||
<div class="web-author">
|
||||
<div class="flex items-center gap-2">
|
||||
<img
|
||||
class="web-author-image"
|
||||
loading="lazy"
|
||||
src={avatar}
|
||||
width="24"
|
||||
height="24"
|
||||
alt={author}
|
||||
/>
|
||||
<div class="web-author-info">
|
||||
<h4 class="web-sub-body-400 web-u-color-text-primary">{author}</h4>
|
||||
<ul class="web-metadata web-caption-400 web-is-not-mobile">
|
||||
<li>
|
||||
{formatDate(date)}
|
||||
</li>
|
||||
<li>{timeToRead} min</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a class="web-grid-articles-item is-transparent" {href}>
|
||||
<div class="web-grid-articles-item-image">
|
||||
<Media
|
||||
src={cover}
|
||||
class="web-u-media-ratio-16-9"
|
||||
alt={title}
|
||||
autoplay
|
||||
controls={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="web-grid-articles-item-content">
|
||||
<h4 class="web-label web-u-color-text-primary">
|
||||
{title}
|
||||
</h4>
|
||||
<div class="web-author">
|
||||
<div class="flex items-center gap-2">
|
||||
<img
|
||||
class="web-author-image"
|
||||
loading="lazy"
|
||||
src={avatar}
|
||||
width="24"
|
||||
height="24"
|
||||
alt={author}
|
||||
/>
|
||||
<div class="web-author-info">
|
||||
<h4 class="web-sub-body-400 web-u-color-text-primary">{author}</h4>
|
||||
<ul class="web-metadata web-caption-400 web-is-not-mobile">
|
||||
<li>
|
||||
{formatDate(date)}
|
||||
</li>
|
||||
<li>{timeToRead} min</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -1,152 +1,146 @@
|
||||
<script lang="ts">
|
||||
let carousel: HTMLElement;
|
||||
let carousel: HTMLElement;
|
||||
|
||||
export let size: "default" | "medium" | "big" = "default";
|
||||
export let gap = 32;
|
||||
let scroll = 0;
|
||||
export let size: 'default' | 'medium' | 'big' = 'default';
|
||||
export let gap = 32;
|
||||
let scroll = 0;
|
||||
|
||||
function calculateScrollAmount(prev = false) {
|
||||
const direction = prev ? -1 : 1;
|
||||
const carouselSize = carousel?.clientWidth;
|
||||
const childSize =
|
||||
(carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap;
|
||||
function calculateScrollAmount(prev = false) {
|
||||
const direction = prev ? -1 : 1;
|
||||
const carouselSize = carousel?.clientWidth;
|
||||
const childSize = (carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap;
|
||||
|
||||
scroll = scroll || carouselSize;
|
||||
scroll = scroll || carouselSize;
|
||||
|
||||
const numberOfItems = Math.floor(carouselSize / childSize);
|
||||
const overflow = scroll % childSize;
|
||||
const amount = numberOfItems * childSize - overflow * direction;
|
||||
scroll += amount * direction;
|
||||
return amount * direction;
|
||||
}
|
||||
const numberOfItems = Math.floor(carouselSize / childSize);
|
||||
const overflow = scroll % childSize;
|
||||
const amount = numberOfItems * childSize - overflow * direction;
|
||||
scroll += amount * direction;
|
||||
return amount * direction;
|
||||
}
|
||||
|
||||
function next() {
|
||||
carousel.scrollBy({
|
||||
left: calculateScrollAmount(),
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
function prev() {
|
||||
carousel.scrollBy({
|
||||
left: calculateScrollAmount(true),
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
function next() {
|
||||
carousel.scrollBy({
|
||||
left: calculateScrollAmount(),
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
function prev() {
|
||||
carousel.scrollBy({
|
||||
left: calculateScrollAmount(true),
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
let isEnd = false;
|
||||
let isStart = true;
|
||||
let isEnd = false;
|
||||
let isStart = true;
|
||||
|
||||
function handleScroll() {
|
||||
isStart = carousel.scrollLeft <= 0;
|
||||
isEnd =
|
||||
Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >=
|
||||
carousel.scrollWidth;
|
||||
}
|
||||
function handleScroll() {
|
||||
isStart = carousel.scrollLeft <= 0;
|
||||
isEnd = Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >= carousel.scrollWidth;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="mt-2 flex flex-wrap items-center">
|
||||
<slot name="header" />
|
||||
<div class="nav ml-auto flex items-end gap-3">
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel backward"
|
||||
disabled={isStart}
|
||||
on:click={prev}
|
||||
>
|
||||
<span class="web-icon-arrow-left" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel forward"
|
||||
disabled={isEnd}
|
||||
on:click={next}
|
||||
>
|
||||
<span class="web-icon-arrow-right" aria-hidden="true" />
|
||||
</button>
|
||||
<div class="mt-2 flex flex-wrap items-center">
|
||||
<slot name="header" />
|
||||
<div class="nav ml-auto flex items-end gap-3">
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel backward"
|
||||
disabled={isStart}
|
||||
on:click={prev}
|
||||
>
|
||||
<span class="web-icon-arrow-left" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
class="web-icon-button"
|
||||
aria-label="Move carousel forward"
|
||||
disabled={isEnd}
|
||||
on:click={next}
|
||||
>
|
||||
<span class="web-icon-arrow-right" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="carousel-wrapper"
|
||||
data-state={isStart ? "start" : isEnd ? "end" : "middle"}
|
||||
>
|
||||
<ul
|
||||
class="web-grid-articles carousel mt-8"
|
||||
class:is-medium={size === "medium"}
|
||||
class:is-big={size === "big"}
|
||||
style:gap="{gap}px"
|
||||
bind:this={carousel}
|
||||
on:scroll={handleScroll}
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
<div class="carousel-wrapper" data-state={isStart ? 'start' : isEnd ? 'end' : 'middle'}>
|
||||
<ul
|
||||
class="web-grid-articles carousel mt-8"
|
||||
class:is-medium={size === 'medium'}
|
||||
class:is-big={size === 'big'}
|
||||
style:gap="{gap}px"
|
||||
bind:this={carousel}
|
||||
on:scroll={handleScroll}
|
||||
>
|
||||
<slot />
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.nav {
|
||||
button {
|
||||
@media screen and (max-width: 1023.9px) {
|
||||
display: none !important;
|
||||
}
|
||||
.nav {
|
||||
button {
|
||||
@media screen and (max-width: 1023.9px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.carousel-wrapper {
|
||||
position: relative;
|
||||
.carousel-wrapper {
|
||||
position: relative;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
transition: ease 250ms;
|
||||
z-index: 100;
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
transition: ease 250ms;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
hsl(var(--web-color-background-docs)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
&[data-state='start']::before {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
hsl(var(--web-color-background-docs)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
&[data-state='end']::after {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
hsl(var(--web-color-background-docs)),
|
||||
transparent
|
||||
);
|
||||
.carousel {
|
||||
grid-auto-flow: column;
|
||||
overflow-x: scroll;
|
||||
scroll-snap-type: x proximity;
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-state="start"]::before {
|
||||
opacity: 0;
|
||||
.carousel :global(li) {
|
||||
scroll-margin: 48px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
hsl(var(--web-color-background-docs)),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
&[data-state="end"]::after {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.carousel {
|
||||
grid-auto-flow: column;
|
||||
overflow-x: scroll;
|
||||
scroll-snap-type: x proximity;
|
||||
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.carousel :global(li) {
|
||||
scroll-margin: 48px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { DropdownCtx } from "./DropdownMenu.svelte";
|
||||
import { DropdownCtx } from './DropdownMenu.svelte';
|
||||
|
||||
export let checked = false;
|
||||
export let checked = false;
|
||||
|
||||
const {
|
||||
builders: { createCheckboxItem },
|
||||
} = DropdownCtx.get();
|
||||
const {
|
||||
builders: { createCheckboxItem }
|
||||
} = DropdownCtx.get();
|
||||
|
||||
const {
|
||||
elements: { checkboxItem },
|
||||
states: { checked: localChecked },
|
||||
} = createCheckboxItem({
|
||||
defaultChecked: checked,
|
||||
onCheckedChange({ next }) {
|
||||
checked = !!next;
|
||||
return next;
|
||||
},
|
||||
});
|
||||
const {
|
||||
elements: { checkboxItem },
|
||||
states: { checked: localChecked }
|
||||
} = createCheckboxItem({
|
||||
defaultChecked: checked,
|
||||
onCheckedChange({ next }) {
|
||||
checked = !!next;
|
||||
return next;
|
||||
}
|
||||
});
|
||||
|
||||
$: localChecked.set(checked);
|
||||
$: localChecked.set(checked);
|
||||
</script>
|
||||
|
||||
<slot checkboxItem={$checkboxItem} />
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
<script lang="ts" context="module">
|
||||
const ctxKey = Symbol("dropdown");
|
||||
const ctxKey = Symbol('dropdown');
|
||||
|
||||
export const DropdownCtx = {
|
||||
get() {
|
||||
return getContext<DropdownMenu>(ctxKey);
|
||||
},
|
||||
set(ctx: DropdownMenu) {
|
||||
setContext(ctxKey, ctx);
|
||||
},
|
||||
};
|
||||
export const DropdownCtx = {
|
||||
get() {
|
||||
return getContext<DropdownMenu>(ctxKey);
|
||||
},
|
||||
set(ctx: DropdownMenu) {
|
||||
setContext(ctxKey, ctx);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createDropdownMenu, type DropdownMenu } from "@melt-ui/svelte";
|
||||
import { getContext, setContext } from "svelte";
|
||||
import { createDropdownMenu, type DropdownMenu } from '@melt-ui/svelte';
|
||||
import { getContext, setContext } from 'svelte';
|
||||
|
||||
const dropdown = createDropdownMenu({
|
||||
forceVisible: true,
|
||||
positioning: {
|
||||
placement: "bottom-start",
|
||||
},
|
||||
});
|
||||
const dropdown = createDropdownMenu({
|
||||
forceVisible: true,
|
||||
positioning: {
|
||||
placement: 'bottom-start'
|
||||
}
|
||||
});
|
||||
|
||||
DropdownCtx.set(dropdown);
|
||||
DropdownCtx.set(dropdown);
|
||||
|
||||
const {
|
||||
elements: { menu, trigger },
|
||||
states: { open },
|
||||
} = dropdown;
|
||||
const {
|
||||
elements: { menu, trigger },
|
||||
states: { open }
|
||||
} = dropdown;
|
||||
</script>
|
||||
|
||||
<slot menu={$menu} trigger={$trigger} open={$open} />
|
||||
|
||||
@@ -1,166 +1,152 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let date: string | undefined = undefined;
|
||||
let showFeedback = false;
|
||||
let feedbackType = "";
|
||||
let email = "";
|
||||
let comment = "";
|
||||
let error: string | undefined;
|
||||
let submitted = false;
|
||||
let submitting = false;
|
||||
export let date: string | undefined = undefined;
|
||||
let showFeedback = false;
|
||||
let feedbackType = '';
|
||||
let email = '';
|
||||
let comment = '';
|
||||
let error: string | undefined;
|
||||
let submitted = false;
|
||||
let submitting = false;
|
||||
|
||||
async function handleSubmit() {
|
||||
submitting = true;
|
||||
error = undefined;
|
||||
const response = await fetch(
|
||||
"https://growth.appwrite.io/v1/feedback/docs",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
type: feedbackType,
|
||||
route: $page.route.id,
|
||||
comment,
|
||||
}),
|
||||
},
|
||||
);
|
||||
submitting = false;
|
||||
if (response.status >= 400) {
|
||||
error =
|
||||
response.status >= 500 ? "Server Error." : "Error submitting form.";
|
||||
return;
|
||||
async function handleSubmit() {
|
||||
submitting = true;
|
||||
error = undefined;
|
||||
const response = await fetch('https://growth.appwrite.io/v1/feedback/docs', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
type: feedbackType,
|
||||
route: $page.route.id,
|
||||
comment
|
||||
})
|
||||
});
|
||||
submitting = false;
|
||||
if (response.status >= 400) {
|
||||
error = response.status >= 500 ? 'Server Error.' : 'Error submitting form.';
|
||||
return;
|
||||
}
|
||||
comment = email = '';
|
||||
submitted = true;
|
||||
}
|
||||
comment = email = "";
|
||||
submitted = true;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
comment = email = "";
|
||||
feedbackType = "";
|
||||
submitted = false;
|
||||
error = undefined;
|
||||
}
|
||||
function reset() {
|
||||
comment = email = '';
|
||||
feedbackType = '';
|
||||
submitted = false;
|
||||
error = undefined;
|
||||
}
|
||||
|
||||
$: if (!showFeedback) {
|
||||
reset();
|
||||
}
|
||||
$: if (!showFeedback) {
|
||||
reset();
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="web-content-footer">
|
||||
<header class="web-content-footer-header w-full">
|
||||
<div
|
||||
class="flex w-full items-center justify-between gap-8"
|
||||
style="flex-wrap: wrap-reverse;"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<h5 class="web-main-body-600 web-u-color-text-primary">
|
||||
Was this page helpful?
|
||||
</h5>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="web-radio-button"
|
||||
aria-label="helpful"
|
||||
on:click={() => {
|
||||
showFeedback = feedbackType === "positive" ? false : true;
|
||||
feedbackType = "positive";
|
||||
}}
|
||||
>
|
||||
<span class="icon-thumb-up" />
|
||||
</button>
|
||||
<button
|
||||
class="web-radio-button"
|
||||
aria-label="unhelpful"
|
||||
on:click={() => {
|
||||
showFeedback = feedbackType === "negative" ? false : true;
|
||||
feedbackType = "negative";
|
||||
}}
|
||||
>
|
||||
<!-- TODO: fix the icon name on pink -->
|
||||
<span class="icon-thumb-dowm" />
|
||||
</button>
|
||||
<header class="web-content-footer-header w-full">
|
||||
<div
|
||||
class="flex w-full items-center justify-between gap-8"
|
||||
style="flex-wrap: wrap-reverse;"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<h5 class="web-main-body-600 web-u-color-text-primary">Was this page helpful?</h5>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="web-radio-button"
|
||||
aria-label="helpful"
|
||||
on:click={() => {
|
||||
showFeedback = feedbackType === 'positive' ? false : true;
|
||||
feedbackType = 'positive';
|
||||
}}
|
||||
>
|
||||
<span class="icon-thumb-up" />
|
||||
</button>
|
||||
<button
|
||||
class="web-radio-button"
|
||||
aria-label="unhelpful"
|
||||
on:click={() => {
|
||||
showFeedback = feedbackType === 'negative' ? false : true;
|
||||
feedbackType = 'negative';
|
||||
}}
|
||||
>
|
||||
<!-- TODO: fix the icon name on pink -->
|
||||
<span class="icon-thumb-dowm" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="web-content-footer-header-end">
|
||||
<ul class="web-metadata web-caption-400">
|
||||
{#if date}
|
||||
<li>Last updated on {new Date(date)?.toLocaleDateString()}</li>
|
||||
{/if}
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/appwrite/website"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="web-link flex items-baseline gap-1"
|
||||
>
|
||||
<span class="icon-pencil-alt contents" aria-hidden="true" />
|
||||
<span>Update on GitHub</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="web-content-footer-header-end">
|
||||
<ul class="web-metadata web-caption-400">
|
||||
{#if date}
|
||||
<li>Last updated on {new Date(date)?.toLocaleDateString()}</li>
|
||||
{/if}
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/appwrite/website"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="web-link flex items-baseline gap-1"
|
||||
>
|
||||
<span class="icon-pencil-alt contents" aria-hidden="true" />
|
||||
<span>Update on GitHub</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{#if showFeedback}
|
||||
<form
|
||||
on:submit|preventDefault={handleSubmit}
|
||||
class="web-card is-normal"
|
||||
style="--card-padding:1rem"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="message">
|
||||
<span class="web-u-color-text-primary">
|
||||
What did you {feedbackType === "negative" ? "dislike" : "like"}?
|
||||
(optional)
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
class="web-input-text"
|
||||
id="message"
|
||||
placeholder="Write your message"
|
||||
bind:value={comment}
|
||||
/>
|
||||
<label for="message" class="mt-2">
|
||||
<span class="web-u-color-text-primary">Email</span>
|
||||
</label>
|
||||
<input
|
||||
class="web-input-text"
|
||||
placeholder="Enter your email"
|
||||
type="email"
|
||||
name="email"
|
||||
required
|
||||
bind:value={email}
|
||||
/>
|
||||
</div>
|
||||
{#if submitted}
|
||||
<p class="web-u-color-text-primary mt-4">
|
||||
Your message has been sent successfully. We appreciate your feedback.
|
||||
</p>
|
||||
{/if}
|
||||
{#if error}
|
||||
<p class="web-u-color-text-primary mt-4">
|
||||
There was an error submitting your feedback. Please try again later.
|
||||
</p>
|
||||
{/if}
|
||||
</header>
|
||||
{#if showFeedback}
|
||||
<form
|
||||
on:submit|preventDefault={handleSubmit}
|
||||
class="web-card is-normal"
|
||||
style="--card-padding:1rem"
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label for="message">
|
||||
<span class="web-u-color-text-primary">
|
||||
What did you {feedbackType === 'negative' ? 'dislike' : 'like'}? (optional)
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
class="web-input-text"
|
||||
id="message"
|
||||
placeholder="Write your message"
|
||||
bind:value={comment}
|
||||
/>
|
||||
<label for="message" class="mt-2">
|
||||
<span class="web-u-color-text-primary">Email</span>
|
||||
</label>
|
||||
<input
|
||||
class="web-input-text"
|
||||
placeholder="Enter your email"
|
||||
type="email"
|
||||
name="email"
|
||||
required
|
||||
bind:value={email}
|
||||
/>
|
||||
</div>
|
||||
{#if submitted}
|
||||
<p class="web-u-color-text-primary mt-4">
|
||||
Your message has been sent successfully. We appreciate your feedback.
|
||||
</p>
|
||||
{/if}
|
||||
{#if error}
|
||||
<p class="web-u-color-text-primary mt-4">
|
||||
There was an error submitting your feedback. Please try again later.
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button
|
||||
class="web-button is-text"
|
||||
on:click={() => (showFeedback = false)}
|
||||
>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="web-button"
|
||||
disabled={submitting || !email}
|
||||
>
|
||||
<span>Submit</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
<div class="mt-4 flex justify-end gap-2">
|
||||
<button class="web-button is-text" on:click={() => (showFeedback = false)}>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<button type="submit" class="web-button" disabled={submitting || !email}>
|
||||
<span>Submit</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
@@ -1,55 +1,51 @@
|
||||
<script lang="ts">
|
||||
export let src: string;
|
||||
export let alt = "";
|
||||
export let size: number;
|
||||
export let src: string;
|
||||
export let alt = '';
|
||||
export let size: number;
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
let className = '';
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="head-wrapper {className}"
|
||||
style:width="{size}px"
|
||||
style:height="{size}px"
|
||||
>
|
||||
<img {src} {alt} />
|
||||
<div class="head-wrapper {className}" style:width="{size}px" style:height="{size}px">
|
||||
<img {src} {alt} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@use "$scss/abstract/mixins/border-gradient" as gradients;
|
||||
@use '$scss/abstract/mixins/border-gradient' as gradients;
|
||||
|
||||
.head-wrapper {
|
||||
@include gradients.border-gradient;
|
||||
--m-border-radius: 50%;
|
||||
--m-border-gradient-before: linear-gradient(
|
||||
135.1deg,
|
||||
rgba(255, 255, 255, 0.08) 10.1%,
|
||||
rgba(255, 255, 255, 0) 120.69%
|
||||
);
|
||||
.head-wrapper {
|
||||
@include gradients.border-gradient;
|
||||
--m-border-radius: 50%;
|
||||
--m-border-gradient-before: linear-gradient(
|
||||
135.1deg,
|
||||
rgba(255, 255, 255, 0.08) 10.1%,
|
||||
rgba(255, 255, 255, 0) 120.69%
|
||||
);
|
||||
|
||||
display: inline-block;
|
||||
position: var(--position, absolute);
|
||||
top: var(--top);
|
||||
left: var(--left);
|
||||
bottom: var(--bottom);
|
||||
right: var(--right);
|
||||
display: inline-block;
|
||||
position: var(--position, absolute);
|
||||
top: var(--top);
|
||||
left: var(--left);
|
||||
bottom: var(--bottom);
|
||||
right: var(--right);
|
||||
|
||||
background: linear-gradient(
|
||||
132.35deg,
|
||||
rgba(255, 255, 255, 0.08) 12.05%,
|
||||
rgba(255, 255, 255, 0) 161.63%
|
||||
);
|
||||
box-shadow: 0px 5.35px 10.7px 0px rgba(0, 0, 0, 0.02);
|
||||
background: linear-gradient(
|
||||
132.35deg,
|
||||
rgba(255, 255, 255, 0.08) 12.05%,
|
||||
rgba(255, 255, 255, 0) 161.63%
|
||||
);
|
||||
box-shadow: 0px 5.35px 10.7px 0px rgba(0, 0, 0, 0.02);
|
||||
|
||||
&.isRelative {
|
||||
position: relative;
|
||||
&.isRelative {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
padding: 10%;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
img {
|
||||
border-radius: 50%;
|
||||
padding: 10%;
|
||||
aspect-ratio: 1 / 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { clamp } from "$lib/utils/clamp";
|
||||
import FloatingHead from "./FloatingHead.svelte";
|
||||
import { clamp } from '$lib/utils/clamp';
|
||||
import FloatingHead from './FloatingHead.svelte';
|
||||
|
||||
type Head = {
|
||||
src: string;
|
||||
display: [number, number, number];
|
||||
};
|
||||
type Head = {
|
||||
src: string;
|
||||
display: [number, number, number];
|
||||
};
|
||||
|
||||
const headPositions: Array<Head["display"]> = [
|
||||
[120, -25, 40],
|
||||
[120, 20, -40],
|
||||
[64, -40, 20],
|
||||
[64, 25, 35],
|
||||
[64, 10, 45],
|
||||
[64, 40, 55],
|
||||
[64, 40, -20],
|
||||
[64, 10, -55],
|
||||
[40, -45, 50],
|
||||
[40, -40, -40],
|
||||
];
|
||||
const headPositions: Array<Head['display']> = [
|
||||
[120, -25, 40],
|
||||
[120, 20, -40],
|
||||
[64, -40, 20],
|
||||
[64, 25, 35],
|
||||
[64, 10, 45],
|
||||
[64, 40, 55],
|
||||
[64, 40, -20],
|
||||
[64, 10, -55],
|
||||
[40, -45, 50],
|
||||
[40, -40, -40]
|
||||
];
|
||||
|
||||
export let images: Array<string>;
|
||||
export let images: Array<string>;
|
||||
</script>
|
||||
|
||||
<div class="web-u-hide-mobile root absolute">
|
||||
{#each headPositions as [size, top, left], i}
|
||||
{@const image = clamp(0, images.length - 1, i % images.length)}
|
||||
<FloatingHead
|
||||
--top={`calc(50% - ${size / 2}px + ${top}%)`}
|
||||
--left={`calc(50% - ${size / 2}px + ${left}%)`}
|
||||
src={images[image]}
|
||||
{size}
|
||||
/>
|
||||
<div style:margin-block-end="0" style:padding="10%">
|
||||
<img style:border-radius="50%" class="block" alt="" />
|
||||
</div>
|
||||
{/each}
|
||||
{#each headPositions as [size, top, left], i}
|
||||
{@const image = clamp(0, images.length - 1, i % images.length)}
|
||||
<FloatingHead
|
||||
--top={`calc(50% - ${size / 2}px + ${top}%)`}
|
||||
--left={`calc(50% - ${size / 2}px + ${left}%)`}
|
||||
src={images[image]}
|
||||
{size}
|
||||
/>
|
||||
<div style:margin-block-end="0" style:padding="10%">
|
||||
<img style:border-radius="50%" class="block" alt="" />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.root {
|
||||
inline-size: 100vw;
|
||||
block-size: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-inline-size: 1280px;
|
||||
}
|
||||
.root {
|
||||
inline-size: 100vw;
|
||||
block-size: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-inline-size: 1280px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,157 +1,147 @@
|
||||
<script lang="ts">
|
||||
import { createAccordion, melt } from "@melt-ui/svelte";
|
||||
import { slide } from "svelte/transition";
|
||||
import { createAccordion, melt } from '@melt-ui/svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
export let noBorder = false;
|
||||
export let noBorder = false;
|
||||
|
||||
const {
|
||||
elements: { content, heading, item, root, trigger },
|
||||
helpers: { isSelected },
|
||||
} = createAccordion({
|
||||
multiple: true,
|
||||
forceVisible: true,
|
||||
});
|
||||
const {
|
||||
elements: { content, heading, item, root, trigger },
|
||||
helpers: { isSelected }
|
||||
} = createAccordion({
|
||||
multiple: true,
|
||||
forceVisible: true
|
||||
});
|
||||
|
||||
const links: Record<
|
||||
string,
|
||||
{ label: string; href: string; target?: string; rel?: string }[]
|
||||
> = {
|
||||
"Quick starts": [
|
||||
{ label: "Web", href: "/docs/quick-starts/web" },
|
||||
{ label: "Next.js", href: "/docs/quick-starts/nextjs" },
|
||||
{ label: "React", href: "/docs/quick-starts/react" },
|
||||
{ label: "Vue.js", href: "/docs/quick-starts/vue" },
|
||||
{ label: "Nuxt", href: "/docs/quick-starts/nuxt" },
|
||||
{ label: "SvelteKit", href: "/docs/quick-starts/sveltekit" },
|
||||
{ label: "Refine", href: "/docs/quick-starts/refine" },
|
||||
{ label: "Angular", href: "/docs/quick-starts/angular" },
|
||||
{ label: "React Native", href: "/docs/quick-starts/react-native" },
|
||||
{ label: "Flutter", href: "/docs/quick-starts/flutter" },
|
||||
{ label: "Apple", href: "/docs/quick-starts/apple" },
|
||||
{ label: "Android", href: "/docs/quick-starts/android" },
|
||||
{ label: "Qwik", href: "/docs/quick-starts/qwik" },
|
||||
{ label: "Astro", href: "/docs/quick-starts/astro" },
|
||||
{ label: "Solid", href: "/docs/quick-starts/solid" },
|
||||
],
|
||||
Products: [
|
||||
{ label: "Auth", href: "/docs/products/auth" },
|
||||
{ label: "Databases", href: "/docs/products/databases" },
|
||||
{ label: "Functions", href: "/docs/products/functions" },
|
||||
{ label: "Messaging", href: "/products/messaging" },
|
||||
{ label: "Storage", href: "/docs/products/storage" },
|
||||
{ label: "Realtime", href: "/docs/apis/realtime" },
|
||||
],
|
||||
Learn: [
|
||||
{ label: "Docs", href: "/docs" },
|
||||
{ label: "Integrations", href: "/integrations" },
|
||||
{ label: "Community", href: "/community" },
|
||||
{ label: "Init", href: "/init" },
|
||||
{ label: "Threads", href: "/threads" },
|
||||
{ label: "Blog", href: "/blog" },
|
||||
{ label: "Changelog", href: "/changelog" },
|
||||
{
|
||||
label: "Roadmap",
|
||||
href: "https://github.com/orgs/appwrite/projects",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
},
|
||||
{
|
||||
label: "Source code",
|
||||
href: "https://github.com/appwrite",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
},
|
||||
// {
|
||||
// label: 'Status',
|
||||
// href: 'https://appwrite.online',
|
||||
// target: '_blank',
|
||||
// rel: 'noopener noreferrer'
|
||||
// }
|
||||
],
|
||||
Programs: [
|
||||
{ label: "Heroes", href: "/heroes" },
|
||||
{ label: "Startups", href: "/startups" },
|
||||
],
|
||||
About: [
|
||||
{ label: "Company", href: "/company" },
|
||||
{ label: "Pricing", href: "/pricing" },
|
||||
{
|
||||
label: "Careers",
|
||||
href: "https://appwrite.careers",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
},
|
||||
{
|
||||
label: "Store",
|
||||
href: "https://appwrite.store",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
},
|
||||
{ label: "Contact us", href: "/contact-us" },
|
||||
{ label: "Assets", href: "/assets" },
|
||||
],
|
||||
};
|
||||
const links: Record<string, { label: string; href: string; target?: string; rel?: string }[]> =
|
||||
{
|
||||
'Quick starts': [
|
||||
{ label: 'Web', href: '/docs/quick-starts/web' },
|
||||
{ label: 'Next.js', href: '/docs/quick-starts/nextjs' },
|
||||
{ label: 'React', href: '/docs/quick-starts/react' },
|
||||
{ label: 'Vue.js', href: '/docs/quick-starts/vue' },
|
||||
{ label: 'Nuxt', href: '/docs/quick-starts/nuxt' },
|
||||
{ label: 'SvelteKit', href: '/docs/quick-starts/sveltekit' },
|
||||
{ label: 'Refine', href: '/docs/quick-starts/refine' },
|
||||
{ label: 'Angular', href: '/docs/quick-starts/angular' },
|
||||
{ label: 'React Native', href: '/docs/quick-starts/react-native' },
|
||||
{ label: 'Flutter', href: '/docs/quick-starts/flutter' },
|
||||
{ label: 'Apple', href: '/docs/quick-starts/apple' },
|
||||
{ label: 'Android', href: '/docs/quick-starts/android' },
|
||||
{ label: 'Qwik', href: '/docs/quick-starts/qwik' },
|
||||
{ label: 'Astro', href: '/docs/quick-starts/astro' },
|
||||
{ label: 'Solid', href: '/docs/quick-starts/solid' }
|
||||
],
|
||||
Products: [
|
||||
{ label: 'Auth', href: '/docs/products/auth' },
|
||||
{ label: 'Databases', href: '/docs/products/databases' },
|
||||
{ label: 'Functions', href: '/docs/products/functions' },
|
||||
{ label: 'Messaging', href: '/products/messaging' },
|
||||
{ label: 'Storage', href: '/docs/products/storage' },
|
||||
{ label: 'Realtime', href: '/docs/apis/realtime' }
|
||||
],
|
||||
Learn: [
|
||||
{ label: 'Docs', href: '/docs' },
|
||||
{ label: 'Integrations', href: '/integrations' },
|
||||
{ label: 'Community', href: '/community' },
|
||||
{ label: 'Init', href: '/init' },
|
||||
{ label: 'Threads', href: '/threads' },
|
||||
{ label: 'Blog', href: '/blog' },
|
||||
{ label: 'Changelog', href: '/changelog' },
|
||||
{
|
||||
label: 'Roadmap',
|
||||
href: 'https://github.com/orgs/appwrite/projects',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
},
|
||||
{
|
||||
label: 'Source code',
|
||||
href: 'https://github.com/appwrite',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
}
|
||||
// {
|
||||
// label: 'Status',
|
||||
// href: 'https://appwrite.online',
|
||||
// target: '_blank',
|
||||
// rel: 'noopener noreferrer'
|
||||
// }
|
||||
],
|
||||
Programs: [
|
||||
{ label: 'Heroes', href: '/heroes' },
|
||||
{ label: 'Startups', href: '/startups' }
|
||||
],
|
||||
About: [
|
||||
{ label: 'Company', href: '/company' },
|
||||
{ label: 'Pricing', href: '/pricing' },
|
||||
{
|
||||
label: 'Careers',
|
||||
href: 'https://appwrite.careers',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
},
|
||||
{
|
||||
label: 'Store',
|
||||
href: 'https://appwrite.store',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
},
|
||||
{ label: 'Contact us', href: '/contact-us' },
|
||||
{ label: 'Assets', href: '/assets' }
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<nav
|
||||
aria-label="Footer"
|
||||
class="web-footer-nav relative mt-24"
|
||||
class:web-u-sep-block-start={!noBorder}
|
||||
aria-label="Footer"
|
||||
class="web-footer-nav relative mt-24"
|
||||
class:web-u-sep-block-start={!noBorder}
|
||||
>
|
||||
<img
|
||||
class="web-logo"
|
||||
src="/images/logos/appwrite.svg"
|
||||
alt="appwrite"
|
||||
height="24"
|
||||
width="130"
|
||||
/>
|
||||
<ul class="web-footer-nav-main-list" use:melt={$root}>
|
||||
{#each Object.entries(links) as [title, items]}
|
||||
<li class="web-footer-nav-main-item web-is-not-mobile">
|
||||
<h2
|
||||
class="web-footer-nav-main-title web-is-not-mobile web-caption-500 web-eyebrow"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
<ul class="web-footer-nav-secondary-list web-sub-body-400">
|
||||
{#each items as { href, label, target, rel }}
|
||||
<li>
|
||||
<a class="web-link" {href} {target} {rel}>{label}</a>
|
||||
<img class="web-logo" src="/images/logos/appwrite.svg" alt="appwrite" height="24" width="130" />
|
||||
<ul class="web-footer-nav-main-list" use:melt={$root}>
|
||||
{#each Object.entries(links) as [title, items]}
|
||||
<li class="web-footer-nav-main-item web-is-not-mobile">
|
||||
<h2 class="web-footer-nav-main-title web-is-not-mobile web-caption-500 web-eyebrow">
|
||||
{title}
|
||||
</h2>
|
||||
<ul class="web-footer-nav-secondary-list web-sub-body-400">
|
||||
{#each items as { href, label, target, rel }}
|
||||
<li>
|
||||
<a class="web-link" {href} {target} {rel}>{label}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
<li
|
||||
class="web-footer-nav-main-item web-is-only-mobile"
|
||||
use:melt={$item({ value: title })}
|
||||
>
|
||||
<h5 use:melt={$heading({ level: 5 })}>
|
||||
<button
|
||||
class="web-footer-nav-button web-is-only-mobile"
|
||||
use:melt={$trigger({ value: title })}
|
||||
>
|
||||
<span class="web-caption-500 web-eyebrow">{title}</span>
|
||||
<span
|
||||
class="web-icon-chevron-down web-u-transition"
|
||||
class:web-u-rotate-180={$isSelected(title)}
|
||||
style:font-size="1rem"
|
||||
/>
|
||||
</button>
|
||||
</h5>
|
||||
{#if $isSelected(title)}
|
||||
<ul
|
||||
class="web-footer-nav-secondary-list web-sub-body-400"
|
||||
use:melt={$content({ value: title })}
|
||||
transition:slide={{ duration: 250 }}
|
||||
>
|
||||
{#each items as { href, label, target, rel }}
|
||||
<li>
|
||||
<a class="web-link" {href} {target} {rel}>{label}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<li
|
||||
class="web-footer-nav-main-item web-is-only-mobile"
|
||||
use:melt={$item({ value: title })}
|
||||
>
|
||||
<h5 use:melt={$heading({ level: 5 })}>
|
||||
<button
|
||||
class="web-footer-nav-button web-is-only-mobile"
|
||||
use:melt={$trigger({ value: title })}
|
||||
>
|
||||
<span class="web-caption-500 web-eyebrow">{title}</span>
|
||||
<span
|
||||
class="web-icon-chevron-down web-u-transition"
|
||||
class:web-u-rotate-180={$isSelected(title)}
|
||||
style:font-size="1rem"
|
||||
/>
|
||||
</button>
|
||||
</h5>
|
||||
{#if $isSelected(title)}
|
||||
<ul
|
||||
class="web-footer-nav-secondary-list web-sub-body-400"
|
||||
use:melt={$content({ value: title })}
|
||||
transition:slide={{ duration: 250 }}
|
||||
>
|
||||
{#each items as { href, label, target, rel }}
|
||||
<li>
|
||||
<a class="web-link" {href} {target} {rel}>{label}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -1,254 +1,254 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { onMount } from "svelte";
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let mounted = false;
|
||||
let mounted = false;
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
});
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
});
|
||||
|
||||
let groups = [1, 2, 1, 3, 1, 2, 1, 3, 3, 2];
|
||||
let groups = [1, 2, 1, 3, 1, 2, 1, 3, 3, 2];
|
||||
|
||||
const getRandomXValue = () => {
|
||||
return Math.floor(Math.random() * 120);
|
||||
};
|
||||
const getRandomXValue = () => {
|
||||
return Math.floor(Math.random() * 120);
|
||||
};
|
||||
|
||||
const getRandomWidth = (index: number) => {
|
||||
const minWidth = 80;
|
||||
const maxRandomWidth = 125 - minWidth;
|
||||
const getRandomWidth = (index: number) => {
|
||||
const minWidth = 80;
|
||||
const maxRandomWidth = 125 - minWidth;
|
||||
|
||||
const seed = index;
|
||||
const random = Math.sin(seed) * 10000;
|
||||
const randomNumber = random - Math.floor(random);
|
||||
const seed = index;
|
||||
const random = Math.sin(seed) * 10000;
|
||||
const randomNumber = random - Math.floor(random);
|
||||
|
||||
return Math.floor(randomNumber * maxRandomWidth) + minWidth;
|
||||
};
|
||||
return Math.floor(randomNumber * maxRandomWidth) + minWidth;
|
||||
};
|
||||
|
||||
const randomDelay = () => Math.floor(Math.random() * 750);
|
||||
const randomDelay = () => Math.floor(Math.random() * 750);
|
||||
</script>
|
||||
|
||||
<div class="banner" class:hidden={$page.url.pathname.includes("init")}>
|
||||
<div class="content web-u-color-text-primary">
|
||||
<div class="headings">
|
||||
<span style:font-weight="500"
|
||||
><span
|
||||
style:font-size="20px"
|
||||
style:font-family="var(--web-font-family-aeonik-pro)"
|
||||
style:font-weight="600">init</span
|
||||
> has started
|
||||
</span>
|
||||
<span class="web-u-color-text-secondary">The start of something new</span>
|
||||
<div class="shadow" />
|
||||
</div>
|
||||
<a href="/init" rel="noopener noreferrer" class="action">
|
||||
<span class="web-caption-500">Join now</span>
|
||||
<span class="web-icon-arrow-right" aria-hidden="true" />
|
||||
<div class="shadow" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="shine" />
|
||||
<div class="border" />
|
||||
<div class="lines">
|
||||
{#if mounted}
|
||||
{#each Array.from({ length: groups.length }) as _, i}
|
||||
<div style:position="relative" class="group">
|
||||
{#each Array.from({ length: groups[i] }) as _, index}
|
||||
<div
|
||||
class="line"
|
||||
style={`--width:${getRandomWidth(index)}px;--initial-delay:${randomDelay()}ms;left:${getRandomXValue()}px;`}
|
||||
/>
|
||||
{/each}
|
||||
<div class="banner" class:hidden={$page.url.pathname.includes('init')}>
|
||||
<div class="content web-u-color-text-primary">
|
||||
<div class="headings">
|
||||
<span style:font-weight="500"
|
||||
><span
|
||||
style:font-size="20px"
|
||||
style:font-family="var(--web-font-family-aeonik-pro)"
|
||||
style:font-weight="600">init</span
|
||||
> has started
|
||||
</span>
|
||||
<span class="web-u-color-text-secondary">The start of something new</span>
|
||||
<div class="shadow" />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
<a href="/init" rel="noopener noreferrer" class="action">
|
||||
<span class="web-caption-500">Join now</span>
|
||||
<span class="web-icon-arrow-right" aria-hidden="true" />
|
||||
<div class="shadow" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="shine" />
|
||||
<div class="border" />
|
||||
<div class="lines">
|
||||
{#if mounted}
|
||||
{#each Array.from({ length: groups.length }) as _, i}
|
||||
<div style:position="relative" class="group">
|
||||
{#each Array.from({ length: groups[i] }) as _, index}
|
||||
<div
|
||||
class="line"
|
||||
style={`--width:${getRandomWidth(index)}px;--initial-delay:${randomDelay()}ms;left:${getRandomXValue()}px;`}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.banner {
|
||||
--shine: rgba(255, 255, 255, 0.04);
|
||||
position: relative;
|
||||
min-height: 76px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 100vw;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid hsl(var(--web-color-border));
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
padding-inline: clamp(1.25rem, 4vw, 120rem);
|
||||
|
||||
.headings {
|
||||
z-index: 10;
|
||||
.banner {
|
||||
--shine: rgba(255, 255, 255, 0.04);
|
||||
position: relative;
|
||||
min-height: 76px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 100vw;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid hsl(var(--web-color-border));
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
width: 250px;
|
||||
height: 76px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
z-index: 0;
|
||||
backdrop-filter: blur(6px);
|
||||
background-color: hsl(var(--web-color-background) / 50%);
|
||||
mask-composite: intersect;
|
||||
mask-image: linear-gradient(
|
||||
to top,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 1) 25%,
|
||||
rgba(0, 0, 0, 1) 75%,
|
||||
transparent
|
||||
),
|
||||
linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 1) 25%,
|
||||
rgba(0, 0, 0, 1) 75%,
|
||||
transparent
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
padding-inline: clamp(1.25rem, 4vw, 120rem);
|
||||
|
||||
.headings {
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
width: 250px;
|
||||
height: 76px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
z-index: 0;
|
||||
backdrop-filter: blur(6px);
|
||||
background-color: hsl(var(--web-color-background) / 50%);
|
||||
mask-composite: intersect;
|
||||
mask-image: linear-gradient(
|
||||
to top,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 1) 25%,
|
||||
rgba(0, 0, 0, 1) 75%,
|
||||
transparent
|
||||
),
|
||||
linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 1) 25%,
|
||||
rgba(0, 0, 0, 1) 75%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
width: 200px;
|
||||
height: 76px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
z-index: 0;
|
||||
backdrop-filter: blur(6px);
|
||||
background-color: hsl(var(--web-color-background) / 50%);
|
||||
mask-composite: intersect;
|
||||
mask-image: linear-gradient(
|
||||
to top,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 1) 25%,
|
||||
rgba(0, 0, 0, 1) 75%,
|
||||
transparent
|
||||
),
|
||||
linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 1) 25%,
|
||||
rgba(0, 0, 0, 1) 75%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shine {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: hsl(var(--web-color-background))
|
||||
linear-gradient(
|
||||
-24deg,
|
||||
rgba(255, 255, 255, 0) 0,
|
||||
var(--shine) 20%,
|
||||
rgba(255, 255, 255, 0) 40%,
|
||||
var(--shine) 50%,
|
||||
rgba(255, 255, 255, 0) 60%,
|
||||
var(--shine) 80%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
}
|
||||
.border {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: hsl(var(--web-color-border));
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.lines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
margin: 0 auto;
|
||||
opacity: 0.6;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 16px 0;
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
--gradient: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 1) 50%,
|
||||
rgba(255, 255, 255, 1) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
--height: 1px;
|
||||
--starting-position: -80vw;
|
||||
--duration: 1s;
|
||||
|
||||
.action {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
.line {
|
||||
position: relative;
|
||||
height: var(--height);
|
||||
width: var(--width);
|
||||
z-index: 20;
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
@keyframes line {
|
||||
from {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
to {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
.shadow {
|
||||
width: 200px;
|
||||
height: 76px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
z-index: 0;
|
||||
backdrop-filter: blur(6px);
|
||||
background-color: hsl(var(--web-color-background) / 50%);
|
||||
mask-composite: intersect;
|
||||
mask-image: linear-gradient(
|
||||
to top,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 1) 25%,
|
||||
rgba(0, 0, 0, 1) 75%,
|
||||
transparent
|
||||
),
|
||||
linear-gradient(
|
||||
to right,
|
||||
transparent,
|
||||
rgba(0, 0, 0, 1) 25%,
|
||||
rgba(0, 0, 0, 1) 75%,
|
||||
transparent
|
||||
);
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
transform-origin: left;
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
background: var(--gradient);
|
||||
border-radius: 8px;
|
||||
transform: scaleX(0);
|
||||
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8));
|
||||
animation: line var(--duration) ease forwards;
|
||||
animation-delay: var(--initial-delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shine {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: hsl(var(--web-color-background))
|
||||
linear-gradient(
|
||||
-24deg,
|
||||
rgba(255, 255, 255, 0) 0,
|
||||
var(--shine) 20%,
|
||||
rgba(255, 255, 255, 0) 40%,
|
||||
var(--shine) 50%,
|
||||
rgba(255, 255, 255, 0) 60%,
|
||||
var(--shine) 80%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
}
|
||||
.border {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: hsl(var(--web-color-border));
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.lines {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
margin: 0 auto;
|
||||
opacity: 0.6;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 16px 0;
|
||||
|
||||
.group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
--gradient: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 1) 50%,
|
||||
rgba(255, 255, 255, 1) 100%
|
||||
);
|
||||
--height: 1px;
|
||||
--starting-position: -80vw;
|
||||
--duration: 1s;
|
||||
|
||||
.line {
|
||||
position: relative;
|
||||
height: var(--height);
|
||||
width: var(--width);
|
||||
z-index: 20;
|
||||
|
||||
@keyframes line {
|
||||
from {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
to {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
transform-origin: left;
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
background: var(--gradient);
|
||||
border-radius: 8px;
|
||||
transform: scaleX(0);
|
||||
filter: drop-shadow(0 0 2px rgba(255, 255, 255, 0.8));
|
||||
animation: line var(--duration) ease forwards;
|
||||
animation-delay: var(--initial-delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
<script lang="ts">
|
||||
import Button from "./ui/Button.svelte";
|
||||
import { PUBLIC_APPWRITE_DASHBOARD } from "$env/static/public";
|
||||
import Button from './ui/Button.svelte';
|
||||
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
|
||||
|
||||
export let classes = "";
|
||||
export let classes = '';
|
||||
</script>
|
||||
|
||||
<Button class={classes} href={PUBLIC_APPWRITE_DASHBOARD}>
|
||||
<span class="hidden [data-logged-in]:block"
|
||||
><slot name="isLoggedIn">Go to Console</slot></span
|
||||
>
|
||||
<span class="btton [data-logged-in]:hidden"
|
||||
><slot name="isNotLoggedIn">Get started</slot></span
|
||||
>
|
||||
<span class="hidden [data-logged-in]:block"><slot name="isLoggedIn">Go to Console</slot></span>
|
||||
<span class="btton [data-logged-in]:hidden"><slot name="isNotLoggedIn">Get started</slot></span>
|
||||
</Button>
|
||||
|
||||
<style lang="scss">
|
||||
:global(body[data-logged-in]) {
|
||||
.logged-in {
|
||||
display: block;
|
||||
:global(body[data-logged-in]) {
|
||||
.logged-in {
|
||||
display: block;
|
||||
}
|
||||
.not-logged-in {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.not-logged-in {
|
||||
display: none;
|
||||
display: block;
|
||||
}
|
||||
.logged-in {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.not-logged-in {
|
||||
display: block;
|
||||
}
|
||||
.logged-in {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,98 +1,97 @@
|
||||
<script lang="ts">
|
||||
export let title =
|
||||
"Trusted by developers from the world's leading organizations";
|
||||
export let title = "Trusted by developers from the world's leading organizations";
|
||||
|
||||
const logos = [
|
||||
{
|
||||
src: "/images/logos/trusted-by/apple.svg",
|
||||
alt: "Apple",
|
||||
width: 42,
|
||||
height: 48,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/oracle.svg",
|
||||
alt: "ORACLE",
|
||||
width: 136,
|
||||
height: 17,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/tiktok.svg",
|
||||
alt: "TikTok",
|
||||
width: 133,
|
||||
height: 32,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/intel.svg",
|
||||
alt: "intel",
|
||||
width: 76,
|
||||
height: 30,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/ibm.svg",
|
||||
alt: "IBM",
|
||||
width: 74,
|
||||
height: 30,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/american-airlines.svg",
|
||||
alt: "American Airlines",
|
||||
width: 147,
|
||||
height: 24,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/deloitte.svg",
|
||||
alt: "Deloitte.",
|
||||
width: 103,
|
||||
height: 20,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/gm.svg",
|
||||
alt: "GM",
|
||||
width: 48,
|
||||
height: 48,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/ey.svg",
|
||||
alt: "EY",
|
||||
width: 46,
|
||||
height: 48,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/nestle.svg",
|
||||
alt: "Nestle",
|
||||
width: 119,
|
||||
height: 34,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/bosch.svg",
|
||||
alt: "BOSCH",
|
||||
width: 110,
|
||||
height: 37,
|
||||
},
|
||||
{
|
||||
src: "/images/logos/trusted-by/decathlon.svg",
|
||||
alt: "DECATHLON",
|
||||
width: 127,
|
||||
height: 32,
|
||||
},
|
||||
];
|
||||
const logos = [
|
||||
{
|
||||
src: '/images/logos/trusted-by/apple.svg',
|
||||
alt: 'Apple',
|
||||
width: 42,
|
||||
height: 48
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/oracle.svg',
|
||||
alt: 'ORACLE',
|
||||
width: 136,
|
||||
height: 17
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/tiktok.svg',
|
||||
alt: 'TikTok',
|
||||
width: 133,
|
||||
height: 32
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/intel.svg',
|
||||
alt: 'intel',
|
||||
width: 76,
|
||||
height: 30
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/ibm.svg',
|
||||
alt: 'IBM',
|
||||
width: 74,
|
||||
height: 30
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/american-airlines.svg',
|
||||
alt: 'American Airlines',
|
||||
width: 147,
|
||||
height: 24
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/deloitte.svg',
|
||||
alt: 'Deloitte.',
|
||||
width: 103,
|
||||
height: 20
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/gm.svg',
|
||||
alt: 'GM',
|
||||
width: 48,
|
||||
height: 48
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/ey.svg',
|
||||
alt: 'EY',
|
||||
width: 46,
|
||||
height: 48
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/nestle.svg',
|
||||
alt: 'Nestle',
|
||||
width: 119,
|
||||
height: 34
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/bosch.svg',
|
||||
alt: 'BOSCH',
|
||||
width: 110,
|
||||
height: 37
|
||||
},
|
||||
{
|
||||
src: '/images/logos/trusted-by/decathlon.svg',
|
||||
alt: 'DECATHLON',
|
||||
width: 127,
|
||||
height: 32
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="my-32">
|
||||
<div class="container">
|
||||
<h2
|
||||
class="font-aeonik-pro text-greyscale-100 mx-auto max-w-xl text-center text-4xl leading-10"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
<ul
|
||||
class="web-u-padding-block-start-80 grid grid-cols-3 text-center md:grid-cols-6 md:gap-10"
|
||||
>
|
||||
{#each logos as { src, alt, width, height }, i}
|
||||
<li class="grid place-content-center">
|
||||
<img {src} {alt} {width} {height} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="container">
|
||||
<h2
|
||||
class="font-aeonik-pro text-greyscale-100 mx-auto max-w-xl text-center text-4xl leading-10"
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
<ul
|
||||
class="web-u-padding-block-start-80 grid grid-cols-3 text-center md:grid-cols-6 md:gap-10"
|
||||
>
|
||||
{#each logos as { src, alt, width, height }, i}
|
||||
<li class="grid place-content-center">
|
||||
<img {src} {alt} {width} {height} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user