This commit is contained in:
Jesse Winton
2024-08-27 15:06:06 -04:00
parent 9a74791115
commit dc674dd31b
478 changed files with 40014 additions and 41409 deletions

View File

@@ -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'
}
}
]
};

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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" } }]
}

View File

@@ -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

View File

@@ -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!**

View File

@@ -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
View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}
}
]

View File

@@ -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"
}
}

View File

@@ -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;

View File

@@ -1,5 +1,5 @@
export default {
plugins: {
"@tailwindcss/postcss": {},
},
plugins: {
'@tailwindcss/postcss': {}
}
};

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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}`);
}

View File

@@ -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
View File

@@ -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 {};

View File

@@ -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>

View File

@@ -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`

View File

@@ -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();

View File

@@ -1,3 +1,3 @@
import { generateIcons } from "./scripts.js";
import { generateIcons } from './scripts.js';
generateIcons();

View File

@@ -1,3 +1,3 @@
import { optimizeSVG } from "./scripts.js";
import { optimizeSVG } from './scripts.js';
optimizeSVG();

View File

@@ -1,314 +1,314 @@
{
"apple": {
"encodedCode": "\\ea01",
"prefix": "web-icon",
"className": "web-icon-apple",
"unicode": "&#59905;"
},
"appwrite": {
"encodedCode": "\\ea02",
"prefix": "web-icon",
"className": "web-icon-appwrite",
"unicode": "&#59906;"
},
"arrow-down": {
"encodedCode": "\\ea03",
"prefix": "web-icon",
"className": "web-icon-arrow-down",
"unicode": "&#59907;"
},
"arrow-ext-link": {
"encodedCode": "\\ea04",
"prefix": "web-icon",
"className": "web-icon-arrow-ext-link",
"unicode": "&#59908;"
},
"arrow-left": {
"encodedCode": "\\ea05",
"prefix": "web-icon",
"className": "web-icon-arrow-left",
"unicode": "&#59909;"
},
"arrow-right": {
"encodedCode": "\\ea06",
"prefix": "web-icon",
"className": "web-icon-arrow-right",
"unicode": "&#59910;"
},
"arrow-up": {
"encodedCode": "\\ea07",
"prefix": "web-icon",
"className": "web-icon-arrow-up",
"unicode": "&#59911;"
},
"calendar": {
"encodedCode": "\\ea08",
"prefix": "web-icon",
"className": "web-icon-calendar",
"unicode": "&#59912;"
},
"check": {
"encodedCode": "\\ea09",
"prefix": "web-icon",
"className": "web-icon-check",
"unicode": "&#59913;"
},
"chevron-down": {
"encodedCode": "\\ea0a",
"prefix": "web-icon",
"className": "web-icon-chevron-down",
"unicode": "&#59914;"
},
"chevron-left": {
"encodedCode": "\\ea0b",
"prefix": "web-icon",
"className": "web-icon-chevron-left",
"unicode": "&#59915;"
},
"chevron-right": {
"encodedCode": "\\ea0c",
"prefix": "web-icon",
"className": "web-icon-chevron-right",
"unicode": "&#59916;"
},
"chevron-up": {
"encodedCode": "\\ea0d",
"prefix": "web-icon",
"className": "web-icon-chevron-up",
"unicode": "&#59917;"
},
"close": {
"encodedCode": "\\ea0e",
"prefix": "web-icon",
"className": "web-icon-close",
"unicode": "&#59918;"
},
"command": {
"encodedCode": "\\ea0f",
"prefix": "web-icon",
"className": "web-icon-command",
"unicode": "&#59919;"
},
"copy": {
"encodedCode": "\\ea10",
"prefix": "web-icon",
"className": "web-icon-copy",
"unicode": "&#59920;"
},
"daily-dev": {
"encodedCode": "\\ea11",
"prefix": "web-icon",
"className": "web-icon-daily-dev",
"unicode": "&#59921;"
},
"dark": {
"encodedCode": "\\ea12",
"prefix": "web-icon",
"className": "web-icon-dark",
"unicode": "&#59922;"
},
"discord": {
"encodedCode": "\\ea13",
"prefix": "web-icon",
"className": "web-icon-discord",
"unicode": "&#59923;"
},
"divider-vertical": {
"encodedCode": "\\ea14",
"prefix": "web-icon",
"className": "web-icon-divider-vertical",
"unicode": "&#59924;"
},
"download": {
"encodedCode": "\\ea15",
"prefix": "web-icon",
"className": "web-icon-download",
"unicode": "&#59925;"
},
"ext-link": {
"encodedCode": "\\ea16",
"prefix": "web-icon",
"className": "web-icon-ext-link",
"unicode": "&#59926;"
},
"firebase": {
"encodedCode": "\\ea17",
"prefix": "web-icon",
"className": "web-icon-firebase",
"unicode": "&#59927;"
},
"github": {
"encodedCode": "\\ea18",
"prefix": "web-icon",
"className": "web-icon-github",
"unicode": "&#59928;"
},
"google": {
"encodedCode": "\\ea19",
"prefix": "web-icon",
"className": "web-icon-google",
"unicode": "&#59929;"
},
"hamburger-menu": {
"encodedCode": "\\ea1a",
"prefix": "web-icon",
"className": "web-icon-hamburger-menu",
"unicode": "&#59930;"
},
"light": {
"encodedCode": "\\ea1b",
"prefix": "web-icon",
"className": "web-icon-light",
"unicode": "&#59931;"
},
"linkedin": {
"encodedCode": "\\ea1c",
"prefix": "web-icon",
"className": "web-icon-linkedin",
"unicode": "&#59932;"
},
"location": {
"encodedCode": "\\ea1d",
"prefix": "web-icon",
"className": "web-icon-location",
"unicode": "&#59933;"
},
"logout-left": {
"encodedCode": "\\ea1e",
"prefix": "web-icon",
"className": "web-icon-logout-left",
"unicode": "&#59934;"
},
"logout-right": {
"encodedCode": "\\ea1f",
"prefix": "web-icon",
"className": "web-icon-logout-right",
"unicode": "&#59935;"
},
"mailgun": {
"encodedCode": "\\ea20",
"prefix": "web-icon",
"className": "web-icon-mailgun",
"unicode": "&#59936;"
},
"message": {
"encodedCode": "\\ea21",
"prefix": "web-icon",
"className": "web-icon-message",
"unicode": "&#59937;"
},
"microsoft": {
"encodedCode": "\\ea22",
"prefix": "web-icon",
"className": "web-icon-microsoft",
"unicode": "&#59938;"
},
"minus": {
"encodedCode": "\\ea23",
"prefix": "web-icon",
"className": "web-icon-minus",
"unicode": "&#59939;"
},
"nuxt": {
"encodedCode": "\\ea24",
"prefix": "web-icon",
"className": "web-icon-nuxt",
"unicode": "&#59940;"
},
"platform": {
"encodedCode": "\\ea25",
"prefix": "web-icon",
"className": "web-icon-platform",
"unicode": "&#59941;"
},
"play": {
"encodedCode": "\\ea26",
"prefix": "web-icon",
"className": "web-icon-play",
"unicode": "&#59942;"
},
"plus": {
"encodedCode": "\\ea27",
"prefix": "web-icon",
"className": "web-icon-plus",
"unicode": "&#59943;"
},
"product-hunt": {
"encodedCode": "\\ea28",
"prefix": "web-icon",
"className": "web-icon-product-hunt",
"unicode": "&#59944;"
},
"refine": {
"encodedCode": "\\ea29",
"prefix": "web-icon",
"className": "web-icon-refine",
"unicode": "&#59945;"
},
"rest": {
"encodedCode": "\\ea2a",
"prefix": "web-icon",
"className": "web-icon-rest",
"unicode": "&#59946;"
},
"search": {
"encodedCode": "\\ea2b",
"prefix": "web-icon",
"className": "web-icon-search",
"unicode": "&#59947;"
},
"sendgrid": {
"encodedCode": "\\ea2c",
"prefix": "web-icon",
"className": "web-icon-sendgrid",
"unicode": "&#59948;"
},
"star": {
"encodedCode": "\\ea2d",
"prefix": "web-icon",
"className": "web-icon-star",
"unicode": "&#59949;"
},
"system": {
"encodedCode": "\\ea2e",
"prefix": "web-icon",
"className": "web-icon-system",
"unicode": "&#59950;"
},
"textmagic": {
"encodedCode": "\\ea2f",
"prefix": "web-icon",
"className": "web-icon-textmagic",
"unicode": "&#59951;"
},
"twitter": {
"encodedCode": "\\ea30",
"prefix": "web-icon",
"className": "web-icon-twitter",
"unicode": "&#59952;"
},
"vue": {
"encodedCode": "\\ea31",
"prefix": "web-icon",
"className": "web-icon-vue",
"unicode": "&#59953;"
},
"x": {
"encodedCode": "\\ea32",
"prefix": "web-icon",
"className": "web-icon-x",
"unicode": "&#59954;"
},
"ycombinator": {
"encodedCode": "\\ea33",
"prefix": "web-icon",
"className": "web-icon-ycombinator",
"unicode": "&#59955;"
},
"youtube": {
"encodedCode": "\\ea34",
"prefix": "web-icon",
"className": "web-icon-youtube",
"unicode": "&#59956;"
}
"apple": {
"encodedCode": "\\ea01",
"prefix": "web-icon",
"className": "web-icon-apple",
"unicode": "&#59905;"
},
"appwrite": {
"encodedCode": "\\ea02",
"prefix": "web-icon",
"className": "web-icon-appwrite",
"unicode": "&#59906;"
},
"arrow-down": {
"encodedCode": "\\ea03",
"prefix": "web-icon",
"className": "web-icon-arrow-down",
"unicode": "&#59907;"
},
"arrow-ext-link": {
"encodedCode": "\\ea04",
"prefix": "web-icon",
"className": "web-icon-arrow-ext-link",
"unicode": "&#59908;"
},
"arrow-left": {
"encodedCode": "\\ea05",
"prefix": "web-icon",
"className": "web-icon-arrow-left",
"unicode": "&#59909;"
},
"arrow-right": {
"encodedCode": "\\ea06",
"prefix": "web-icon",
"className": "web-icon-arrow-right",
"unicode": "&#59910;"
},
"arrow-up": {
"encodedCode": "\\ea07",
"prefix": "web-icon",
"className": "web-icon-arrow-up",
"unicode": "&#59911;"
},
"calendar": {
"encodedCode": "\\ea08",
"prefix": "web-icon",
"className": "web-icon-calendar",
"unicode": "&#59912;"
},
"check": {
"encodedCode": "\\ea09",
"prefix": "web-icon",
"className": "web-icon-check",
"unicode": "&#59913;"
},
"chevron-down": {
"encodedCode": "\\ea0a",
"prefix": "web-icon",
"className": "web-icon-chevron-down",
"unicode": "&#59914;"
},
"chevron-left": {
"encodedCode": "\\ea0b",
"prefix": "web-icon",
"className": "web-icon-chevron-left",
"unicode": "&#59915;"
},
"chevron-right": {
"encodedCode": "\\ea0c",
"prefix": "web-icon",
"className": "web-icon-chevron-right",
"unicode": "&#59916;"
},
"chevron-up": {
"encodedCode": "\\ea0d",
"prefix": "web-icon",
"className": "web-icon-chevron-up",
"unicode": "&#59917;"
},
"close": {
"encodedCode": "\\ea0e",
"prefix": "web-icon",
"className": "web-icon-close",
"unicode": "&#59918;"
},
"command": {
"encodedCode": "\\ea0f",
"prefix": "web-icon",
"className": "web-icon-command",
"unicode": "&#59919;"
},
"copy": {
"encodedCode": "\\ea10",
"prefix": "web-icon",
"className": "web-icon-copy",
"unicode": "&#59920;"
},
"daily-dev": {
"encodedCode": "\\ea11",
"prefix": "web-icon",
"className": "web-icon-daily-dev",
"unicode": "&#59921;"
},
"dark": {
"encodedCode": "\\ea12",
"prefix": "web-icon",
"className": "web-icon-dark",
"unicode": "&#59922;"
},
"discord": {
"encodedCode": "\\ea13",
"prefix": "web-icon",
"className": "web-icon-discord",
"unicode": "&#59923;"
},
"divider-vertical": {
"encodedCode": "\\ea14",
"prefix": "web-icon",
"className": "web-icon-divider-vertical",
"unicode": "&#59924;"
},
"download": {
"encodedCode": "\\ea15",
"prefix": "web-icon",
"className": "web-icon-download",
"unicode": "&#59925;"
},
"ext-link": {
"encodedCode": "\\ea16",
"prefix": "web-icon",
"className": "web-icon-ext-link",
"unicode": "&#59926;"
},
"firebase": {
"encodedCode": "\\ea17",
"prefix": "web-icon",
"className": "web-icon-firebase",
"unicode": "&#59927;"
},
"github": {
"encodedCode": "\\ea18",
"prefix": "web-icon",
"className": "web-icon-github",
"unicode": "&#59928;"
},
"google": {
"encodedCode": "\\ea19",
"prefix": "web-icon",
"className": "web-icon-google",
"unicode": "&#59929;"
},
"hamburger-menu": {
"encodedCode": "\\ea1a",
"prefix": "web-icon",
"className": "web-icon-hamburger-menu",
"unicode": "&#59930;"
},
"light": {
"encodedCode": "\\ea1b",
"prefix": "web-icon",
"className": "web-icon-light",
"unicode": "&#59931;"
},
"linkedin": {
"encodedCode": "\\ea1c",
"prefix": "web-icon",
"className": "web-icon-linkedin",
"unicode": "&#59932;"
},
"location": {
"encodedCode": "\\ea1d",
"prefix": "web-icon",
"className": "web-icon-location",
"unicode": "&#59933;"
},
"logout-left": {
"encodedCode": "\\ea1e",
"prefix": "web-icon",
"className": "web-icon-logout-left",
"unicode": "&#59934;"
},
"logout-right": {
"encodedCode": "\\ea1f",
"prefix": "web-icon",
"className": "web-icon-logout-right",
"unicode": "&#59935;"
},
"mailgun": {
"encodedCode": "\\ea20",
"prefix": "web-icon",
"className": "web-icon-mailgun",
"unicode": "&#59936;"
},
"message": {
"encodedCode": "\\ea21",
"prefix": "web-icon",
"className": "web-icon-message",
"unicode": "&#59937;"
},
"microsoft": {
"encodedCode": "\\ea22",
"prefix": "web-icon",
"className": "web-icon-microsoft",
"unicode": "&#59938;"
},
"minus": {
"encodedCode": "\\ea23",
"prefix": "web-icon",
"className": "web-icon-minus",
"unicode": "&#59939;"
},
"nuxt": {
"encodedCode": "\\ea24",
"prefix": "web-icon",
"className": "web-icon-nuxt",
"unicode": "&#59940;"
},
"platform": {
"encodedCode": "\\ea25",
"prefix": "web-icon",
"className": "web-icon-platform",
"unicode": "&#59941;"
},
"play": {
"encodedCode": "\\ea26",
"prefix": "web-icon",
"className": "web-icon-play",
"unicode": "&#59942;"
},
"plus": {
"encodedCode": "\\ea27",
"prefix": "web-icon",
"className": "web-icon-plus",
"unicode": "&#59943;"
},
"product-hunt": {
"encodedCode": "\\ea28",
"prefix": "web-icon",
"className": "web-icon-product-hunt",
"unicode": "&#59944;"
},
"refine": {
"encodedCode": "\\ea29",
"prefix": "web-icon",
"className": "web-icon-refine",
"unicode": "&#59945;"
},
"rest": {
"encodedCode": "\\ea2a",
"prefix": "web-icon",
"className": "web-icon-rest",
"unicode": "&#59946;"
},
"search": {
"encodedCode": "\\ea2b",
"prefix": "web-icon",
"className": "web-icon-search",
"unicode": "&#59947;"
},
"sendgrid": {
"encodedCode": "\\ea2c",
"prefix": "web-icon",
"className": "web-icon-sendgrid",
"unicode": "&#59948;"
},
"star": {
"encodedCode": "\\ea2d",
"prefix": "web-icon",
"className": "web-icon-star",
"unicode": "&#59949;"
},
"system": {
"encodedCode": "\\ea2e",
"prefix": "web-icon",
"className": "web-icon-system",
"unicode": "&#59950;"
},
"textmagic": {
"encodedCode": "\\ea2f",
"prefix": "web-icon",
"className": "web-icon-textmagic",
"unicode": "&#59951;"
},
"twitter": {
"encodedCode": "\\ea30",
"prefix": "web-icon",
"className": "web-icon-twitter",
"unicode": "&#59952;"
},
"vue": {
"encodedCode": "\\ea31",
"prefix": "web-icon",
"className": "web-icon-vue",
"unicode": "&#59953;"
},
"x": {
"encodedCode": "\\ea32",
"prefix": "web-icon",
"className": "web-icon-x",
"unicode": "&#59954;"
},
"ycombinator": {
"encodedCode": "\\ea33",
"prefix": "web-icon",
"className": "web-icon-ycombinator",
"unicode": "&#59955;"
},
"youtube": {
"encodedCode": "\\ea34",
"prefix": "web-icon",
"className": "web-icon-youtube",
"unicode": "&#59956;"
}
}

View File

@@ -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';
}

View File

@@ -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
});
};

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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';

View File

@@ -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() };
};

View File

@@ -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);
}
};
}

View File

@@ -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>');
});
};

View File

@@ -1,3 +1,3 @@
export * from "./portal";
export * from "./rect";
export * from "./visible";
export * from './portal';
export * from './rect';
export * from './visible';

View File

@@ -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
};
};

View File

@@ -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
}
};
};

View File

@@ -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);
}
};
};

View File

@@ -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);
},
};
};

View File

@@ -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>

View File

@@ -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 -->

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}',

View File

@@ -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>

View File

@@ -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
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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}',

View File

@@ -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
};

View File

@@ -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>

View File

@@ -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
}
};

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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(),

View File

@@ -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
};

View File

@@ -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>

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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)
};

View File

@@ -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)
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 };

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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} />

View File

@@ -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} />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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