mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-09 12:57:48 +00:00
Merge branch 'main' into fix-team-invites-headings
This commit is contained in:
@@ -2,7 +2,7 @@ PUBLIC_APPWRITE_COL_MESSAGES_ID=
|
|||||||
PUBLIC_APPWRITE_COL_THREADS_ID=
|
PUBLIC_APPWRITE_COL_THREADS_ID=
|
||||||
PUBLIC_APPWRITE_DB_MAIN_ID=
|
PUBLIC_APPWRITE_DB_MAIN_ID=
|
||||||
PUBLIC_APPWRITE_FN_TLDR_ID=
|
PUBLIC_APPWRITE_FN_TLDR_ID=
|
||||||
PUBLIC_APPWRITE_ENDPOINT=
|
PUBLIC_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
|
||||||
PUBLIC_APPWRITE_PROJECT_ID=
|
PUBLIC_APPWRITE_PROJECT_ID=
|
||||||
PUBLIC_APPWRITE_DASHBOARD=https://cloud.appwrite.io
|
PUBLIC_APPWRITE_DASHBOARD=https://cloud.appwrite.io
|
||||||
PUBLIC_APPWRITE_PROJECT_INIT_ID=
|
PUBLIC_APPWRITE_PROJECT_INIT_ID=
|
||||||
@@ -11,4 +11,4 @@ PUBLIC_POSTHOG_API_KEY=
|
|||||||
APPWRITE_DB_INIT_ID=
|
APPWRITE_DB_INIT_ID=
|
||||||
APPWRITE_COL_INIT_ID=
|
APPWRITE_COL_INIT_ID=
|
||||||
APPWRITE_API_KEY_INIT=
|
APPWRITE_API_KEY_INIT=
|
||||||
SENTRY_AUTH_TOKEN=
|
SENTRY_AUTH_TOKEN=
|
||||||
|
|||||||
@@ -1,33 +1,51 @@
|
|||||||
import prettier from 'eslint-config-prettier';
|
import { includeIgnoreFile } from '@eslint/compat';
|
||||||
import js from '@eslint/js';
|
import js from '@eslint/js';
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
import svelte from 'eslint-plugin-svelte';
|
import svelte from 'eslint-plugin-svelte';
|
||||||
import globals from 'globals';
|
import globals from 'globals';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
import ts from 'typescript-eslint';
|
import ts from 'typescript-eslint';
|
||||||
|
import svelteConfig from './svelte.config.js';
|
||||||
|
|
||||||
|
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||||
|
|
||||||
export default ts.config(
|
export default ts.config(
|
||||||
|
includeIgnoreFile(gitignorePath),
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
...ts.configs.recommended,
|
...ts.configs.recommended,
|
||||||
...svelte.configs['flat/recommended'],
|
...svelte.configs.recommended,
|
||||||
prettier,
|
prettier,
|
||||||
...svelte.configs['flat/prettier'],
|
...svelte.configs.prettier,
|
||||||
{
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: { ...globals.browser, ...globals.node }
|
||||||
...globals.browser,
|
},
|
||||||
...globals.node
|
rules: {
|
||||||
}
|
// TODO: remove them one by one
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-duplicate-enum-values': 'off',
|
||||||
|
'@typescript-eslint/no-empty-object-type': 'off',
|
||||||
|
'@typescript-eslint/no-unused-expressions': 'off',
|
||||||
|
'svelte/infinite-reactive-loop': 'off',
|
||||||
|
'svelte/require-each-key': 'off',
|
||||||
|
'svelte/no-immutable-reactive-statements': 'off',
|
||||||
|
'svelte/no-at-html-tags': 'off',
|
||||||
|
'svelte/no-useless-mustaches': 'off',
|
||||||
|
'svelte/no-reactive-reassign': 'off',
|
||||||
|
'svelte/no-reactive-literals': 'off'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['**/*.svelte'],
|
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||||
|
ignores: ['eslint.config.js', 'svelte.config.js'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
parser: ts.parser
|
// Only uncomment this if you want it to take 3 minutes https://github.com/sveltejs/eslint-plugin-svelte/issues/1084
|
||||||
|
// projectService: true,
|
||||||
|
extraFileExtensions: ['.svelte'],
|
||||||
|
parser: ts.parser,
|
||||||
|
svelteConfig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
ignores: ['build/', '.svelte-kit/', 'dist/']
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
1
global.d.ts
vendored
Normal file
1
global.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
declare module 'reodotdev';
|
||||||
31
package.json
31
package.json
@@ -26,8 +26,8 @@
|
|||||||
"packageManager": "pnpm@10.6.2",
|
"packageManager": "pnpm@10.6.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@number-flow/svelte": "^0.3.3",
|
"@number-flow/svelte": "^0.3.3",
|
||||||
"@sentry/sveltekit": "^8.51.0",
|
|
||||||
"h3": "^1.14.0",
|
"h3": "^1.14.0",
|
||||||
|
"melt": "^0.28.2",
|
||||||
"posthog-js": "^1.210.2",
|
"posthog-js": "^1.210.2",
|
||||||
"sharp": "^0.33.5"
|
"sharp": "^0.33.5"
|
||||||
},
|
},
|
||||||
@@ -36,17 +36,18 @@
|
|||||||
"@appwrite.io/pink": "~0.26.0",
|
"@appwrite.io/pink": "~0.26.0",
|
||||||
"@appwrite.io/pink-icons": "~0.26.0",
|
"@appwrite.io/pink-icons": "~0.26.0",
|
||||||
"@appwrite.io/repo": "github:appwrite/appwrite#1.6.x",
|
"@appwrite.io/repo": "github:appwrite/appwrite#1.6.x",
|
||||||
|
"@eslint/compat": "^1.2.7",
|
||||||
"@eslint/js": "^9.21.0",
|
"@eslint/js": "^9.21.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.5.1",
|
"@fingerprintjs/fingerprintjs": "^4.5.1",
|
||||||
"@internationalized/date": "3.5.0",
|
"@internationalized/date": "3.5.0",
|
||||||
"@melt-ui/pp": "^0.3.2",
|
"@melt-ui/pp": "^0.3.2",
|
||||||
"@melt-ui/svelte": "^0.86.2",
|
"@melt-ui/svelte": "^0.86.5",
|
||||||
"@playwright/test": "^1.50.0",
|
"@playwright/test": "^1.50.0",
|
||||||
"@sveltejs/adapter-node": "^4.0.1",
|
"@sveltejs/adapter-node": "^5.2.12",
|
||||||
"@sveltejs/enhanced-img": "^0.1.9",
|
"@sveltejs/enhanced-img": "^0.4.4",
|
||||||
"@sveltejs/kit": "^2.16.1",
|
"@sveltejs/kit": "^2.20.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||||
"@tailwindcss/postcss": "4.0.0-alpha.17",
|
"@tailwindcss/postcss": "^4.0.17",
|
||||||
"@types/compression": "^1.7.5",
|
"@types/compression": "^1.7.5",
|
||||||
"@types/glob": "^8.1.0",
|
"@types/glob": "^8.1.0",
|
||||||
"@types/markdown-it": "^13.0.9",
|
"@types/markdown-it": "^13.0.9",
|
||||||
@@ -80,27 +81,27 @@
|
|||||||
"prettier-plugin-svelte": "^3.3.3",
|
"prettier-plugin-svelte": "^3.3.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"remeda": "^2.20.0",
|
"remeda": "^2.20.0",
|
||||||
|
"reodotdev": "^1.0.0",
|
||||||
"sass": "^1.83.4",
|
"sass": "^1.83.4",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^5.25.6",
|
||||||
"svelte-check": "^3.8.6",
|
"svelte-check": "^4.0.0",
|
||||||
"svelte-markdoc-preprocess": "^2.1.0",
|
"svelte-markdoc-preprocess": "3.0.0",
|
||||||
"svelte-markdown": "^0.4.1",
|
"svelte-markdown": "^0.4.1",
|
||||||
"svgtofont": "^4.2.3",
|
"svgtofont": "^4.2.3",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^3.0.2",
|
||||||
"tailwindcss": "4.0.0-alpha.17",
|
"tailwindcss": "^4.0.17",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.21.0",
|
"typescript-eslint": "^8.21.0",
|
||||||
"vite": "^5.4.14",
|
"vite": "^6.2.4",
|
||||||
"vite-plugin-dynamic-import": "^1.6.0",
|
"vite-plugin-dynamic-import": "^1.6.0",
|
||||||
"vite-plugin-image-optimizer": "^1.1.8",
|
"vite-plugin-image-optimizer": "^1.1.8",
|
||||||
"vite-plugin-manifest-sri": "^0.2.0",
|
"vite-plugin-manifest-sri": "^0.2.0",
|
||||||
"vitest": "^1.6.0"
|
"vitest": "^3.1.1"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@parcel/watcher",
|
"@parcel/watcher",
|
||||||
"@sentry/cli",
|
|
||||||
"core-js",
|
"core-js",
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"sharp",
|
"sharp",
|
||||||
|
|||||||
2981
pnpm-lock.yaml
generated
2981
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,13 @@
|
|||||||
import { createApp, fromNodeMiddleware, toNodeListener } from 'h3';
|
import { sitemaps } from './sitemap.js';
|
||||||
import { createServer } from 'node:http';
|
import { createServer } from 'node:http';
|
||||||
import { handler } from '../build/handler.js';
|
import { handler } from '../build/handler.js';
|
||||||
import { sitemap } from './sitemap.js';
|
import { createApp, fromNodeMiddleware, toNodeListener } from 'h3';
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
const app = createApp();
|
const app = createApp();
|
||||||
app.use('/sitemap.xml', await sitemap());
|
app.use(['/sitemap.xml', '/sitemaps'], await sitemaps());
|
||||||
|
|
||||||
app.use(fromNodeMiddleware(handler));
|
app.use(fromNodeMiddleware(handler));
|
||||||
const server = createServer(toNodeListener(app)).listen(port);
|
const server = createServer(toNodeListener(app)).listen(port);
|
||||||
server.addListener('listening', () => {
|
server.addListener('listening', () => {
|
||||||
|
|||||||
@@ -1,59 +1,154 @@
|
|||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
import { createRequire } from 'node:module';
|
import { createRequire } from 'node:module';
|
||||||
import { defineEventHandler, setResponseHeader } from 'h3';
|
import { dirname, join } from 'node:path';
|
||||||
|
import { readFile, stat } from 'node:fs/promises';
|
||||||
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||||
|
import {
|
||||||
|
defineEventHandler,
|
||||||
|
getRequestURL,
|
||||||
|
sendRedirect,
|
||||||
|
serveStatic,
|
||||||
|
setResponseHeader
|
||||||
|
} from 'h3';
|
||||||
|
|
||||||
/**
|
const MAX_THREADS_PER_FILE = 1000;
|
||||||
* @returns {Promise<import('h3').EventHandler>}
|
const BASE_URL = 'https://appwrite.io';
|
||||||
*/
|
const BASE_DIR = dirname(fileURLToPath(import.meta.url));
|
||||||
export async function sitemap() {
|
|
||||||
|
const SITEMAP_DIR = join(BASE_DIR, './sitemaps');
|
||||||
|
const THREADS_DIR = join(SITEMAP_DIR, 'threads');
|
||||||
|
const NAMED_GROUPS = {
|
||||||
|
blog: '/blog',
|
||||||
|
docs: '/docs',
|
||||||
|
integrations: '/integrations'
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function sitemaps() {
|
||||||
console.info('Preparing Sitemap...');
|
console.info('Preparing Sitemap...');
|
||||||
const { manifest } = await import('../build/server/manifest.js');
|
const { manifest } = await import('../build/server/manifest.js');
|
||||||
const sveltekit_routes = manifest._.routes
|
const threads = collectThreads().map((id) => `/threads/${id}`);
|
||||||
.filter((route) => route.params.length === 0)
|
const otherRoutes = manifest._.routes
|
||||||
.map((route) => route.id);
|
.filter((r) => r.params.length === 0)
|
||||||
const threads = collectThreads();
|
.map((r) => r.id)
|
||||||
const all_routes = [...sveltekit_routes, ...threads];
|
.filter(
|
||||||
const document_routes = all_routes.filter(
|
(id) => !id.startsWith('/threads/') && !id.endsWith('.json') && !id.endsWith('.xml')
|
||||||
(route) => !['.json', '.xml'].some((ext) => route.endsWith(ext))
|
);
|
||||||
);
|
|
||||||
const routes = new Set(document_routes);
|
|
||||||
console.info(`Sitemap loaded with ${routes.length} routes!`);
|
|
||||||
console.group();
|
|
||||||
console.info(`sveltekit: ${sveltekit_routes.length}`);
|
|
||||||
console.info(`threads: ${threads.length}`);
|
|
||||||
console.groupEnd();
|
|
||||||
|
|
||||||
const sitemap = `
|
mkdirSync(SITEMAP_DIR, { recursive: true });
|
||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
mkdirSync(THREADS_DIR, { recursive: true });
|
||||||
<urlset
|
|
||||||
xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
|
let totalCount = 0;
|
||||||
xmlns:xhtml="https://www.w3.org/1999/xhtml"
|
const sitemapIndexOrder = [];
|
||||||
xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"
|
|
||||||
xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"
|
const grouped = {},
|
||||||
xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"
|
fallback = [];
|
||||||
xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"
|
|
||||||
>
|
for (const route of otherRoutes) {
|
||||||
${[...routes]
|
const match = Object.entries(NAMED_GROUPS).find(([, prefix]) => route.startsWith(prefix));
|
||||||
|
if (match) {
|
||||||
|
const [group] = match;
|
||||||
|
grouped[group] ??= [];
|
||||||
|
grouped[group].push(route);
|
||||||
|
} else fallback.push(route);
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCount += writeSitemap('pages.xml', fallback, SITEMAP_DIR);
|
||||||
|
sitemapIndexOrder.push('pages.xml');
|
||||||
|
|
||||||
|
for (const group of ['docs', 'blog', 'integrations']) {
|
||||||
|
if (grouped[group]?.length) {
|
||||||
|
const filename = `${group}.xml`;
|
||||||
|
totalCount += writeSitemap(filename, grouped[group], SITEMAP_DIR);
|
||||||
|
sitemapIndexOrder.push(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const threadChunks = chunkArray(threads, MAX_THREADS_PER_FILE);
|
||||||
|
threadChunks.forEach((chunk, i) => {
|
||||||
|
const filename = `${i + 1}.xml`;
|
||||||
|
totalCount += writeSitemap(filename, chunk, THREADS_DIR);
|
||||||
|
sitemapIndexOrder.push(`threads/${filename}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const sitemapIndex = `
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<sitemapindex xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
${sitemapIndexOrder
|
||||||
.map(
|
.map(
|
||||||
(route) => `<url>
|
(name) => `
|
||||||
<loc>https://appwrite.io${route}</loc>
|
<sitemap>
|
||||||
</url>
|
<loc>${BASE_URL}/sitemaps/${name}</loc>
|
||||||
`
|
</sitemap>`
|
||||||
)
|
)
|
||||||
.join('')}
|
.join('\n')}
|
||||||
</urlset>`.trim();
|
</sitemapindex>`.trim();
|
||||||
|
|
||||||
return defineEventHandler((event) => {
|
console.info(`✅ Sitemap generation complete — ${totalCount} URLs in total.\n`);
|
||||||
setResponseHeader(event, 'Content-Type', 'application/xml');
|
|
||||||
|
|
||||||
return sitemap;
|
return defineEventHandler(async (event) => {
|
||||||
|
const url = getRequestURL(event);
|
||||||
|
|
||||||
|
if (url.pathname === '/sitemap.xml') {
|
||||||
|
setResponseHeader(event, 'Content-Type', 'application/xml');
|
||||||
|
return sitemapIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === '/sitemaps') {
|
||||||
|
return sendRedirect(event, '/sitemap.xml', 307);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.pathname === '/sitemaps/threads') {
|
||||||
|
return sendRedirect(event, '/sitemaps/threads/1.xml', 307);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dir = import.meta.resolve('./sitemaps');
|
||||||
|
return serveStatic(event, {
|
||||||
|
fallthrough: true,
|
||||||
|
indexNames: undefined,
|
||||||
|
getContents: (id) => readFile(new URL(dir + id)),
|
||||||
|
getMeta: async (id) => {
|
||||||
|
const stats = await stat(new URL(dir + id)).catch(() => null);
|
||||||
|
if (!stats?.isFile()) return;
|
||||||
|
return {
|
||||||
|
size: stats.size,
|
||||||
|
mtime: stats.mtimeMs
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function writeSitemap(filename, routes, dir) {
|
||||||
* @returns {string[]}
|
const body = `
|
||||||
*/
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
function collectThreads() {
|
<urlset
|
||||||
const threads = createRequire(import.meta.url)('../build/prerendered/threads/data.json');
|
xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
xmlns:xhtml="https://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"
|
||||||
|
xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"
|
||||||
|
xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"
|
||||||
|
xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"
|
||||||
|
>
|
||||||
|
${routes.map((route) => ` <url>\n <loc>${BASE_URL}${route}</loc>\n </url>`).join('\n')}
|
||||||
|
</urlset>`.trim();
|
||||||
|
|
||||||
return threads.map((id) => `/threads/${id}`);
|
const filepath = join(dir, filename);
|
||||||
|
writeFileSync(filepath, body);
|
||||||
|
|
||||||
|
const label = filepath.replace(BASE_DIR + '/sitemaps', '');
|
||||||
|
console.info(` └── Generated ${label} with ${routes.length} URLs`);
|
||||||
|
|
||||||
|
return routes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function chunkArray(arr, size) {
|
||||||
|
const chunks = [];
|
||||||
|
for (let i = 0; i < arr.length; i += size) {
|
||||||
|
chunks.push(arr.slice(i, i + size));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectThreads() {
|
||||||
|
return createRequire(import.meta.url)('../build/prerendered/threads/data.json');
|
||||||
}
|
}
|
||||||
|
|||||||
181
src/app.css
181
src/app.css
@@ -1,4 +1,5 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
@import './styles/typography.css';
|
||||||
@variant dark (&:is(.dark *));
|
@variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
@@ -6,8 +7,13 @@
|
|||||||
--color-*: initial;
|
--color-*: initial;
|
||||||
|
|
||||||
/* base */
|
/* base */
|
||||||
--color-primary: hsl(var(--color-primary));
|
--color-black: #000;
|
||||||
--color-secondary: hsl(var(--color-secondary));
|
--color-white: #fff;
|
||||||
|
--color-transparent: transparent;
|
||||||
|
|
||||||
|
/* theme */
|
||||||
|
--color-primary: var(--color-primary);
|
||||||
|
--color-secondary: var(--color-secondary);
|
||||||
--color-accent: var(--color-secondary);
|
--color-accent: var(--color-secondary);
|
||||||
--color-smooth: var(--color-smooth);
|
--color-smooth: var(--color-smooth);
|
||||||
|
|
||||||
@@ -55,9 +61,6 @@
|
|||||||
--color-accent-200: hsl(var(--color-secondary-hue), 78%, 60%, 0.32);
|
--color-accent-200: hsl(var(--color-secondary-hue), 78%, 60%, 0.32);
|
||||||
|
|
||||||
/* greyscale */
|
/* greyscale */
|
||||||
--color-white: hsl(0 0% 100%);
|
|
||||||
--color-black: hsl(0 0% 0%);
|
|
||||||
--color-transparent: rgba(0, 0, 0, 0);
|
|
||||||
--color-offset: hsl(var(--color-greyscale-hue) 2%, 11%, 0.94);
|
--color-offset: hsl(var(--color-greyscale-hue) 2%, 11%, 0.94);
|
||||||
--color-greyscale-25: hsl(var(--color-greyscale-hue) 11% 98%);
|
--color-greyscale-25: hsl(var(--color-greyscale-hue) 11% 98%);
|
||||||
--color-greyscale-50: hsl(var(--color-greyscale-hue) 11% 94%);
|
--color-greyscale-50: hsl(var(--color-greyscale-hue) 11% 94%);
|
||||||
@@ -74,34 +77,9 @@
|
|||||||
--color-greyscale-850: hsl(var(--color-greyscale-hue) 3% 14%);
|
--color-greyscale-850: hsl(var(--color-greyscale-hue) 3% 14%);
|
||||||
--color-greyscale-900: hsl(var(--color-greyscale-hue) 5.7% 10.4%);
|
--color-greyscale-900: hsl(var(--color-greyscale-hue) 5.7% 10.4%);
|
||||||
|
|
||||||
/* utility colors */
|
|
||||||
--color-badge-bg-light: #f2c8d6;
|
|
||||||
--color-badge-border-light: #f69db7;
|
|
||||||
--color-badge-bg-dark: #2c2c2f;
|
|
||||||
--color-badge-border-dark: #39393c;
|
|
||||||
|
|
||||||
/* Easings */
|
/* Easings */
|
||||||
--transition-timing-function-bounce: linear(
|
--easing-bounce: linear(0, 0.063, 0.25 18.2%, 1 36.4%, 0.813, 0.75, 0.813, 1, 0.938, 1, 1);
|
||||||
0,
|
--easing-spring: linear(0, 0.938 16.7%, 1.149 24.3%, 1.154 29.9%, 0.977 51%, 1);
|
||||||
0.063,
|
|
||||||
0.25 18.2%,
|
|
||||||
1 36.4%,
|
|
||||||
0.813,
|
|
||||||
0.75,
|
|
||||||
0.813,
|
|
||||||
1,
|
|
||||||
0.938,
|
|
||||||
1,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
--transition-timing-function-spring: linear(
|
|
||||||
0,
|
|
||||||
0.938 16.7%,
|
|
||||||
1.149 24.3%,
|
|
||||||
1.154 29.9%,
|
|
||||||
0.977 51%,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Animations */
|
/* Animations */
|
||||||
--animate-scale-in: scale-in 200ms ease-out forwards;
|
--animate-scale-in: scale-in 200ms ease-out forwards;
|
||||||
@@ -112,9 +90,6 @@
|
|||||||
--animate-fade-in: fade-in 0.5s ease-in-out both;
|
--animate-fade-in: fade-in 0.5s ease-in-out both;
|
||||||
--animate-marquee: marquee var(--speed, 30s) linear infinite var(--direction, forwards);
|
--animate-marquee: marquee var(--speed, 30s) linear infinite var(--direction, forwards);
|
||||||
|
|
||||||
/* Pink polyfills */
|
|
||||||
--transition: 0.2s;
|
|
||||||
|
|
||||||
/* Keyframes */
|
/* Keyframes */
|
||||||
@keyframes scale-in {
|
@keyframes scale-in {
|
||||||
0% {
|
0% {
|
||||||
@@ -180,62 +155,84 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Fonts */
|
/* Fonts */
|
||||||
--font-family-sans: 'Inter', arial, sans-serif;
|
--font-sans: 'Inter', arial, sans-serif;
|
||||||
--font-family-mono: 'Fira Code', monospace;
|
--font-mono: 'Fira Code', monospace;
|
||||||
--font-family-aeonik-fono: 'Aenoik Fono', monospace;
|
--font-aeonik-fono: 'Aenoik Fono', monospace;
|
||||||
--font-family-aeonik-pro: 'Aeonik Pro', var(--font-family-sans);
|
--font-aeonik-pro: 'Aeonik Pro', var(--font-sans);
|
||||||
--font-family-archia: 'Archia', arial, sans-serif;
|
--font-archia: 'Archia', arial, sans-serif;
|
||||||
|
|
||||||
/* Font sizes */
|
/* Font sizes */
|
||||||
--font-size-x-micro: 0.625rem;
|
--text-x-micro: 0.625rem;
|
||||||
--font-size-x-micro--line-height: 0.875rem;
|
--text-x-micro--line-height: 0.875rem;
|
||||||
--font-size-x-micro--letter-spacing: var(--letter-spacing-tighter);
|
--text-x-micro--letter-spacing: var(--tracking-tighter);
|
||||||
--font-size-micro: 0.75rem;
|
--text-micro: 0.75rem;
|
||||||
--font-size-micro--line-height: 1rem;
|
--text-micro--line-height: 1rem;
|
||||||
--font-size-micro--letter-spacing: var(--letter-spacing-tighter);
|
--text-micro--letter-spacing: var(--tracking-tighter);
|
||||||
--font-size-caption: 0.875rem;
|
--text-caption: 0.875rem;
|
||||||
--font-size-caption--line-height: 1.375rem;
|
--text-caption--line-height: 1.375rem;
|
||||||
--font-size-caption--letter-spacing: var(--letter-spacing-tight);
|
--text-caption--letter-spacing: var(--tracking-tight);
|
||||||
--font-size-sub-body: clamp(0.875rem, 2vw, 1rem);
|
--text-sub-body: clamp(0.875rem, 2vw, 1rem);
|
||||||
--font-size-sub-body--line-height: 1.375rem;
|
--text-sub-body--line-height: 1.375rem;
|
||||||
--font-size-sub-body--letter-spacing: var(--letter-spacing-tight);
|
--text-sub-body--letter-spacing: var(--tracking-tight);
|
||||||
--font-size-body: clamp(1rem, 2.5vw, 1.125rem);
|
--text-body: clamp(1rem, 2.5vw, 1.125rem);
|
||||||
--font-size-body--line-height: clamp(1.375rem, 3vw, 1.625rem);
|
--text-body--line-height: clamp(1.375rem, 3vw, 1.625rem);
|
||||||
--font-size-body--letter-spacing: var(--letter-spacing-tight);
|
--text-body--letter-spacing: var(--tracking-tight);
|
||||||
--font-size-paragraph-md: 1rem;
|
--text-paragraph-md: 1rem;
|
||||||
--font-size-paragraph-md--line-height: 1.625rem;
|
--text-paragraph-md--line-height: 1.625rem;
|
||||||
--font-size-paragraph-md--letter-spacing: var(--letter-spacing-tight);
|
--text-paragraph-md--letter-spacing: var(--tracking-tight);
|
||||||
--font-size-paragraph-lg: 1.125rem;
|
--text-paragraph-lg: 1.125rem;
|
||||||
--font-size-paragraph-lg--line-height: 1.75rem;
|
--text-paragraph-lg--line-height: 1.75rem;
|
||||||
--font-size-paragraph-lg--letter-spacing: var(--letter-spacing-tight);
|
--text-paragraph-lg--letter-spacing: var(--tracking-tight);
|
||||||
--font-size-description: clamp(1.125rem, 3vw, 1.25rem);
|
--text-description: clamp(1.125rem, 3vw, 1.25rem);
|
||||||
--font-size-description--line-height: clamp(1.625rem, 3.5vw, 1.75rem);
|
--text-description--line-height: clamp(1.625rem, 3.5vw, 1.75rem);
|
||||||
--font-size-description--letter-spacing: var(--letter-spacing-tighter);
|
--text-description--letter-spacing: var(--tracking-tighter);
|
||||||
--font-size-label: 1.5rem;
|
--text-label: 1.5rem;
|
||||||
--font-size-label--line-height: 1.75rem;
|
--text-label--line-height: 1.75rem;
|
||||||
--font-size-title: clamp(2rem, 5vw, 2.5rem);
|
--text-title: clamp(2rem, 5vw, 2.5rem);
|
||||||
--font-size-title--line-height: clamp(2.125rem, 5.5vw, 2.75rem);
|
--text-title--line-height: clamp(2.125rem, 5.5vw, 2.75rem);
|
||||||
--font-size-title--letter-spacing: var(--letter-spacing-squeezed);
|
--text-title--letter-spacing: var(--tracking-squeezed);
|
||||||
--font-size-display: clamp(3rem, 7vw, 4rem);
|
--text-display: clamp(3rem, 7vw, 4rem);
|
||||||
--font-size-display--line-height: clamp(3.125rem, 7.5vw, 4.25rem);
|
--text-display--line-height: clamp(3.125rem, 7.5vw, 4.25rem);
|
||||||
--font-size-display--letter-spacing: var(--letter-spacing-compressed);
|
--text-display--letter-spacing: var(--tracking-compressed);
|
||||||
--font-size-headline: clamp(3.5rem, 8vw, 5.5rem);
|
--text-headline: clamp(3.5rem, 8vw, 5.5rem);
|
||||||
--font-size-headline--line-height: clamp(3.5rem, 8.5vw, 5.75rem);
|
--text-headline--line-height: clamp(3.5rem, 8.5vw, 5.75rem);
|
||||||
--font-size-headline--letter-spacing: var(--letter-spacing-compressed);
|
--text-headline--letter-spacing: var(--tracking-compressed);
|
||||||
|
|
||||||
/* Letter spacing */
|
/* Letter spacing */
|
||||||
--letter-spacing-*: initial;
|
--tracking-*: initial;
|
||||||
--letter-spacing-compressed: -0.022em;
|
--tracking-compressed: -0.022em;
|
||||||
--letter-spacing-squeezed: -0.01em;
|
--tracking-squeezed: -0.01em;
|
||||||
--letter-spacing-tighter: -0.018em;
|
--tracking-tighter: -0.018em;
|
||||||
--letter-spacing-tight: -0.0045em;
|
--tracking-tight: -0.0045em;
|
||||||
--letter-spacing-none: 0em;
|
--tracking-none: 0em;
|
||||||
--letter-spacing-loose: 0.08em;
|
--tracking-loose: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility container {
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: calc(var(--spacing) * 5);
|
||||||
|
box-sizing: box-content;
|
||||||
|
max-width: 75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.mask {
|
||||||
|
mask-image: linear-gradient(
|
||||||
|
to var(--mask-direction, top),
|
||||||
|
transparent,
|
||||||
|
black var(--mask-height, 32px),
|
||||||
|
black calc(100% - var(--mask-height, 32px)),
|
||||||
|
black
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Themes */
|
|
||||||
:root,
|
:root,
|
||||||
.light {
|
.light {
|
||||||
|
/* pink polyfills */
|
||||||
|
--transition: 0.2s;
|
||||||
|
|
||||||
|
/* color hues */
|
||||||
--color-pink-hue: 343;
|
--color-pink-hue: 343;
|
||||||
--color-secondary-hue: 351;
|
--color-secondary-hue: 351;
|
||||||
--color-red-hue: 3;
|
--color-red-hue: 3;
|
||||||
@@ -255,7 +252,6 @@
|
|||||||
--color-smooth: hsl(var(--color-greyscale-hue) 6%, 10%, 0.04);
|
--color-smooth: hsl(var(--color-greyscale-hue) 6%, 10%, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* dark theme */
|
|
||||||
.dark {
|
.dark {
|
||||||
--color-primary: var(--color-greyscale-100);
|
--color-primary: var(--color-greyscale-100);
|
||||||
--color-secondary: var(--color-greyscale-300);
|
--color-secondary: var(--color-greyscale-300);
|
||||||
@@ -263,20 +259,3 @@
|
|||||||
--color-badge-border: var(--color-badge-border-dark);
|
--color-badge-border: var(--color-badge-border-dark);
|
||||||
--color-smooth: hsl(0 0%, 100%, 0.06);
|
--color-smooth: hsl(0 0%, 100%, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Container */
|
|
||||||
@layer components {
|
|
||||||
.container {
|
|
||||||
@apply mx-auto box-content max-w-[75rem] px-5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mask {
|
|
||||||
mask-image: linear-gradient(
|
|
||||||
to var(--mask-direction, top),
|
|
||||||
transparent,
|
|
||||||
black var(--mask-height, 32px),
|
|
||||||
black calc(100% - var(--mask-height, 32px)),
|
|
||||||
black
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { dev } from '$app/environment';
|
|
||||||
import { SENTRY_DSN } from '$lib/constants';
|
|
||||||
import { handleErrorWithSentry } from '@sentry/sveltekit';
|
|
||||||
import * as Sentry from '@sentry/sveltekit';
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
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,
|
|
||||||
|
|
||||||
// If the entire session is not sampled, use the below sample rate to sample
|
|
||||||
// sessions when an error occurs.
|
|
||||||
replaysOnErrorSampleRate: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// If you have a custom error handler, pass it to `handleErrorWithSentry`
|
|
||||||
export const handleError = handleErrorWithSentry();
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
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/]
|
|
||||||
});
|
|
||||||
|
|
||||||
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) ?? ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return await resolve(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
const securityheaders: Handle = async ({ event, resolve }) => {
|
|
||||||
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
|
|
||||||
(event.locals as { nonce: string }).nonce = nonce;
|
|
||||||
|
|
||||||
const response = await resolve(event, {
|
|
||||||
transformPageChunk: ({ html }) => {
|
|
||||||
return html.replace(/%sveltekit.nonce%/g, nonce);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// `true` if deployed via Coolify.
|
|
||||||
const isPreview = !!process.env.COOLIFY_FQDN;
|
|
||||||
// COOLIFY_FQDN already includes `http`.
|
|
||||||
const previewDomain = isPreview ? `${process.env.COOLIFY_FQDN}` : null;
|
|
||||||
const join = (arr: string[]) => arr.join(' ');
|
|
||||||
|
|
||||||
const cspDirectives: Record<string, string> = {
|
|
||||||
'default-src': "'self'",
|
|
||||||
'script-src': join([
|
|
||||||
"'self'",
|
|
||||||
'blob:',
|
|
||||||
"'unsafe-inline'",
|
|
||||||
"'unsafe-eval'",
|
|
||||||
'https://*.posthog.com',
|
|
||||||
'https://*.plausible.io',
|
|
||||||
'https://*.reo.dev',
|
|
||||||
'https://plausible.io',
|
|
||||||
'https://js.zi-scripts.com',
|
|
||||||
'https://ws.zoominfo.com'
|
|
||||||
]),
|
|
||||||
'style-src': "'self' 'unsafe-inline'",
|
|
||||||
'img-src': "'self' data: https:",
|
|
||||||
'font-src': "'self'",
|
|
||||||
'object-src': "'none'",
|
|
||||||
'base-uri': "'self'",
|
|
||||||
'form-action': "'self'",
|
|
||||||
'frame-ancestors': join(["'self'", 'https://www.youtube.com', 'https://*.vimeo.com']),
|
|
||||||
'block-all-mixed-content': '',
|
|
||||||
'upgrade-insecure-requests': '',
|
|
||||||
'connect-src': join([
|
|
||||||
"'self'",
|
|
||||||
'https://*.appwrite.io',
|
|
||||||
'https://*.appwrite.org',
|
|
||||||
'https://*.posthog.com',
|
|
||||||
'https://*.sentry.io',
|
|
||||||
'https://*.plausible.io',
|
|
||||||
'https://plausible.io',
|
|
||||||
'https://*.reo.dev',
|
|
||||||
'https://js.zi-scripts.com',
|
|
||||||
'https://aorta.clickagy.com',
|
|
||||||
'https://hemsync.clickagy.com',
|
|
||||||
'https://ws.zoominfo.com '
|
|
||||||
]),
|
|
||||||
'frame-src': join([
|
|
||||||
"'self'",
|
|
||||||
'https://www.youtube.com',
|
|
||||||
'https://status.appwrite.online',
|
|
||||||
'https://www.youtube-nocookie.com',
|
|
||||||
'https://player.vimeo.com',
|
|
||||||
'https://hemsync.clickagy.com'
|
|
||||||
])
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isPreview) {
|
|
||||||
delete cspDirectives['block-all-mixed-content'];
|
|
||||||
delete cspDirectives['upgrade-insecure-requests'];
|
|
||||||
['default-src', 'script-src', 'style-src', 'img-src', 'font-src', 'connect-src'].forEach(
|
|
||||||
(key) => {
|
|
||||||
cspDirectives[key] += ` ${previewDomain}`;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cspDirectivesString = Object.entries(cspDirectives)
|
|
||||||
.map(([key, value]) => `${key} ${value}`.trim())
|
|
||||||
.join('; ');
|
|
||||||
|
|
||||||
// Set security headers
|
|
||||||
response.headers.set('Content-Security-Policy', cspDirectivesString);
|
|
||||||
|
|
||||||
// HTTP Strict Transport Security
|
|
||||||
// max-age is set to 1 year in seconds
|
|
||||||
response.headers.set(
|
|
||||||
'Strict-Transport-Security',
|
|
||||||
'max-age=31536000; includeSubDomains; preload'
|
|
||||||
);
|
|
||||||
|
|
||||||
// X-Content-Type-Options
|
|
||||||
response.headers.set('X-Content-Type-Options', 'nosniff');
|
|
||||||
|
|
||||||
// X-Frame-Options
|
|
||||||
response.headers.set('X-Frame-Options', 'DENY');
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
const bannerRewriter: Handle = async ({ event, resolve }) => {
|
|
||||||
const response = await resolve(event, {
|
|
||||||
transformPageChunk: ({ html }) => html.replace('%aw_banner_key%', BANNER_KEY)
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handle = sequence(Sentry.sentryHandle(), redirecter, bannerRewriter, securityheaders);
|
|
||||||
export const handleError = Sentry.handleErrorWithSentry();
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
class="true-body"
|
class="true-body"
|
||||||
style:width={`${$bodyRect?.width ?? 0}px`}
|
style:width={`${$bodyRect?.width ?? 0}px`}
|
||||||
style:height={`${$bodyRect?.height ?? 0}px`}
|
style:height={`${$bodyRect?.height ?? 0}px`}
|
||||||
/>
|
></div>
|
||||||
<div class="body" use:rect={bodyRect}>
|
<div class="body" use:rect={bodyRect}>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,16 +5,16 @@
|
|||||||
|
|
||||||
<div class="code-console">
|
<div class="code-console">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="ellipse" />
|
<div class="ellipse"></div>
|
||||||
<div class="ellipse-2" />
|
<div class="ellipse-2"></div>
|
||||||
<div class="ellipse-3" />
|
<div class="ellipse-3"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<AutoBox>
|
<AutoBox>
|
||||||
<slot {Code} />
|
<slot {Code} />
|
||||||
</AutoBox>
|
</AutoBox>
|
||||||
</div>
|
</div>
|
||||||
<div id="code-bottom" />
|
<div id="code-bottom"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -191,7 +191,7 @@
|
|||||||
class="web-icon-discord web-u-font-size-40"
|
class="web-icon-discord web-u-font-size-40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label="Discord"
|
aria-label="Discord"
|
||||||
/>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-title font-aeonik-pro mt-auto">17k+ Discord Members</div>
|
<div class="text-title font-aeonik-pro mt-auto">17k+ Discord Members</div>
|
||||||
</a>
|
</a>
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
class="web-icon-github web-u-font-size-40"
|
class="web-icon-github web-u-font-size-40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label="GitHub"
|
aria-label="GitHub"
|
||||||
/>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-title font-aeonik-pro mt-auto">
|
<div class="text-title font-aeonik-pro mt-auto">
|
||||||
{GITHUB_STARS}+ GitHub Stars
|
{GITHUB_STARS}+ GitHub Stars
|
||||||
@@ -223,7 +223,7 @@
|
|||||||
class="web-icon-x web-u-font-size-40"
|
class="web-icon-x web-u-font-size-40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label="Twitter"
|
aria-label="Twitter"
|
||||||
/>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-title font-aeonik-pro mt-auto">128k+ Twitter Followers</div>
|
<div class="text-title font-aeonik-pro mt-auto">128k+ Twitter Followers</div>
|
||||||
</a>
|
</a>
|
||||||
@@ -238,7 +238,7 @@
|
|||||||
class="web-icon-youtube web-u-font-size-40"
|
class="web-icon-youtube web-u-font-size-40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label="YouTube"
|
aria-label="YouTube"
|
||||||
/>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-title font-aeonik-pro mt-auto">7k+ Youtube Subscribers</div>
|
<div class="text-title font-aeonik-pro mt-auto">7k+ Youtube Subscribers</div>
|
||||||
</a>
|
</a>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
class="web-icon-github web-u-font-size-40"
|
class="web-icon-github web-u-font-size-40"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label="GitHub"
|
aria-label="GitHub"
|
||||||
/>
|
></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-title font-aeonik-pro mt-auto">21k+ Code Commits</div>
|
<div class="text-title font-aeonik-pro mt-auto">21k+ Code Commits</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -237,7 +237,7 @@
|
|||||||
>
|
>
|
||||||
{#if scrollInfo.percentage > -0.1}
|
{#if scrollInfo.percentage > -0.1}
|
||||||
<span
|
<span
|
||||||
class="web-badges text-micro uppercase !text-white"
|
class="web-badges text-micro !text-white uppercase"
|
||||||
transition:slide={{ axis: 'x' }}>Products_</span
|
transition:slide={{ axis: 'x' }}>Products_</span
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<div class="outside">
|
<div class="outside">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<span class="web-badges text-micro uppercase !text-white">Products_</span>
|
<span class="web-badges text-micro !text-white uppercase">Products_</span>
|
||||||
|
|
||||||
<h2 class="text-display font-aeonik-pro text-primary mt-4">
|
<h2 class="text-display font-aeonik-pro text-primary mt-4">
|
||||||
Your backend, minus the hassle
|
Your backend, minus the hassle
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="img-overlay" />
|
<div class="img-overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<button use:melt={$root} class="anim-checkbox">
|
<button use:melt={$root} class="anim-checkbox">
|
||||||
{#if $isChecked}
|
{#if $isChecked}
|
||||||
<span class="web-icon-check" />
|
<span class="web-icon-check"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
{#each objectKeys($state.controls) as provider, i}
|
{#each objectKeys($state.controls) as provider, i}
|
||||||
{@const isLast = i === objectKeys($state.controls).length - 1}
|
{@const isLast = i === objectKeys($state.controls).length - 1}
|
||||||
<div>
|
<div>
|
||||||
<span class={getIcon(provider)} />
|
<span class={getIcon(provider)}></span>
|
||||||
<span>{provider}</span>
|
<span>{provider}</span>
|
||||||
<Switch bind:checked={$state.controls[provider]} />
|
<Switch bind:checked={$state.controls[provider]} />
|
||||||
</div>
|
</div>
|
||||||
{#if !isLast}
|
{#if !isLast}
|
||||||
<div class="sep" />
|
<div class="sep"></div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
animate:flip={{ duration: 250 }}
|
animate:flip={{ duration: 250 }}
|
||||||
>
|
>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<span class="web-icon-{provider.toLowerCase()}" />
|
<span class="web-icon-{provider.toLowerCase()}"></span>
|
||||||
<span>{provider}</span>
|
<span>{provider}</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
{#each $state.tasks.slice(0, $state.tableSlice) as task (task.id)}
|
{#each $state.tasks.slice(0, $state.tableSlice) as task (task.id)}
|
||||||
<div class="row" transition:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
|
<div class="row" transition:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
|
||||||
<div class="copy-button">
|
<div class="copy-button">
|
||||||
<span class="web-icon-copy" />
|
<span class="web-icon-copy"></span>
|
||||||
<span>{task.id}</span>
|
<span>{task.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="truncated">{task.title}</span>
|
<span class="truncated">{task.title}</span>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div data-theme-ignore class="inner-phone light">
|
<div data-theme-ignore class="inner-phone light">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<p class="title">Your tasks</p>
|
<p class="title">Your tasks</p>
|
||||||
<span class="icon-menu" aria-label="menu" />
|
<span class="icon-menu" aria-label="menu"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="date">Today</div>
|
<div class="date">Today</div>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="add-btn">
|
<button class="add-btn">
|
||||||
<span class="web-icon-plus" />
|
<span class="web-icon-plus"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ return res.json({ success: true });`.trim();
|
|||||||
|
|
||||||
<div use:portal={{ target: '#code-bottom' }} class="bottom">
|
<div use:portal={{ target: '#code-bottom' }} class="bottom">
|
||||||
{#if $state.submit !== 'idle'}
|
{#if $state.submit !== 'idle'}
|
||||||
<span class="web-icon-github" in:fade />
|
<span class="web-icon-github" in:fade></span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $state.submit === 'loading'}
|
{#if $state.submit === 'loading'}
|
||||||
<span in:fade>Pushing to GitHub...</span>
|
<span in:fade>Pushing to GitHub...</span>
|
||||||
<div class="loader is-small" in:fade />
|
<div class="loader is-small" in:fade></div>
|
||||||
{:else if $state.submit === 'success'}
|
{:else if $state.submit === 'success'}
|
||||||
<span>Deployed to Appwrite Cloud</span>
|
<span>Deployed to Appwrite Cloud</span>
|
||||||
<span class="web-icon-check" />
|
<span class="web-icon-check"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<div data-theme-ignore class="inner-phone light">
|
<div data-theme-ignore class="inner-phone light">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<p class="title">Upgrade plan</p>
|
<p class="title">Upgrade plan</p>
|
||||||
<span class="icon-menu" aria-label="menu" />
|
<span class="icon-menu" aria-label="menu"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="plan">
|
<div class="plan">
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
{#each $state.messages.slice(0, $state.tableSlice) as task (task.id)}
|
{#each $state.messages.slice(0, $state.tableSlice) as task (task.id)}
|
||||||
<div class="row" transition:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
|
<div class="row" transition:slide={{ duration: 150 }} animate:flip={{ duration: 150 }}>
|
||||||
<div class="copy-button">
|
<div class="copy-button">
|
||||||
<span class="web-icon-copy" />
|
<span class="web-icon-copy"></span>
|
||||||
<span>{task.id}</span>
|
<span>{task.id}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon-button">
|
<div class="icon-button">
|
||||||
@@ -27,9 +27,9 @@
|
|||||||
|
|
||||||
<div class="status-indicator">
|
<div class="status-indicator">
|
||||||
{#if task.status === 'sending'}
|
{#if task.status === 'sending'}
|
||||||
<div class="loader is-small" in:fade />
|
<div class="loader is-small" in:fade></div>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="web-icon-check" />
|
<span class="web-icon-check"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
{#if $state.submit === 'success'}
|
{#if $state.submit === 'success'}
|
||||||
<div class="push-notification" in:fly={{ y: -20 }}>
|
<div class="push-notification" in:fly={{ y: -20 }}>
|
||||||
<div class="icon" />
|
<div class="icon"></div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h3 class="title">New task assigned to you</h3>
|
<h3 class="title">New task assigned to you</h3>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<div data-theme-ignore class="inner-phone light">
|
<div data-theme-ignore class="inner-phone light">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<p class="title">Your tasks</p>
|
<p class="title">Your tasks</p>
|
||||||
<span class="icon-menu" aria-label="menu" />
|
<span class="icon-menu" aria-label="menu"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="date">Today</div>
|
<div class="date">Today</div>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<button class="add-btn">
|
<button class="add-btn">
|
||||||
<span class="web-icon-plus" />
|
<span class="web-icon-plus"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<div class="gradient-box auth" id="post-auth-{$elId}">
|
<div class="gradient-box auth" id="post-auth-{$elId}">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<p class="icon-user-group" />
|
<p class="icon-user-group"></p>
|
||||||
<p class="f-eyebrow">Authentication</p>
|
<p class="f-eyebrow">Authentication</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="f-display mbs-16">
|
<p class="f-display mbs-16">
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
<div class="gradient-box storage" id="post-storage-{$elId}">
|
<div class="gradient-box storage" id="post-storage-{$elId}">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<p class="icon-folder" />
|
<p class="icon-folder"></p>
|
||||||
<p class="f-eyebrow">Storage</p>
|
<p class="f-eyebrow">Storage</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="f-display mbs-16">
|
<p class="f-display mbs-16">
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
|
|
||||||
<div class="gradient-box functions" id="post-functions-{$elId}">
|
<div class="gradient-box functions" id="post-functions-{$elId}">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<p class="icon-lightning-bolt" />
|
<p class="icon-lightning-bolt"></p>
|
||||||
<p class="f-eyebrow">Functions</p>
|
<p class="f-eyebrow">Functions</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="f-display mbs-16">
|
<p class="f-display mbs-16">
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
<div class="gradient-box databases" id="post-databases-{$elId}">
|
<div class="gradient-box databases" id="post-databases-{$elId}">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<p class="icon-database" />
|
<p class="icon-database"></p>
|
||||||
<p class="f-eyebrow">Databases</p>
|
<p class="f-eyebrow">Databases</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="f-display mbs-16">
|
<p class="f-display mbs-16">
|
||||||
|
|||||||
@@ -55,13 +55,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="vertical-sep" />
|
<div class="vertical-sep"></div>
|
||||||
<span class="icon-menu" />
|
<span class="icon-menu"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<span class="web-icon-search" />
|
<span class="web-icon-search"></span>
|
||||||
<span class="text"> Search </span>
|
<span class="text"> Search </span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flow gap-8">
|
<div class="flow gap-8">
|
||||||
@@ -81,11 +81,11 @@
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
<span class="text capitalize">{col}</span>
|
<span class="text capitalize">{col}</span>
|
||||||
<span class="tgl-inline-tag">{tasks.length}</span>
|
<span class="tgl-inline-tag">{tasks.length}</span>
|
||||||
<span class="icon-dots-horizontal" />
|
<span class="icon-dots-horizontal"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flow-v mbs-8 gap-12">
|
<div class="flow-v mbs-8 gap-12">
|
||||||
<button class="dashed-btn" id="add-{col}-{$elId}">
|
<button class="dashed-btn" id="add-{col}-{$elId}">
|
||||||
<span class="icon-plus" />
|
<span class="icon-plus"></span>
|
||||||
<span class="text">New Task</span>
|
<span class="text">New Task</span>
|
||||||
</button>
|
</button>
|
||||||
{#each tasks as task (task.title)}
|
{#each tasks as task (task.title)}
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if !isLast}
|
{#if !isLast}
|
||||||
<div class="vertical-sep" />
|
<div class="vertical-sep"></div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div data-theme-ignore class="inner-phone light">
|
<div data-theme-ignore class="inner-phone light">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<p class="title">Your tasks</p>
|
<p class="title">Your tasks</p>
|
||||||
<span class="icon-menu" aria-label="menu" />
|
<span class="icon-menu" aria-label="menu"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="date">Today</div>
|
<div class="date">Today</div>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="add-btn">
|
<button class="add-btn">
|
||||||
<span class="web-icon-plus" />
|
<span class="web-icon-plus"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="overlay" id="overlay-{$elId}">
|
<div class="overlay" id="overlay-{$elId}">
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
<div class="drop-zone">
|
<div class="drop-zone">
|
||||||
<span id="upload-text-{$elId}"> Drop media here </span>
|
<span id="upload-text-{$elId}"> Drop media here </span>
|
||||||
<div class="loading-overlay" id="upload-loading-{$elId}">
|
<div class="loading-overlay" id="upload-loading-{$elId}">
|
||||||
<div class="loader" />
|
<div class="loader"></div>
|
||||||
</div>
|
</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="" />
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
style:--y={`${y}px`}
|
style:--y={`${y}px`}
|
||||||
style:--percentage={`${easedPercentage * 100}%`}
|
style:--percentage={`${easedPercentage * 100}%`}
|
||||||
>
|
>
|
||||||
<div class="absolute -top-[8px] left-1/2" />
|
<div class="absolute -top-[8px] left-1/2"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
>
|
>
|
||||||
<span class="text-primary text-sub-body font-medium">{title}</span>
|
<span class="text-primary text-sub-body font-medium">{title}</span>
|
||||||
<div class="icon text-primary transition-transform group-[&[open]]:rotate-180">
|
<div class="icon text-primary transition-transform group-[&[open]]:rotate-180">
|
||||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
<span class="icon-cheveron-down" aria-hidden="true"></span>
|
||||||
</div>
|
</div>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="collapsible-content text-secondary text-sub-body flex flex-col">
|
<div class="collapsible-content text-secondary text-sub-body flex flex-col">
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
aria-label="close discord message"
|
aria-label="close discord message"
|
||||||
on:click={hideTopBanner}
|
on:click={hideTopBanner}
|
||||||
>
|
>
|
||||||
<span class="web-icon-close" aria-hidden="true" />
|
<span class="web-icon-close" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,13 +22,13 @@
|
|||||||
class="web-button cursor-pointer transition-opacity hover:opacity-90 active:scale-95"
|
class="web-button cursor-pointer transition-opacity hover:opacity-90 active:scale-95"
|
||||||
style:box-shadow="0 2px 40px rgba(0, 0, 0, 0.5)"
|
style:box-shadow="0 2px 40px rgba(0, 0, 0, 0.5)"
|
||||||
>
|
>
|
||||||
<span class="web-icon-play" />
|
<span class="web-icon-play"></span>
|
||||||
<span>Appwrite in 100 seconds</span>
|
<span>Appwrite in 100 seconds</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{#if $open}
|
{#if $open}
|
||||||
<div use:melt={$portalled}>
|
<div use:melt={$portalled}>
|
||||||
<div use:melt={$overlay} class="overlay" transition:fade={{ duration: 150 }} />
|
<div use:melt={$overlay} class="overlay" transition:fade={{ duration: 150 }}></div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="web-media content"
|
class="web-media content"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
frameborder="0"
|
frameborder="0"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
allowfullscreen
|
allowfullscreen
|
||||||
/>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
let carousel: HTMLElement;
|
let carousel: HTMLElement;
|
||||||
|
|
||||||
export let size: 'default' | 'medium' | 'big' = 'default';
|
interface Props {
|
||||||
export let gap = 32;
|
size?: 'default' | 'medium' | 'big';
|
||||||
|
gap?: number;
|
||||||
|
header?: Snippet;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { size = 'default', gap = 32, header, children }: Props = $props();
|
||||||
let scroll = 0;
|
let scroll = 0;
|
||||||
|
|
||||||
function calculateScrollAmount(prev = false) {
|
function calculateScrollAmount(prev = false) {
|
||||||
@@ -32,8 +40,8 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let isEnd = false;
|
let isEnd = $state(false);
|
||||||
let isStart = true;
|
let isStart = $state(true);
|
||||||
|
|
||||||
function handleScroll() {
|
function handleScroll() {
|
||||||
isStart = carousel.scrollLeft <= 0;
|
isStart = carousel.scrollLeft <= 0;
|
||||||
@@ -43,23 +51,25 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="mt-2 flex flex-wrap items-center">
|
<div class="mt-2 flex flex-wrap items-center">
|
||||||
<slot name="header" />
|
{#if header}
|
||||||
|
{@render header()}
|
||||||
|
{/if}
|
||||||
<div class="nav ml-auto flex items-end gap-3">
|
<div class="nav ml-auto flex items-end gap-3">
|
||||||
<button
|
<button
|
||||||
class="web-icon-button"
|
class="web-icon-button"
|
||||||
aria-label="Move carousel backward"
|
aria-label="Move carousel backward"
|
||||||
disabled={isStart}
|
disabled={isStart}
|
||||||
on:click={prev}
|
onclick={prev}
|
||||||
>
|
>
|
||||||
<span class="web-icon-arrow-left" aria-hidden="true" />
|
<span class="web-icon-arrow-left" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="web-icon-button"
|
class="web-icon-button"
|
||||||
aria-label="Move carousel forward"
|
aria-label="Move carousel forward"
|
||||||
disabled={isEnd}
|
disabled={isEnd}
|
||||||
on:click={next}
|
onclick={next}
|
||||||
>
|
>
|
||||||
<span class="web-icon-arrow-right" aria-hidden="true" />
|
<span class="web-icon-arrow-right" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,9 +81,9 @@
|
|||||||
class:is-big={size === 'big'}
|
class:is-big={size === 'big'}
|
||||||
style:gap="{gap}px"
|
style:gap="{gap}px"
|
||||||
bind:this={carousel}
|
bind:this={carousel}
|
||||||
on:scroll={handleScroll}
|
onscroll={handleScroll}
|
||||||
>
|
>
|
||||||
<slot />
|
{@render children()}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
feedbackType = 'positive';
|
feedbackType = 'positive';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="icon-thumb-up" />
|
<span class="icon-thumb-up"></span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="web-radio-button"
|
class="web-radio-button"
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<!-- TODO: fix the icon name on pink -->
|
<!-- TODO: fix the icon name on pink -->
|
||||||
<span class="icon-thumb-dowm" />
|
<span class="icon-thumb-dowm"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="web-link flex items-baseline gap-1"
|
class="web-link flex items-baseline gap-1"
|
||||||
>
|
>
|
||||||
<span class="icon-pencil-alt contents" aria-hidden="true" />
|
<span class="icon-pencil-alt contents" aria-hidden="true"></span>
|
||||||
<span>Update on GitHub</span>
|
<span>Update on GitHub</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
id="message"
|
id="message"
|
||||||
placeholder="Write your message"
|
placeholder="Write your message"
|
||||||
bind:value={comment}
|
bind:value={comment}
|
||||||
/>
|
></textarea>
|
||||||
<label for="message" class="mt-2">
|
<label for="message" class="mt-2">
|
||||||
<span class="text-primary">Email</span>
|
<span class="text-primary">Email</span>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
class="web-icon-chevron-down web-u-transition"
|
class="web-icon-chevron-down web-u-transition"
|
||||||
class:web-u-rotate-180={$isSelected(title)}
|
class:web-u-rotate-180={$isSelected(title)}
|
||||||
style:font-size="1rem"
|
style:font-size="1rem"
|
||||||
/>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
</h5>
|
</h5>
|
||||||
{#if $isSelected(title)}
|
{#if $isSelected(title)}
|
||||||
|
|||||||
@@ -39,16 +39,16 @@
|
|||||||
> has started
|
> has started
|
||||||
</span>
|
</span>
|
||||||
<span class="web-u-color-text-secondary">The start of something new</span>
|
<span class="web-u-color-text-secondary">The start of something new</span>
|
||||||
<div class="shadow" />
|
<div class="shadow"></div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/init" rel="noopener noreferrer" class="action">
|
<a href="/init" rel="noopener noreferrer" class="action">
|
||||||
<span class="text-caption font-medium">Join now</span>
|
<span class="text-caption font-medium">Join now</span>
|
||||||
<span class="web-icon-arrow-right" aria-hidden="true" />
|
<span class="web-icon-arrow-right" aria-hidden="true"></span>
|
||||||
<div class="shadow" />
|
<div class="shadow"></div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="shine" />
|
<div class="shine"></div>
|
||||||
<div class="border" />
|
<div class="border"></div>
|
||||||
<div class="lines">
|
<div class="lines">
|
||||||
{#if mounted}
|
{#if mounted}
|
||||||
{#each Array.from({ length: groups.length }) as _, i}
|
{#each Array.from({ length: groups.length }) as _, i}
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<div
|
<div
|
||||||
class="line"
|
class="line"
|
||||||
style={`--width:${getRandomWidth(index)}px;--initial-delay:${randomDelay()}ms;left:${getRandomXValue()}px;`}
|
style={`--width:${getRandomWidth(index)}px;--initial-delay:${randomDelay()}ms;left:${getRandomXValue()}px;`}
|
||||||
/>
|
></div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<span class={social.icon} aria-hidden="true" />
|
<span class={social.icon} aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
scrolling="no"
|
scrolling="no"
|
||||||
style:color-scheme="none"
|
style:color-scheme="none"
|
||||||
style:margin-top="-4px"
|
style:margin-top="-4px"
|
||||||
/>
|
></iframe>
|
||||||
|
|
||||||
<ul class="flex gap-4">
|
<ul class="flex gap-4">
|
||||||
<li><a class="web-link" href="/terms">Terms</a></li>
|
<li><a class="web-link" href="/terms">Terms</a></li>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<span class={social.icon} aria-hidden="true" />
|
<span class={social.icon} aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="web-button is-text web-u-inline-width-100-percent-mobile"
|
class="web-button is-text web-u-inline-width-100-percent-mobile"
|
||||||
>
|
>
|
||||||
<span class="web-icon-star" aria-hidden="true" />
|
<span class="web-icon-star" aria-hidden="true"></span>
|
||||||
<span class="text">Star on GitHub</span>
|
<span class="text">Star on GitHub</span>
|
||||||
<span class="web-inline-tag text-sub-body">{GITHUB_STARS}</span>
|
<span class="web-inline-tag text-sub-body">{GITHUB_STARS}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
<!-- @migration-task Error while migrating Svelte code: Cannot use `export let` in runes mode — use `$props()` instead
|
||||||
|
https://svelte.dev/e/legacy_export_invalid -->
|
||||||
|
<!-- @migration-task Error while migrating Svelte code: Cannot use `export let` in runes mode — use `$props()` instead
|
||||||
|
https://svelte.dev/e/legacy_export_invalid -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { platformMap } from '$lib/utils/references';
|
import { Select, Tooltip } from '$lib/components';
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
import { getCodeHtml, type Language } from '$lib/utils/code';
|
import { getCodeHtml, type Language } from '$lib/utils/code';
|
||||||
import { copy } from '$lib/utils/copy';
|
import { copy } from '$lib/utils/copy';
|
||||||
import { Select, Tooltip } from '$lib/components';
|
import { platformMap } from '$lib/utils/references';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
export let selected: Language = 'js';
|
export let selected: Language = 'js';
|
||||||
export let data: { language: string; content: string; platform?: string }[] = [];
|
export let data: { language: string; content: string; platform?: string }[] = [];
|
||||||
@@ -22,11 +26,15 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
enum CopyStatus {
|
const CopyStatus = {
|
||||||
Copy = 'Copy',
|
Copy: 'Copy',
|
||||||
Copied = 'Copied!'
|
Copied: 'Copied!'
|
||||||
}
|
} as const;
|
||||||
let copyText = CopyStatus.Copy;
|
type CopyStatusType = keyof typeof CopyStatus;
|
||||||
|
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||||
|
|
||||||
|
let copyText: CopyStatusValue = CopyStatus.Copy;
|
||||||
|
|
||||||
async function handleCopy() {
|
async function handleCopy() {
|
||||||
await copy(content);
|
await copy(content);
|
||||||
|
|
||||||
@@ -75,11 +83,13 @@
|
|||||||
on:click={handleCopy}
|
on:click={handleCopy}
|
||||||
class="web-icon-button"
|
class="web-icon-button"
|
||||||
aria-label="copy code from code-snippet"
|
aria-label="copy code from code-snippet"
|
||||||
><span class="web-icon-copy" aria-hidden="true" /></button
|
><span class="web-icon-copy" aria-hidden="true"></span></button
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="tooltip">
|
{#snippet tooltip()}
|
||||||
{copyText}
|
<span>
|
||||||
</svelte:fragment>
|
{copyText}
|
||||||
|
</span>
|
||||||
|
{/snippet}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
<div class="web-strip-plans-plan">
|
<div class="web-strip-plans-plan">
|
||||||
<h4 class="title text-description">Free</h4>
|
<h4 class="title text-description">Free</h4>
|
||||||
<div class="text-title font-aeonik-pro text-primary">$0</div>
|
<div class="text-title font-aeonik-pro text-primary">$0</div>
|
||||||
<div class="info text-caption font-medium" />
|
<div class="info text-caption font-medium"></div>
|
||||||
</div>
|
</div>
|
||||||
<p class="web-strip-plans-info text-caption font-medium">
|
<p class="web-strip-plans-info text-caption font-medium">
|
||||||
A great fit for passion projects and small applications.
|
A great fit for passion projects and small applications.
|
||||||
|
|||||||
@@ -23,18 +23,18 @@
|
|||||||
class={classNames('web-icon-chevron-down transition-transform', {
|
class={classNames('web-icon-chevron-down transition-transform', {
|
||||||
'rotate-180': $open
|
'rotate-180': $open
|
||||||
})}
|
})}
|
||||||
/></button
|
></span></button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{#if $open}
|
{#if $open}
|
||||||
<div use:melt={$content} transition:slide class="py-3 px-2">
|
<div use:melt={$content} transition:slide class="px-2 py-3">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{#each products as product}
|
{#each products as product}
|
||||||
<a
|
<a
|
||||||
href={product.href}
|
href={product.href}
|
||||||
class="group flex gap-3 rounded-xl p-2 text-white outline-none transition-colors focus:bg-white/8"
|
class="group flex gap-3 rounded-xl p-2 text-white transition-colors outline-none focus:bg-white/8"
|
||||||
on:click={() =>
|
on:click={() =>
|
||||||
trackEvent({
|
trackEvent({
|
||||||
plausible: {
|
plausible: {
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
{#if product.beta}
|
{#if product.beta}
|
||||||
<span
|
<span
|
||||||
class="text-caption bg-accent/24 ml-1 rounded py-1 px-2 font-medium text-white"
|
class="text-caption bg-accent/24 ml-1 rounded px-2 py-1 font-medium text-white"
|
||||||
>Coming soon</span
|
>Coming soon</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
href={sublink.href}
|
href={sublink.href}
|
||||||
class="text-caption text-primary flex items-center gap-2"
|
class="text-caption text-primary flex items-center gap-2"
|
||||||
>
|
>
|
||||||
{sublink.label} <span class="web-icon-chevron-right" />
|
{sublink.label} <span class="web-icon-chevron-right"></span>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -65,9 +65,9 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
|
import { trackEvent } from '$lib/actions/analytics';
|
||||||
import { classNames } from '$lib/utils/classnames';
|
import { classNames } from '$lib/utils/classnames';
|
||||||
import { createDropdownMenu, melt } from '@melt-ui/svelte';
|
import { createDropdownMenu, melt } from '@melt-ui/svelte';
|
||||||
import { trackEvent } from '$lib/actions/analytics';
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
elements: { trigger, menu, item, overlay },
|
elements: { trigger, menu, item, overlay },
|
||||||
@@ -94,13 +94,13 @@
|
|||||||
class={classNames('web-icon-chevron-down block transition-transform', {
|
class={classNames('web-icon-chevron-down block transition-transform', {
|
||||||
'rotate-180': $open
|
'rotate-180': $open
|
||||||
})}
|
})}
|
||||||
/>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
use:melt={$menu}
|
use:melt={$menu}
|
||||||
class={classNames(
|
class={classNames(
|
||||||
'data-[state=closed]:animate-fade-out data-[state=open]:animate-fade-in relative !left-1/2 z-10 mt-6 mx-auto hidden w-full -translate-x-1/2 flex-col items-center p-0 outline-none [max-inline-size:86.875rem] md:flex'
|
'data-[state=closed]:animate-fade-out data-[state=open]:animate-fade-in relative !left-1/2 z-10 mx-auto mt-6 hidden w-full -translate-x-1/2 flex-col items-center p-0 outline-none [max-inline-size:86.875rem] md:flex'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="is-special-padding w-full rounded-2xl border border-white/8 bg-[#232325] p-6">
|
<div class="is-special-padding w-full rounded-2xl border border-white/8 bg-[#232325] p-6">
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
name: `${product.name} in products submenu`
|
name: `${product.name} in products submenu`
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
class="group flex gap-3 rounded-xl p-1 text-white outline-none transition-colors focus:bg-white/8"
|
class="group flex gap-3 rounded-xl p-1 text-white transition-colors outline-none focus:bg-white/8"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex size-12 shrink-0 items-center justify-center rounded-lg border border-white/12 bg-white/6"
|
class="flex size-12 shrink-0 items-center justify-center rounded-lg border border-white/12 bg-white/6"
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
|
|
||||||
{#if product.beta}
|
{#if product.beta}
|
||||||
<span
|
<span
|
||||||
class="text-caption bg-accent/24 ml-1 rounded py-1 px-2 font-medium text-white"
|
class="text-caption bg-accent/24 ml-1 rounded px-2 py-1 font-medium text-white"
|
||||||
>Coming soon</span
|
>Coming soon</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -154,8 +154,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-4 -ml-12 border-l border-white/6 pl-12">
|
<div class="col-span-4 -ml-12 border-l border-white/6 pl-12">
|
||||||
<a
|
<div
|
||||||
href="/blog/post/customer-story-storealert"
|
|
||||||
use:melt={$item}
|
use:melt={$item}
|
||||||
class="group block rounded-2xl border border-white/12 bg-white/6 p-4 outline-none focus-within:bg-white/12"
|
class="group block rounded-2xl border border-white/12 bg-white/6 p-4 outline-none focus-within:bg-white/12"
|
||||||
>
|
>
|
||||||
@@ -169,11 +168,14 @@
|
|||||||
class="text-primary text-caption flex items-center gap-2"
|
class="text-primary text-caption flex items-center gap-2"
|
||||||
>See more <span
|
>See more <span
|
||||||
class="web-icon-chevron-right transition-transform group-hover:translate-x-0.5"
|
class="web-icon-chevron-right transition-transform group-hover:translate-x-0.5"
|
||||||
/></a
|
></span></a
|
||||||
>
|
>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="my-4 flex flex-1 gap-3 outline-none">
|
<a
|
||||||
|
href="/blog/post/customer-story-storealert"
|
||||||
|
class="my-4 flex flex-1 gap-3 outline-none"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src="/images/blog/customer-story-storealert/cover.png"
|
src="/images/blog/customer-story-storealert/cover.png"
|
||||||
alt="Case study cover"
|
alt="Case study cover"
|
||||||
@@ -183,8 +185,8 @@
|
|||||||
Empowering Shopify merchants with real-time store monitoring using
|
Empowering Shopify merchants with real-time store monitoring using
|
||||||
StoreAlert
|
StoreAlert
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<span
|
<span
|
||||||
@@ -200,7 +202,7 @@
|
|||||||
{sublink.label}
|
{sublink.label}
|
||||||
<span
|
<span
|
||||||
class="web-icon-chevron-right transition-transform group-hover:translate-x-0.5"
|
class="web-icon-chevron-right transition-transform group-hover:translate-x-0.5"
|
||||||
/>
|
></span>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -211,5 +213,5 @@
|
|||||||
<div
|
<div
|
||||||
use:melt={$overlay}
|
use:melt={$overlay}
|
||||||
class="data-[state=closed]:animate-fade-out fixed inset-0 bg-black/60"
|
class="data-[state=closed]:animate-fade-out fixed inset-0 bg-black/60"
|
||||||
/>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -160,8 +160,9 @@
|
|||||||
on:click={handleExit}
|
on:click={handleExit}
|
||||||
>
|
>
|
||||||
<div class="web-input-text-search-wrapper web-u-margin-inline-20 w-full max-w-[680px]">
|
<div class="web-input-text-search-wrapper web-u-margin-inline-20 w-full max-w-[680px]">
|
||||||
<span class="web-icon-search z-[5]" aria-hidden="true" style="inset-block-start:0.9rem" />
|
<span class="web-icon-search z-[5]" aria-hidden="true" style="inset-block-start:0.9rem"
|
||||||
<div id="searchbox" />
|
></span>
|
||||||
|
<div id="searchbox"></div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
class="web-input-button bg-greyscale-800/75! relative z-1 !rounded-b-none !pl-10"
|
class="web-input-button bg-greyscale-800/75! relative z-1 !rounded-b-none !pl-10"
|
||||||
@@ -222,7 +223,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if hit.p}
|
{#if hit.p}
|
||||||
<div
|
<div
|
||||||
class="web-u-color-text-secondary w-full overflow-hidden text-ellipsis whitespace-nowrap text-left"
|
class="web-u-color-text-secondary w-full overflow-hidden text-left text-ellipsis whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{hit.p}
|
{hit.p}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -97,11 +97,11 @@
|
|||||||
>
|
>
|
||||||
<div class="physical-select">
|
<div class="physical-select">
|
||||||
{#if selectedOption?.icon}
|
{#if selectedOption?.icon}
|
||||||
<span class={selectedOption.icon} aria-hidden="true" />
|
<span class={selectedOption.icon} aria-hidden="true"></span>
|
||||||
{/if}
|
{/if}
|
||||||
<span>{$selectedLabel || initialLabel}</span>
|
<span>{$selectedLabel || initialLabel}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" />
|
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{#if $open}
|
{#if $open}
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
{#each group.options as option}
|
{#each group.options as option}
|
||||||
<button class="web-select-option" use:melt={$optionEl(option)}>
|
<button class="web-select-option" use:melt={$optionEl(option)}>
|
||||||
{#if option.icon}
|
{#if option.icon}
|
||||||
<span class={option.icon} aria-hidden="true" />
|
<span class={option.icon} aria-hidden="true"></span>
|
||||||
{/if}
|
{/if}
|
||||||
<span>{option.label}</span>
|
<span>{option.label}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
{#each group.options as option}
|
{#each group.options as option}
|
||||||
<button class="web-select-option" use:melt={$optionEl(option)}>
|
<button class="web-select-option" use:melt={$optionEl(option)}>
|
||||||
{#if option.icon}
|
{#if option.icon}
|
||||||
<span class={option.icon} aria-hidden="true" />
|
<span class={option.icon} aria-hidden="true"></span>
|
||||||
{/if}
|
{/if}
|
||||||
<span style:text-transform="capitalize">{option.label}</span>
|
<span style:text-transform="capitalize">{option.label}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
style:display={nativeMobile ? undefined : 'none'}
|
style:display={nativeMobile ? undefined : 'none'}
|
||||||
>
|
>
|
||||||
{#if selectedOption?.icon}
|
{#if selectedOption?.icon}
|
||||||
<span class={selectedOption.icon} aria-hidden="true" />
|
<span class={selectedOption.icon} aria-hidden="true"></span>
|
||||||
{/if}
|
{/if}
|
||||||
<select {id} bind:value>
|
<select {id} bind:value>
|
||||||
{#each groups as group}
|
{#each groups as group}
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true" />
|
<span class="icon-cheveron-{$open ? 'up' : 'down'}" aria-hidden="true"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<div class="melt-switch">
|
<div class="melt-switch">
|
||||||
<button use:melt={$root}>
|
<button use:melt={$root}>
|
||||||
<span class="thumb" />
|
<span class="thumb"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,9 @@
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<svelte:fragment slot="tooltip">{platform.name}</svelte:fragment>
|
{#snippet tooltip()}
|
||||||
|
{platform.name}
|
||||||
|
{/snippet}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,12 +1,28 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createTooltip, melt } from '@melt-ui/svelte';
|
import { createTooltip, melt } from '@melt-ui/svelte';
|
||||||
import type { FloatingConfig } from '@melt-ui/svelte/internal/actions';
|
import type { FloatingConfig } from '@melt-ui/svelte/internal/actions';
|
||||||
|
import { type Snippet } from 'svelte';
|
||||||
import { fly, type FlyParams } from 'svelte/transition';
|
import { fly, type FlyParams } from 'svelte/transition';
|
||||||
|
|
||||||
export let placement: NonNullable<FloatingConfig>['placement'] = 'top';
|
interface Props {
|
||||||
export let disabled = false;
|
placement?: NonNullable<FloatingConfig>['placement'];
|
||||||
export let closeOnPointerDown = false;
|
disabled?: boolean;
|
||||||
export let disableHoverableContent = false;
|
closeOnPointerDown?: boolean;
|
||||||
|
disableHoverableContent?: boolean;
|
||||||
|
asChild?: Snippet<[any]>;
|
||||||
|
children?: Snippet;
|
||||||
|
tooltip: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
placement = 'top',
|
||||||
|
disabled = false,
|
||||||
|
closeOnPointerDown = false,
|
||||||
|
disableHoverableContent = false,
|
||||||
|
asChild,
|
||||||
|
children,
|
||||||
|
tooltip
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
elements: { trigger, content, arrow },
|
elements: { trigger, content, arrow },
|
||||||
@@ -21,50 +37,52 @@
|
|||||||
disableHoverableContent
|
disableHoverableContent
|
||||||
});
|
});
|
||||||
|
|
||||||
$: flyParams = (function getFlyParams() {
|
let flyParams = $derived(
|
||||||
const params: FlyParams = {
|
(function getFlyParams() {
|
||||||
duration: 150
|
const params: FlyParams = {
|
||||||
};
|
duration: 150
|
||||||
|
};
|
||||||
|
|
||||||
switch (placement) {
|
switch (placement) {
|
||||||
case 'top':
|
case 'top':
|
||||||
case 'top-start':
|
case 'top-start':
|
||||||
case 'top-end':
|
case 'top-end':
|
||||||
params.y = 4;
|
params.y = 4;
|
||||||
break;
|
break;
|
||||||
case 'bottom':
|
case 'bottom':
|
||||||
case 'bottom-start':
|
case 'bottom-start':
|
||||||
case 'bottom-end':
|
case 'bottom-end':
|
||||||
params.y = -4;
|
params.y = -4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'left':
|
case 'left':
|
||||||
case 'left-start':
|
case 'left-start':
|
||||||
case 'left-end':
|
case 'left-end':
|
||||||
params.x = 4;
|
params.x = 4;
|
||||||
break;
|
break;
|
||||||
case 'right':
|
case 'right':
|
||||||
case 'right-start':
|
case 'right-start':
|
||||||
case 'right-end':
|
case 'right-end':
|
||||||
params.x = -4;
|
params.x = -4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
})();
|
})()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot name="asChild" trigger={$trigger} />
|
{#if asChild}
|
||||||
|
{@render asChild({ trigger: $trigger })}
|
||||||
{#if !$$slots.asChild}
|
{:else if children}
|
||||||
<span use:melt={$trigger}>
|
<span use:melt={$trigger}>
|
||||||
<slot />
|
{@render children()}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $open && !disabled}
|
{#if tooltip && $open && !disabled}
|
||||||
<div use:melt={$content} class="web-tooltip text-sub-body" transition:fly={flyParams}>
|
<div use:melt={$content} class="web-tooltip text-sub-body" transition:fly={flyParams}>
|
||||||
<div use:melt={$arrow} />
|
<div use:melt={$arrow}></div>
|
||||||
<slot name="tooltip" />
|
{@render tooltip()}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -73,14 +73,14 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener, noreferrer"
|
rel="noopener, noreferrer"
|
||||||
>
|
>
|
||||||
<span class={sharingOption.icon} aria-hidden="true" />
|
<span class={sharingOption.icon} aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
aria-label={sharingOption.label}
|
aria-label={sharingOption.label}
|
||||||
on:click={() => handleCopy(currentURL)}
|
on:click={() => handleCopy(currentURL)}
|
||||||
>
|
>
|
||||||
<span class={sharingOption.icon} aria-hidden="true" />
|
<span class={sharingOption.icon} aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
out:fade
|
out:fade
|
||||||
in:fade
|
in:fade
|
||||||
>
|
>
|
||||||
<span class="web-icon-arrow-up transition group-hover:-translate-y-0.5" />
|
<span class="web-icon-arrow-up transition group-hover:-translate-y-0.5"></span>
|
||||||
Back to Top
|
Back to Top
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@
|
|||||||
<slot />
|
<slot />
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="shadow" />
|
<div class="shadow"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if showBullets}
|
{#if showBullets}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="border-smooth box-content flex items-center border-b bg-[url(/images/bgs/mobile-hero.png)] bg-cover bg-bottom py-12 px-5 md:bg-[url(/images/bgs/hero.png)] md:bg-center md:pt-32 md:pb-40 lg:px-8 xl:px-16"
|
class="border-smooth box-content flex items-center border-b bg-[url(/images/bgs/mobile-hero.png)] bg-cover bg-bottom px-5 py-12 md:bg-[url(/images/bgs/hero.png)] md:bg-center md:pt-32 md:pb-40 lg:px-8 xl:px-16"
|
||||||
>
|
>
|
||||||
<div class="mx-auto grid max-w-[75rem] items-center gap-16 md:grid-cols-2">
|
<div class="mx-auto grid max-w-[75rem] items-center gap-16 md:grid-cols-2">
|
||||||
<div class="space-y-8">
|
<div class="space-y-8">
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-description text-secondary text-pretty font-medium">
|
<p class="text-description text-secondary font-medium text-pretty">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<img src={product.icon} alt="auth" width="32" height="32" />
|
<img src={product.icon} alt="auth" width="32" height="32" />
|
||||||
<h4 class="text-body text-primary">{product.title}</h4>
|
<h4 class="text-body text-primary">{product.title}</h4>
|
||||||
<span class="web-icon-arrow-right ml-auto" aria-hidden="true" />
|
<span class="web-icon-arrow-right ml-auto" aria-hidden="true"></span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sub-body">
|
<p class="text-sub-body">
|
||||||
{product.description}
|
{product.description}
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { classNames } from '$lib/utils/classnames';
|
|
||||||
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
|
|
||||||
import { cva, type VariantProps } from 'cva';
|
|
||||||
import InlineTag from './InlineTag.svelte';
|
|
||||||
|
|
||||||
const button = cva(
|
|
||||||
[
|
|
||||||
'flex w-fit justify-center px-[0.875rem] h-10 transition-all text-center no-underline select-none min-w-10 bg-origin-border text-white font-medium items-center gap-2 rounded-lg border border-transparent button duration-200'
|
|
||||||
],
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
primary: [
|
|
||||||
'bg-[linear-gradient(135deg,_var(--color-accent)_0%,_var(--color-accent)_61%,_var(--color-secondary-100)_100%)]',
|
|
||||||
'hover:shadow-[0_0_2rem_var(--color-accent-200)] active:not:disabled:shadow-[0_0_2rem_var(--color-accent-200)]'
|
|
||||||
],
|
|
||||||
secondary: [
|
|
||||||
'bg-[#fd366e0a] relative',
|
|
||||||
'hover:shadow-[0_-6px_10px_0px_rgba(253,54,110,0.08)_inset]'
|
|
||||||
],
|
|
||||||
text: [
|
|
||||||
'bg-transparent border-transparent text-white',
|
|
||||||
'hover:backdrop-blur-md hover:bg-[linear-gradient(135deg,_rgba(255,_255,_255,_0.06)_0%,_rgba(255,_255,_255,_0.10)_54.74%,_rgba(255,_255,_255,_0.06)_100%)]'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
type ButtonProps =
|
|
||||||
| (HTMLButtonAttributes & { href?: undefined })
|
|
||||||
| (HTMLAnchorAttributes & { href: string });
|
|
||||||
|
|
||||||
type $$Props = ButtonProps & VariantProps<typeof button>;
|
|
||||||
|
|
||||||
export let href: $$Props['href'] = undefined;
|
|
||||||
export let variant: $$Props['variant'] = 'primary';
|
|
||||||
const { class: classes, ...props } = $$restProps;
|
|
||||||
|
|
||||||
const buttonClasses = classNames(button({ variant }), classes, {
|
|
||||||
secondary: variant === 'secondary',
|
|
||||||
'leading-tight': $$slots.icon
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if href}
|
|
||||||
<a {...props} {href} class={buttonClasses}>
|
|
||||||
{#if $$slots.icon}
|
|
||||||
<slot name="icon" />
|
|
||||||
{/if}
|
|
||||||
<slot />
|
|
||||||
{#if $$slots.tag}
|
|
||||||
<InlineTag>
|
|
||||||
<slot name="tag" />
|
|
||||||
</InlineTag>
|
|
||||||
{/if}
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
<button {...props} class={buttonClasses}>
|
|
||||||
{#if $$slots.icon}
|
|
||||||
<slot name="icon" />
|
|
||||||
{/if}
|
|
||||||
<slot />
|
|
||||||
{#if $$slots.tag}
|
|
||||||
<InlineTag>
|
|
||||||
<slot name="tag" />
|
|
||||||
</InlineTag>
|
|
||||||
{/if}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
on:blur
|
on:blur
|
||||||
bind:value
|
bind:value
|
||||||
class={classNames(
|
class={classNames(
|
||||||
'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 flex items-center gap-1 rounded-lg border py-2 px-3 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30',
|
'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 flex items-center gap-1 rounded-lg border px-3 py-2 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30',
|
||||||
classes
|
classes
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
></textarea>
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { classNames } from '$lib/utils/classnames';
|
import { classNames } from '$lib/utils/classnames';
|
||||||
import type { SvelteHTMLElements } from 'svelte/elements';
|
import type { SvelteHTMLElements } from 'svelte/elements';
|
||||||
|
import Eyebrow from './eyebrow.svelte';
|
||||||
|
|
||||||
type $$Props = SvelteHTMLElements['span'];
|
type $$Props = SvelteHTMLElements['span'];
|
||||||
|
|
||||||
const { class: classes, ...props } = $$restProps;
|
const { class: classes, ...props } = $$restProps;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span
|
<Eyebrow class="text-white">
|
||||||
class={classNames(
|
<span
|
||||||
'badge font-aeonik-fono self-start rounded-[0.375rem] py-[0.375rem] px-3 text-xs uppercase text-white backdrop-blur-2xl',
|
class={classNames(
|
||||||
classes
|
'badge self-start rounded-[0.375rem] px-3 py-[0.375rem] backdrop-blur-2xl',
|
||||||
)}
|
classes
|
||||||
{...props}
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span></Eyebrow
|
||||||
>
|
>
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root,
|
:root,
|
||||||
97
src/lib/components/ui/button.svelte
Normal file
97
src/lib/components/ui/button.svelte
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { classNames } from '$lib/utils/classnames';
|
||||||
|
import { emptyMeltElement, melt, type AnyMeltElement } from '@melt-ui/svelte';
|
||||||
|
import { cva, type VariantProps } from 'cva';
|
||||||
|
import type { Action } from 'svelte/action';
|
||||||
|
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
||||||
|
import { createBubbler } from 'svelte/legacy';
|
||||||
|
import InlineTag from './inline-tag.svelte';
|
||||||
|
|
||||||
|
// TODO: replace _button.scss with Tailwind classes for long-term maintainability
|
||||||
|
const button = cva(['web-button'], {
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
primary: [''],
|
||||||
|
secondary: ['is-secondary'],
|
||||||
|
text: ['is-text']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type ButtonProps =
|
||||||
|
| (HTMLButtonAttributes & { href?: undefined })
|
||||||
|
| (HTMLAnchorAttributes & { href: string });
|
||||||
|
|
||||||
|
type $$Props = ButtonProps &
|
||||||
|
VariantProps<typeof button> & {
|
||||||
|
use?:
|
||||||
|
| Action<HTMLButtonElement | HTMLAnchorElement, any>
|
||||||
|
| [Action<HTMLButtonElement | HTMLAnchorElement, any>, any];
|
||||||
|
} & { element?: AnyMeltElement };
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
href?: $$Props['href'];
|
||||||
|
variant?: $$Props['variant'];
|
||||||
|
use?: $$Props['use'];
|
||||||
|
element?: AnyMeltElement | undefined;
|
||||||
|
icon?: import('svelte').Snippet;
|
||||||
|
children: import('svelte').Snippet;
|
||||||
|
tag?: import('svelte').Snippet;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
href = undefined,
|
||||||
|
variant = 'primary',
|
||||||
|
use = undefined,
|
||||||
|
element = undefined,
|
||||||
|
icon,
|
||||||
|
children,
|
||||||
|
tag,
|
||||||
|
...rest
|
||||||
|
}: Props = $props();
|
||||||
|
let meltElement = $derived(element ?? emptyMeltElement);
|
||||||
|
|
||||||
|
const { class: classes, href: _ } = rest;
|
||||||
|
|
||||||
|
const buttonClasses = classNames(button({ variant }), classes);
|
||||||
|
|
||||||
|
const applyAction = (node: HTMLButtonElement | HTMLAnchorElement) => {
|
||||||
|
if (!use) return { destroy: () => {} };
|
||||||
|
|
||||||
|
if (typeof use === 'function') {
|
||||||
|
return use(node);
|
||||||
|
} else if (Array.isArray(use)) {
|
||||||
|
const [action, params] = use;
|
||||||
|
return action(node, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { destroy: () => {} };
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if href}
|
||||||
|
<a {...rest} {href} class={buttonClasses} use:melt={$meltElement} use:applyAction>
|
||||||
|
{#if icon}
|
||||||
|
{@render icon()}
|
||||||
|
{/if}
|
||||||
|
{@render children()}
|
||||||
|
{#if tag}
|
||||||
|
<InlineTag>
|
||||||
|
{@render tag()}
|
||||||
|
</InlineTag>
|
||||||
|
{/if}
|
||||||
|
</a>
|
||||||
|
{:else}
|
||||||
|
<button {...rest} class={buttonClasses} use:melt={$meltElement} use:applyAction>
|
||||||
|
{#if icon}
|
||||||
|
{@render icon()}
|
||||||
|
{/if}
|
||||||
|
{@render children()}
|
||||||
|
{#if tag}
|
||||||
|
<InlineTag>
|
||||||
|
{@render tag()}
|
||||||
|
</InlineTag>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { classNames } from '$lib/utils/classnames';
|
import { classNames } from '$lib/utils/classnames';
|
||||||
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
|
|
||||||
import { cva, type VariantProps } from 'cva';
|
import { cva, type VariantProps } from 'cva';
|
||||||
|
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
const card = cva(
|
const card = cva(
|
||||||
[
|
[
|
||||||
41
src/lib/components/ui/eyebrow.svelte
Normal file
41
src/lib/components/ui/eyebrow.svelte
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { classNames } from '$lib/utils/classnames';
|
||||||
|
|
||||||
|
// html text elements
|
||||||
|
type ElementType =
|
||||||
|
| 'p'
|
||||||
|
| 'h1'
|
||||||
|
| 'h2'
|
||||||
|
| 'h3'
|
||||||
|
| 'h4'
|
||||||
|
| 'h5'
|
||||||
|
| 'h6'
|
||||||
|
| 'span'
|
||||||
|
| 'div'
|
||||||
|
| 'strong'
|
||||||
|
| 'em'
|
||||||
|
| 'small'
|
||||||
|
| 'mark'
|
||||||
|
| 'abbr'
|
||||||
|
| 'blockquote'
|
||||||
|
| 'cite'
|
||||||
|
| 'code'
|
||||||
|
| 'kbd'
|
||||||
|
| 'var'
|
||||||
|
| 'samp'
|
||||||
|
| 'sub'
|
||||||
|
| 'sup';
|
||||||
|
|
||||||
|
let className: string = '';
|
||||||
|
export let as: ElementType = 'span';
|
||||||
|
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element
|
||||||
|
this={as}
|
||||||
|
class={classNames(
|
||||||
|
'text-micro tracking-loose text-primary font-aeonik-fono uppercase',
|
||||||
|
className
|
||||||
|
)}><slot /></svelte:element
|
||||||
|
>
|
||||||
16
src/lib/components/ui/icon/icon.svelte
Normal file
16
src/lib/components/ui/icon/icon.svelte
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { classNames } from '$lib/utils/classnames';
|
||||||
|
import type { SvelteHTMLElements } from 'svelte/elements';
|
||||||
|
import type { Icon } from './types';
|
||||||
|
|
||||||
|
type $$Props = {
|
||||||
|
icon?: Icon;
|
||||||
|
class?: string;
|
||||||
|
} & SvelteHTMLElements['span'];
|
||||||
|
|
||||||
|
let className: $$Props['class'] = '';
|
||||||
|
export { className as class };
|
||||||
|
export let icon: $$Props['icon'] = 'arrow-right';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class={classNames(`web-icon-${icon}`, className)} {...$$restProps}></span>
|
||||||
1
src/lib/components/ui/icon/index.ts
Normal file
1
src/lib/components/ui/icon/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './icon.svelte';
|
||||||
57
src/lib/components/ui/icon/types.ts
Normal file
57
src/lib/components/ui/icon/types.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
export type Icon =
|
||||||
|
| 'apple'
|
||||||
|
| 'appwrite'
|
||||||
|
| 'arrow-down'
|
||||||
|
| 'arrow-ext-link'
|
||||||
|
| 'arrow-left'
|
||||||
|
| 'arrow-right'
|
||||||
|
| 'arrow-up'
|
||||||
|
| 'bluesky'
|
||||||
|
| 'calendar'
|
||||||
|
| 'check'
|
||||||
|
| 'chevron-down'
|
||||||
|
| 'chevron-left'
|
||||||
|
| 'chevron-right'
|
||||||
|
| 'chevron-up'
|
||||||
|
| 'close'
|
||||||
|
| 'command'
|
||||||
|
| 'copy'
|
||||||
|
| 'daily-dev'
|
||||||
|
| 'dark'
|
||||||
|
| 'discord'
|
||||||
|
| 'divider-vertical'
|
||||||
|
| 'download'
|
||||||
|
| 'ext-link'
|
||||||
|
| 'firebase'
|
||||||
|
| 'github'
|
||||||
|
| 'google'
|
||||||
|
| 'hamburger-menu'
|
||||||
|
| 'instagram'
|
||||||
|
| 'light'
|
||||||
|
| 'linkedin'
|
||||||
|
| 'location'
|
||||||
|
| 'logout-left'
|
||||||
|
| 'logout-right'
|
||||||
|
| 'mailgun'
|
||||||
|
| 'mcp'
|
||||||
|
| 'message'
|
||||||
|
| 'microsoft'
|
||||||
|
| 'minus'
|
||||||
|
| 'nuxt'
|
||||||
|
| 'platform'
|
||||||
|
| 'play'
|
||||||
|
| 'plus'
|
||||||
|
| 'product-hunt'
|
||||||
|
| 'refine'
|
||||||
|
| 'rest'
|
||||||
|
| 'search'
|
||||||
|
| 'sendgrid'
|
||||||
|
| 'star'
|
||||||
|
| 'system'
|
||||||
|
| 'textmagic'
|
||||||
|
| 'tiktok'
|
||||||
|
| 'twitter'
|
||||||
|
| 'vue'
|
||||||
|
| 'x'
|
||||||
|
| 'ycombinator'
|
||||||
|
| 'youtube';
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div
|
<div
|
||||||
class="tracking-none text-sub-body text-greyscale-900 dark:text-greyscale-100 py-0.25 -mr-0.5 rounded-[.25rem] bg-black/8 px-1 dark:bg-white/[0.12]"
|
class="tracking-none text-sub-body text-greyscale-900 dark:text-greyscale-100 -mr-0.5 rounded-[.25rem] bg-black/8 px-1 py-0.25 dark:bg-white/[0.12]"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
@@ -1,36 +1,41 @@
|
|||||||
|
<!-- @migration-task Error while migrating Svelte code: migrating this component would require adding a `$props` rune but there's already a variable named props.
|
||||||
|
Rename the variable and try again or migrate by hand. -->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { classNames } from '$lib/utils/classnames';
|
import { classNames } from '$lib/utils/classnames';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
type $$Props = HTMLInputAttributes & {
|
interface Props extends HTMLInputAttributes {
|
||||||
label?: string;
|
label?: string;
|
||||||
};
|
icon?: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
export let label: $$Props['label'] = '';
|
let {
|
||||||
export let type: $$Props['type'] = 'text';
|
label = '',
|
||||||
export let value: $$Props['value'] = '';
|
type = 'text',
|
||||||
const { class: classes, name, ...props } = $$restProps;
|
value = $bindable(''),
|
||||||
|
icon,
|
||||||
|
class: classes,
|
||||||
|
name,
|
||||||
|
...rest
|
||||||
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $$slots.icon}
|
{#if icon}
|
||||||
<label
|
<label
|
||||||
class={classNames(
|
class={classNames(
|
||||||
'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 flex items-center gap-1 rounded-lg border py-2 px-3 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30',
|
'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 flex items-center gap-1 rounded-lg border px-3 py-2 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30',
|
||||||
classes
|
classes
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<slot name="icon" />
|
{@render icon?.()}
|
||||||
{#key type}
|
{#key type}
|
||||||
<input
|
<input
|
||||||
{name}
|
{name}
|
||||||
{...{ type }}
|
{...{ type }}
|
||||||
bind:value
|
bind:value
|
||||||
on:input
|
|
||||||
on:change
|
|
||||||
on:focus
|
|
||||||
on:blur
|
|
||||||
class="w-full border-0 ring-0 outline-none"
|
class="w-full border-0 ring-0 outline-none"
|
||||||
{...props}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</label>
|
</label>
|
||||||
@@ -45,15 +50,11 @@
|
|||||||
{name}
|
{name}
|
||||||
{...{ type }}
|
{...{ type }}
|
||||||
bind:value
|
bind:value
|
||||||
on:input
|
|
||||||
on:change
|
|
||||||
on:focus
|
|
||||||
on:blur
|
|
||||||
class={classNames(
|
class={classNames(
|
||||||
'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 mt-2 flex w-full items-center gap-1 rounded-lg border py-2 px-3 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30',
|
'focus:border-greyscale-100 bg-greyscale-800 border-greyscale-700 mt-2 flex w-full items-center gap-1 rounded-lg border px-3 py-2 text-sm font-light transition-colors focus-within:border-white active:shadow-sm active:shadow-black/30',
|
||||||
classes
|
classes
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
35
src/lib/components/ui/switch.svelte
Normal file
35
src/lib/components/ui/switch.svelte
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { classNames } from '$lib/utils/classnames';
|
||||||
|
import { createSwitch, melt } from '@melt-ui/svelte';
|
||||||
|
|
||||||
|
export let checked = false;
|
||||||
|
|
||||||
|
const {
|
||||||
|
elements: { root },
|
||||||
|
states: { checked: meltChecked }
|
||||||
|
} = createSwitch({
|
||||||
|
onCheckedChange({ next }) {
|
||||||
|
checked = next;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$: meltChecked.set(checked);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<button
|
||||||
|
use:melt={$root}
|
||||||
|
class={classNames(
|
||||||
|
'bg-smooth group relative h-6 w-9 cursor-default rounded-full transition duration-150',
|
||||||
|
'data-[state="checked"]:bg-accent'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class={classNames(
|
||||||
|
'absolute top-1/2 block size-5 translate-x-0.5 -translate-y-1/2 rounded-full bg-white transition duration-150',
|
||||||
|
'group-[data-state="checked"]:translate-x-3.5'
|
||||||
|
)}
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
export const GITHUB_STARS = '47K';
|
export const GITHUB_STARS = '47K';
|
||||||
export const GITHUB_REPO_LINK = 'https://github.com/appwrite/appwrite';
|
export const GITHUB_REPO_LINK = 'https://github.com/appwrite/appwrite';
|
||||||
export const BANNER_KEY: Banners = 'discord-banner-01'; // Change key to force banner to show again
|
export const BANNER_KEY: Banners = 'discord-banner-01'; // Change key to force banner to show again
|
||||||
export const SENTRY_DSN =
|
|
||||||
'https://27d41dc8bb67b596f137924ab8599e59@o1063647.ingest.us.sentry.io/4507497727000576';
|
|
||||||
|
|
||||||
export const BLOG_POSTS_PER_PAGE = 12;
|
export const BLOG_POSTS_PER_PAGE = 12;
|
||||||
|
|
||||||
|
|||||||
@@ -114,9 +114,9 @@
|
|||||||
on:click={toggleSidenav}
|
on:click={toggleSidenav}
|
||||||
>
|
>
|
||||||
{#if $layoutState.showSidenav}
|
{#if $layoutState.showSidenav}
|
||||||
<span aria-hidden="true" class="web-icon-close" />
|
<span aria-hidden="true" class="web-icon-close"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<span aria-hidden="true" class="web-icon-hamburger-menu" />
|
<span aria-hidden="true" class="web-icon-hamburger-menu"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
class="web-input-button web-u-flex-basis-400"
|
class="web-input-button web-u-flex-basis-400"
|
||||||
on:click={() => ($layoutState.showSearch = true)}
|
on:click={() => ($layoutState.showSearch = true)}
|
||||||
>
|
>
|
||||||
<span class="web-icon-search" aria-hidden="true" />
|
<span class="web-icon-search" aria-hidden="true"></span>
|
||||||
<span class="text">Search in docs</span>
|
<span class="text">Search in docs</span>
|
||||||
|
|
||||||
<div class="ml-auto flex gap-1">
|
<div class="ml-auto flex gap-1">
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="web-button is-text"
|
class="web-button is-text"
|
||||||
>
|
>
|
||||||
<span class="web-icon-star" aria-hidden="true" />
|
<span class="web-icon-star" aria-hidden="true"></span>
|
||||||
<span class="text">Star on GitHub</span>
|
<span class="text">Star on GitHub</span>
|
||||||
<span class="web-inline-tag text-sub-body">{GITHUB_STARS}</span>
|
<span class="web-inline-tag text-sub-body">{GITHUB_STARS}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
class="web-icon-button web-is-only-mobile"
|
class="web-icon-button web-is-only-mobile"
|
||||||
aria-label="previous page"
|
aria-label="previous page"
|
||||||
>
|
>
|
||||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
<span class="icon-cheveron-left" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<ul class="web-metadata text-caption">
|
<ul class="web-metadata text-caption">
|
||||||
@@ -56,13 +56,13 @@
|
|||||||
<span
|
<span
|
||||||
class="icon-cheveron-left web-u-font-size-24 text-primary web-is-not-mobile"
|
class="icon-cheveron-left web-u-font-size-24 text-primary web-is-not-mobile"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
></span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<h1 class="text-title font-aeonik-pro text-primary">{title}</h1>
|
<h1 class="text-title font-aeonik-pro text-primary">{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="web-article-header-end" />
|
<div class="web-article-header-end"></div>
|
||||||
</header>
|
</header>
|
||||||
<div class="web-article-content" class:web-reduced-article-size={$reducedArticleSize}>
|
<div class="web-article-content" class:web-reduced-article-size={$reducedArticleSize}>
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
class="web-icon-button web-is-only-mobile"
|
class="web-icon-button web-is-only-mobile"
|
||||||
aria-label="previous page"
|
aria-label="previous page"
|
||||||
>
|
>
|
||||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
<span class="icon-cheveron-left" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<ul class="web-metadata web-caption-400">
|
<ul class="web-metadata web-caption-400">
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
<span
|
<span
|
||||||
class="icon-cheveron-left web-u-font-size-24 web-u-color-text-primary"
|
class="icon-cheveron-left web-u-font-size-24 web-u-color-text-primary"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
></span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
<h1 class="web-title {currentStep === 1 ? 'lg:-ml-5' : ''}">
|
<h1 class="web-title {currentStep === 1 ? 'lg:-ml-5' : ''}">
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="web-article-header-end" />
|
<div class="web-article-header-end"></div>
|
||||||
</header>
|
</header>
|
||||||
<div class="web-article-content">
|
<div class="web-article-content">
|
||||||
<section class="web-article-content-section">
|
<section class="web-article-content-section">
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
<div class="web-u-padding-block-start-32 flex justify-between">
|
<div class="web-u-padding-block-start-32 flex justify-between">
|
||||||
{#if prevStep}
|
{#if prevStep}
|
||||||
<a href={prevStep.href} class="web-button is-text previous-step-anchor">
|
<a href={prevStep.href} class="web-button is-text previous-step-anchor">
|
||||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
<span class="icon-cheveron-left" aria-hidden="true"></span>
|
||||||
<span class="web-sub-body-500">
|
<span class="web-sub-body-500">
|
||||||
Step {prevStep.step}<span class="web-is-not-mobile"
|
Step {prevStep.step}<span class="web-is-not-mobile"
|
||||||
>: {getCorrectTitle(prevStep, 1)}</span
|
>: {getCorrectTitle(prevStep, 1)}</span
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
>: {nextStep.title}</span
|
>: {nextStep.title}</span
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
<span class="icon-cheveron-right" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -256,7 +256,7 @@
|
|||||||
</ol>
|
</ol>
|
||||||
<div class="border-greyscale-900/4 border-t pt-5">
|
<div class="border-greyscale-900/4 border-t pt-5">
|
||||||
<button class="web-link inline-flex items-center gap-2" use:scrollToTop>
|
<button class="web-link inline-flex items-center gap-2" use:scrollToTop>
|
||||||
<span class="web-icon-arrow-up" aria-hidden="true" />
|
<span class="web-icon-arrow-up" aria-hidden="true"></span>
|
||||||
<span class="text-caption">Back to top</span>
|
<span class="text-caption">Back to top</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -178,9 +178,9 @@
|
|||||||
on:click={() => ($isMobileNavOpen = !$isMobileNavOpen)}
|
on:click={() => ($isMobileNavOpen = !$isMobileNavOpen)}
|
||||||
>
|
>
|
||||||
{#if $isMobileNavOpen}
|
{#if $isMobileNavOpen}
|
||||||
<span aria-hidden="true" class="web-icon-close" />
|
<span aria-hidden="true" class="web-icon-close"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<span aria-hidden="true" class="web-icon-hamburger-menu" />
|
<span aria-hidden="true" class="web-icon-hamburger-menu"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
posthog: { name: 'github-stars_nav_click' }
|
posthog: { name: 'github-stars_nav_click' }
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class="web-icon-star" aria-hidden="true" />
|
<span class="web-icon-star" aria-hidden="true"></span>
|
||||||
<span class="text">Star on GitHub</span>
|
<span class="text">Star on GitHub</span>
|
||||||
<span class="web-inline-tag text-sub-body">{GITHUB_STARS}</span>
|
<span class="web-inline-tag text-sub-body">{GITHUB_STARS}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -52,14 +52,14 @@
|
|||||||
class="web-input-text web-is-not-desktop"
|
class="web-input-text web-is-not-desktop"
|
||||||
on:click={() => ($layoutState.showSearch = true)}
|
on:click={() => ($layoutState.showSearch = true)}
|
||||||
>
|
>
|
||||||
<span class="web-icon-search" />
|
<span class="web-icon-search"></span>
|
||||||
<span class="text">Search in docs</span>
|
<span class="text">Search in docs</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="web-side-nav-scroll">
|
<div class="web-side-nav-scroll">
|
||||||
{#if parent}
|
{#if parent}
|
||||||
<section class="web-side-nav-wrapper-parent">
|
<section class="web-side-nav-wrapper-parent">
|
||||||
<a href={parent.href} aria-label="go back">
|
<a href={parent.href} aria-label="go back">
|
||||||
<span class="icon-cheveron-left" aria-hidden="true" />
|
<span class="icon-cheveron-left" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
<span class="web-side-nav-wrapper-parent-title text-micro uppercase"
|
<span class="web-side-nav-wrapper-parent-title text-micro uppercase"
|
||||||
>{parent.label}</span
|
>{parent.label}</span
|
||||||
@@ -72,7 +72,9 @@
|
|||||||
{#if expandable && !$layoutState.showSidenav}
|
{#if expandable && !$layoutState.showSidenav}
|
||||||
<Tooltip placement="right">
|
<Tooltip placement="right">
|
||||||
<SidebarNavButton groupItem={navGroup} />
|
<SidebarNavButton groupItem={navGroup} />
|
||||||
<svelte:fragment slot="tooltip">{navGroup.label}</svelte:fragment>
|
{#snippet tooltip()}
|
||||||
|
<span>{navGroup.label}</span>
|
||||||
|
{/snippet}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else}
|
{:else}
|
||||||
<SidebarNavButton groupItem={navGroup} />
|
<SidebarNavButton groupItem={navGroup} />
|
||||||
@@ -89,9 +91,9 @@
|
|||||||
{#if expandable && !$layoutState.showSidenav}
|
{#if expandable && !$layoutState.showSidenav}
|
||||||
<Tooltip placement="right">
|
<Tooltip placement="right">
|
||||||
<SidebarNavButton {groupItem} />
|
<SidebarNavButton {groupItem} />
|
||||||
<svelte:fragment slot="tooltip"
|
{#snippet tooltip()}
|
||||||
>{groupItem.label}</svelte:fragment
|
<span>{groupItem.label}</span>
|
||||||
>
|
{/snippet}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{:else}
|
{:else}
|
||||||
<SidebarNavButton {groupItem} />
|
<SidebarNavButton {groupItem} />
|
||||||
@@ -110,7 +112,7 @@
|
|||||||
style:margin-bottom="1rem"
|
style:margin-bottom="1rem"
|
||||||
aria-label="toggle nav"
|
aria-label="toggle nav"
|
||||||
>
|
>
|
||||||
<span class="icon-cheveron-right" aria-hidden="true" />
|
<span class="icon-cheveron-right" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="web-side-nav-mobile-footer-buttons">
|
<div class="web-side-nav-mobile-footer-buttons">
|
||||||
@@ -122,7 +124,7 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="web-button is-text web-u-inline-width-100-percent-mobile"
|
class="web-button is-text web-u-inline-width-100-percent-mobile"
|
||||||
>
|
>
|
||||||
<span class="web-icon-star" aria-hidden="true" />
|
<span class="web-icon-star" aria-hidden="true"></span>
|
||||||
<span class="text">Star on GitHub</span>
|
<span class="text">Star on GitHub</span>
|
||||||
<span class="web-inline-tag text-sub-body">{GITHUB_STARS}</span>
|
<span class="web-inline-tag text-sub-body">{GITHUB_STARS}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="web-side-nav-button flex size-10 w-full items-center whitespace-nowrap rounded-lg p-2"
|
class="web-side-nav-button flex size-10 w-full items-center rounded-lg p-2 whitespace-nowrap"
|
||||||
class:is-selected={$page.url?.pathname === groupItem.href}
|
class:is-selected={$page.url?.pathname === groupItem.href}
|
||||||
href={groupItem.href}
|
href={groupItem.href}
|
||||||
target={groupItem.openInNewTab ? '_blank' : '_self'}
|
target={groupItem.openInNewTab ? '_blank' : '_self'}
|
||||||
>
|
>
|
||||||
{#if groupItem.icon}
|
{#if groupItem.icon}
|
||||||
<span class="icon {groupItem.icon}" aria-hidden="true" />
|
<span class="icon {groupItem.icon}" aria-hidden="true"></span>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="text-caption flex gap-2">
|
<span class="text-caption flex gap-2">
|
||||||
<span>{groupItem.label}</span>
|
<span>{groupItem.label}</span>
|
||||||
@@ -22,11 +22,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if groupItem.openInNewTab}
|
{#if groupItem.openInNewTab}
|
||||||
<span class="icon icon-external-link icon-secondary" aria-hidden="true" />
|
<span class="icon icon-external-link icon-secondary" aria-hidden="true"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if groupItem.isParent}
|
{#if groupItem.isParent}
|
||||||
<span class="icon-cheveron-right ml-auto" aria-hidden="true" />
|
<span class="icon-cheveron-right ml-auto" aria-hidden="true"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
import { clsx, type ClassValue } from 'clsx';
|
import { clsx, type ClassValue } from 'clsx';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { extendTailwindMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
const twMerge = extendTailwindMerge({
|
||||||
|
extend: {
|
||||||
|
theme: {
|
||||||
|
text: [
|
||||||
|
'x-micro',
|
||||||
|
'micro',
|
||||||
|
'caption',
|
||||||
|
'sub-body',
|
||||||
|
'body',
|
||||||
|
'paragraph-md',
|
||||||
|
'paragraph-lg',
|
||||||
|
'description',
|
||||||
|
'label',
|
||||||
|
'title',
|
||||||
|
'display',
|
||||||
|
'headline'
|
||||||
|
],
|
||||||
|
color: ['primary', 'secondary']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export const classNames = (...inputs: ClassValue[]) => {
|
export const classNames = (...inputs: ClassValue[]) => {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ client.setEndpoint(PUBLIC_APPWRITE_ENDPOINT).setProject('console');
|
|||||||
const account = new Account(client);
|
const account = new Account(client);
|
||||||
const teams = new Teams(client);
|
const teams = new Teams(client);
|
||||||
|
|
||||||
enum BillingPlan {
|
const BillingPlan = {
|
||||||
STARTER = 'tier-0',
|
STARTER: 'tier-0',
|
||||||
PRO = 'tier-1',
|
PRO: 'tier-1',
|
||||||
SCALE = 'tier-2'
|
SCALE: 'tier-2'
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
export async function createSource(
|
export async function createSource(
|
||||||
ref: string | null,
|
ref: string | null,
|
||||||
|
|||||||
@@ -20,51 +20,57 @@ export const versions: Readonly<Array<Omit<Version, 'cloud'>>> = allVersions.fil
|
|||||||
(v) => v !== 'cloud'
|
(v) => v !== 'cloud'
|
||||||
);
|
);
|
||||||
|
|
||||||
export enum Service {
|
export const Service = {
|
||||||
Account = 'account',
|
Account: 'account',
|
||||||
Avatars = 'avatars',
|
Avatars: 'avatars',
|
||||||
Databases = 'databases',
|
Databases: 'databases',
|
||||||
Functions = 'functions',
|
Functions: 'functions',
|
||||||
Messaging = 'messaging',
|
Messaging: 'messaging',
|
||||||
Health = 'health',
|
Health: 'health',
|
||||||
Locale = 'locale',
|
Locale: 'locale',
|
||||||
Storage = 'storage',
|
Storage: 'storage',
|
||||||
Teams = 'teams',
|
Teams: 'teams',
|
||||||
Users = 'users'
|
Users: 'users'
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
export enum Platform {
|
export type ServiceType = typeof Service;
|
||||||
ClientWeb = 'client-web',
|
export type ServiceValue = (typeof Service)[keyof typeof Service];
|
||||||
ClientFlutter = 'client-flutter',
|
|
||||||
ClientReactNative = 'client-react-native',
|
|
||||||
ClientApple = 'client-apple',
|
|
||||||
ClientAndroidKotlin = 'client-android-kotlin',
|
|
||||||
ClientAndroidJava = 'client-android-java',
|
|
||||||
ClientGraphql = 'client-graphql',
|
|
||||||
ClientRest = 'client-rest',
|
|
||||||
ServerNodeJs = 'server-nodejs',
|
|
||||||
ServerPython = 'server-python',
|
|
||||||
ServerDart = 'server-dart',
|
|
||||||
ServerPhp = 'server-php',
|
|
||||||
ServerRuby = 'server-ruby',
|
|
||||||
ServerDotNet = 'server-dotnet',
|
|
||||||
ServerDeno = 'server-deno',
|
|
||||||
ServerGo = 'server-go',
|
|
||||||
ServerSwift = 'server-swift',
|
|
||||||
ServerKotlin = 'server-kotlin',
|
|
||||||
ServerJava = 'server-java',
|
|
||||||
ServerGraphql = 'server-graphql',
|
|
||||||
ServerRest = 'server-rest'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum Framework {
|
export const Platform = {
|
||||||
NextJs = 'Next.js',
|
ClientWeb: 'client-web',
|
||||||
SvelteKit = 'SvelteKit',
|
ClientFlutter: 'client-flutter',
|
||||||
VueJs = 'Vue.js',
|
ClientReactNative: 'client-react-native',
|
||||||
Nuxt3 = 'Nuxt3',
|
ClientApple: 'client-apple',
|
||||||
Astro = 'Astro',
|
ClientAndroidKotlin: 'client-android-kotlin',
|
||||||
Remix = 'Remix'
|
ClientAndroidJava: 'client-android-java',
|
||||||
}
|
ClientGraphql: 'client-graphql',
|
||||||
|
ClientRest: 'client-rest',
|
||||||
|
ServerNodeJs: 'server-nodejs',
|
||||||
|
ServerPython: 'server-python',
|
||||||
|
ServerDart: 'server-dart',
|
||||||
|
ServerPhp: 'server-php',
|
||||||
|
ServerRuby: 'server-ruby',
|
||||||
|
ServerDotNet: 'server-dotnet',
|
||||||
|
ServerDeno: 'server-deno',
|
||||||
|
ServerGo: 'server-go',
|
||||||
|
ServerSwift: 'server-swift',
|
||||||
|
ServerKotlin: 'server-kotlin',
|
||||||
|
ServerJava: 'server-java',
|
||||||
|
ServerGraphql: 'server-graphql',
|
||||||
|
ServerRest: 'server-rest'
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type PlatformType = typeof Platform;
|
||||||
|
export type Platform = (typeof Platform)[keyof typeof Platform];
|
||||||
|
|
||||||
|
export const Framework = {
|
||||||
|
NextJs: 'Next.js',
|
||||||
|
SvelteKit: 'SvelteKit',
|
||||||
|
VueJs: 'Vue.js',
|
||||||
|
Nuxt3: 'Nuxt3',
|
||||||
|
Astro: 'Astro',
|
||||||
|
Remix: 'Remix'
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const platformMap: Record<Language | string, string> = {
|
export const platformMap: Record<Language | string, string> = {
|
||||||
[Platform.ClientApple]: 'Apple',
|
[Platform.ClientApple]: 'Apple',
|
||||||
@@ -126,7 +132,7 @@ export const platformMap: Record<Language | string, string> = {
|
|||||||
go: 'Go'
|
go: 'Go'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const serviceMap: Record<Service, string> = {
|
export const serviceMap: Record<ServiceValue, string> = {
|
||||||
[Service.Account]: 'Account',
|
[Service.Account]: 'Account',
|
||||||
[Service.Avatars]: 'Avatars',
|
[Service.Avatars]: 'Avatars',
|
||||||
[Service.Databases]: 'Databases',
|
[Service.Databases]: 'Databases',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { Platform, type Service } from './references';
|
import { Platform, type ServiceValue } from './references';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
export type SDKMethod = {
|
export type SDKMethod = {
|
||||||
@@ -60,10 +60,13 @@ export interface Property {
|
|||||||
} & OpenAPIV3.ReferenceObject;
|
} & OpenAPIV3.ReferenceObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ModelType {
|
export const ModelType = {
|
||||||
REST = 'REST',
|
REST: 'REST',
|
||||||
GRAPHQL = 'GraphQL'
|
GRAPHQL: 'GraphQL'
|
||||||
}
|
} as const;
|
||||||
|
|
||||||
|
type ModelTypeType = keyof typeof ModelType;
|
||||||
|
type ModelTypeValue = (typeof ModelType)[ModelTypeType];
|
||||||
|
|
||||||
function getExamples(version: string) {
|
function getExamples(version: string) {
|
||||||
switch (version) {
|
switch (version) {
|
||||||
@@ -217,7 +220,7 @@ export async function getApi(version: string, platform: string): Promise<OpenAPI
|
|||||||
isServer ? 'server' : isClient ? 'client' : 'console'
|
isServer ? 'server' : isClient ? 'client' : 'console'
|
||||||
}.json`;
|
}.json`;
|
||||||
|
|
||||||
return specs[target]();
|
return specs[target]() as unknown as OpenAPIV3.Document;
|
||||||
}
|
}
|
||||||
|
|
||||||
const descriptions = import.meta.glob(
|
const descriptions = import.meta.glob(
|
||||||
@@ -228,14 +231,14 @@ const descriptions = import.meta.glob(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export async function getDescription(service: string): Promise<string> {
|
export async function getDescription(service: string) {
|
||||||
const target = `/src/routes/docs/references/[version]/[platform]/[service]/descriptions/${service}.md`;
|
const target = `/src/routes/docs/references/[version]/[platform]/[service]/descriptions/${service}.md`;
|
||||||
|
|
||||||
if (!(target in descriptions)) {
|
if (!(target in descriptions)) {
|
||||||
throw new Error('Missing service description');
|
throw new Error('Missing service description');
|
||||||
}
|
}
|
||||||
|
|
||||||
return descriptions[target]();
|
return descriptions[target]() as unknown as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getService(
|
export async function getService(
|
||||||
@@ -244,7 +247,7 @@ export async function getService(
|
|||||||
service: string
|
service: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
service: {
|
service: {
|
||||||
name: Service;
|
name: ServiceValue;
|
||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
methods: SDKMethod[];
|
methods: SDKMethod[];
|
||||||
@@ -262,7 +265,7 @@ export async function getService(
|
|||||||
|
|
||||||
const data: Awaited<ReturnType<typeof getService>> = {
|
const data: Awaited<ReturnType<typeof getService>> = {
|
||||||
service: {
|
service: {
|
||||||
name: service as Service,
|
name: service as ServiceValue,
|
||||||
description: await getDescription(service)
|
description: await getDescription(service)
|
||||||
},
|
},
|
||||||
methods: []
|
methods: []
|
||||||
@@ -324,7 +327,7 @@ export async function getService(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const demo = await examples[path]();
|
const demo = (await examples[path]()) as unknown as string;
|
||||||
|
|
||||||
data.methods.push({
|
data.methods.push({
|
||||||
id: operation['x-appwrite'].method,
|
id: operation['x-appwrite'].method,
|
||||||
@@ -370,7 +373,7 @@ export function resolveReference(
|
|||||||
export const generateExample = (
|
export const generateExample = (
|
||||||
schema: OpenAPIV3.SchemaObject,
|
schema: OpenAPIV3.SchemaObject,
|
||||||
api: OpenAPIV3.Document<object>,
|
api: OpenAPIV3.Document<object>,
|
||||||
modelType: ModelType = ModelType.REST
|
modelType: ModelTypeValue = ModelType.REST
|
||||||
): object => {
|
): object => {
|
||||||
const properties = Object.keys(schema.properties ?? {}).map((key) => {
|
const properties = Object.keys(schema.properties ?? {}).map((key) => {
|
||||||
const name = key;
|
const name = key;
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<span class="web-icon-github" aria-hidden="true" />
|
<span class="web-icon-github" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<span class="web-icon-x" aria-hidden="true" />
|
<span class="web-icon-x" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -153,7 +153,7 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<span class="web-icon-linkedin" aria-hidden="true" />
|
<span class="web-icon-linkedin" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<div class="web-big-padding-section-level-2">
|
<div class="web-big-padding-section-level-2">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="web-link web-u-color-text-secondary items-baseline" href="/blog">
|
<a class="web-link web-u-color-text-secondary items-baseline" href="/blog">
|
||||||
<span class="web-icon-chevron-left" aria-hidden="true" />
|
<span class="web-icon-chevron-left" aria-hidden="true"></span>
|
||||||
<span>Back to blog</span>
|
<span>Back to blog</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="web-category-header mt-6">
|
<div class="web-category-header mt-6">
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
class="web-u-sep-block-end pb-0"
|
class="web-u-sep-block-end pb-0"
|
||||||
style="background-color:rgba(23, 23, 26, 1); margin-block-end: 2.5rem"
|
style="background-color:rgba(23, 23, 26, 1); margin-block-end: 2.5rem"
|
||||||
>
|
>
|
||||||
<div class="container dark">
|
<div class="dark container">
|
||||||
<div class="web-integrations-top-section">
|
<div class="web-integrations-top-section">
|
||||||
<div class="web-carousel-wrapper">
|
<div class="web-carousel-wrapper">
|
||||||
<a href="/integrations" class="web-button is-text mb-12">
|
<a href="/integrations" class="web-button is-text mb-12">
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext } from 'svelte';
|
import { setContext, type Snippet } from 'svelte';
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children }: Props = $props();
|
||||||
|
|
||||||
setContext('no-paragraph', true);
|
setContext('no-paragraph', true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<blockquote class="web-blockquote">
|
<blockquote class="web-blockquote">
|
||||||
<p class="text-description">
|
<p class="text-description">
|
||||||
<slot />
|
{@render children()}
|
||||||
</p>
|
</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Writable } from 'svelte/store';
|
|
||||||
import { getContext, hasContext } from 'svelte';
|
import { getContext, hasContext } from 'svelte';
|
||||||
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export let content: string;
|
interface Props {
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { content }: Props = $props();
|
||||||
|
|
||||||
if (hasContext('isCodeInsideTd')) {
|
if (hasContext('isCodeInsideTd')) {
|
||||||
// setting `true` correctly uses flex & align center.
|
// setting `true` correctly uses flex & align center.
|
||||||
|
|||||||
@@ -1,30 +1,45 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '$scss/hljs.css';
|
|
||||||
import { getCodeHtml, type Language } from '$lib/utils/code';
|
|
||||||
import { getContext, hasContext } from 'svelte';
|
|
||||||
import { platformMap } from '$lib/utils/references';
|
|
||||||
import { Tooltip } from '$lib/components';
|
import { Tooltip } from '$lib/components';
|
||||||
import { copy } from '$lib/utils/copy';
|
|
||||||
import type { CodeContext } from '../tags/MultiCode.svelte';
|
|
||||||
import { melt } from '@melt-ui/svelte';
|
|
||||||
import { isInTutorialDocs } from '$lib/layouts/Docs.svelte';
|
import { isInTutorialDocs } from '$lib/layouts/Docs.svelte';
|
||||||
|
import { getCodeHtml, type Language } from '$lib/utils/code';
|
||||||
|
import { copy } from '$lib/utils/copy';
|
||||||
|
import { platformMap } from '$lib/utils/references';
|
||||||
|
import '$scss/hljs.css';
|
||||||
|
import { melt } from '@melt-ui/svelte';
|
||||||
|
import { getContext, hasContext } from 'svelte';
|
||||||
|
import type { CodeContext } from '../tags/MultiCode.svelte';
|
||||||
|
|
||||||
export let content: string;
|
interface Props {
|
||||||
export let toCopy: string | undefined = undefined;
|
content: string;
|
||||||
export let language: Language;
|
toCopy?: string | undefined;
|
||||||
export let process: boolean;
|
language: Language;
|
||||||
export let withLineNumbers = true;
|
process: boolean;
|
||||||
export let badge: string | null = null;
|
withLineNumbers?: boolean;
|
||||||
|
badge?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
content,
|
||||||
|
toCopy = undefined,
|
||||||
|
language,
|
||||||
|
process,
|
||||||
|
withLineNumbers = true,
|
||||||
|
badge = null
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
const inTutorialDocs = isInTutorialDocs();
|
const inTutorialDocs = isInTutorialDocs();
|
||||||
const insideMultiCode = hasContext('multi-code');
|
const insideMultiCode = hasContext('multi-code');
|
||||||
const selected = insideMultiCode ? getContext<CodeContext>('multi-code').selected : null;
|
const selected = insideMultiCode ? getContext<CodeContext>('multi-code').selected : null;
|
||||||
|
|
||||||
enum CopyStatus {
|
const CopyStatus = {
|
||||||
Copy = 'Copy',
|
Copy: 'Copy',
|
||||||
Copied = 'Copied!'
|
Copied: 'Copied!'
|
||||||
}
|
} as const;
|
||||||
let copyText = CopyStatus.Copy;
|
type CopyStatusType = keyof typeof CopyStatus;
|
||||||
|
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||||
|
|
||||||
|
let copyText = $state<CopyStatusValue>(CopyStatus.Copy);
|
||||||
|
|
||||||
async function handleCopy() {
|
async function handleCopy() {
|
||||||
await copy(toCopy ?? content);
|
await copy(toCopy ?? content);
|
||||||
|
|
||||||
@@ -50,11 +65,11 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$: result = process
|
let result = $derived(
|
||||||
? getCodeHtml({ content, language: language ?? 'sh', withLineNumbers })
|
process ? getCodeHtml({ content, language: language ?? 'sh', withLineNumbers }) : content
|
||||||
: content;
|
);
|
||||||
|
|
||||||
$: badgeValue = badge ?? platformMap[language];
|
let badgeValue = $derived(badge ?? platformMap[language]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if insideMultiCode}
|
{#if insideMultiCode}
|
||||||
@@ -80,19 +95,19 @@
|
|||||||
<ul class="buttons-list flex gap-2">
|
<ul class="buttons-list flex gap-2">
|
||||||
<li class="buttons-list-item ps-5">
|
<li class="buttons-list-item ps-5">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<button
|
{#snippet asChild({ trigger })}
|
||||||
slot="asChild"
|
<button
|
||||||
let:trigger
|
use:melt={trigger}
|
||||||
use:melt={trigger}
|
onclick={handleCopy}
|
||||||
on:click={handleCopy}
|
class="web-icon-button"
|
||||||
class="web-icon-button"
|
aria-label="copy code from code-snippet"
|
||||||
aria-label="copy code from code-snippet"
|
>
|
||||||
>
|
<span class="web-icon-copy" aria-hidden="true"></span>
|
||||||
<span class="web-icon-copy" aria-hidden="true" />
|
</button>
|
||||||
</button>
|
{/snippet}
|
||||||
<svelte:fragment slot="tooltip">
|
{#snippet tooltip()}
|
||||||
{copyText}
|
{copyText}
|
||||||
</svelte:fragment>
|
{/snippet}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,15 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, hasContext, onMount } from 'svelte';
|
|
||||||
import type { LayoutContext } from '../layouts/Article.svelte';
|
|
||||||
import { isInPolicy } from '$markdoc/layouts/Policy.svelte';
|
|
||||||
import { slugify } from '$lib/utils/slugify';
|
import { slugify } from '$lib/utils/slugify';
|
||||||
|
import { isInPolicy } from '$markdoc/layouts/Policy.svelte';
|
||||||
|
import { getContext, hasContext, onMount, type Snippet } from 'svelte';
|
||||||
|
import type { LayoutContext } from '../layouts/Article.svelte';
|
||||||
|
|
||||||
export let level: number;
|
interface Props {
|
||||||
export let id: string | undefined = undefined;
|
level: number;
|
||||||
export let step: number | undefined = undefined;
|
id?: string | undefined;
|
||||||
export let inReferences = false;
|
step?: number | undefined;
|
||||||
|
inReferences?: boolean;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
let href: string;
|
const {
|
||||||
|
level,
|
||||||
|
id = undefined,
|
||||||
|
step = undefined,
|
||||||
|
inReferences = false,
|
||||||
|
children
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let href: string = $state('');
|
||||||
const tag = `h${level + 1}`;
|
const tag = `h${level + 1}`;
|
||||||
const ctx = hasContext('headings') ? getContext<LayoutContext>('headings') : undefined;
|
const ctx = hasContext('headings') ? getContext<LayoutContext>('headings') : undefined;
|
||||||
|
|
||||||
@@ -20,7 +31,7 @@
|
|||||||
4: 'text-sub-body font-medium'
|
4: 'text-sub-body font-medium'
|
||||||
};
|
};
|
||||||
|
|
||||||
let element: HTMLElement | undefined;
|
let element: HTMLElement | undefined = $state();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!element || !$ctx) {
|
if (!element || !$ctx) {
|
||||||
@@ -57,8 +68,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const inPolicy = isInPolicy();
|
const inPolicy = isInPolicy();
|
||||||
$: headingClass =
|
let headingClass = $derived(
|
||||||
inPolicy && level === 1 ? 'text-title font-aeonik-pro mb-4 mt-8' : classList[level];
|
inPolicy && level === 1 ? 'text-title font-aeonik-pro mb-4 mt-8' : classList[level]
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:element
|
<svelte:element
|
||||||
@@ -69,5 +81,5 @@
|
|||||||
class:web-snap-location-references={id && inReferences}
|
class:web-snap-location-references={id && inReferences}
|
||||||
class="{headingClass} text-primary scroll-m-32 font-medium"
|
class="{headingClass} text-primary scroll-m-32 font-medium"
|
||||||
>
|
>
|
||||||
<a href={`#${href}`} class=""><slot /></a>
|
<a href={`#${href}`} class="">{@render children()}</a>
|
||||||
</svelte:element>
|
</svelte:element>
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
import { createDialog, melt } from '@melt-ui/svelte';
|
import { createDialog, melt } from '@melt-ui/svelte';
|
||||||
import { getContext, hasContext } from 'svelte';
|
import { getContext, hasContext } from 'svelte';
|
||||||
import { fade, scale } from 'svelte/transition';
|
|
||||||
import { quadInOut } from 'svelte/easing';
|
import { quadInOut } from 'svelte/easing';
|
||||||
|
import { fade, scale } from 'svelte/transition';
|
||||||
|
|
||||||
export let src: string;
|
interface Props {
|
||||||
export let alt: string;
|
src: string;
|
||||||
export let title: string;
|
alt: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { src, alt, title }: Props = $props();
|
||||||
|
|
||||||
const inTable = hasContext('in-table') ? getContext('in-table') : false;
|
const inTable = hasContext('in-table') ? getContext('in-table') : false;
|
||||||
const isAudio = /\.(wav|mp3|m4a|ogg)$/i.test(src);
|
const isAudio = /\.(wav|mp3|m4a|ogg)$/i.test(src);
|
||||||
@@ -25,7 +29,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:scroll={handleScroll} />
|
<svelte:window onscroll={handleScroll} />
|
||||||
|
|
||||||
{#if inTable || isAudio}
|
{#if inTable || isAudio}
|
||||||
{#if isAudio}
|
{#if isAudio}
|
||||||
@@ -41,16 +45,18 @@
|
|||||||
<div class="abs">
|
<div class="abs">
|
||||||
<Tooltip closeOnPointerDown>
|
<Tooltip closeOnPointerDown>
|
||||||
<button class="web-button is-secondary cursor-pointer" use:melt={$trigger}>
|
<button class="web-button is-secondary cursor-pointer" use:melt={$trigger}>
|
||||||
<span class="icon-arrow-expand" aria-hidden="true" />
|
<span class="icon-arrow-expand" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<svelte:fragment slot="tooltip">Expand</svelte:fragment>
|
{#snippet tooltip()}
|
||||||
|
Expand
|
||||||
|
{/snippet}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $open}
|
{#if $open}
|
||||||
<div use:melt={$portalled}>
|
<div use:melt={$portalled}>
|
||||||
<div use:melt={$overlay} class="overlay" transition:fade={{ duration: 350 }} />
|
<div use:melt={$overlay} class="overlay" transition:fade={{ duration: 350 }}></div>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="web-media content"
|
class="web-media content"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
setContext('isCodeInsideTd', isCodeInsideTd);
|
setContext('isCodeInsideTd', isCodeInsideTd);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<td class="py-[0.5625rem] px-3 text-sm leading-[1.375rem]" {align} {colspan} {rowspan}>
|
<td class="px-3 py-[0.5625rem] text-sm leading-[1.375rem]" {align} {colspan} {rowspan}>
|
||||||
<div class:center-align={isCodeInsideTd}>
|
<div class:center-align={isCodeInsideTd}>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
style:width={width ? `${width}px` : undefined}
|
style:width={width ? `${width}px` : undefined}
|
||||||
style:min-inline-size={width ? 'unset' : undefined}
|
style:min-inline-size={width ? 'unset' : undefined}
|
||||||
role="columnheader"
|
role="columnheader"
|
||||||
class="min-w-44 py-[0.5625rem] px-3"
|
class="min-w-44 px-3 py-[0.5625rem]"
|
||||||
{align}
|
{align}
|
||||||
>
|
>
|
||||||
<span class="text-sm leading-[1.375rem] text-[hsl(var(--web-color-primary))]">
|
<span class="text-sm leading-[1.375rem] text-[hsl(var(--web-color-primary))]">
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { Accordion } from '$lib/components/Accordion';
|
import { Accordion } from '$lib/components/Accordion';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<slot />
|
{@render children()}
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AccordionItem } from '$lib/components/Accordion';
|
import { AccordionItem } from '$lib/components/Accordion';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
export let title: string;
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AccordionItem {title}>
|
<AccordionItem {title}>
|
||||||
<slot />
|
{@render children()}
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { isInDocs } from '$lib/layouts/Docs.svelte';
|
import { isInDocs } from '$lib/layouts/Docs.svelte';
|
||||||
import { isInChangelog } from '$markdoc/layouts/Changelog.svelte';
|
import { isInChangelog } from '$markdoc/layouts/Changelog.svelte';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
interface Props {
|
||||||
|
href: string;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
export let href: string;
|
const { href, children }: Props = $props();
|
||||||
|
|
||||||
const isExternal = ['http://', 'https://'].some((prefix) => href.startsWith(prefix));
|
const isExternal = ['http://', 'https://'].some((prefix) => href.startsWith(prefix));
|
||||||
const target = isExternal ? '_blank' : undefined;
|
const target = isExternal ? '_blank' : undefined;
|
||||||
@@ -11,15 +16,17 @@
|
|||||||
const inChangelog = isInChangelog();
|
const inChangelog = isInChangelog();
|
||||||
const inDocs = isInDocs();
|
const inDocs = isInDocs();
|
||||||
|
|
||||||
$: classes = (() => {
|
let classes = $derived(
|
||||||
if (inDocs) return 'web-link text-paragraph-md';
|
(() => {
|
||||||
if (inChangelog) return 'web-link text-paragraph-lg';
|
if (inDocs) return 'web-link text-paragraph-md';
|
||||||
return '';
|
if (inChangelog) return 'web-link text-paragraph-lg';
|
||||||
})();
|
return '';
|
||||||
|
})()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a class={classes} data-in-changelog={inChangelog ? '' : undefined} {href} {target} {rel}
|
<a class={classes} data-in-changelog={inChangelog ? '' : undefined} {href} {target} {rel}
|
||||||
><slot /><span class="icon-cheveron-right" style:font-size="16px" /></a
|
>{@render children()}<span class="icon-cheveron-right" style:font-size="16px"></span></a
|
||||||
>
|
>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -2,17 +2,29 @@
|
|||||||
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
|
import { PUBLIC_APPWRITE_DASHBOARD } from '$env/static/public';
|
||||||
import { classNames } from '$lib/utils/classnames';
|
import { classNames } from '$lib/utils/classnames';
|
||||||
|
|
||||||
export let title: string = 'Start building with Appwrite today';
|
interface Props {
|
||||||
export let description: string = 'Build with the frameworks and languages you want.';
|
title?: string;
|
||||||
export let point1: string = 'Built in security';
|
description?: string;
|
||||||
export let point2: string = 'Scalable infrastructure';
|
point1?: string;
|
||||||
export let point3: string = 'No vendor lock in';
|
point2?: string;
|
||||||
export let point4: string = 'Highly customizable backend';
|
point3?: string;
|
||||||
export let cta: string = 'Get started';
|
point4?: string;
|
||||||
|
cta?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
title = 'Start building with Appwrite today',
|
||||||
|
description = 'Build with the frameworks and languages you want.',
|
||||||
|
point1 = 'Built in security',
|
||||||
|
point2 = 'Scalable infrastructure',
|
||||||
|
point3 = 'No vendor lock in',
|
||||||
|
point4 = 'Highly customizable backend',
|
||||||
|
cta = 'Get started',
|
||||||
|
url = PUBLIC_APPWRITE_DASHBOARD
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
let benefits: Array<string> = [point1, point2, point3, point4];
|
let benefits: Array<string> = [point1, point2, point3, point4];
|
||||||
|
|
||||||
export let url: string = PUBLIC_APPWRITE_DASHBOARD;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<ul class="web-grid-row-2">
|
<ul class="web-grid-row-2">
|
||||||
<slot />
|
{@render children()}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext } from 'svelte';
|
import { setContext, type Snippet } from 'svelte';
|
||||||
|
|
||||||
export let href: string;
|
interface Props {
|
||||||
export let light = '';
|
href: string;
|
||||||
export let dark = '';
|
light?: string;
|
||||||
export let title: string;
|
dark?: string;
|
||||||
|
title: string;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { href, light = '', dark = '', title, children }: Props = $props();
|
||||||
|
|
||||||
setContext('no-paragraph', true);
|
setContext('no-paragraph', true);
|
||||||
</script>
|
</script>
|
||||||
@@ -17,7 +22,7 @@
|
|||||||
{title}
|
{title}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="text-sub-body mt-1">
|
<p class="text-sub-body mt-1">
|
||||||
<slot />
|
{@render children()}
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext } from 'svelte';
|
import { setContext, type Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
href: string;
|
||||||
|
icon?: string;
|
||||||
|
image?: string;
|
||||||
|
title: string;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { href, icon = '', image = '', title, children }: Props = $props();
|
||||||
|
|
||||||
export let href: string;
|
|
||||||
export let icon = '';
|
|
||||||
export let image = '';
|
|
||||||
export let title: string;
|
|
||||||
setContext('no-paragraph', true);
|
setContext('no-paragraph', true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -12,7 +18,7 @@
|
|||||||
<a {href} class="web-card is-normal" style:margin-block-end="0">
|
<a {href} class="web-card is-normal" style:margin-block-end="0">
|
||||||
<header class="flex items-center gap-1">
|
<header class="flex items-center gap-1">
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<span class="{icon} web-u-font-size-24" aria-hidden="true" />
|
<span class="{icon} web-u-font-size-24" aria-hidden="true"></span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if image}
|
{#if image}
|
||||||
<img src={image} alt={title} />
|
<img src={image} alt={title} />
|
||||||
@@ -22,7 +28,7 @@
|
|||||||
</h4>
|
</h4>
|
||||||
</header>
|
</header>
|
||||||
<p class="text-sub-body mt-1" style:margin-block="0">
|
<p class="text-sub-body mt-1" style:margin-block="0">
|
||||||
<slot />
|
{@render children()}
|
||||||
</p>
|
</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
<section class="count-secondary-title">
|
<section class="count-secondary-title">
|
||||||
<slot />
|
{@render children()}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let title = '';
|
interface Props {
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title = '' }: Props = $props();
|
||||||
|
|
||||||
const el = title ? 'h3' : 'span';
|
const el = title ? 'h3' : 'span';
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let icon: string;
|
interface Props {
|
||||||
export let size = 's';
|
icon: string;
|
||||||
|
size?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { icon, size = 's' }: Props = $props();
|
||||||
|
|
||||||
const sizes: Record<string, string> = {
|
const sizes: Record<string, string> = {
|
||||||
s: '16px',
|
s: '16px',
|
||||||
@@ -10,4 +14,4 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<span class="icon-{icon}" style:font-size={sizes[size]} />
|
<span class="icon-{icon}" style:font-size={sizes[size]}></span>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let src: string;
|
interface Props {
|
||||||
export let alt: string;
|
src: string;
|
||||||
export let size: string = 's';
|
alt: string;
|
||||||
|
size?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { src, alt, size = 's' }: Props = $props();
|
||||||
|
|
||||||
const sizes: Record<string, string> = {
|
const sizes: Record<string, string> = {
|
||||||
s: '16px',
|
s: '16px',
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let title: string;
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="web-inline-info">
|
<div class="web-inline-info">
|
||||||
<span class="icon-info" aria-hidden="true" />
|
<span class="icon-info" aria-hidden="true"></span>
|
||||||
<h5 class="text-sub-body text-primary font-medium">{title}</h5>
|
<h5 class="text-sub-body text-primary font-medium">{title}</h5>
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script context="module" lang="ts">
|
<script module lang="ts">
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
|
|
||||||
export type CodeContext = {
|
export type CodeContext = {
|
||||||
@@ -9,12 +9,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { copy } from '$lib/utils/copy';
|
|
||||||
import { get, type Readable, writable } from 'svelte/store';
|
|
||||||
import { Select, Tooltip } from '$lib/components';
|
import { Select, Tooltip } from '$lib/components';
|
||||||
import { getContext, hasContext, onMount, setContext } from 'svelte';
|
|
||||||
import { type Language, multiCodeSelectedLanguage } from '$lib/utils/code';
|
import { type Language, multiCodeSelectedLanguage } from '$lib/utils/code';
|
||||||
|
import { copy } from '$lib/utils/copy';
|
||||||
import { Platform, platformMap, preferredPlatform } from '$lib/utils/references';
|
import { Platform, platformMap, preferredPlatform } from '$lib/utils/references';
|
||||||
|
import { getContext, hasContext, onMount, setContext, type Snippet } from 'svelte';
|
||||||
|
import { get, type Readable, writable } from 'svelte/store';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children }: Props = $props();
|
||||||
|
|
||||||
setContext<CodeContext>('multi-code', {
|
setContext<CodeContext>('multi-code', {
|
||||||
content: writable(''),
|
content: writable(''),
|
||||||
@@ -42,12 +48,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
enum CopyStatus {
|
const CopyStatus = {
|
||||||
Copy = 'Copy',
|
Copy: 'Copy',
|
||||||
Copied = 'Copied!'
|
Copied: 'Copied!'
|
||||||
}
|
} as const;
|
||||||
|
type CopyStatusType = keyof typeof CopyStatus;
|
||||||
|
type CopyStatusValue = (typeof CopyStatus)[CopyStatusType];
|
||||||
|
|
||||||
let copyText = CopyStatus.Copy;
|
let copyText = $state<CopyStatusValue>(CopyStatus.Copy);
|
||||||
|
|
||||||
async function handleCopy() {
|
async function handleCopy() {
|
||||||
await copy($content);
|
await copy($content);
|
||||||
@@ -117,20 +125,20 @@
|
|||||||
<li class="buttons-list-item" style="padding-inline-start: 13px">
|
<li class="buttons-list-item" style="padding-inline-start: 13px">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<button
|
<button
|
||||||
on:click={handleCopy}
|
onclick={handleCopy}
|
||||||
class="web-icon-button"
|
class="web-icon-button"
|
||||||
aria-label="copy code from code-snippet"
|
aria-label="copy code from code-snippet"
|
||||||
><span class="web-icon-copy" aria-hidden="true" /></button
|
><span class="web-icon-copy" aria-hidden="true"></span></button
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="tooltip">
|
{#snippet tooltip()}
|
||||||
{copyText}
|
{copyText}
|
||||||
</svelte:fragment>
|
{/snippet}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="web-code-snippet-content">
|
<div class="web-code-snippet-content">
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1 +1,11 @@
|
|||||||
<span class="web-u-only-dark"><slot /></span>
|
<script lang="ts">
|
||||||
|
import { Accordion } from '$lib/components/Accordion';
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="web-u-only-dark">{@render children()}</span>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user