Landing page (#7108)
103
.github/actions/deploy-to-cloudflare-pages/action.yml
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
name: "Deploy to Cloudflare Pages"
|
||||
description: "Deploys to Cloudflare Pages on either a temporary branch with preview comment, or on the production version if on the main branch."
|
||||
inputs:
|
||||
project_name:
|
||||
description: "CloudFlare Pages project name"
|
||||
comment_body:
|
||||
description: "The message to display when deployment is ready"
|
||||
default: "Deployment is ready."
|
||||
required: false
|
||||
production_url:
|
||||
description: "The URL to mention as the production URL."
|
||||
required: true
|
||||
deploy_dir:
|
||||
description: "The directory from which to deploy."
|
||||
required: true
|
||||
cloudflare_api_token:
|
||||
description: "The Cloudflare API token to use for deployment."
|
||||
required: true
|
||||
cloudflare_account_id:
|
||||
description: "The Cloudflare account ID to use for deployment."
|
||||
required: true
|
||||
github_token:
|
||||
description: "The GitHub token to use for posting PR comments."
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# Install wrangler globally to avoid workspace issues
|
||||
- name: Install Wrangler
|
||||
shell: bash
|
||||
run: npm install -g wrangler
|
||||
|
||||
# Deploy using Wrangler (use pre-installed wrangler)
|
||||
- name: Deploy to Cloudflare Pages
|
||||
id: deploy
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ inputs.cloudflare_api_token }}
|
||||
accountId: ${{ inputs.cloudflare_account_id }}
|
||||
command: pages deploy ${{ inputs.deploy_dir }} --project-name=${{ inputs.project_name}} --branch=${{ github.ref_name }}
|
||||
wranglerVersion: '' # Use pre-installed version
|
||||
|
||||
# Deploy preview for PRs
|
||||
- name: Deploy Preview to Cloudflare Pages
|
||||
id: preview-deployment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ inputs.cloudflare_api_token }}
|
||||
accountId: ${{ inputs.cloudflare_account_id }}
|
||||
command: pages deploy ${{ inputs.deploy_dir }} --project-name=${{ inputs.project_name}} --branch=pr-${{ github.event.pull_request.number }}
|
||||
wranglerVersion: '' # Use pre-installed version
|
||||
|
||||
# Post deployment URL as PR comment
|
||||
- name: Comment PR with Preview URL
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v8
|
||||
env:
|
||||
COMMENT_BODY: ${{ inputs.comment_body }}
|
||||
PRODUCTION_URL: ${{ inputs.production_url }}
|
||||
PROJECT_NAME: ${{ inputs.project_name }}
|
||||
with:
|
||||
github-token: ${{ inputs.github_token }}
|
||||
script: |
|
||||
const prNumber = context.issue.number;
|
||||
// Construct preview URL based on Cloudflare Pages pattern
|
||||
const projectName = process.env.PROJECT_NAME;
|
||||
const previewUrl = `https://pr-${prNumber}.${projectName}.pages.dev`;
|
||||
|
||||
// Check if we already commented
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const customMessage = process.env.COMMENT_BODY;
|
||||
const botComment = comments.data.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes(customMessage)
|
||||
);
|
||||
|
||||
const mainUrl = process.env.PRODUCTION_URL;
|
||||
const commentBody = `${customMessage}!\n\n🔗 Preview URL: ${previewUrl}\n📖 Production URL: ${mainUrl}\n\n✅ All checks passed\n\n_This preview will be updated automatically with new commits._`;
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: commentBody
|
||||
});
|
||||
} else {
|
||||
// Create new comment
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: prNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody
|
||||
});
|
||||
}
|
78
.github/workflows/deploy-docs.yml
vendored
@ -116,73 +116,13 @@ jobs:
|
||||
test -d site/assets || (echo "ERROR: site/assets directory not found" && exit 1)
|
||||
echo "✅ Site validation passed"
|
||||
|
||||
# Install wrangler globally to avoid workspace issues
|
||||
- name: Install Wrangler
|
||||
run: |
|
||||
npm install -g wrangler
|
||||
|
||||
# Deploy using Wrangler (use pre-installed wrangler)
|
||||
- name: Deploy to Cloudflare Pages
|
||||
id: deploy
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
- name: Deploy
|
||||
uses: ./.github/actions/deploy-to-cloudflare-pages
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy site --project-name=trilium-docs --branch=${{ github.ref_name }}
|
||||
wranglerVersion: '' # Use pre-installed version
|
||||
|
||||
# Deploy preview for PRs
|
||||
- name: Deploy Preview to Cloudflare Pages
|
||||
id: preview-deployment
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy site --project-name=trilium-docs --branch=pr-${{ github.event.pull_request.number }}
|
||||
wranglerVersion: '' # Use pre-installed version
|
||||
|
||||
# Post deployment URL as PR comment
|
||||
- name: Comment PR with Preview URL
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const prNumber = context.issue.number;
|
||||
// Construct preview URL based on Cloudflare Pages pattern
|
||||
const previewUrl = `https://pr-${prNumber}.trilium-docs.pages.dev`;
|
||||
const mainUrl = 'https://docs.triliumnotes.org';
|
||||
|
||||
// Check if we already commented
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const botComment = comments.data.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('Documentation preview is ready')
|
||||
);
|
||||
|
||||
const commentBody = `📚 Documentation preview is ready!\n\n🔗 Preview URL: ${previewUrl}\n📖 Production URL: ${mainUrl}\n\n✅ All checks passed\n\n_This preview will be updated automatically with new commits._`;
|
||||
|
||||
if (botComment) {
|
||||
// Update existing comment
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: commentBody
|
||||
});
|
||||
} else {
|
||||
// Create new comment
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: prNumber,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: commentBody
|
||||
});
|
||||
}
|
||||
project_name: "trilium-docs"
|
||||
comment_body: "📚 Documentation preview is ready"
|
||||
production_url: "https://docs.triliumnotes.org"
|
||||
deploy_dir: "site"
|
||||
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
2
.github/workflows/playwright.yml
vendored
@ -4,6 +4,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "apps/website/**"
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
|
48
.github/workflows/website.yml
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
name: Deploy website
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "apps/website/**"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/website/**"
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build & deploy website
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: write # For PR preview comments
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: pnpm/action-setup@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --filter website --frozen-lockfile
|
||||
|
||||
- name: Build the website
|
||||
run: pnpm website:build
|
||||
|
||||
- name: Deploy
|
||||
uses: ./.github/actions/deploy-to-cloudflare-pages
|
||||
with:
|
||||
project_name: "trilium-homepage"
|
||||
comment_body: "📚 Website preview is ready"
|
||||
production_url: "https://triliumnotes.org"
|
||||
deploy_dir: "apps/website/dist"
|
||||
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
48
apps/website/.gitignore
vendored
@ -1,28 +1,28 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
.netlify
|
||||
.wrangler
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
|
||||
# Paraglide
|
||||
src/lib/paraglide
|
||||
|
||||
project.inlang/cache
|
||||
*.d.ts
|
||||
!types-assets.d.ts
|
||||
*.map
|
@ -1 +0,0 @@
|
||||
engine-strict=true
|
@ -1,11 +1,15 @@
|
||||
# apps/website
|
||||
# `create-preact`
|
||||
|
||||
Landing page for Trilium Notes powered by [Svelte](https://github.com/sveltejs/cli) and [Tailwind CSS](https://tailwindcss.com/).
|
||||
<h2 align="center">
|
||||
<img height="256" width="256" src="./src/assets/preact.svg">
|
||||
</h2>
|
||||
|
||||
## Developing
|
||||
<h3 align="center">Get started using Preact and Vite!</h3>
|
||||
|
||||
To run a dev server that will hot-reload changes: `pnpm dev`
|
||||
## Getting Started
|
||||
|
||||
## Building
|
||||
- `npm run dev` - Starts a dev server at http://localhost:5173/
|
||||
|
||||
To create a production build: `pnpm build`
|
||||
- `npm run build` - Builds for production, emitting to `dist/`. Prerenders all found routes in app to static HTML
|
||||
|
||||
- `npm run preview` - Starts a server at http://localhost:4173/ to test production build locally
|
||||
|
@ -1,39 +0,0 @@
|
||||
import js from '@eslint/js';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import globals from 'globals';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelteConfig from './svelte.config.js';
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
||||
|
||||
export default ts.config(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: { ...globals.browser, ...globals.node }
|
||||
},
|
||||
rules: { // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
"no-undef": 'off' }
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/*.svelte',
|
||||
'**/*.svelte.ts',
|
||||
'**/*.svelte.js'
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
extraFileExtensions: ['.svelte'],
|
||||
parser: ts.parser,
|
||||
svelteConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
14
apps/website/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>Trilium Notes</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script prerender type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||
"hello_world": "Hello, {name} from en!"
|
||||
}
|
@ -1,37 +1,25 @@
|
||||
{
|
||||
"name": "website",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"name": "@triliumnext/website",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.5",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@sveltejs/adapter-auto": "^6.0.0",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-svelte": "^3.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"mdsvex": "^0.12.3",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.20.0",
|
||||
"vite": "^7.0.0"
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inlang/paraglide-js": "^2.0.0"
|
||||
"preact": "^10.26.9",
|
||||
"preact-iso": "^2.10.0",
|
||||
"preact-render-to-string": "^6.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.10.2",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-config-preact": "^2.0.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.0.4"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "preact"
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
dv1iXGpHP2mMvuQQo4
|
@ -1,14 +0,0 @@
|
||||
{
|
||||
"$schema": "https://inlang.com/schema/project-settings",
|
||||
"modules": [
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
|
||||
],
|
||||
"plugin.inlang.messageFormat": {
|
||||
"pathPattern": "./messages/{locale}.json"
|
||||
},
|
||||
"baseLocale": "en",
|
||||
"locales": [
|
||||
"en"
|
||||
]
|
||||
}
|
BIN
apps/website/public/collection_board.webp
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
apps/website/public/collection_calendar.webp
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
apps/website/public/collection_geomap.webp
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
apps/website/public/collection_table.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
apps/website/public/screenshot_desktop_mac_dark.webp
Normal file
After Width: | Height: | Size: 147 KiB |
BIN
apps/website/public/screenshot_desktop_mac_light.webp
Normal file
After Width: | Height: | Size: 157 KiB |
BIN
apps/website/public/screenshot_desktop_win_dark.webp
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
apps/website/public/screenshot_desktop_win_light.webp
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
apps/website/public/type_canvas.webp
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
apps/website/public/type_code.webp
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
apps/website/public/type_file.webp
Normal file
After Width: | Height: | Size: 124 KiB |
BIN
apps/website/public/type_mermaid.webp
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
apps/website/public/type_mindmap.webp
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
apps/website/public/type_text.webp
Normal file
After Width: | Height: | Size: 57 KiB |
@ -1,10 +0,0 @@
|
||||
@import 'tailwindcss';
|
||||
@plugin '@tailwindcss/typography';
|
||||
|
||||
main a {
|
||||
text-decoration: revert;
|
||||
}
|
||||
|
||||
a.rounded-full, a.rounded-xl {
|
||||
text-decoration: none;
|
||||
}
|
13
apps/website/src/app.d.ts
vendored
@ -1,13 +0,0 @@
|
||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
@ -1,12 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="%paraglide.lang%">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover" class="dark:bg-black dark:text-white">
|
||||
%sveltekit.body%
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M11 3v7H7l5 6 5-6h-4V3z"/><path d="M19 19H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2z"/></svg>
|
After Width: | Height: | Size: 253 B |
1
apps/website/src/assets/boxicons/bx-buy-me-a-coffee.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M12 8.07v-.01c-.01 0-.03.02-.04.03h.02Zm-.02 12.51h.01s.02-.03.03-.04c-.02 0-.03.02-.05.04Zm.03.12s-.03-.02-.05-.02c.01 0 .03.01.04.02Zm2.25-.66c.23-.21.37-.51.4-.82l.78-8.29c-.35-.12-.7-.2-1.1-.2-.69 0-1.24.24-1.88.51-.72.31-1.53.66-2.59.66-.44 0-.88-.06-1.31-.18l.73 7.5c.03.31.17.61.4.82s.53.33.85.33c0 0 1.04.05 1.38.05.37 0 1.49-.05 1.49-.05.31 0 .62-.12.85-.33M12.01 8.05l-.02-.02H12v.02Z"/><path d="M18.74 6.79c-.1-.5-.32-.97-.83-1.15-.16-.06-.35-.08-.48-.2s-.16-.3-.19-.48c-.05-.32-.1-.63-.16-.94-.05-.27-.09-.57-.21-.82-.16-.33-.5-.53-.83-.66-.17-.06-.34-.12-.52-.16-.83-.22-1.71-.3-2.56-.35-1.03-.06-2.06-.04-3.08.05-.76.07-1.57.15-2.29.42-.27.1-.54.21-.74.42-.25.25-.33.64-.15.95.13.22.35.38.58.48.3.13.61.24.94.3.9.2 1.82.28 2.74.31 1.01.04 2.03 0 3.04-.1.25-.03.5-.06.75-.1.29-.04.48-.43.39-.7-.1-.32-.38-.44-.7-.39-.05 0-.09.01-.14.02h-.03c-.11.02-.21.03-.32.04l-.66.06c-.49.03-.99.05-1.49.05-.49 0-.97-.01-1.46-.05q-.33-.015-.66-.06l-.3-.03h-.1l-.02-.02h-.1l-.6-.12c-.02 0-.04-.02-.05-.03-.01-.02-.02-.04-.02-.06s0-.04.02-.06c.01-.02.03-.03.05-.03.17-.04.35-.07.52-.1.06 0 .12-.02.18-.03.11 0 .22-.03.33-.04.95-.1 1.9-.13 2.85-.1.46.01.92.04 1.39.09l.3.03c.04 0 .08 0 .11.01h.08c.22.04.44.08.66.13.33.07.75.09.89.45.05.11.07.24.09.36l.03.15c.08.36.15.72.23 1.08v.08c0 .03-.02.05-.03.07s-.04.04-.06.06c-.02.01-.05.02-.08.03H16l-.05.01c-.15.02-.3.04-.44.05l-.87.09q-.87.075-1.74.09c-.3 0-.59.01-.89.01-1.18 0-2.36-.07-3.53-.21-.12-.01-.24-.03-.36-.04-.02 0-.11-.01-.13-.02-.08-.01-.16-.02-.24-.04-.27-.04-.54-.09-.81-.13-.33-.05-.64-.03-.94.13-.24.13-.44.34-.56.58-.13.26-.17.55-.22.83-.06.28-.15.59-.11.88.07.63.51 1.14 1.14 1.25a33 33 0 0 0 8.88.37l.58-.06c.06 0 .12 0 .18.02s.11.05.15.09.08.09.1.15.03.12.02.18l-.06.58-.36 3.52c-.13 1.23-.25 2.46-.38 3.7-.04.35-.07.69-.11 1.04-.03.34-.04.69-.1 1.03-.1.53-.46.86-.99.98-.48.11-.97.17-1.46.17-.55 0-1.09-.02-1.64-.02-.58 0-1.3-.05-1.75-.48-.4-.38-.45-.98-.5-1.49l-.21-2.05-.4-3.8-.26-2.46s0-.08-.01-.12c-.03-.29-.24-.58-.57-.57-.28.01-.6.25-.57.57l.19 1.82.39 3.77.33 3.21.06.62c.12 1.12.98 1.72 2.04 1.89.62.1 1.25.12 1.88.13.81.01 1.62.04 2.41-.1 1.17-.22 2.05-1 2.18-2.21.04-.35.07-.7.11-1.05l.36-3.48.39-3.79.18-1.74c0-.09.05-.17.1-.23s.14-.11.22-.12c.34-.07.66-.18.89-.43.38-.41.46-.94.32-1.47l-.11-.56ZM6.26 7.97s.01 0 .02.02zm11.36.01c-.12.11-.3.17-.48.19-2.01.3-4.05.45-6.09.38-1.46-.05-2.9-.21-4.34-.42-.14-.02-.29-.05-.39-.15-.06-.06-.08-.14-.1-.23v.01-.01c-.03-.2.02-.43.05-.6.04-.22.13-.51.39-.54.4-.05.87.12 1.27.18.48.07.96.13 1.45.18 2.07.19 4.17.16 6.23-.12.38-.05.75-.11 1.12-.18.33-.06.7-.17.9.17.14.23.16.55.13.81 0 .12-.06.22-.14.3Z"/><path d="M6.22 7.77c0-.02.01-.05 0-.05z"/></svg>
|
After Width: | Height: | Size: 2.8 KiB |
1
apps/website/src/assets/boxicons/bx-calendar.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M19 4h-2V2h-2v2H9V2H7v2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2M5 20V8h14V6v14z"/></svg>
|
After Width: | Height: | Size: 273 B |
1
apps/website/src/assets/boxicons/bx-chevrons-up.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m6.29 11.29 1.42 1.42L12 8.41l4.29 4.3 1.42-1.42L12 5.59z"/><path d="m6.29 16.29 1.42 1.42 4.29-4.3 4.29 4.3 1.42-1.42-5.71-5.7z"/></svg>
|
After Width: | Height: | Size: 292 B |
1
apps/website/src/assets/boxicons/bx-code.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M9.71 16.29 5.41 12l4.3-4.29-1.42-1.42L2.59 12l5.7 5.71zm6 1.42 5.7-5.71-5.7-5.71-1.42 1.42 4.3 4.29-4.3 4.29z"/></svg>
|
After Width: | Height: | Size: 274 B |
1
apps/website/src/assets/boxicons/bx-columns-3.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M20 3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2M4 19V5h4v14zm6 0V5h4v14zm6 0V5h4v14z"/></svg>
|
After Width: | Height: | Size: 276 B |
1
apps/website/src/assets/boxicons/bx-discussion.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M20 8h-3V4c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h1v2c0 .38.21.72.55.89.14.07.29.11.45.11.21 0 .42-.07.6-.2L9 15v2c0 1.1.9 2 2 2h3.67l3.73 2.8c.18.13.39.2.6.2.15 0 .31-.04.45-.11A1 1 0 0 0 20 21v-2c1.1 0 2-.9 2-2v-7c0-1.1-.9-2-2-2M6 12H4V4h11v8h-5c-.12 0-.24.03-.35.07-.04.02-.07.04-.11.06-.05.02-.09.04-.14.07L7 14v-1c0-.55-.45-1-1-1m14 5h-1c-.55 0-1 .45-1 1v1l-2.4-1.8a1 1 0 0 0-.6-.2h-4v-3h4c1.1 0 2-.9 2-2v-2h3z"/></svg>
|
After Width: | Height: | Size: 589 B |
1
apps/website/src/assets/boxicons/bx-docker.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M20.17 9.82a5 5 0 0 0-.84.07 3.12 3.12 0 0 0-1.43-2.14l-.28-.16-.19.27a3.7 3.7 0 0 0-.51 1.19 2.84 2.84 0 0 0 .33 2.22 4.1 4.1 0 0 1-1.45.35H2.63a.63.63 0 0 0-.63.62 9.6 9.6 0 0 0 .58 3.39 5 5 0 0 0 2 2.6 8.86 8.86 0 0 0 4.42.95 13.3 13.3 0 0 0 2.42-.18 10.1 10.1 0 0 0 3.19-1.15A8.9 8.9 0 0 0 16.78 16a12 12 0 0 0 2.13-3.67h.19a3.08 3.08 0 0 0 2.23-.84 2.36 2.36 0 0 0 .59-.87l.08-.22-.2-.16a2.7 2.7 0 0 0-1.63-.42"/><path d="M5.61 9.35H3.85a.16.16 0 0 0-.16.15v1.58a.16.16 0 0 0 .16.15h1.76a.16.16 0 0 0 .16-.15V9.5a.16.16 0 0 0-.16-.15m2.44 0H6.28a.16.16 0 0 0-.16.15v1.58a.16.16 0 0 0 .16.15h1.77a.15.15 0 0 0 .15-.15V9.5a.15.15 0 0 0-.15-.15m2.47 0H8.75a.15.15 0 0 0-.15.15v1.58a.15.15 0 0 0 .15.15h1.77a.15.15 0 0 0 .15-.15V9.5a.15.15 0 0 0-.15-.15m.67 0a.15.15 0 0 0-.19.15v1.58a.15.15 0 0 0 .15.15H13a.15.15 0 0 0 .15-.15V9.5a.15.15 0 0 0-.15-.15zM6.28 7.09H8a.16.16 0 0 1 .16.16v1.56A.16.16 0 0 1 8 9H6.28a.15.15 0 0 1-.15-.15V7.24a.15.15 0 0 1 .15-.15m2.47 0h1.77a.15.15 0 0 1 .15.15v1.57a.16.16 0 0 1-.16.16H8.75a.15.15 0 0 1-.15-.15V7.24a.15.15 0 0 1 .15-.15m2.44 0H13a.15.15 0 0 1 .15.15v1.57A.15.15 0 0 1 13 9h-1.81a.16.16 0 0 1-.19-.19V7.24a.15.15 0 0 1 .19-.15"/><rect width="2.07" height="1.88" x="11.04" y="4.82" rx=".15"/><path d="M13.65 9.35a.15.15 0 0 0-.15.15v1.58a.15.15 0 0 0 .15.15h1.77a.15.15 0 0 0 .15-.15V9.5a.15.15 0 0 0-.15-.15z"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
apps/website/src/assets/boxicons/bx-extension.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M19 6h-4V5c0-1.65-1.35-3-3-3S9 3.35 9 5v1H5c-1.1 0-2 .9-2 2v4c0 .55.45 1 1 1h2c.55 0 1 .45 1 1s-.45 1-1 1H4c-.55 0-1 .45-1 1v4c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2m0 14H5v-3h1c1.65 0 3-1.35 3-3s-1.35-3-3-3H5V8h5c.55 0 1-.45 1-1V5c0-.55.45-1 1-1s1 .45 1 1v2c0 .55.45 1 1 1h5z"/></svg>
|
After Width: | Height: | Size: 450 B |
1
apps/website/src/assets/boxicons/bx-file.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m19.94 7.68-.03-.09a.8.8 0 0 0-.2-.29l-5-5c-.09-.09-.19-.15-.29-.2l-.09-.03a.8.8 0 0 0-.26-.05c-.02 0-.04-.01-.06-.01H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-12s-.01-.04-.01-.06c0-.09-.02-.17-.05-.26ZM6 20V4h7v4c0 .55.45 1 1 1h4v11z"/></svg>
|
After Width: | Height: | Size: 410 B |
1
apps/website/src/assets/boxicons/bx-folder.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M20 4h-8.59L10 2.59C9.62 2.21 9.12 2 8.59 2H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2m0 14H4V6h16z"/></svg>
|
After Width: | Height: | Size: 290 B |
1
apps/website/src/assets/boxicons/bx-github.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path fill-rule="evenodd" d="M12.026 2c-5.509 0-9.974 4.465-9.974 9.974 0 4.406 2.857 8.145 6.821 9.465.499.09.679-.217.679-.481 0-.237-.008-.865-.011-1.696-2.775.602-3.361-1.338-3.361-1.338-.452-1.152-1.107-1.459-1.107-1.459-.905-.619.069-.605.069-.605 1.002.07 1.527 1.028 1.527 1.028.89 1.524 2.336 1.084 2.902.829.091-.645.351-1.085.635-1.334-2.214-.251-4.542-1.107-4.542-4.93 0-1.087.389-1.979 1.024-2.675-.101-.253-.446-1.268.099-2.64 0 0 .837-.269 2.742 1.021a9.6 9.6 0 0 1 2.496-.336 9.6 9.6 0 0 1 2.496.336c1.906-1.291 2.742-1.021 2.742-1.021.545 1.372.203 2.387.099 2.64.64.696 1.024 1.587 1.024 2.675 0 3.833-2.33 4.675-4.552 4.922.355.308.675.916.675 1.846 0 1.334-.012 2.41-.012 2.737 0 .267.178.577.687.479C19.146 20.115 22 16.379 22 11.974 22 6.465 17.535 2 12.026 2" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 956 B |
1
apps/website/src/assets/boxicons/bx-globe.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2M4 12c0-.9.16-1.76.43-2.57L6 11l2 2v2l2 2 1 1v1.93c-3.94-.49-7-3.86-7-7.93m14.33 4.87c-.65-.53-1.64-.87-2.33-.87v-1c0-1.1-.9-2-2-2h-4v-3c1.1 0 2-.9 2-2V7h1c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 1.83-.63 3.52-1.67 4.87"/></svg>
|
After Width: | Height: | Size: 454 B |
1
apps/website/src/assets/boxicons/bx-help-circle.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8"/><path d="M11 16h2v2h-2zm2.27-9.75c-2.08-.75-4.47.35-5.21 2.41l1.88.68c.18-.5.56-.9 1.07-1.13s1.08-.26 1.58-.08a2.01 2.01 0 0 1 1.32 1.86c0 1.04-1.66 1.86-2.24 2.07-.4.14-.67.52-.67.94v1h2v-.34c1.04-.51 2.91-1.69 2.91-3.68a4.015 4.015 0 0 0-2.64-3.73"/></svg>
|
After Width: | Height: | Size: 538 B |
1
apps/website/src/assets/boxicons/bx-history.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M21.21 8.11c-.25-.59-.56-1.16-.92-1.7-.36-.53-.77-1.03-1.22-1.48s-.95-.86-1.48-1.22c-.54-.36-1.11-.67-1.7-.92-.6-.26-1.24-.45-1.88-.58-1.31-.27-2.72-.27-4.03 0-.64.13-1.27.33-1.88.58-.59.25-1.16.56-1.7.92-.53.36-1.03.77-1.48 1.22-.17.17-.32.35-.48.52L1.99 3v6h6L5.86 6.87c.15-.18.31-.36.48-.52.36-.36.76-.69 1.18-.98.43-.29.89-.54 1.36-.74.48-.2.99-.36 1.5-.47 1.05-.21 2.18-.21 3.23 0 .51.11 1.02.26 1.5.47.47.2.93.45 1.36.74.42.29.82.62 1.18.98s.69.76.98 1.18c.29.43.54.89.74 1.36.2.48.36.99.47 1.5.11.53.16 1.07.16 1.61a7.85 7.85 0 0 1-.63 3.11c-.2.47-.45.93-.74 1.36-.29.42-.62.82-.98 1.18s-.76.69-1.18.98c-.43.29-.89.54-1.36.74-.48.2-.99.36-1.5.47-1.05.21-2.18.21-3.23 0a8 8 0 0 1-1.5-.47c-.47-.2-.93-.45-1.36-.74-.42-.29-.82-.62-1.18-.98s-.69-.76-.98-1.18c-.29-.43-.54-.89-.74-1.36-.2-.48-.36-.99-.47-1.5A8 8 0 0 1 3.99 12h-2c0 .68.07 1.35.2 2.01.13.64.33 1.27.58 1.88.25.59.56 1.16.92 1.7.36.53.77 1.03 1.22 1.48s.95.86 1.48 1.22c.54.36 1.11.67 1.7.92.6.26 1.24.45 1.88.58.66.13 1.33.2 2.01.2s1.36-.07 2.01-.2c.64-.13 1.27-.33 1.88-.58.59-.25 1.16-.56 1.7-.92.53-.36 1.03-.77 1.48-1.22s.86-.95 1.22-1.48c.36-.54.67-1.11.92-1.7.26-.6.45-1.24.58-1.88.13-.66.2-1.34.2-2.01s-.07-1.35-.2-2.01c-.13-.64-.33-1.27-.58-1.88Z"/><path d="M11 7v6h6v-2h-4V7z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
apps/website/src/assets/boxicons/bx-map.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m21.51 6.14-5-3a.99.99 0 0 0-.87-.08L8.09 5.89 3.51 3.14a.99.99 0 0 0-1.01-.01c-.31.18-.51.51-.51.87v13c0 .35.18.68.49.86l5 3c.26.16.58.19.87.08l7.55-2.83 4.59 2.75c.16.1.34.14.51.14s.34-.04.49-.13c.31-.18.51-.51.51-.87V7a.99.99 0 0 0-.49-.86M7 18.23l-3-1.8V5.77l3 1.8v10.67Zm8-1.93-6 2.25V7.69l6-2.25zm5 1.93-3-1.8V5.77l3 1.8v10.67Z"/></svg>
|
After Width: | Height: | Size: 497 B |
1
apps/website/src/assets/boxicons/bx-menu.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"/></svg>
|
After Width: | Height: | Size: 200 B |
1
apps/website/src/assets/boxicons/bx-message-dots.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M4 19h3v2c0 .36.19.69.51.87a1 1 0 0 0 1-.01L13.27 19h6.72c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2M4 5h16v12h-7c-.18 0-.36.05-.51.14L9 19.23V18c0-.55-.45-1-1-1H4z"/><path d="M8 10a1 1 0 1 0 0 2 1 1 0 1 0 0-2m4 0a1 1 0 1 0 0 2 1 1 0 1 0 0-2m4 0a1 1 0 1 0 0 2 1 1 0 1 0 0-2"/></svg>
|
After Width: | Height: | Size: 461 B |
1
apps/website/src/assets/boxicons/bx-network-chart.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M19 3c-1.65 0-3 1.35-3 3 0 .5.14.97.35 1.38l-1.12 1.3c-.64-.43-1.41-.69-2.24-.69s-1.53.24-2.15.64l-2.2-1.65c.22-.45.35-.96.35-1.49 0-1.93-1.57-3.5-3.5-3.5s-3.5 1.57-3.5 3.5 1.57 3.5 3.5 3.5c.66 0 1.28-.2 1.81-.52l2.18 1.64c-.3.56-.49 1.2-.49 1.88 0 1 .38 1.9.99 2.6l-1.69 1.69.03.03c-.4-.2-.84-.32-1.32-.32-1.65 0-3 1.35-3 3s1.35 3 3 3 3-1.35 3-3c0-.48-.12-.92-.32-1.32l.03.03 1.95-1.95c.42.15.87.25 1.34.25 2.21 0 4-1.79 4-4 0-.64-.17-1.24-.44-1.78l1.25-1.46c.36.16.76.25 1.19.25 1.65 0 3-1.35 3-3s-1.35-3-3-3ZM7 20c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1M4 5.5C4 4.67 4.67 4 5.5 4S7 4.67 7 5.5 6.33 7 5.5 7 4 6.33 4 5.5m9 8.5c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2m6-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1"/></svg>
|
After Width: | Height: | Size: 892 B |
1
apps/website/src/assets/boxicons/bx-note.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8c.13 0 .26-.03.38-.08s.23-.12.33-.22l7-7c.09-.09.15-.19.2-.29l.03-.09c.03-.08.05-.17.05-.26 0-.02.01-.04.01-.06V5c0-1.1-.9-2-2-2M5 5h14v7h-6c-.55 0-1 .45-1 1v6H5z"/></svg>
|
After Width: | Height: | Size: 368 B |
1
apps/website/src/assets/boxicons/bx-paperclip.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M17 5H9c-3.86 0-7 3.14-7 7s3.14 7 7 7h9v-2H9c-2.76 0-5-2.24-5-5s2.24-5 5-5h8c.79 0 1.54.31 2.11.89a2.967 2.967 0 0 1 .01 4.23 3 3 0 0 1-2.12.89H9c-.26 0-.5-.11-.7-.3-.19-.2-.3-.44-.3-.7s.11-.51.3-.7.44-.3.7-.3h8v-2H9c-.79 0-1.54.32-2.11.89S6 11.22 6 12.01s.31 1.54.89 2.11c.57.57 1.32.89 2.11.89h8c1.32 0 2.58-.52 3.53-1.47S22 11.34 22 10.01s-.52-2.58-1.47-3.53a4.95 4.95 0 0 0-3.52-1.47Z"/></svg>
|
After Width: | Height: | Size: 552 B |
1
apps/website/src/assets/boxicons/bx-paypal.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M19.554 9.488q.18.844-.04 2.051-.873 4.467-5.683 4.466h-.442a.67.67 0 0 0-.444.166.72.72 0 0 0-.239.427l-.041.189-.553 3.479-.021.151a.7.7 0 0 1-.247.426.67.67 0 0 1-.447.166H8.874a.4.4 0 0 1-.331-.15.46.46 0 0 1-.09-.363q.09-.561.267-1.689.175-1.126.267-1.689c.092-.563.15-.938.272-1.685q.182-1.122.271-1.685.05-.371.433-.371h1.316q1.338.02 2.375-.211 1.758-.392 2.886-1.449 1.026-.956 1.56-2.473.241-.706.352-1.338.009-.062.025-.074.014-.016.035-.011a.4.4 0 0 1 .062.035c.524.398.854.941.98 1.632m-1.728-2.836q0 1.075-.465 2.374-.804 2.343-3.037 3.168-1.136.401-2.535.425 0 .008-.904.007l-.903-.007q-1.008-.001-1.187.964-.02.08-.855 5.329-.012.1-.121.102H4.854a.47.47 0 0 1-.369-.165.47.47 0 0 1-.115-.39L6.702 3.664a.78.78 0 0 1 .276-.483.8.8 0 0 1 .519-.19h6.014q.342 0 .979.131c.428.084.801.194 1.123.321q1.077.412 1.645 1.237t.568 1.972"/></svg>
|
After Width: | Height: | Size: 1006 B |
1
apps/website/src/assets/boxicons/bx-pen.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M17 17.76v-5.35l2.91-2.91L21 8.41c.38-.38.58-.88.58-1.42s-.21-1.04-.59-1.41L18.4 3c-.38-.38-.88-.58-1.41-.58s-1.04.21-1.41.59L13.8 4.8l-2.21 2.21H6.24l-3.35 12.3 1.82 1.82 12.3-3.35Zm0-13.35 2.59 2.58-1.09 1.09-2.59-2.59 1.08-1.08ZM7.77 9h4.65l2.09-2.09L17.1 9.5l-2.09 2.09v4.65L7 18.42l3.43-3.43h.08c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5v.08L5.58 17l2.18-8.01Z"/></svg>
|
After Width: | Height: | Size: 541 B |
1
apps/website/src/assets/boxicons/bx-reddit.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M15.09 11.65c-.65 0-1.15.69-1.11 1.53.04.85.53 1.15 1.18 1.15s1.23-.34 1.19-1.19-.6-1.5-1.26-1.5Zm-.78 3.45c-.72-.07-1.5-.11-2.31-.11s-1.59.04-2.31.11c-.12.01-.2.14-.15.25a2.666 2.666 0 0 0 4.92 0c.05-.11-.03-.24-.15-.25m-4.28-1.92c.04-.85-.46-1.53-1.11-1.53s-1.22.65-1.26 1.5.53 1.19 1.19 1.19 1.14-.31 1.18-1.15Z"/><path d="M12 2C6.48 2 2 6.48 2 12c0 2.76 1.12 5.26 2.93 7.07l-1.9 1.9a.596.596 0 0 0 .42 1.02H12c5.52 0 10-4.48 10-10S17.52 2 12 2m6.67 11.77c-.07 2.71-3.03 4.9-6.66 4.9s-6.59-2.18-6.66-4.89a2.33 2.33 0 0 1-1.34-2.11c0-1.29 1.05-2.34 2.34-2.34.54 0 1.03.18 1.43.49 1.06-.66 2.4-1.08 3.87-1.14A2.69 2.69 0 0 1 14.05 6c.16-.76.83-1.33 1.63-1.33.92 0 1.67.75 1.67 1.67s-.75 1.67-1.67 1.67c-.79 0-1.45-.55-1.62-1.28-.96.14-1.69.96-1.69 1.95 1.48.06 2.83.48 3.91 1.14a2.343 2.343 0 0 1 3.76 1.86c0 .93-.55 1.73-1.33 2.11Z"/></svg>
|
After Width: | Height: | Size: 997 B |
1
apps/website/src/assets/boxicons/bx-refresh-cw.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M19.07 4.93a9.9 9.9 0 0 0-3.18-2.14 10.12 10.12 0 0 0-7.79 0c-1.19.5-2.26 1.23-3.18 2.14S3.28 6.92 2.78 8.11A9.95 9.95 0 0 0 1.99 12h2c0-1.08.21-2.13.63-3.11.4-.95.98-1.81 1.72-2.54.73-.74 1.59-1.31 2.54-1.71 1.97-.83 4.26-.83 6.23 0 .95.4 1.81.98 2.54 1.72.17.17.33.34.48.52L16 9.01h6V3l-2.45 2.45c-.15-.18-.31-.36-.48-.52m.3 10.18c-.4.95-.98 1.81-1.72 2.54-.73.74-1.59 1.31-2.54 1.71-1.97.83-4.26.83-6.23 0-.95-.4-1.81-.98-2.54-1.72-.17-.17-.33-.34-.48-.52l2.13-2.13H2v6l2.45-2.45c.15.18.31.36.48.52.92.92 1.99 1.64 3.18 2.14 1.23.52 2.54.79 3.89.79s2.66-.26 3.89-.79c1.19-.5 2.26-1.23 3.18-2.14s1.64-1.99 2.14-3.18c.52-1.23.79-2.54.79-3.89h-2c0 1.08-.21 2.13-.63 3.11Z"/></svg>
|
After Width: | Height: | Size: 835 B |
1
apps/website/src/assets/boxicons/bx-search.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M18 10c0-4.41-3.59-8-8-8s-8 3.59-8 8 3.59 8 8 8c1.85 0 3.54-.63 4.9-1.69l5.1 5.1L21.41 20l-5.1-5.1A8 8 0 0 0 18 10M4 10c0-3.31 2.69-6 6-6s6 2.69 6 6-2.69 6-6 6-6-2.69-6-6"/></svg>
|
After Width: | Height: | Size: 334 B |
1
apps/website/src/assets/boxicons/bx-send-alt.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m21.41 11.09-18-8a1 1 0 0 0-1.09.19c-.29.28-.39.7-.25 1.08l2.87 7.65-2.87 7.65c-.14.38-.04.8.25 1.08a1 1 0 0 0 1.1.18l18-8a.998.998 0 0 0 0-1.82ZM4.78 18.12l1.23-3.27V15l6-3-6-3v.15L4.78 5.88 18.54 12z"/></svg>
|
After Width: | Height: | Size: 365 B |
1
apps/website/src/assets/boxicons/bx-shield.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="m20.42 6.11-7.97-4c-.28-.14-.62-.14-.9 0l-7.97 4c-.31.15-.51.45-.55.79-.01.11-.96 10.76 8.55 15.01a.98.98 0 0 0 .82 0C21.91 17.66 20.97 7 20.95 6.9a.98.98 0 0 0-.55-.79ZM12 19.9C5.26 16.63 4.94 9.64 5 7.64l7-3.51 7 3.51c.04 1.99-.33 9.02-7 12.26"/></svg>
|
After Width: | Height: | Size: 409 B |
1
apps/website/src/assets/boxicons/bx-swap-horizontal.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M17 14H9v2h8v3l5-4-5-4zm-2-4V8H7V5L2 9l5 4v-3z"/></svg>
|
After Width: | Height: | Size: 210 B |
1
apps/website/src/assets/boxicons/bx-table.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M21 3H3c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1m-1 2v2H4V5zM8 13H4V9h4zm2-4h4v4h-4zm6 0h4v4h-4zM4 19v-4h4v4zm6 0v-4h4v4zm6 0v-4h4v4z"/></svg>
|
After Width: | Height: | Size: 328 B |
1
apps/website/src/assets/boxicons/bx-tag.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M20 4H8.51c-.64 0-1.25.31-1.63.84l-4.7 6.58a.99.99 0 0 0 0 1.16l4.7 6.58c.37.52.98.84 1.63.84H20c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2m0 14H8.51l-4.29-6 4.29-6H20z"/></svg>
|
After Width: | Height: | Size: 321 B |
1
apps/website/src/assets/boxicons/bx-vector-square.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><!--Boxicons v3.0 https://boxicons.com | License https://docs.boxicons.com/free--><path d="M20 3h-4c-.55 0-1 .45-1 1v1H9V4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1h1v6H4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1h6v1c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1h-1V9h1c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1M5 5h2v2H5zm2 14H5v-2h2zm12 0h-2v-2h2zm-2-4h-1c-.55 0-1 .45-1 1v1H9v-1c0-.55-.45-1-1-1H7V9h1c.55 0 1-.45 1-1V7h6v1c0 .55.45 1 1 1h1zm2-8h-2V5h2z"/></svg>
|
After Width: | Height: | Size: 549 B |
BIN
apps/website/src/assets/favicon.ico
Normal file
After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
20
apps/website/src/components/Button.css
Normal file
@ -0,0 +1,20 @@
|
||||
a.button {
|
||||
display: block;
|
||||
background-color: var(--brand-1);
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 6px;
|
||||
color: var(--brand-foreground-color);
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.15);
|
||||
|
||||
&.outline {
|
||||
border: 1px solid var(--brand-1);
|
||||
color: var(--brand-1);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.text {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
44
apps/website/src/components/Button.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { ComponentChildren } from "preact";
|
||||
import Icon from "./Icon.js";
|
||||
import "./Button.css";
|
||||
|
||||
interface LinkProps {
|
||||
className?: string;
|
||||
href?: string;
|
||||
openExternally?: boolean;
|
||||
children: ComponentChildren;
|
||||
title?: string;
|
||||
onClick?: (e: MouseEvent) => void;
|
||||
}
|
||||
|
||||
interface ButtonProps extends Omit<LinkProps, "children"> {
|
||||
href?: string;
|
||||
iconSvg?: string;
|
||||
text: ComponentChildren;
|
||||
openExternally?: boolean;
|
||||
outline?: boolean;
|
||||
}
|
||||
|
||||
export default function Button({ iconSvg, text, className, outline, ...restProps }: ButtonProps) {
|
||||
return (
|
||||
<Link
|
||||
className={`button ${className} ${outline ? "outline" : ""}`}
|
||||
{...restProps}
|
||||
>
|
||||
{iconSvg && <><Icon svg={iconSvg} />{" "}</>}
|
||||
<span class="text">{text}</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export function Link({ openExternally, children, ...restProps }: LinkProps) {
|
||||
return (
|
||||
<a
|
||||
{...restProps}
|
||||
target={openExternally ? "_blank" : undefined}
|
||||
rel={openExternally ? "noopener noreferrer" : undefined}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
37
apps/website/src/components/Card.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { ComponentChildren, HTMLAttributes } from "preact";
|
||||
import { Link } from "./Button.js";
|
||||
import Icon from "./Icon.js";
|
||||
|
||||
interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
||||
title: ComponentChildren;
|
||||
imageUrl?: string;
|
||||
iconSvg?: string;
|
||||
className?: string;
|
||||
moreInfoUrl?: string;
|
||||
children: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function Card({ title, children, imageUrl, iconSvg, className, moreInfoUrl, ...restProps }: CardProps) {
|
||||
return (
|
||||
<div className={`card ${className}`} {...restProps}>
|
||||
{imageUrl && <img class="image" src={imageUrl} loading="lazy" />}
|
||||
|
||||
<div className="card-content">
|
||||
<h3>
|
||||
{iconSvg && <Icon svg={iconSvg} />}{" "}
|
||||
<span>{title}</span>
|
||||
</h3>
|
||||
|
||||
<div className="card-content-inner">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{moreInfoUrl && (
|
||||
<div className="more-info-container">
|
||||
<Link href={moreInfoUrl} className="more-info" openExternally>Learn more...</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
27
apps/website/src/components/DownloadButton.css
Normal file
@ -0,0 +1,27 @@
|
||||
a.download-button {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
|
||||
@media (min-width: 720px) {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.platform {
|
||||
font-size: 0.75em;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
&.big {
|
||||
padding: 0.5em 3.5em;
|
||||
margin: 1em 0;
|
||||
gap: m;
|
||||
text-align: left;
|
||||
|
||||
.platform {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
apps/website/src/components/DownloadButton.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { getRecommendedDownload, RecommendedDownload } from "../download-helper.js";
|
||||
import "./DownloadButton.css";
|
||||
import Button from "./Button.js";
|
||||
import downloadIcon from "../assets/boxicons/bx-arrow-in-down-square-half.svg?raw";
|
||||
import packageJson from "../../../../package.json" with { type: "json" };
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
interface DownloadButtonProps {
|
||||
big?: boolean;
|
||||
}
|
||||
|
||||
export default function DownloadButton({ big }: DownloadButtonProps) {
|
||||
const [ recommendedDownload, setRecommendedDownload ] = useState<RecommendedDownload | null>();
|
||||
useEffect(() => setRecommendedDownload(getRecommendedDownload()), []);
|
||||
|
||||
return (recommendedDownload &&
|
||||
<Button
|
||||
className={`download-button desktop-only ${big ? "big" : ""}`}
|
||||
href={recommendedDownload.url}
|
||||
iconSvg={downloadIcon}
|
||||
text={<>
|
||||
Download now{" "}
|
||||
{big
|
||||
? <span class="platform">v{packageJson.version} for {recommendedDownload.name}</span>
|
||||
: <span class="platform">for {recommendedDownload.name}</span>
|
||||
}
|
||||
</>}
|
||||
/>
|
||||
)
|
||||
}
|
35
apps/website/src/components/Footer.css
Normal file
@ -0,0 +1,35 @@
|
||||
footer {
|
||||
margin: 0;
|
||||
padding: 2em 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.3);
|
||||
color: var(--muted-color);
|
||||
font-size: 0.8em;
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column-reverse;
|
||||
gap: 2em;
|
||||
|
||||
@media (min-width: 720px) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.social-buttons {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
|
||||
a.social-button {
|
||||
color: var(--muted-color);
|
||||
transition: color 250ms ease-in-out, opacity 250ms ease-in-out;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
color: var(--brand-1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
69
apps/website/src/components/Footer.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import "./Footer.css";
|
||||
import Icon from "./Icon.js";
|
||||
import githubIcon from "../assets/boxicons/bx-github.svg?raw";
|
||||
import githubDiscussionsIcon from "../assets/boxicons/bx-discussion.svg?raw";
|
||||
import matrixIcon from "../assets/boxicons/bx-message-dots.svg?raw";
|
||||
import redditIcon from "../assets/boxicons/bx-reddit.svg?raw";
|
||||
import { Link } from "./Button.js";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer>
|
||||
<div class="content-wrapper">
|
||||
<div class="footer-text">
|
||||
© 2024-2025 <Link href="https://github.com/eliandoran" openExternally>Elian Doran</Link> and the <Link href="https://github.com/TriliumNext/Trilium/graphs/contributors" openExternally>community</Link>.<br />
|
||||
© 2017-2024 <Link href="https://github.com/zadam" openExternally>zadam</Link>.
|
||||
</div>
|
||||
|
||||
<SocialButtons />
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export function SocialButtons({ className, withText }: { className?: string, withText?: boolean }) {
|
||||
return (
|
||||
<div className={`social-buttons ${className}`}>
|
||||
<SocialButton
|
||||
name="GitHub"
|
||||
iconSvg={githubIcon}
|
||||
url="https://github.com/TriliumNext/Trilium"
|
||||
withText={withText}
|
||||
/>
|
||||
|
||||
<SocialButton
|
||||
name="GitHub Discussions"
|
||||
iconSvg={githubDiscussionsIcon}
|
||||
url="https://github.com/orgs/TriliumNext/discussions"
|
||||
withText={withText}
|
||||
/>
|
||||
|
||||
<SocialButton
|
||||
name="Matrix"
|
||||
iconSvg={matrixIcon}
|
||||
url="https://matrix.to/#/#triliumnext:matrix.org"
|
||||
withText={withText}
|
||||
/>
|
||||
|
||||
<SocialButton
|
||||
name="Reddit"
|
||||
iconSvg={redditIcon}
|
||||
url="https://www.reddit.com/r/Trilium/"
|
||||
withText={withText}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SocialButton({ name, iconSvg, url, withText }: { name: string, iconSvg: string, url: string, withText?: boolean }) {
|
||||
return (
|
||||
<Link
|
||||
className="social-button"
|
||||
href={url} openExternally
|
||||
title={!withText ? name : undefined}
|
||||
>
|
||||
<Icon svg={iconSvg} />
|
||||
{withText && name}
|
||||
</Link>
|
||||
)
|
||||
}
|
134
apps/website/src/components/Header.css
Normal file
@ -0,0 +1,134 @@
|
||||
header {
|
||||
padding: 1em;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--header-background-color);
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(20px);
|
||||
z-index: 1000;
|
||||
--gap: 1.25em;
|
||||
font-size: 0.8em;
|
||||
|
||||
&>.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
gap: var(--gap);
|
||||
min-width: 80vw;
|
||||
}
|
||||
|
||||
a.banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover { text-decoration: none;}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
img+span {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
nav {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--gap);
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&.active {
|
||||
color: var(--brand-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 719px) {
|
||||
:root {
|
||||
--header-height: 60px;
|
||||
}
|
||||
|
||||
header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
min-height: var(--header-height);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
||||
.content-wrapper {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.first-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
nav {
|
||||
flex-direction: column;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 200ms ease-in;
|
||||
|
||||
&.mobile-shown {
|
||||
display: flex;
|
||||
max-height: 100vh;
|
||||
padding: 2em 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header + main {
|
||||
margin-top: var(--header-height);
|
||||
}
|
||||
|
||||
header nav a {
|
||||
font-size: 1.25em;
|
||||
padding: 0.5em 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
header .social-buttons {
|
||||
justify-content: space-between;
|
||||
padding-top: 1em;
|
||||
|
||||
a {
|
||||
border-bottom: unset;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
header {
|
||||
font-size: inherit;
|
||||
|
||||
img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
66
apps/website/src/components/Header.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import "./Header.css";
|
||||
import { useLocation } from 'preact-iso';
|
||||
import DownloadButton from './DownloadButton.js';
|
||||
import { Link } from "./Button.js";
|
||||
import Icon from "./Icon.js";
|
||||
import logoPath from "../assets/icon-color.svg";
|
||||
import menuIcon from "../assets/boxicons/bx-menu.svg?raw";
|
||||
import { useState } from "preact/hooks";
|
||||
import { SocialButtons } from "./Footer.js";
|
||||
|
||||
interface HeaderLink {
|
||||
url: string;
|
||||
text: string;
|
||||
external?: boolean;
|
||||
}
|
||||
|
||||
const HEADER_LINKS: HeaderLink[] = [
|
||||
{ url: "/get-started/", text: "Get started" },
|
||||
{ url: "https://docs.triliumnotes.org/", text: "Documentation", external: true },
|
||||
{ url: "/support-us/", text: "Support us" }
|
||||
]
|
||||
|
||||
export function Header() {
|
||||
const { url } = useLocation();
|
||||
const [ mobileMenuShown, setMobileMenuShown ] = useState(false);
|
||||
|
||||
return (
|
||||
<header>
|
||||
<div class="content-wrapper">
|
||||
<div class="first-row">
|
||||
<a class="banner" href="/">
|
||||
<img src={logoPath} width="300" height="300" alt="Trilium Notes logo" /> <span>Trilium Notes</span>
|
||||
</a>
|
||||
|
||||
<Link
|
||||
href="#"
|
||||
className="mobile-only menu-toggle"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setMobileMenuShown(!mobileMenuShown)
|
||||
}}
|
||||
>
|
||||
<Icon svg={menuIcon} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav className={`${mobileMenuShown ? "mobile-shown" : ""}`}>
|
||||
{HEADER_LINKS.map(link => (
|
||||
<Link
|
||||
href={link.url}
|
||||
className={url === link.url ? "active" : ""}
|
||||
openExternally={link.external}
|
||||
onClick={() => {
|
||||
setMobileMenuShown(false);
|
||||
}}
|
||||
>{link.text}</Link>
|
||||
))}
|
||||
|
||||
<SocialButtons className="mobile-only" withText />
|
||||
</nav>
|
||||
|
||||
<DownloadButton />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
9
apps/website/src/components/Icon.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
interface IconProps {
|
||||
svg: string;
|
||||
}
|
||||
|
||||
export default function Icon({ svg }: IconProps) {
|
||||
return (
|
||||
<span className="bx" dangerouslySetInnerHTML={{ __html: svg }} />
|
||||
)
|
||||
}
|
18
apps/website/src/components/Section.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { ComponentChildren } from "preact";
|
||||
|
||||
interface SectionProps {
|
||||
title?: string;
|
||||
children: ComponentChildren;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function Section({ className, title, children }: SectionProps) {
|
||||
return (
|
||||
<section className={className}>
|
||||
<div className="content-wrapper">
|
||||
{title && <h2>{title}</h2>}
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import rootPackageJson from '../../../../package.json';
|
||||
import rootPackageJson from '../../../package.json' with { type: "json" };
|
||||
|
||||
export type App = "desktop" | "server";
|
||||
|
||||
export type Architecture = 'x64' | 'arm64';
|
||||
|
||||
export type Platform = "macos" | "windows" | "linux" | "pikapod";
|
||||
export type Platform = "macos" | "windows" | "linux" | "pikapod" | "docker";
|
||||
|
||||
const version = rootPackageJson.version;
|
||||
|
||||
@ -18,6 +18,15 @@ export interface DownloadMatrixEntry {
|
||||
title: Record<Architecture, string> | string;
|
||||
description: Record<Architecture, string> | string;
|
||||
downloads: Record<string, DownloadInfo>;
|
||||
helpUrl?: string;
|
||||
quickStartCode?: string;
|
||||
}
|
||||
|
||||
export interface RecommendedDownload {
|
||||
architecture: Architecture;
|
||||
platform: Platform;
|
||||
url: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
type DownloadMatrix = Record<App, { [ P in Platform ]?: DownloadMatrixEntry }>;
|
||||
@ -34,6 +43,7 @@ export const downloadMatrix: DownloadMatrix = {
|
||||
x64: "Compatible with Intel or AMD devices running Windows 10 and 11.",
|
||||
arm64: "Compatible with ARM devices (e.g. with Qualcomm Snapdragon).",
|
||||
},
|
||||
quickStartCode: "winget install TriliumNext.Notes",
|
||||
downloads: {
|
||||
exe: {
|
||||
recommended: true,
|
||||
@ -44,7 +54,7 @@ export const downloadMatrix: DownloadMatrix = {
|
||||
},
|
||||
scoop: {
|
||||
name: "Scoop",
|
||||
url: "https://scoop.sh/#/apps?q=triliumnext"
|
||||
url: "https://scoop.sh/#/apps?q=trilium&id=7c08bc3c105b9ee5c00dd4245efdea0f091b8a5c"
|
||||
},
|
||||
winget: {
|
||||
name: "Winget",
|
||||
@ -67,7 +77,8 @@ export const downloadMatrix: DownloadMatrix = {
|
||||
name: "Download .deb"
|
||||
},
|
||||
rpm: {
|
||||
name: ".rpm"
|
||||
recommended: true,
|
||||
name: "Download .rpm"
|
||||
},
|
||||
flatpak: {
|
||||
name: ".flatpak"
|
||||
@ -94,11 +105,16 @@ export const downloadMatrix: DownloadMatrix = {
|
||||
x64: "For Intel-based Macs running macOS Big Sur or later.",
|
||||
arm64: "For Apple Silicon Macs such as those with M1 and M2 chips.",
|
||||
},
|
||||
quickStartCode: "brew install --cask trilium-notes",
|
||||
downloads: {
|
||||
dmg: {
|
||||
recommended: true,
|
||||
name: "Download Installer (.dmg)"
|
||||
},
|
||||
homebrew: {
|
||||
name: "Homebrew Cask",
|
||||
url: "https://formulae.brew.sh/cask/trilium-notes#default"
|
||||
},
|
||||
zip: {
|
||||
name: "Portable (.zip)"
|
||||
}
|
||||
@ -106,32 +122,46 @@ export const downloadMatrix: DownloadMatrix = {
|
||||
}
|
||||
},
|
||||
server: {
|
||||
linux: {
|
||||
title: "Self-hosted (Linux)",
|
||||
description: "Deploy Trilium Notes on your own server or VPS, compatible with most Linux distributions.",
|
||||
docker: {
|
||||
title: "Self-hosted using Docker",
|
||||
description: "Easily deploy on Windows, Linux or macOS using a Docker container.",
|
||||
helpUrl: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Installation%20%26%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.html",
|
||||
quickStartCode: "docker pull triliumnext/trilium\ndocker run -p 8080:8080 -d ./data:/home/node/trilium-data triliumnext/trilium",
|
||||
downloads: {
|
||||
docker: {
|
||||
recommended: true,
|
||||
name: "View on Docker Hub",
|
||||
url: "https://hub.docker.com/r/triliumnext/notes"
|
||||
dockerhub: {
|
||||
name: "Docker Hub",
|
||||
url: "https://hub.docker.com/r/triliumnext/trilium"
|
||||
},
|
||||
ghcr: {
|
||||
name: "ghcr.io",
|
||||
url: "https://github.com/TriliumNext/Trilium/pkgs/container/trilium"
|
||||
}
|
||||
}
|
||||
},
|
||||
linux: {
|
||||
title: "Self-hosted on Linux",
|
||||
description: "Deploy Trilium Notes on your own server or VPS, compatible with most distributions.",
|
||||
helpUrl: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Installation%20%26%20Setup/Server%20Installation/1.%20Installing%20the%20server/Packaged%20version%20for%20Linux.html",
|
||||
downloads: {
|
||||
tarX64: {
|
||||
name: "x86 (.tar.xz)",
|
||||
url: `https://github.com/TriliumNext/Trilium/releases/download/v${version}/TriliumNotes-Server-v${version}-linux-x64.tar.xz`
|
||||
recommended: true,
|
||||
name: "x64 (.tar.xz)",
|
||||
url: `https://github.com/TriliumNext/Trilium/releases/download/v${version}/TriliumNotes-Server-v${version}-linux-x64.tar.xz`,
|
||||
},
|
||||
tarArm64: {
|
||||
recommended: true,
|
||||
name: "ARM (.tar.xz)",
|
||||
url: `https://github.com/TriliumNext/Trilium/releases/download/v${version}/TriliumNotes-Server-v${version}-linux-arm64.tar.xz`
|
||||
},
|
||||
nixos: {
|
||||
name: "NixOS module",
|
||||
url: "https://search.nixos.org/options?query=trilium-server"
|
||||
url: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/On%20NixOS"
|
||||
}
|
||||
}
|
||||
},
|
||||
pikapod: {
|
||||
title: "Paid hosting",
|
||||
description: "Trilium Notes hosted on PikaPods, a paid service for easy access and management.",
|
||||
description: "Trilium Notes hosted on PikaPods, a paid service for easy access and management. Not directly affiliated with the Trilium team.",
|
||||
downloads: {
|
||||
pikapod: {
|
||||
recommended: true,
|
||||
@ -158,7 +188,9 @@ export function buildDownloadUrl(app: App, platform: Platform, format: string, a
|
||||
}
|
||||
}
|
||||
|
||||
export function getArchitecture(): Architecture {
|
||||
export function getArchitecture(): Architecture | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.includes('arm64') || userAgent.includes('aarch64')) {
|
||||
return 'arm64';
|
||||
@ -167,7 +199,9 @@ export function getArchitecture(): Architecture {
|
||||
return "x64";
|
||||
}
|
||||
|
||||
function getPlatform(): Platform {
|
||||
export function getPlatform(): Platform | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.includes('macintosh') || userAgent.includes('mac os x')) {
|
||||
return "macos";
|
||||
@ -178,18 +212,30 @@ function getPlatform(): Platform {
|
||||
}
|
||||
}
|
||||
|
||||
export function getRecommendedDownload() {
|
||||
export function getRecommendedDownload(): RecommendedDownload | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
|
||||
const architecture = getArchitecture();
|
||||
const platform = getPlatform();
|
||||
if (!platform || !architecture) return null;
|
||||
|
||||
const downloadInfo = downloadMatrix.desktop[platform]?.downloads;
|
||||
const platformInfo = downloadMatrix.desktop[platform];
|
||||
if (!platformInfo) return null;
|
||||
|
||||
const downloadInfo = platformInfo.downloads;
|
||||
const recommendedDownload = Object.entries(downloadInfo || {}).find(d => d[1].recommended);
|
||||
const format = recommendedDownload?.[0];
|
||||
if (!recommendedDownload) return null;
|
||||
|
||||
const format = recommendedDownload[0];
|
||||
const url = buildDownloadUrl("desktop", platform, format || 'zip', architecture);
|
||||
|
||||
const platformTitle = platformInfo.title;
|
||||
const name = typeof platformTitle === "string" ? platformTitle : platformTitle[architecture] as string;
|
||||
|
||||
return {
|
||||
architecture,
|
||||
platform,
|
||||
url
|
||||
url,
|
||||
name
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { paraglideMiddleware } from '$lib/paraglide/server';
|
||||
|
||||
const handleParaglide: Handle = ({ event, resolve }) => paraglideMiddleware(event.request, ({ request, locale }) => {
|
||||
event.request = request;
|
||||
|
||||
return resolve(event, {
|
||||
transformPageChunk: ({ html }) => html.replace('%paraglide.lang%', locale)
|
||||
});
|
||||
});
|
||||
|
||||
export const handle: Handle = handleParaglide;
|
@ -1,6 +0,0 @@
|
||||
import { deLocalizeUrl } from '$lib/paraglide/runtime';
|
||||
|
||||
export const reroute = (request: {
|
||||
url: URL;
|
||||
fetch: typeof fetch;
|
||||
}) => deLocalizeUrl(request.url).pathname;
|
27
apps/website/src/hooks.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
export function usePageTitle(title: string) {
|
||||
useEffect(() => {
|
||||
if (title.length) {
|
||||
document.title = `${title} - Trilium Notes`;
|
||||
} else {
|
||||
document.title = "Trilium Notes";
|
||||
}
|
||||
}, [ title ]);
|
||||
}
|
||||
|
||||
export function useColorScheme() {
|
||||
const defaultValue = (typeof window !== "undefined" && (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches));
|
||||
const [ prefersDark, setPrefersDark ] = useState(defaultValue);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
const mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const listener = () => setPrefersDark((window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches));
|
||||
|
||||
mediaQueryList.addEventListener("change", listener);
|
||||
return () => mediaQueryList.removeEventListener("change", listener);
|
||||
}, []);
|
||||
|
||||
return prefersDark ? "dark" : "light";
|
||||
}
|
34
apps/website/src/index.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { LocationProvider, Router, Route, hydrate, prerender as ssr } from 'preact-iso';
|
||||
|
||||
import { Header } from './components/Header.jsx';
|
||||
import { Home } from './pages/Home/index.jsx';
|
||||
import { NotFound } from './pages/_404.jsx';
|
||||
import './style.css';
|
||||
import Footer from './components/Footer.js';
|
||||
import GetStarted from './pages/GetStarted/get-started.js';
|
||||
import SupportUs from './pages/SupportUs/SupportUs.js';
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<LocationProvider>
|
||||
<Header />
|
||||
<main>
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route default component={NotFound} />
|
||||
<Route path="/get-started" component={GetStarted} />
|
||||
<Route path="/support-us" component={SupportUs} />
|
||||
</Router>
|
||||
</main>
|
||||
<Footer />
|
||||
</LocationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
hydrate(<App />, document.getElementById('app')!);
|
||||
}
|
||||
|
||||
export async function prerender(data) {
|
||||
return await ssr(<App {...data} />);
|
||||
}
|
@ -1 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
133
apps/website/src/pages/GetStarted/get-started.css
Normal file
@ -0,0 +1,133 @@
|
||||
.download-card {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.download-server .download-card:first-of-type {
|
||||
grid-column: 1 / 4;
|
||||
}
|
||||
|
||||
.download-card {
|
||||
h3 {
|
||||
color: var(--accent-color);
|
||||
font-size: 1.5em;
|
||||
position: relative;
|
||||
|
||||
a.more-info {
|
||||
margin-left: 0.5em;
|
||||
right: 0;
|
||||
|
||||
.bx {
|
||||
vertical-align: sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content-inner {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
a:not(.button) {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.quick-start {
|
||||
background-color: #ececec;
|
||||
padding: 0.75em;
|
||||
border-radius: 6px;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
code {
|
||||
text-wrap: wrap;
|
||||
color: var(--muted-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.download-options {
|
||||
justify-content: flex-end;
|
||||
align-items: stretch;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.recommended-options {
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
|
||||
a.recommended {
|
||||
display: block;
|
||||
background: var(--accent-color);
|
||||
color: var(--brand-foreground-color);
|
||||
border-radius: calc(infinity * 1px);
|
||||
margin: 1em 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.other-options {
|
||||
display: flex;
|
||||
gap: 0.5em 1em;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.download-desktop {
|
||||
.download-card:first-of-type { --accent-color: var(--brand-1); }
|
||||
.download-card:nth-of-type(2) { --accent-color: var(--brand-2); }
|
||||
.download-card:last-of-type { --accent-color: var(--brand-3); }
|
||||
|
||||
.download-footer {
|
||||
text-align: center;
|
||||
margin-top: 1em;
|
||||
font-size: 0.9em;
|
||||
|
||||
a {
|
||||
color: var(--muted-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.download-server .download-card {
|
||||
--accent-color: var(--foreground-color);
|
||||
--brand-foreground-color: var(--background-color);
|
||||
}
|
||||
|
||||
.architecture-switch {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 1em 0;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
background: var(--card-background-color);
|
||||
padding: 0.25em 0.5em;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
min-width: 3em;
|
||||
color: inherit;
|
||||
|
||||
&.active {
|
||||
background-color: var(--brand-1);
|
||||
color: var(--brand-foreground-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-wrapper {
|
||||
border-radius: calc(infinity * 1px);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
106
apps/website/src/pages/GetStarted/get-started.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import Card from "../../components/Card.js";
|
||||
import Section from "../../components/Section.js";
|
||||
import { App, Architecture, buildDownloadUrl, downloadMatrix, DownloadMatrixEntry, getArchitecture, Platform } from "../../download-helper.js";
|
||||
import { usePageTitle } from "../../hooks.js";
|
||||
import Button, { Link } from "../../components/Button.js";
|
||||
import Icon from "../../components/Icon.js";
|
||||
import helpIcon from "../../assets/boxicons/bx-help-circle.svg?raw";
|
||||
import "./get-started.css";
|
||||
import packageJson from "../../../../../package.json" with { type: "json" };
|
||||
|
||||
export default function DownloadPage() {
|
||||
const [ currentArch, setCurrentArch ] = useState(getArchitecture() ?? "x64");
|
||||
usePageTitle("Download");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section title={`Download the desktop application (v${packageJson.version})`} className="fill accented download-desktop">
|
||||
<div className="architecture-switch">
|
||||
<span>Architecture:</span>
|
||||
|
||||
<div class="toggle-wrapper">
|
||||
{(["x64", "arm64"] as const).map(arch => (
|
||||
<a
|
||||
href="#"
|
||||
className={`arch ${arch === currentArch ? "active" : ""}`}
|
||||
onClick={() => setCurrentArch(arch)}
|
||||
>{arch}</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid-3-cols download-desktop">
|
||||
{Object.entries(downloadMatrix.desktop).map(entry => <DownloadCard app="desktop" arch={currentArch} entry={entry} />)}
|
||||
</div>
|
||||
|
||||
<div class="download-footer">
|
||||
<Link href="https://github.com/TriliumNext/Trilium/releases/" openExternally>See older releases</Link>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Set up a server for access on multiple devices">
|
||||
<div className="grid-2-cols download-server">
|
||||
{Object.entries(downloadMatrix.server).map(entry => <DownloadCard app="server" arch={currentArch} entry={entry} />)}
|
||||
</div>
|
||||
</Section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function DownloadCard({ app, arch, entry: [ platform, entry ] }: { app: App, arch: Architecture, entry: [string, DownloadMatrixEntry] }) {
|
||||
function unwrapText(text: string | Record<Architecture, string>) {
|
||||
return (typeof text === "string" ? text : text[arch]);
|
||||
}
|
||||
|
||||
const allDownloads = Object.entries(entry.downloads);
|
||||
const recommendedDownloads = allDownloads.filter(download => download[1].recommended);
|
||||
const restDownloads = allDownloads.filter(download => !download[1].recommended);
|
||||
|
||||
return (
|
||||
<Card title={<>
|
||||
{unwrapText(entry.title)}
|
||||
{entry.helpUrl && (
|
||||
<Link
|
||||
className="more-info"
|
||||
href={entry.helpUrl}
|
||||
openExternally
|
||||
>
|
||||
<Icon svg={helpIcon} />
|
||||
</Link>
|
||||
)}
|
||||
</>} className="download-card">
|
||||
{unwrapText(entry.description)}
|
||||
|
||||
{entry.quickStartCode && (
|
||||
<pre className="quick-start">
|
||||
<code>{entry.quickStartCode}</code>
|
||||
</pre>
|
||||
)}
|
||||
|
||||
<div class="download-options">
|
||||
<div className="recommended-options">
|
||||
{recommendedDownloads.map(recommendedDownload => (
|
||||
<Button
|
||||
className="recommended"
|
||||
href={buildDownloadUrl(app, platform as Platform, recommendedDownload[0], arch)}
|
||||
text={recommendedDownload[1].name}
|
||||
openExternally={!!recommendedDownload[1].url}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="other-options">
|
||||
{restDownloads.map(download => (
|
||||
<Link
|
||||
href={buildDownloadUrl(app, platform as Platform, download[0], arch)}
|
||||
openExternally={!!download[1].url}
|
||||
>
|
||||
{download[1].name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
244
apps/website/src/pages/Home/index.css
Normal file
@ -0,0 +1,244 @@
|
||||
section.hero-section {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(40vmax 40vmax at 15% 25%, rgba(228, 123, 25, 0.25), transparent 70%),
|
||||
radial-gradient(35vmax 35vmax at 75% 20%, rgba(79, 165, 43, 0.22), transparent 70%),
|
||||
radial-gradient(28vmax 28vmax at 60% 75%, rgba(227, 63, 59, 0.22), transparent 70%),
|
||||
radial-gradient(20vmax 20vmax at 85% 65%, rgba(228, 123, 25, 0.18), transparent 70%),
|
||||
radial-gradient(15vmax 15vmax at 40% 50%, rgba(79, 165, 43, 0.15), transparent 70%);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background:
|
||||
radial-gradient(40vmax 40vmax at 20% 30%, rgba(228, 123, 25, 0.25), transparent 70%),
|
||||
radial-gradient(30vmax 30vmax at 75% 25%, rgba(79, 165, 43, 0.25), transparent 70%),
|
||||
radial-gradient(25vmax 25vmax at 60% 75%, rgba(227, 63, 59, 0.25), transparent 70%),
|
||||
radial-gradient(20vmax 20vmax at 85% 65%, rgba(228, 123, 25, 0.15), transparent 70%);
|
||||
}
|
||||
}
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0) 60%, rgba(0, 0, 0, 0.06) 100%);
|
||||
pointer-events: none;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
opacity: 0.04;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
&>.content-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
min-width: 80vw;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
flex-basis: 40%;
|
||||
color: var(--muted-color);
|
||||
|
||||
h1 {
|
||||
line-height: 1.1;
|
||||
font-weight: 100;
|
||||
color: var(--foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.download-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.download-button {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.more-download-options {
|
||||
display: inline-block;
|
||||
color: var(--brand-1);
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.additional-options {
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
font-size: 0.85em;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 719px) {
|
||||
section.hero-section {
|
||||
padding-bottom: 0;
|
||||
|
||||
.content-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
section.hero-section > .content-wrapper {
|
||||
flex-direction: row;
|
||||
gap: 3em;
|
||||
padding: 3em;
|
||||
}
|
||||
|
||||
section.hero-section .download-wrapper {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
section.accented {
|
||||
background: linear-gradient(135deg, rgba(228, 123, 25, 0.08), rgba(79, 165, 43, 0.08));
|
||||
}
|
||||
|
||||
.benefits-container .card {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
section.final-cta {
|
||||
text-align: center;
|
||||
padding: 6em 0;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--muted-color);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
justify-content: center;
|
||||
margin-top: 2em;
|
||||
|
||||
.button {
|
||||
padding: 0.75em 2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section.faq {
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.15);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
box-shadow: unset;
|
||||
border-top: 1px solid var(--brand-1);
|
||||
}
|
||||
}
|
||||
|
||||
.list-with-screenshot {
|
||||
gap: 2em;
|
||||
|
||||
@media (min-width: 720px) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-bottom: 1em;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
&.selected .card {
|
||||
border: 1px solid var(--brand-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
flex-basis: 50%;
|
||||
flex-shrink: 0;
|
||||
|
||||
@media (max-width: 719px) {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
flex-direction: column-reverse;
|
||||
|
||||
ul {
|
||||
gap: 1em;
|
||||
display: grid;
|
||||
|
||||
@media (min-width: 720px) {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: var(--brand-1);
|
||||
}
|
||||
|
||||
.details {
|
||||
max-height: 35vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
304
apps/website/src/pages/Home/index.tsx
Normal file
@ -0,0 +1,304 @@
|
||||
import { ComponentChildren } from 'preact';
|
||||
import Card from '../../components/Card.js';
|
||||
import Section from '../../components/Section.js';
|
||||
import DownloadButton from '../../components/DownloadButton.js';
|
||||
import "./index.css";
|
||||
import { useColorScheme, usePageTitle } from '../../hooks.js';
|
||||
import Button, { Link } from '../../components/Button.js';
|
||||
import gitHubIcon from "../../assets/boxicons/bx-github.svg?raw";
|
||||
import dockerIcon from "../../assets/boxicons/bx-docker.svg?raw";
|
||||
import noteStructureIcon from "../../assets/boxicons/bx-folder.svg?raw";
|
||||
import attributesIcon from "../../assets/boxicons/bx-tag.svg?raw";
|
||||
import hoistingIcon from "../../assets/boxicons/bx-chevrons-up.svg?raw";
|
||||
import revisionsIcon from "../../assets/boxicons/bx-history.svg?raw";
|
||||
import syncIcon from "../../assets/boxicons/bx-refresh-cw.svg?raw";
|
||||
import protectedNotesIcon from "../../assets/boxicons/bx-shield.svg?raw";
|
||||
import jumpToIcon from "../../assets/boxicons/bx-send-alt.svg?raw";
|
||||
import searchIcon from "../../assets/boxicons/bx-search.svg?raw";
|
||||
import webClipperIcon from "../../assets/boxicons/bx-paperclip.svg?raw";
|
||||
import importExportIcon from "../../assets/boxicons/bx-swap-horizontal.svg?raw";
|
||||
import shareIcon from "../../assets/boxicons/bx-globe.svg?raw";
|
||||
import codeIcon from "../../assets/boxicons/bx-code.svg?raw";
|
||||
import restApiIcon from "../../assets/boxicons/bx-extension.svg?raw";
|
||||
import textNoteIcon from "../../assets/boxicons/bx-note.svg?raw";
|
||||
import fileIcon from "../../assets/boxicons/bx-file.svg?raw";
|
||||
import canvasIcon from "../../assets/boxicons/bx-pen.svg?raw";
|
||||
import mermaidIcon from "../../assets/boxicons/bx-vector-square.svg?raw";
|
||||
import mindmapIcon from "../../assets/boxicons/bx-network-chart.svg?raw";
|
||||
import calendarIcon from "../../assets/boxicons/bx-calendar.svg?raw";
|
||||
import tableIcon from "../../assets/boxicons/bx-table.svg?raw";
|
||||
import boardIcon from "../../assets/boxicons/bx-columns-3.svg?raw";
|
||||
import geomapIcon from "../../assets/boxicons/bx-map.svg?raw";
|
||||
import { getPlatform } from '../../download-helper.js';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
|
||||
export function Home() {
|
||||
usePageTitle("");
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeroSection />
|
||||
<OrganizationBenefitsSection />
|
||||
<ProductivityBenefitsSection />
|
||||
<NoteTypesSection />
|
||||
<ExtensibilityBenefitsSection />
|
||||
<CollectionsSection />
|
||||
<FaqSection />
|
||||
<FinalCta />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function HeroSection() {
|
||||
const platform = getPlatform();
|
||||
const colorScheme = useColorScheme();
|
||||
const [ screenshotUrl, setScreenshotUrl ] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
switch (platform) {
|
||||
case "macos":
|
||||
setScreenshotUrl(`/screenshot_desktop_mac_${colorScheme}.webp`);
|
||||
break;
|
||||
case "linux":
|
||||
break;
|
||||
case "windows":
|
||||
default:
|
||||
setScreenshotUrl(`/screenshot_desktop_win_${colorScheme}.webp`);
|
||||
break;
|
||||
}
|
||||
}, [ colorScheme ]);
|
||||
|
||||
return (
|
||||
<Section className="hero-section">
|
||||
<div class="title-section">
|
||||
<h1>Organize your thoughts. Build your personal knowledge base.</h1>
|
||||
<p>Trilium is an open-source solution for note-taking and organizing a personal knowledge base. Use it locally on your desktop, or sync it with your self-hosted server to keep your notes everywhere you go.</p>
|
||||
|
||||
<div className="download-wrapper">
|
||||
<DownloadButton big />
|
||||
<a class="more-download-options desktop-only" href="./get-started/">More platforms & server setup</a>
|
||||
<Button href="./get-started/" className="mobile-only" text="Get started" />
|
||||
<div className="additional-options">
|
||||
<Button iconSvg={gitHubIcon} outline text="GitHub" href="https://github.com/TriliumNext/Trilium/" openExternally />
|
||||
<Button iconSvg={dockerIcon} outline text="Docker Hub" href="https://hub.docker.com/r/triliumnext/trilium" openExternally />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{screenshotUrl && <img class="screenshot" src={screenshotUrl} alt="Screenshot of the Trilium Notes desktop application" />}
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
function OrganizationBenefitsSection() {
|
||||
return (
|
||||
<>
|
||||
<Section className="benefits" title="Organization">
|
||||
<div className="benefits-container grid-3-cols">
|
||||
<Card iconSvg={noteStructureIcon} title="Note structure" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes/index.html">Notes can be arranged hierarchically. There's no need for folders, since each note can contain sub-notes. A single note can be added in multiple places in the hierarchy.</Card>
|
||||
<Card iconSvg={attributesIcon} title="Note labels and relationships" moreInfoUrl="https://docs.triliumnotes.org/User Guide/User Guide/Advanced Usage/Attributes/index.html">Use <em>relations</em> between notes or add <em>labels</em> for easy categorization. Use promoted attributes to enter structured information which can be used in tables, boards.</Card>
|
||||
<Card iconSvg={hoistingIcon} title="Workspaces and hoisting" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Navigation/Note%20Hoisting.html">Easily separate your personal and work notes by grouping them under a workspace, which focuses your note tree to only show a specific set of notes.</Card>
|
||||
</div>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ProductivityBenefitsSection() {
|
||||
return (
|
||||
<>
|
||||
<Section className="benefits accented" title="Productivity and safety">
|
||||
<div className="benefits-container grid-3-cols">
|
||||
<Card iconSvg={revisionsIcon} title="Note revisions" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes/Note%20Revisions.html">Notes are periodically saved in the background and revisions can be used for review or to undo accidental changes. Revisions can also be created on-demand.</Card>
|
||||
<Card iconSvg={syncIcon} title="Synchronization" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Installation%20%26%20Setup/Synchronization.html">Use a self-hosted or cloud instance to easily synchronize your notes across multiple devices, and to access it from your mobile phone using a PWA.</Card>
|
||||
<Card iconSvg={protectedNotesIcon} title="Protected notes" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes/Protected%20Notes.html">Protect sensitive personal information by encrypting the notes and locking them behind a password-protected session.</Card>
|
||||
<Card iconSvg={jumpToIcon} title="Quick search and commands" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Navigation/Jump%20to.html">Jump quickly to notes or UI commands across the hierarchy by searching for their title, with fuzzy matching to account for typos or slight differences.</Card>
|
||||
<Card iconSvg={searchIcon} title="Powerful search" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Navigation/Search.html">Or search for text inside notes and narrow down the search by filtering by the parent note, or by depth.</Card>
|
||||
<Card iconSvg={webClipperIcon} title="Web clipper" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Installation%20%26%20Setup/Web%20Clipper.html">Grab web pages (or screenshots) and place them directly into Trilium using the web clipper browser extension.</Card>
|
||||
</div>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function NoteTypesSection() {
|
||||
return (
|
||||
<Section className="note-types" title="Multiple ways to represent your information">
|
||||
<ListWithScreenshot horizontal items={[
|
||||
{
|
||||
title: "Text notes",
|
||||
imageUrl: "/type_text.webp",
|
||||
iconSvg: textNoteIcon,
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Text/index.html",
|
||||
description: "The notes are edited using a visual (WYSIWYG) editor, with support for tables, images, math expressions, code blocks with syntax highlighting. Quickly format the text using Markdown-like syntax or using slash commands."
|
||||
},
|
||||
{
|
||||
title: "Code notes",
|
||||
imageUrl: "/type_code.webp",
|
||||
iconSvg: codeIcon,
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Code.html",
|
||||
description: "Large samples of source code or scripts use a dedicated editor, with syntax highlighting for many programming languages and with various color themes."
|
||||
},
|
||||
{
|
||||
title: "File notes",
|
||||
imageUrl: "/type_file.webp",
|
||||
iconSvg: fileIcon,
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/File.html",
|
||||
description: "Embed multimedia files such as PDFs, images, videos with an in-application preview."
|
||||
},
|
||||
{
|
||||
title: "Canvas",
|
||||
imageUrl: "/type_canvas.webp",
|
||||
iconSvg: canvasIcon,
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Canvas.html",
|
||||
description: "Arrange shapes, images and text across an infinite canvas, using the same technology behind excalidraw.com. Ideal for diagrams, sketches and visual planning."
|
||||
},
|
||||
{
|
||||
title: "Mermaid diagrams",
|
||||
imageUrl: "/type_mermaid.webp",
|
||||
iconSvg: mermaidIcon,
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Mermaid%20Diagrams/index.html",
|
||||
description: "Create diagrams such as flowcharts, class & sequence diagrams, Gantt charts and many more, using the Mermaid syntax."
|
||||
},
|
||||
{
|
||||
title: "Mindmap",
|
||||
imageUrl: "/type_mindmap.webp",
|
||||
iconSvg: mindmapIcon,
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Mind%20Map.html",
|
||||
description: "Organize your thoughts visually or do a brainstorming session."
|
||||
}
|
||||
]} />
|
||||
<p>
|
||||
and others:{" "}
|
||||
<Link href="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Note%20Map.html" openExternally>note map</Link>,{" "}
|
||||
<Link href="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Relation%20Map.html" openExternally>relation map</Link>,{" "}
|
||||
<Link href="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Saved%20Search.html" openExternally>saved searches</Link>,{" "}
|
||||
<Link href="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Render%20Note.html" openExternally>render note</Link>,{" "}
|
||||
<Link href="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Web%20View.html" openExternally>web views</Link>.
|
||||
</p>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function ExtensibilityBenefitsSection() {
|
||||
return (
|
||||
<>
|
||||
<Section className="benefits accented" title="Sharing & extensibility">
|
||||
<div className="benefits-container grid-4-cols">
|
||||
<Card iconSvg={importExportIcon} title="Import/export" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Import%20%26%20Export/Markdown/index.html">Easily interact with other applications using Markdown, ENEX, OML formats.</Card>
|
||||
<Card iconSvg={shareIcon} title="Share notes on the web" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Advanced%20Usage/Sharing/Serving%20directly%20the%20content%20o.html">If you have a server, it can be used to share a subset of your notes with other people.</Card>
|
||||
<Card iconSvg={codeIcon} title="Advanced scripting" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Scripting/Custom%20Widgets/index.html">Build your own integrations within Trilium with custom widgets, or server-side logic.</Card>
|
||||
<Card iconSvg={restApiIcon} title="REST API" moreInfoUrl="https://docs.triliumnotes.org/User%20Guide/User%20Guide/Advanced%20Usage/ETAPI%20%28REST%20API%29/index.html">Interact with Trilium programatically using its builtin REST API.</Card>
|
||||
</div>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function CollectionsSection() {
|
||||
return (
|
||||
<Section className="collections" title="Collections">
|
||||
<ListWithScreenshot items={[
|
||||
{
|
||||
title: "Calendar",
|
||||
imageUrl: "/collection_calendar.webp",
|
||||
iconSvg: calendarIcon,
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Collections/Calendar%20View.html",
|
||||
description: "Organize your personal or professional events using a calendar, with support for all-day and multi-day events. See your events at a glance with the week, month and year views. Easy interaction to add or drag events."
|
||||
},
|
||||
{
|
||||
title: "Table",
|
||||
iconSvg: tableIcon,
|
||||
imageUrl: "/collection_table.webp",
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Collections/Table%20View.html",
|
||||
description: "Display and edit information about notes in a tabular structure, with various column types such as text, number, check boxes, date & time, links and colors and support for relations. Optionally, display the notes within a tree hierarchy inside the table." },
|
||||
{
|
||||
title: "Board",
|
||||
iconSvg: boardIcon,
|
||||
imageUrl: "/collection_board.webp",
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Collections/Board%20View.html",
|
||||
description: "Organize your tasks or project status into a Kanban board with an easy way to create new items and columns and simply changing their status by dragging across the board."
|
||||
},
|
||||
{
|
||||
title: "Geomap",
|
||||
iconSvg: geomapIcon,
|
||||
imageUrl: "/collection_geomap.webp",
|
||||
moreInfo: "https://docs.triliumnotes.org/User%20Guide/User%20Guide/Note%20Types/Collections/Geo%20Map%20View.html",
|
||||
description: "Plan your vacations or mark your points of interest directly on a geographical map using customizable markers. Display recorded GPX tracks to track itineraries."
|
||||
}
|
||||
]} />
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function ListWithScreenshot({ items, horizontal, cardExtra }: {
|
||||
items: { title: string, imageUrl: string, description: string, moreInfo: string, iconSvg?: string }[];
|
||||
horizontal?: boolean;
|
||||
cardExtra?: ComponentChildren;
|
||||
}) {
|
||||
const [ selectedItem, setSelectedItem ] = useState(items[0]);
|
||||
|
||||
return (
|
||||
<div className={`list-with-screenshot ${horizontal ? "horizontal" : ""}`}>
|
||||
<ul>
|
||||
{items.map(item => (
|
||||
<li className={`${item === selectedItem ? "selected" : ""}`}>
|
||||
<Card
|
||||
title={item.title}
|
||||
onMouseEnter={() => setSelectedItem(item)}
|
||||
onClick={() => setSelectedItem(item)}
|
||||
moreInfoUrl={item.moreInfo}
|
||||
iconSvg={item.iconSvg}
|
||||
>
|
||||
{item.description}
|
||||
</Card>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="details">
|
||||
{selectedItem && (
|
||||
<>
|
||||
<img src={selectedItem.imageUrl} alt="Screenshot of the feature being selected" loading="lazy" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FaqSection() {
|
||||
return (
|
||||
<Section className="faq" title="Frequently Asked Questions">
|
||||
<div class="grid-2-cols">
|
||||
<FaqItem question="Is there a mobile application?">Currently there is no official mobile application. However, if you have a server instance you can access it using a web browser and even install it as a PWA. For Android, there is an unofficial application called TriliumDroid that even works offline (same as a desktop client).</FaqItem>
|
||||
<FaqItem question="Where is the data stored?">All your notes will be stored in an SQLite database in an application folder. The reasoning why Trilium uses a database instead of plain text files is both performance and some features would be much more difficult to implement such as clones (same note in multiple places in the tree). To find the application folder, simply go to the About window.</FaqItem>
|
||||
<FaqItem question="Do I need a server to use Trilium?">No, the server allows access via a web browser and manages the synchronization if you have multiple devices. To get started, it's enough to download the desktop application and start using it.</FaqItem>
|
||||
<FaqItem question="How well does the application scale with a large amount of notes?">Depending on usage, the application should be able to handle at least 100.000 notes without an issue. Do note that the sync process can sometimes fail if uploading many large files (> 1 GB per file) since Trilium is meant more as a knowledge base application rather than a file store (like NextCloud, for example).</FaqItem>
|
||||
<FaqItem question="Can I share my database over a network drive?">No, it's generally not a good idea to share a SQLite database over a network drive. Although sometimes it might work, there are chances that the database will get corrupted due to imperfect file locks over a network.</FaqItem>
|
||||
<FaqItem question="How is my data protected?">By default, notes are not encrypted and can be read directly from the database. Once a note is marked as encrypted, the note is encrypted using AES-128-CBC.</FaqItem>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function FaqItem({ question, children }: { question: string; children: ComponentChildren }) {
|
||||
return (
|
||||
<Card title={question}>
|
||||
{children}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function FinalCta() {
|
||||
return (
|
||||
<Section className="final-cta accented" title="Ready to get started with Trilium Notes?">
|
||||
<p>Build your personal knowledge base with powerful features and full privacy.</p>
|
||||
|
||||
<div class="buttons">
|
||||
<Button href="./get-started/" text="Get started" />
|
||||
</div>
|
||||
</Section>
|
||||
)
|
||||
}
|
21
apps/website/src/pages/SupportUs/SupportUs.css
Normal file
@ -0,0 +1,21 @@
|
||||
section.donate {
|
||||
background: var(--background-color);
|
||||
|
||||
ul.donate-buttons {
|
||||
list-style-type: none;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
padding: 0;
|
||||
flex-direction: column;
|
||||
|
||||
@media (min-width: 720px) {
|
||||
gap: 0 1em;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5em;
|
||||
color: var(--brand-1);
|
||||
}
|
||||
}
|
70
apps/website/src/pages/SupportUs/SupportUs.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import Section from "../../components/Section.js";
|
||||
import "./SupportUs.css";
|
||||
import githubIcon from "../../assets/boxicons/bx-github.svg?raw";
|
||||
import paypalIcon from "../../assets/boxicons/bx-paypal.svg?raw";
|
||||
import buyMeACoffeeIcon from "../../assets/boxicons/bx-buy-me-a-coffee.svg?raw";
|
||||
import Button, { Link } from "../../components/Button.js";
|
||||
import Card from "../../components/Card.js";
|
||||
import { usePageTitle } from "../../hooks.js";
|
||||
|
||||
export default function Donate() {
|
||||
usePageTitle("Support us");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section title="Support us" className="donate fill">
|
||||
<div class="grid-2-cols">
|
||||
<Card title="Financial donations">
|
||||
<p>
|
||||
Trilium is built and maintained with <Link href="https://github.com/TriliumNext/Trilium/graphs/commit-activity" openExternally>hundreds of hours of work</Link>.
|
||||
Your support keeps it open-source, improves features, and covers costs such as hosting.
|
||||
</p>
|
||||
|
||||
<p>Consider supporting the main developer (<Link href="https://github.com/eliandoran" openExternally>eliandoran</Link>) of the application via:</p>
|
||||
|
||||
<ul class="donate-buttons">
|
||||
<li>
|
||||
<Button
|
||||
iconSvg={githubIcon}
|
||||
href="https://github.com/sponsors/eliandoran"
|
||||
text="GitHub Sponsors"
|
||||
openExternally
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Button
|
||||
iconSvg={paypalIcon}
|
||||
href="https://paypal.me/eliandoran"
|
||||
text="PayPal"
|
||||
openExternally
|
||||
outline
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Button
|
||||
iconSvg={buyMeACoffeeIcon}
|
||||
href="https://buymeacoffee.com/eliandoran"
|
||||
text="Buy Me A Coffee"
|
||||
openExternally
|
||||
outline
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<Card title="Other ways to contribute">
|
||||
<ul>
|
||||
<li>Translate the application into your native language via <Link href="https://hosted.weblate.org/engage/trilium/" openExternally>Weblate</Link>.</li>
|
||||
<li>Interact with the community on <Link href="https://github.com/orgs/TriliumNext/discussions" openExternally>GitHub Discussions</Link> or on <Link href="https://matrix.to/#/#triliumnext:matrix.org" openExternally>Matrix</Link>.</li>
|
||||
<li>Report bugs via <Link href="https://github.com/TriliumNext/Trilium/issues" openExternally>GitHub issues</Link>.</li>
|
||||
<li>Improve the documentation by informing us on gaps in the documentation or contributing guides, FAQs or tutorials.</li>
|
||||
<li>Spread the word: Share Trilium Notes with friends, or on blogs and social media.</li>
|
||||
</ul>
|
||||
</Card>
|
||||
</div>
|
||||
</Section>
|
||||
</>
|
||||
)
|
||||
}
|
9
apps/website/src/pages/_404.css
Normal file
@ -0,0 +1,9 @@
|
||||
section.section-404 {
|
||||
text-align: center;
|
||||
background: var(--background-color);
|
||||
|
||||
h2 {
|
||||
color: var(--brand-3);
|
||||
margin: 1em;
|
||||
}
|
||||
}
|
13
apps/website/src/pages/_404.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import Section from "../components/Section.js";
|
||||
import { usePageTitle } from "../hooks.js";
|
||||
import "./_404.css";
|
||||
|
||||
export function NotFound() {
|
||||
usePageTitle("404");
|
||||
|
||||
return (
|
||||
<Section title="404: Not Found" className="section-404">
|
||||
The page you were looking for could not be found. Maybe it was deleted or the URL is incorrect.
|
||||
</Section>
|
||||
);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import Header from './header.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<footer class="container max-w-screen mx-0 w-full bg-white dark:bg-gray-900 mt-2 py-6 text-sm text-center text-gray-500">
|
||||
© 2024-2025 <a href="https://github.com/eliandoran" class="text-blue-500 hover:underline">Elian Doran</a> and the <a href="https://github.com/TriliumNext/Notes/graphs/contributors" class="text-blue-500 hover:underline">team</a>. <br/> © 2017-2024 <a href="https://github.com/zadam" class="text-blue-500 hover:underline">zadam</a>.
|
||||
</footer>
|
@ -1,176 +0,0 @@
|
||||
<script>
|
||||
import DownloadNow from "./download-now.svelte";
|
||||
import FeatureBlock from "./feature-block.svelte";
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Trilium Notes</title>
|
||||
<!-- TODO: description?
|
||||
<meta name="description" content="This is where the description goes for search engines" />
|
||||
-->
|
||||
</svelte:head>
|
||||
|
||||
<section class="relative overflow-hidden bg-gradient-to-br from-white dark:from-black to-violet-50 dark:to-violet-900">
|
||||
<!-- Bokeh background circles -->
|
||||
<div class="absolute inset-0 pointer-events-none z-0">
|
||||
<div class="absolute w-72 h-72 bg-violet-300 opacity-30 rounded-full blur-3xl top-[-50px] left-[-80px]"></div>
|
||||
<div class="absolute w-96 h-96 bg-pink-200 opacity-20 rounded-full blur-3xl bottom-[-100px] right-[-60px]"></div>
|
||||
<div class="absolute w-64 h-64 bg-indigo-200 opacity-20 rounded-full blur-2xl top-[200px] left-[50%] transform -translate-x-1/2"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 container mx-auto pt-24 pb-24 px-4">
|
||||
<div class="flex flex-col md:flex-row items-center md:justify-between gap-12">
|
||||
|
||||
<!-- Left: Text Content -->
|
||||
<div class="md:w-1/3">
|
||||
<h2 class="text-4xl font-bold mb-4 text-gray-900 dark:text-white">Organize Your Thoughts.<br/> Build Your Knowledge.</h2>
|
||||
<p class="text-lg mb-6 text-gray-700 dark:text-gray-300">
|
||||
Trilium Notes helps you build and organize complex personal knowledge bases effortlessly.
|
||||
Its unique tree structure, rich editing tools, and powerful search features make managing your information intuitive and flexible.
|
||||
<!-- TODO: remove the squiggly autocorrect lines in the screenshot!! -->
|
||||
<!-- TODO: dark mode screenshot -->
|
||||
</p>
|
||||
<div class="flex items-center gap-6">
|
||||
<DownloadNow big />
|
||||
<a href="/download" class="font-medium text-violet-700 hover:underline">
|
||||
More platforms
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Screenshot -->
|
||||
<div class="md:w-2/3">
|
||||
<img src="screenshots/desktop-win.png" alt="Screenshot of the app on desktop Windows" class="w-full rounded-xl shadow-lg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mt-20 max-w-6xl mx-auto px-4">
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Beyond Text: Smarter Note Types</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-10">
|
||||
<FeatureBlock
|
||||
imgSrc="/note-types/canvas.png"
|
||||
imgAlt="Canvas Note Screenshot"
|
||||
title="Canvas Notes"
|
||||
text="Draw and arrange elements freely using an Excalidraw-powered canvas — ideal for diagrams, sketches, and visual planning."
|
||||
/>
|
||||
|
||||
<FeatureBlock
|
||||
imgSrc="/note-types/mermaid.png"
|
||||
imgAlt="Mermaid Diagram Screenshot"
|
||||
title="Mermaid Diagrams"
|
||||
text="Render flowcharts, Gantt charts, and sequence diagrams with Mermaid markdown syntax directly in your notes."
|
||||
/>
|
||||
|
||||
<FeatureBlock
|
||||
imgSrc="/note-types/geo-map.png"
|
||||
imgAlt="Geo Map Screenshot"
|
||||
title="Geo Maps"
|
||||
text="Plot locations and GPX tracks to visualize geography-linked notes and movement patterns on interactive maps."
|
||||
/>
|
||||
|
||||
<FeatureBlock
|
||||
imgSrc="/note-types/mind-map.png"
|
||||
imgAlt="Mind Map Screenshot"
|
||||
title="Mind Maps"
|
||||
text="Organize ideas visually using a drag-and-drop mind map editor powered by Mind Elixir."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Technical Features</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-10">
|
||||
<FeatureBlock
|
||||
imgSrc="/technical-features/sync-server.png"
|
||||
imgAlt="TODO"
|
||||
title="Synchronization Server"
|
||||
text="Seamless mirroring of changes acroll all devices."
|
||||
/>
|
||||
|
||||
<FeatureBlock
|
||||
imgSrc="/technical-features/cross-platform.png"
|
||||
imgAlt="TODO, maybe some icons"
|
||||
title="Cross-platform App + Web UI"
|
||||
text="Use as Electron application or in your browser."
|
||||
/>
|
||||
|
||||
<FeatureBlock
|
||||
imgSrc="/technical-features/scripting.png"
|
||||
imgAlt="TODO"
|
||||
title="Scripting"
|
||||
text="Custom UI widgets and a REST API for automation."
|
||||
/>
|
||||
|
||||
<FeatureBlock
|
||||
imgSrc="/technical-features/grafana-metrics.png"
|
||||
imgAlt="Mind Map Screenshot"
|
||||
title="Grafana Metrics"
|
||||
text="Measure database metrics over time."
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mt-20 max-w-6xl mx-auto px-4">
|
||||
<h2 class="text-3xl font-bold text-center mb-12">Feature Highlights</h2>
|
||||
|
||||
<div class="grid gap-12 md:grid-cols-2 max-w-4xl mx-auto text-gray-700 dark:text-gray-300">
|
||||
<!-- Organization & Navigation -->
|
||||
<div>
|
||||
<h3 class="flex items-center text-xl font-semibold mb-6 text-violet-700">Organization & Navigation</h3>
|
||||
<ul class="list-disc list-inside space-y-3">
|
||||
<li>Arbitrarily deep note tree with cloning support.</li>
|
||||
<li>Fast navigation, full-text search, and note hoisting.</li>
|
||||
<li>Note attributes for organization, querying, and scripting.</li>
|
||||
<li>Seamless note versioning.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Editing & Content -->
|
||||
<div>
|
||||
<h3 class="flex items-center text-xl font-semibold mb-6 text-violet-700">Editing & Content</h3>
|
||||
<ul class="list-disc list-inside space-y-3">
|
||||
<li>Rich WYSIWYG editor with tables, images, math, and markdown autoformat.</li>
|
||||
<li>Source code editing with syntax highlighting.</li>
|
||||
<li>Evernote and Markdown import/export.</li>
|
||||
<li>Web Clipper for easy saving of web content.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Security & Sync -->
|
||||
<div>
|
||||
<h3 class="flex items-center text-xl font-semibold mb-6 text-violet-700">Security & Sync</h3>
|
||||
<ul class="list-disc list-inside space-y-3">
|
||||
<li>Direct OpenID and TOTP integration for secure login.</li>
|
||||
<li>Strong note encryption with per-note granularity.</li>
|
||||
<li>Sharing notes publicly on the internet.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Advanced & Customization -->
|
||||
<div>
|
||||
<h3 class="flex items-center text-xl font-semibold mb-6 text-violet-700">Advanced & Customization</h3>
|
||||
<ul class="list-disc list-inside space-y-3">
|
||||
<li>Relation maps and link maps to visualize notes.</li>
|
||||
<li>Touch-optimized mobile frontend and dark/user themes.</li>
|
||||
<li>Customizable UI with sidebar buttons and user widgets.</li>
|
||||
<li>Scales efficiently beyond 100,000 notes.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section class="bg-violet-50 dark:bg-black py-16 mt-24">
|
||||
<div class="container mx-auto text-center px-4">
|
||||
<h2 class="text-3xl font-bold mb-4">Ready to get started with Trilium Notes?</h2>
|
||||
<p class="text-lg text-gray-700 dark:text-gray-200 mb-8">Build your personal knowledge base with powerful features and full privacy.</p>
|
||||
|
||||
<div class="flex justify-center gap-6">
|
||||
<a href="download" class="py-3 px-6 bg-violet-600 text-white font-semibold rounded-full shadow hover:bg-violet-700 focus:outline-none focus:ring focus:ring-violet-400 focus:ring-opacity-75">
|
||||
Download Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -1 +0,0 @@
|
||||
<a href="/demo/paraglide">paraglide</a>
|
@ -1,15 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { setLocale } from '$lib/paraglide/runtime';
|
||||
import { page } from '$app/state';
|
||||
import { goto } from '$app/navigation';
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<h1>{m.hello_world({ name: 'SvelteKit User' })}</h1>
|
||||
<div>
|
||||
<button onclick={() => setLocale('en')}>en</button>
|
||||
</div><p>
|
||||
If you use VSCode, install the <a href="https://marketplace.visualstudio.com/items?itemName=inlang.vs-code-extension" target="_blank">Sherlock i18n extension</a> for a better i18n experience.
|
||||
</p>
|
@ -1,18 +0,0 @@
|
||||
<script>
|
||||
import { getRecommendedDownload } from "$lib/download-helper";
|
||||
|
||||
export let big = false;
|
||||
const { url, platform, architecture } = getRecommendedDownload();
|
||||
</script>
|
||||
|
||||
{#if url}
|
||||
<a href="{url}"
|
||||
class:text-xl={big}
|
||||
class:py-4={big}
|
||||
class="py-2 px-5 bg-violet-600 text-white font-semibold rounded-xl shadow-md hover:bg-violet-700 focus:outline-none focus:ring focus:ring-violet-400 focus:ring-opacity-75">
|
||||
Download now
|
||||
<span class="text-sm text-gray-300">
|
||||
({platform} {architecture})
|
||||
</span>
|
||||
</a>
|
||||
{/if}
|
@ -1,65 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Platform } from "$lib/download-helper";
|
||||
import { downloadMatrix, getArchitecture } from "$lib/download-helper";
|
||||
import DownloadCard from "./download-card.svelte";
|
||||
|
||||
let architectures = ["x64", "arm64"] as const;
|
||||
let architecture = getArchitecture();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Trilium Notes: Download</title>
|
||||
<!-- TODO: description?
|
||||
<meta name="description" content="This is where the description goes for search engines" />
|
||||
-->
|
||||
</svelte:head>
|
||||
|
||||
<div class="bg-gray-50 dark:bg-black py-20">
|
||||
<section class="max-w-6xl mx-auto px-4">
|
||||
<h2 class="text-4xl font-bold text-center text-gray-900 dark:text-white mb-12">Download the desktop application</h2>
|
||||
|
||||
<!-- Architecture pill selector -->
|
||||
<div class="col-span-3 flex justify-center items-center gap-3 mb-6">
|
||||
<span class="text-gray-600 dark:text-gray-300 font-medium mr-2">Architecture:</span>
|
||||
<div class="inline-flex bg-violet-100 rounded-full shadow p-1">
|
||||
{#each architectures as arch}
|
||||
<button class="py-2 px-6 rounded-full font-semibold focus:outline-none transition
|
||||
text-violet-700 border-violet-700
|
||||
aria-pressed:bg-violet-700 aria-pressed:text-violet-100
|
||||
" aria-pressed={architecture === arch} on:click={() => architecture = arch}>
|
||||
{arch}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-10">
|
||||
{#each Object.entries(downloadMatrix.desktop) as [platformId, platform]}
|
||||
{@const textColor = (platformId === "windows" ? "text-blue-600" : platformId === "linux" ? "text-violet-600" : "text-gray-800 dark:text-gray-100")}
|
||||
{@const bgColor = (platformId === "windows" ? "bg-blue-600" : platformId === "linux" ? "bg-violet-600" : "bg-gray-800")}
|
||||
{@const hoverColor = (platformId === "windows" ? "hover:bg-blue-700" : platformId === "linux" ? "hover:bg-violet-700" : "hover:bg-gray-900")}
|
||||
<DownloadCard app="desktop"
|
||||
{textColor} {bgColor} {hoverColor}
|
||||
{platform} {architecture} platformId={platformId as Platform} />
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="max-w-4xl mx-auto px-4 mt-10">
|
||||
<h2 class="text-3xl font-bold text-center text-gray-900 dark:text-white mb-8">Set up a server for access on multiple devices</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-10">
|
||||
{#each Object.entries(downloadMatrix.server) as [platformId, platform]}
|
||||
{@const textColor = (platformId === "linux" ? "text-violet-600" : "text-gray-800 dark:text-gray-100")}
|
||||
{@const bgColor = (platformId === "linux" ? "bg-violet-600" : "bg-gray-800")}
|
||||
{@const hoverColor = (platformId === "linux" ? "hover:bg-violet-700" : "hover:bg-gray-900")}
|
||||
<DownloadCard app="server"
|
||||
{textColor} {bgColor} {hoverColor}
|
||||
{platform} {architecture} platformId={platformId as Platform} />
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- TODO: mention mobile support here? (alpha Android app / mobile web view) -->
|
||||
|
||||
</div>
|
@ -1,31 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { buildDownloadUrl, type Architecture, type DownloadMatrixEntry, type Platform, type App } from "$lib/download-helper";
|
||||
|
||||
export let app: App = "desktop";
|
||||
export let platformId: Platform;
|
||||
export let platform: DownloadMatrixEntry;
|
||||
export let textColor: string;
|
||||
export let bgColor: string;
|
||||
export let hoverColor: string;
|
||||
export let architecture: Architecture | null = null;
|
||||
const recommended = Object.entries(platform.downloads).find((e) => e[1].recommended);
|
||||
</script>
|
||||
|
||||
<div class="bg-white dark:bg-gray-900 border border-gray-200 rounded-2xl shadow-lg p-8 flex flex-col items-start">
|
||||
<h3 class="text-2xl font-semibold {textColor} mb-2">{typeof platform.title === "object" ? platform.title[architecture] : platform.title}</h3>
|
||||
<p class="text-gray-700 dark:text-gray-200 mb-12">{typeof platform.title === "object" ? platform.description[architecture] : platform.description}</p>
|
||||
<div class="space-y-2 mt-auto w-full">
|
||||
{#if recommended}
|
||||
<a href={buildDownloadUrl(app, platformId as Platform, recommended[0], architecture)} class="mt-auto block text-center {bgColor} {hoverColor} text-white font-medium py-2 px-5 rounded-full shadow transition">
|
||||
{recommended[1].name}
|
||||
</a>
|
||||
{/if}
|
||||
<div class="flex flex-wrap justify-center gap-4 text-sm {textColor} mt-2">
|
||||
{#each Object.entries(platform.downloads).filter((e) => !e[1].recommended) as [format, download]}
|
||||
<a href={buildDownloadUrl(app, platformId as Platform, format, architecture)} class="hover:underline block">
|
||||
{download.name}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,14 +0,0 @@
|
||||
<script>
|
||||
export let imgSrc = "/404.png";
|
||||
export let imgAlt = "screenshot";
|
||||
export let title = "title";
|
||||
export let text = "text";
|
||||
</script>
|
||||
|
||||
<div class="bg-white dark:bg-gray-900 rounded-xl shadow overflow-hidden">
|
||||
<img src="{imgSrc}" alt="{imgAlt}" class="w-full h-56 object-cover object-top">
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold mb-2">{title}</h3>
|
||||
<p class="text-gray-600 dark:text-gray-300">{text}</p>
|
||||
</div>
|
||||
</div>
|
@ -1,23 +0,0 @@
|
||||
<script>
|
||||
import DownloadNow from "./download-now.svelte";
|
||||
|
||||
</script>
|
||||
<header class="header bg-white dark:bg-gray-900 sticky dark:text-white top-0 z-50 shadow">
|
||||
<div class="container mx-auto flex items-center py-4">
|
||||
<a href="/" class="flex items-center gap-x-2 w-100">
|
||||
<img src="icon-color.svg" alt="Trilium Notes Logo" class="w-12 h-12">
|
||||
<span class="text-2xl">Trilium Notes</span>
|
||||
</a>
|
||||
|
||||
<div class="group w-full">
|
||||
<nav class="header-nav">
|
||||
<ul class="flex items-center justify-end gap-4">
|
||||
<li><a href="/">User Guide</a></li>
|
||||
<li><a href="/">Technical Guide</a></li>
|
||||
<li><a href="/" class="text-violet-500">Support us</a></li>
|
||||
<li><DownloadNow /></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
184
apps/website/src/style.css
Normal file
@ -0,0 +1,184 @@
|
||||
:root {
|
||||
--background-color: #fff;
|
||||
--foreground-color: black;
|
||||
--muted-color: #606060;
|
||||
--card-background-color: white;
|
||||
--card-box-shadow: 0 0 3px rgba(0, 0, 0, 0.25);
|
||||
--header-background-color: rgba(255, 255, 255, 0.75);
|
||||
--brand-1: #e47b19;
|
||||
--brand-2: #4fa52b;
|
||||
--brand-3: #e33f3b;
|
||||
--brand-foreground-color: white;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-color: #fff;
|
||||
--background-color: #0a0e14;
|
||||
--muted-color: #cacaca;
|
||||
--header-background-color: rgba(0, 0, 0, 0.75);
|
||||
--card-background-color: #ffffff12;
|
||||
--card-box-shadow: 0 0 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
min-height: calc(100vh - 80px - 90px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: var(--background-color);
|
||||
color: var(--foreground-color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--brand-3);
|
||||
text-decoration: none;
|
||||
|
||||
&:not(.button):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
max-width: 1200px;
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 1em 0;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
|
||||
&.fill {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-weight: 100;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 0;
|
||||
box-shadow: var(--card-box-shadow);
|
||||
background-color: var(--card-background-color);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h3 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 300;
|
||||
margin: 0;
|
||||
color: var(--brand-1);
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
&> span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&> .image {
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
margin: 0;
|
||||
padding: 1em;
|
||||
color: var(--muted-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
|
||||
.more-info-container {
|
||||
margin-top: 0.5em;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
.more-info {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
vertical-align: middle;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 719px) {
|
||||
.grid-4-cols > *,
|
||||
.grid-3-cols > *,
|
||||
.grid-2-cols > * {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 720px) {
|
||||
section {
|
||||
padding: 3em 0;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-4-cols {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.grid-3-cols {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.grid-2-cols {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.mobile-only {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
../../../apps/client/src/assets/icon.png
|