Compare commits
28 Commits
a89c04e316
...
release
| Author | SHA1 | Date | |
|---|---|---|---|
| fa3f7a8403 | |||
| 1d9508f49b | |||
| 6d2ffe6902 | |||
| c352ba7fd3 | |||
| 95087124b5 | |||
| 1d2f617a4e | |||
| 7fc923244e | |||
| f237d73016 | |||
| cae187b939 | |||
| 6f807b4df5 | |||
| ee6e250277 | |||
| 0dfa43c4ed | |||
| b228f3cd8d | |||
| d78fe7b020 | |||
| d8fedfb379 | |||
| 233442b099 | |||
| 0c7eedaee8 | |||
| 7385b7ba41 | |||
| 74f7274a7e | |||
| ab7464eb83 | |||
| 512d102bb2 | |||
| 82e0ee9280 | |||
| ba6a8c1308 | |||
| 7d70ad51e5 | |||
| bbbae5c8a6 | |||
| 518b819fe8 | |||
| 5791b61a48 | |||
| e8b6fcdbba |
18
.drone.yml
Normal file
18
.drone.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
kind: pipeline
|
||||
name: web
|
||||
type: docker
|
||||
|
||||
steps:
|
||||
- name: deploy web
|
||||
image: node:alpine
|
||||
environment:
|
||||
IS_PROD: true
|
||||
TOKEN:
|
||||
from_secret: VERCEL_TOKEN
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
commands:
|
||||
- npm install -g vercel@latest
|
||||
- cd web
|
||||
- /bin/sh deploy.sh
|
||||
50
.github/workflows/publish.yml
vendored
Normal file
50
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: "publish"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
|
||||
jobs:
|
||||
publish-tauri:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, ubuntu-20.04, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: app
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
- name: setup .npmrc
|
||||
run: mv .npmrc.githubactions .npmrc
|
||||
- name: install frontend dependencies
|
||||
run: yarn install # change this to npm or pnpm depending on which one you use
|
||||
env:
|
||||
UNOM_PACKAGES_TOKEN: ${{ secrets.UNOM_PACKAGES_TOKEN }}
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
UNOM_PACKAGES_TOKEN: ${{ secrets.UNOM_PACKAGES_TOKEN }}
|
||||
with:
|
||||
tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version
|
||||
releaseName: "App v__VERSION__"
|
||||
releaseBody: "See the assets to download this version and install."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
39
.github/workflows/test.yml
vendored
Normal file
39
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: "test-on-pr"
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
test-app:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, ubuntu-20.04, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: app
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: install Rust stable
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
- name: setup .npmrc
|
||||
run: mv .npmrc.githubactions .npmrc
|
||||
- name: install frontend dependencies
|
||||
run: yarn install # change this to npm or pnpm depending on which one you use
|
||||
env:
|
||||
UNOM_PACKAGES_TOKEN: ${{ secrets.UNOM_PACKAGES_TOKEN }}
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
UNOM_PACKAGES_TOKEN: ${{ secrets.UNOM_PACKAGES_TOKEN }}
|
||||
24
README.md
24
README.md
@@ -2,10 +2,14 @@
|
||||
|
||||
# tempblade Creator
|
||||
|
||||
tempblade creator is a motion design application, build with typescript, skia, rust using tauri.
|
||||
Its currently in an early alpha stage, and there may be larger design changes to the overall structuring
|
||||
tempblade creator is a motion design application, built on top of rust and skia. Its main goal is to be a flexible motion design toolkit, to be used in different environments. Right now it consists of an editor/ui built with tauri where the ui uses react/typescript and the interpolation/timeline calculations are done in rust. It should also easily be possible to run it completly in the browser thanks to wasm and skia canvaskit. The project is currently in an early alpha stage, and there may be larger design changes to the overall structuring
|
||||
of the project.
|
||||
|
||||
## Why?
|
||||
|
||||
Currently there isn't really an open source 2D motion design tool, and there is no tool that runs on linux, even if you're willing
|
||||
to pay monthly. You could use blender, but its really not optimized for this use case (source: i've tried).
|
||||
|
||||
## How does it work?
|
||||
|
||||
Currently rust is used for things like keyframe interpolation and overall timeline calculation which then gets passed
|
||||
@@ -21,6 +25,16 @@ to the frontend in javascript/typescript. This happens using tauris IPC.
|
||||
- Fully typed
|
||||
- Multithreaded timeline/keyframe interpolation calculation using rayon
|
||||
- Runtime typesafety thanks to zod in typescript
|
||||
- Easy theming thanks to tailwindcss
|
||||
- Cross platform font discovery and loading in rust thanks to FontKit
|
||||
- Caching of skia entity instances like fonts etc.
|
||||
- Pretty fast (if compared to After Effects)
|
||||
|
||||
### Possible use cases
|
||||
|
||||
- Typical motion design creation
|
||||
- Data driven motion design -> craft your animation and automate the rendering/data population thanks to the soon coming typescript library
|
||||
- Creation of generative art
|
||||
|
||||
### Features currently w.i.p
|
||||
|
||||
@@ -30,5 +44,7 @@ to the frontend in javascript/typescript. This happens using tauris IPC.
|
||||
|
||||
- Integration with OpenFX inside rust
|
||||
- Standalone rust rust rendering using vulkan and or metal by using rust-skia
|
||||
- Standalone package for drawing (currently the logic is already decoupled from ui)
|
||||
- Caching system for the rendered keyframes (currently interpolation calculation happens during playback, this could easily be cached)
|
||||
- Standalone ts package for drawing (currently the logic is already decoupled from ui)
|
||||
- Standalone ts package for populating & rendering project files for enabling data driven integrations
|
||||
- Caching system for the rendered keyframes (currently interpolation calculation happens during playback, this could easily be cached)
|
||||
- Full pipeline automation with integrations for blender, houdini in more (Already working on this)
|
||||
|
||||
4
app/.npmrc.githubactions
Normal file
4
app/.npmrc.githubactions
Normal file
@@ -0,0 +1,4 @@
|
||||
//packages.unom.io/:_authToken=${UNOM_PACKAGES_TOKEN}
|
||||
registry=https://registry.npmjs.org/
|
||||
@tempblade:registry=https://packages.unom.io
|
||||
@unom:registry=https://packages.unom.io
|
||||
@@ -1,7 +1,11 @@
|
||||
# Tauri + React + Typescript
|
||||
# tempblade Creator
|
||||
|
||||
This template should help get you started developing with Tauri, React and Typescript in Vite.
|
||||
This is the directory containing the application. It uses tauri with react/vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
## Commands
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||
Start the dev server:
|
||||
```yarn tauri dev```
|
||||
|
||||
Create a production build:
|
||||
```yarn tauri build```
|
||||
|
||||
@@ -15,14 +15,26 @@
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-menubar": "^1.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-scroll-area": "^1.0.4",
|
||||
"@radix-ui/react-select": "^1.2.2",
|
||||
"@radix-ui/react-slider": "^1.1.1",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-toolbar": "^1.0.3",
|
||||
"@tauri-apps/api": "^1.3.0",
|
||||
"@tempblade/common": "^2.0.1",
|
||||
"@types/d3-array": "^3.0.5",
|
||||
"@types/lodash.set": "^4.3.7",
|
||||
"@unom/style": "^0.2.14",
|
||||
"@visx/axis": "^3.1.0",
|
||||
"@visx/event": "^3.0.1",
|
||||
"@visx/glyph": "^3.0.0",
|
||||
"@visx/gradient": "^3.0.0",
|
||||
"@visx/grid": "^3.0.1",
|
||||
"@visx/group": "^3.0.0",
|
||||
"@visx/responsive": "^3.0.0",
|
||||
"@visx/scale": "^3.0.0",
|
||||
"@visx/shape": "^3.0.0",
|
||||
"@visx/tooltip": "^3.1.2",
|
||||
"canvaskit-wasm": "^0.38.1",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"clsx": "^1.2.1",
|
||||
|
||||
98
app/src-tauri/Cargo.lock
generated
98
app/src-tauri/Cargo.lock
generated
@@ -418,6 +418,20 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "creator_rs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"font-kit",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple-easing",
|
||||
"tauri",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
@@ -441,14 +455,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.14"
|
||||
version = "0.9.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
|
||||
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"memoffset 0.9.0",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
@@ -645,9 +659,9 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.0"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
@@ -764,7 +778,7 @@ version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3cf3a800ff6e860c863ca6d4b16fd999db8b752819c1606884047b73e468535"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
"memoffset 0.8.0",
|
||||
"rustc_version 0.4.0",
|
||||
]
|
||||
|
||||
@@ -1081,8 +1095,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1547,12 +1563,12 @@ checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1694,6 +1710,15 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
@@ -1966,9 +1991,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70"
|
||||
checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
@@ -2570,11 +2595,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "2.3.3"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe"
|
||||
checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap",
|
||||
@@ -2586,9 +2611,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "2.3.3"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
|
||||
checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -2881,9 +2906,9 @@ checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.3.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d42ba3a2e8556722f31336a0750c10dbb6a81396a1c452977f515da83f69f842"
|
||||
checksum = "7fbe522898e35407a8e60dc3870f7579fea2fc262a6a6072eccdd37ae1e1d91e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cocoa",
|
||||
@@ -2929,9 +2954,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "929b3bd1248afc07b63e33a6a53c3f82c32d0b0a5e216e4530e94c467e019389"
|
||||
checksum = "7d2edd6a259b5591c8efdeb9d5702cb53515b82a6affebd55c7fd6d3a27b7d1b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -2942,14 +2967,13 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"tauri-winres",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a2105f807c6f50b2fa2ce5abd62ef207bc6f14c9fcc6b8caec437f6fb13bde"
|
||||
checksum = "54ad2d49fdeab4a08717f5b49a163bdc72efc3b1950b6758245fcde79b645e1a"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"brotli",
|
||||
@@ -2973,9 +2997,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8784cfe6f5444097e93c69107d1ac5e8f13d02850efa8d8f2a40fe79674cef46"
|
||||
checksum = "8eb12a2454e747896929338d93b0642144bb51e0dddbb36e579035731f0d76b7"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
@@ -2987,9 +3011,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3b80ea3fcd5fefb60739a3b577b277e8fc30434538a2f5bba82ad7d4368c422"
|
||||
checksum = "108683199cb18f96d2d4134187bb789964143c845d2d154848dda209191fd769"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@@ -3008,9 +3032,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1c396950b1ba06aee1b4ffe6c7cd305ff433ca0e30acbc5fa1a2f92a4ce70f1"
|
||||
checksum = "0b7aa256a1407a3a091b5d843eccc1a5042289baf0a43d1179d9f0fcfea37c1b"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"gtk",
|
||||
@@ -3028,12 +3052,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6f9c2dafef5cbcf52926af57ce9561bd33bb41d7394f8bb849c0330260d864"
|
||||
checksum = "03fc02bb6072bb397e1d473c6f76c953cda48b4a2d0cce605df284aa74a12e84"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"ctor",
|
||||
"dunce",
|
||||
"glob",
|
||||
"heck 0.4.1",
|
||||
"html5ever",
|
||||
@@ -3068,16 +3093,13 @@ dependencies = [
|
||||
name = "tempblade-creator-app"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"font-kit",
|
||||
"creator_rs",
|
||||
"logging_timer",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple-easing",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tint",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3382,15 +3404,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
|
||||
dependencies = [
|
||||
"getrandom 0.2.9",
|
||||
"rand 0.8.5",
|
||||
"uuid-macro-internal",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid-macro-internal"
|
||||
version = "1.3.3"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f67b459f42af2e6e1ee213cb9da4dbd022d3320788c3fb3e1b893093f1e45da"
|
||||
checksum = "8614dda80b9075fbca36bc31b58d1447715b1236af98dee21db521c47a0cc2c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -10,20 +10,20 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.3", features = [] }
|
||||
tauri-build = { version = "1.4", features = [] }
|
||||
|
||||
|
||||
[dependencies]
|
||||
|
||||
uuid = { version = "1.3.3", features = ["v4", "fast-rng", "macro-diagnostics"] }
|
||||
tauri = { version = "1.3", features = ["dialog-open", "dialog-save", "shell-open"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
creator_core = { path = "../../lib/creator_rs", features = [
|
||||
"fonts",
|
||||
"parallelization",
|
||||
"tauri",
|
||||
], version = "*", package = "creator_rs" }
|
||||
tauri = { version = "1.4", features = ["dialog-open", "dialog-save", "shell-open"] }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
tint = "1.0.0"
|
||||
simple-easing = "1.0.1"
|
||||
logging_timer = "1.1.0"
|
||||
rayon = "1.7"
|
||||
font-kit = "0.11.0"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
use super::{
|
||||
entities::common::AnimationData,
|
||||
keyframe::{Keyframe, Keyframes},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub trait AnimatedValue<T> {
|
||||
fn sort_keyframes(&mut self);
|
||||
fn get_value_at_frame(&self, curr_frame: i32, animation_data: &AnimationData, fps: i16) -> T;
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedFloat {
|
||||
pub keyframes: Keyframes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedFloatVec2 {
|
||||
pub keyframes: (AnimatedFloat, AnimatedFloat),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedFloatVec3 {
|
||||
pub keyframes: (AnimatedFloat, AnimatedFloat, AnimatedFloat),
|
||||
}
|
||||
|
||||
impl AnimatedFloat {
|
||||
pub fn new(val: f32) -> AnimatedFloat {
|
||||
AnimatedFloat {
|
||||
keyframes: Keyframes {
|
||||
values: vec![Keyframe {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
value: val,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
}],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedFloatVec2 {
|
||||
pub fn new(x: f32, y: f32) -> AnimatedFloatVec2 {
|
||||
AnimatedFloatVec2 {
|
||||
keyframes: (AnimatedFloat::new(x), AnimatedFloat::new(y)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedFloatVec3 {
|
||||
pub fn new(x: f32, y: f32, z: f32) -> AnimatedFloatVec3 {
|
||||
AnimatedFloatVec3 {
|
||||
keyframes: (
|
||||
AnimatedFloat::new(x),
|
||||
AnimatedFloat::new(y),
|
||||
AnimatedFloat::new(z),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedValue<f32> for AnimatedFloat {
|
||||
fn sort_keyframes(&mut self) {
|
||||
self.keyframes.sort();
|
||||
}
|
||||
|
||||
fn get_value_at_frame(&self, curr_frame: i32, animation_data: &AnimationData, fps: i16) -> f32 {
|
||||
self.keyframes
|
||||
.get_value_at_frame(curr_frame, &animation_data, fps)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedValue<(f32, f32, f32)> for AnimatedFloatVec3 {
|
||||
fn sort_keyframes(&mut self) {
|
||||
self.keyframes.0.sort_keyframes();
|
||||
self.keyframes.1.sort_keyframes();
|
||||
self.keyframes.2.sort_keyframes();
|
||||
}
|
||||
|
||||
fn get_value_at_frame(
|
||||
&self,
|
||||
curr_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> (f32, f32, f32) {
|
||||
let x = self
|
||||
.keyframes
|
||||
.0
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
let y = self
|
||||
.keyframes
|
||||
.1
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
let z = self
|
||||
.keyframes
|
||||
.2
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
return (x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedValue<(f32, f32)> for AnimatedFloatVec2 {
|
||||
fn sort_keyframes(&mut self) {
|
||||
self.keyframes.0.sort_keyframes();
|
||||
self.keyframes.1.sort_keyframes();
|
||||
}
|
||||
|
||||
fn get_value_at_frame(
|
||||
&self,
|
||||
curr_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> (f32, f32) {
|
||||
let x = self
|
||||
.keyframes
|
||||
.0
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
let y = self
|
||||
.keyframes
|
||||
.1
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
return (x, y);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,33 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use crate::{
|
||||
animation::timeline::calculate_timeline_entities_at_frame,
|
||||
fonts::{get_system_families, get_system_font, get_system_fonts},
|
||||
use creator_core::{
|
||||
__cmd__calculate_timeline_at_curr_frame, __cmd__get_system_families, __cmd__get_system_font,
|
||||
__cmd__get_system_fonts, __cmd__get_values_at_frame_range_from_animated_float,
|
||||
__cmd__get_values_at_frame_range_from_animated_float_vec2,
|
||||
__cmd__get_values_at_frame_range_from_animated_float_vec3,
|
||||
animation::{
|
||||
primitives::values::animated_values::{
|
||||
get_values_at_frame_range_from_animated_float,
|
||||
get_values_at_frame_range_from_animated_float_vec2,
|
||||
get_values_at_frame_range_from_animated_float_vec3,
|
||||
},
|
||||
timeline::calculate_timeline_at_curr_frame,
|
||||
*,
|
||||
},
|
||||
fonts::fonts::{get_system_families, get_system_font, get_system_fonts},
|
||||
};
|
||||
|
||||
pub mod animation;
|
||||
pub mod fonts;
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
calculate_timeline_entities_at_frame,
|
||||
calculate_timeline_at_curr_frame,
|
||||
get_system_font,
|
||||
get_system_families,
|
||||
get_system_fonts
|
||||
get_system_fonts,
|
||||
get_values_at_frame_range_from_animated_float,
|
||||
get_values_at_frame_range_from_animated_float_vec2,
|
||||
get_values_at_frame_range_from_animated_float_vec3
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -2,10 +2,11 @@ import "./App.css";
|
||||
import Timeline from "./components/Timeline";
|
||||
import Canvas from "./components/Canvas";
|
||||
import Properties, { PropertiesContainer } from "components/Properties";
|
||||
import MenuBar from "components/MenuBar";
|
||||
import ToolBar from "components/ToolBar";
|
||||
import useKeyControls from "hooks/useKeyControls";
|
||||
import { useFontsStore } from "stores/fonts.store";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import ScrollBar from "components/ScrollArea";
|
||||
|
||||
export default function App() {
|
||||
const fontsStoreDidInit = useFontsStore((store) => store.didInit);
|
||||
@@ -13,26 +14,27 @@ export default function App() {
|
||||
useKeyControls();
|
||||
|
||||
return (
|
||||
<div className="bg-gray-950 h-full w-full flex flex-col overflow-hidden">
|
||||
<MenuBar />
|
||||
<div className="bg-neutral h-full w-full flex flex-col overflow-hidden">
|
||||
{/* <MenuBar /> */}
|
||||
<div className="flex flex-row flex-[1] overflow-hidden">
|
||||
<ToolBar />
|
||||
{fontsStoreDidInit && (
|
||||
<div className="flex flex-col pl-4 gap-4 pr-4 overflow-x-hidden overflow-y-auto">
|
||||
<div className="flex w-full gap-4 flex-col lg:flex-row justify-center items-center">
|
||||
<Canvas />
|
||||
<PropertiesContainer>
|
||||
<Properties />
|
||||
</PropertiesContainer>
|
||||
</div>
|
||||
<Timeline />
|
||||
</div>
|
||||
<ScrollArea.Root className="w-full">
|
||||
<ScrollArea.Viewport className="w-full h-full">
|
||||
<div className="flex w-full flex-col pl-4 gap-4 pr-4 overflow-x-hidden overflow-y-auto">
|
||||
<div className="flex w-full gap-4 flex-col lg:flex-row justify-center items-center mt-4">
|
||||
<Canvas />
|
||||
<PropertiesContainer>
|
||||
<Properties />
|
||||
</PropertiesContainer>
|
||||
</div>
|
||||
<Timeline />
|
||||
</div>
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollBar />
|
||||
</ScrollArea.Root>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
/* */
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ const SelectTrigger = React.forwardRef<
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"shadow-main/10 shadow-[0_0_0_1px]",
|
||||
"flex h-10 w-full items-center justify-between rounded-md",
|
||||
"text-main bg-transparent px-3 py-2 text-sm placeholder:text-main outline-none",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50 transition-all",
|
||||
"focus:outline-none focus:ring-2 focus:ring-offset-1 focus:shadow-primary",
|
||||
"focus:ring-primary",
|
||||
"hover:shadow-primary/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -38,7 +44,8 @@ const SelectContent = React.forwardRef<
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-slate-900 text-popover-foreground shadow-md animate-in fade-in-80",
|
||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-neutral-accent",
|
||||
"text-main shadow-md animate-in fade-in-80",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -57,7 +64,7 @@ const SelectLabel = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm text-main font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
@@ -70,7 +77,8 @@ const SelectItem = React.forwardRef<
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-indigo-800 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
|
||||
"focus:bg-primary focus:text-neutral dark:focus:text-main text-main data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -81,7 +89,7 @@ const SelectItem = React.forwardRef<
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
<SelectPrimitive.ItemText className="text-main">{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
@@ -92,7 +100,7 @@ const SelectSeparator = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-slate-800", className)}
|
||||
className={cn("-mx-1 my-1 h-px bg-slate-main", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
15
app/src/components/Panel.tsx
Normal file
15
app/src/components/Panel.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
|
||||
const Panel: FC<{ title: string; children: ReactNode }> = ({
|
||||
title,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<h3>{title}</h3>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Panel;
|
||||
@@ -36,12 +36,8 @@ export const PaintProperties: FC<PaintPropertiesProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<fieldset>
|
||||
<label htmlFor="staggered-text-letter-font">Font</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="paint-style-type">PaintStyle</label>
|
||||
|
||||
<Select
|
||||
defaultValue={entity.style.type}
|
||||
onValueChange={(value) => {
|
||||
|
||||
17
app/src/components/ScrollArea.tsx
Normal file
17
app/src/components/ScrollArea.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { FC } from "react";
|
||||
|
||||
const ScrollBar: FC<{ orientation?: "horizontal" | "vertical" }> = ({
|
||||
orientation = "vertical",
|
||||
}) => {
|
||||
return (
|
||||
<ScrollArea.Scrollbar
|
||||
className="flex select-none touch-none p-0.5 bg-neutral-accent transition-colors duration-[160ms] ease-out data-[orientation=vertical]:w-2.5 data-[orientation=horizontal]:flex-col data-[orientation=horizontal]:h-2.5"
|
||||
orientation={orientation}
|
||||
>
|
||||
<ScrollArea.Thumb className="flex-1 bg-main rounded-[10px] relative before:content-[''] before:absolute before:top-1/2 before:left-1/2 before:-translate-x-1/2 before:-translate-y-1/2 before:w-full before:h-full before:min-w-[44px] before:min-h-[44px]" />
|
||||
</ScrollArea.Scrollbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollBar;
|
||||
@@ -79,6 +79,14 @@ const KeyframeIndicator: FC<{
|
||||
}}
|
||||
whileTap={{
|
||||
scale: 1.6,
|
||||
transition: {
|
||||
scale: {
|
||||
type: "spring",
|
||||
stiffness: 200,
|
||||
damping: 10,
|
||||
mass: 1,
|
||||
},
|
||||
},
|
||||
}}
|
||||
animate={{
|
||||
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 2,
|
||||
@@ -97,8 +105,8 @@ const KeyframeIndicator: FC<{
|
||||
>
|
||||
<motion.span
|
||||
data-selected={selected}
|
||||
className="bg-gray-200
|
||||
data-[selected=true]:bg-indigo-600
|
||||
className="bg-secondary
|
||||
data-[selected=true]:bg-secondary
|
||||
h-full transition-colors"
|
||||
style={{
|
||||
width: 10,
|
||||
@@ -108,7 +116,7 @@ const KeyframeIndicator: FC<{
|
||||
/>
|
||||
</motion.div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 backdrop-blur-md bg-slate-700/50">
|
||||
<PopoverContent className="w-80 backdrop-blur-md bg-neutral/50">
|
||||
<KeyframePopover
|
||||
onClose={() => deselectKeyframe()}
|
||||
onUpdate={handleValueUpdate}
|
||||
|
||||
@@ -21,11 +21,11 @@ const TimePicker: FC<TimePickerProps> = () => {
|
||||
step={1}
|
||||
aria-label="Current Frame"
|
||||
>
|
||||
<Slider.Track className="bg-blackA10 relative grow rounded-full h-[3px]">
|
||||
<Slider.Range className="absolute bg-white rounded-full h-full" />
|
||||
<Slider.Track className="bg-neutral-accent relative grow rounded-full h-[3px]">
|
||||
<Slider.Range className="absolute bg-main rounded-full h-full" />
|
||||
</Slider.Track>
|
||||
<Slider.Thumb
|
||||
className="block w-5 h-5 bg-white shadow-[0_2px_10px] shadow-blackA7 rounded-[10px] hover:bg-violet3 focus:outline-none focus:shadow-[0_0_0_5px] focus:shadow-blackA8"
|
||||
className="transition-colors block w-4 h-4 bg-main shadow-[0_2px_10px] shadow-main/20 rounded-[10px] hover:bg-secondary focus:outline-none focus:shadow-[0_0_0_2px] focus:shadow-main"
|
||||
aria-label="Volume"
|
||||
/>
|
||||
</Slider.Root>
|
||||
|
||||
@@ -8,8 +8,8 @@ import { shallow } from "zustand/shallow";
|
||||
import KeyframeIndicator from "./KeyframeIndicator";
|
||||
import { TIMELINE_SCALE, calculateOffset } from "./common";
|
||||
import { TriangleDownIcon } from "@radix-ui/react-icons";
|
||||
import TrackPropertiesEditor from "./TrackPropertiesEditor";
|
||||
import { flattenedKeyframesByEntity } from "utils";
|
||||
import TrackPropertiesEditor from "./TrackDisplay/TrackPropertiesEditor";
|
||||
import { cn, flattenedKeyframesByEntity } from "utils";
|
||||
|
||||
type TrackProps = {
|
||||
animationData: z.input<typeof AnimationData>;
|
||||
@@ -18,6 +18,10 @@ type TrackProps = {
|
||||
entity: z.input<typeof AnimatedEntity>;
|
||||
};
|
||||
|
||||
const TrackDisplayTypeOptions = ["Default", "Graph"] as const;
|
||||
|
||||
export const TrackDisplayType = z.enum(TrackDisplayTypeOptions);
|
||||
|
||||
const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
|
||||
const controls = useDragControls();
|
||||
|
||||
@@ -45,7 +49,7 @@ const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
|
||||
dragListener={false}
|
||||
dragControls={controls}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
className="min-h-8 relative flex flex-1 flex-col gap-1 select-none"
|
||||
className="h-6 relative flex flex-1 flex-col gap-1 select-none"
|
||||
>
|
||||
<motion.div
|
||||
layout
|
||||
@@ -56,20 +60,18 @@ const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onPointerDown={(e) => controls.start(e)}
|
||||
className={`h-full transition-all rounded-sm min-w-[200px] p-1 px-2 flex flex-col ${
|
||||
selectedEntity === index ? "bg-gray-800" : "bg-gray-900"
|
||||
selectedEntity === index
|
||||
? "bg-highlight text-neutral dark:text-main"
|
||||
: "bg-neutral-accent text-main"
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<motion.div
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="will-change-transform"
|
||||
className={cn("will-change-transform")}
|
||||
animate={{ rotate: isExpanded ? 0 : -90 }}
|
||||
>
|
||||
<TriangleDownIcon
|
||||
width="32px"
|
||||
height="32px"
|
||||
className="text-white"
|
||||
/>
|
||||
<TriangleDownIcon width="32px" height="32px" />
|
||||
</motion.div>
|
||||
<h3
|
||||
onClick={() =>
|
||||
@@ -77,17 +79,18 @@ const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
|
||||
? deselectEntity()
|
||||
: selectEntity(index)
|
||||
}
|
||||
className="text-white-800 select-none cursor-pointer"
|
||||
className="h-2 text-base leading-loose font-semibold select-none cursor-pointer"
|
||||
>
|
||||
{name}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{ width: TIMELINE_SCALE * 10 }}
|
||||
className="flex h-full flex-row relative bg-gray-900 select-none shrink-0"
|
||||
>
|
||||
<div className="flex h-full w-full flex-row relative rounded-sm bg-neutral-accent/50 select-none shrink-0">
|
||||
<div
|
||||
className="absolute top-0 h-full bg-neutral-accent"
|
||||
style={{ width: TIMELINE_SCALE * 10 }}
|
||||
/>
|
||||
{!isExpanded &&
|
||||
flattenedKeyframes.map((keyframe, index) => (
|
||||
<KeyframeIndicator
|
||||
@@ -132,10 +135,10 @@ const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="z-10 w-4 bg-slate-500 h-8 top-1 absolute rounded-md select-none cursor-w-resize"
|
||||
className="z-10 w-4 bg-primary/50 h-full top-0 absolute rounded-md select-none cursor-w-resize"
|
||||
/>
|
||||
<motion.div
|
||||
className="z-10 w-4 bg-slate-500 h-8 top-1 absolute rounded-md select-none cursor-e-resize"
|
||||
className="z-10 w-4 bg-primary/50 h-full top-0 absolute rounded-md select-none cursor-e-resize"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
drag="x"
|
||||
animate={{
|
||||
@@ -194,7 +197,7 @@ const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="z-5 h-8 top-1 absolute rounded-md transition-colors bg-gray-700 hover:bg-gray-600 select-none cursor-grab"
|
||||
className="z-5 h-full top-0 absolute rounded-md transition-colors bg-primary/30 hover:bg-primary/50 select-none cursor-grab"
|
||||
></motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
64
app/src/components/Timeline/TrackDisplay/Graph.tsx
Normal file
64
app/src/components/Timeline/TrackDisplay/Graph.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { FC } from "react";
|
||||
import { extent, bisector } from "d3-array";
|
||||
import { curveNatural } from "@visx/curve";
|
||||
|
||||
import { scaleLinear } from "@visx/scale";
|
||||
import { LinePath } from "@visx/shape";
|
||||
import { Group } from "@visx/group";
|
||||
|
||||
const HEIGHT = 300;
|
||||
const WIDTH = 1200;
|
||||
|
||||
type PropertyValue = {
|
||||
value: number;
|
||||
frame: number;
|
||||
};
|
||||
|
||||
const getValue = (d: PropertyValue) => d.value;
|
||||
const getFrame = (d: PropertyValue) => d.frame;
|
||||
|
||||
const PropertyGraph: FC<{
|
||||
values: Array<{ frame: number; value: number }>;
|
||||
}> = ({ values }) => {
|
||||
const framesScale = scaleLinear({
|
||||
range: [0, WIDTH],
|
||||
domain: extent(values, getFrame) as [number, number],
|
||||
nice: true,
|
||||
});
|
||||
|
||||
const valuesScale = scaleLinear({
|
||||
range: [HEIGHT, 0],
|
||||
domain: extent(values, getValue) as [number, number],
|
||||
nice: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<Group>
|
||||
<LinePath
|
||||
curve={curveNatural}
|
||||
stroke="white"
|
||||
strokeWidth={3}
|
||||
data={values}
|
||||
x={(d) => framesScale(getFrame(d)) ?? 0}
|
||||
y={(d) => valuesScale(getValue(d)) ?? 0}
|
||||
/>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
|
||||
const Graphs: FC<{ values: Array<Array<number>> }> = ({ values }) => {
|
||||
return (
|
||||
<svg width={WIDTH} height={HEIGHT}>
|
||||
{values.map((propertyValues) => (
|
||||
<PropertyGraph
|
||||
values={propertyValues.map((val, index) => ({
|
||||
frame: index,
|
||||
value: val,
|
||||
}))}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default Graphs;
|
||||
@@ -11,15 +11,22 @@ import {
|
||||
AnimatedNumberKeyframeIndicator,
|
||||
AnimatedVec2KeyframeIndicator,
|
||||
AnimatedVec3KeyframeIndicator,
|
||||
} from "./KeyframeIndicator";
|
||||
} from "../KeyframeIndicator";
|
||||
import { ToggleGroup, ToggleGroupItem } from "components/ToggleGroup";
|
||||
import { produce } from "immer";
|
||||
import set from "lodash.set";
|
||||
import { useEntitiesStore } from "stores/entities.store";
|
||||
import { shallow } from "zustand/shallow";
|
||||
import { AnimatedValue } from "primitives/Values";
|
||||
import { motion } from "framer-motion";
|
||||
import { ease } from "@unom/style";
|
||||
import { TrackDisplayType } from "../Track";
|
||||
import TrackPropertyGraph from "./TrackPropertyGraph";
|
||||
import { LineChart } from "lucide-react";
|
||||
|
||||
type DisplayState = {
|
||||
type: z.input<typeof TrackDisplayType>;
|
||||
selectedAnimatedProperties: Array<number>;
|
||||
};
|
||||
|
||||
const TrackAnimatedPropertyKeyframes: FC<{
|
||||
animatedProperty: z.input<typeof AnimatedProperty>;
|
||||
@@ -70,18 +77,29 @@ const TrackAnimatedPropertyKeyframes: FC<{
|
||||
const TrackAnimatedProperty: FC<{
|
||||
animatedProperty: z.input<typeof AnimatedProperty>;
|
||||
animationData: z.input<typeof AnimationData>;
|
||||
displayState: DisplayState;
|
||||
index: number;
|
||||
onDisplayStateUpdate: (s: DisplayState) => void;
|
||||
onUpdate: (e: z.input<typeof AnimatedProperty>) => void;
|
||||
}> = ({ animatedProperty, animationData, onUpdate }) => {
|
||||
}> = ({
|
||||
animatedProperty,
|
||||
animationData,
|
||||
onUpdate,
|
||||
displayState,
|
||||
index,
|
||||
onDisplayStateUpdate,
|
||||
}) => {
|
||||
const [selectedDimension, setSelectedDimension] = useState<"x" | "y" | "z">();
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
layout
|
||||
transition={ease.quint(0.8).out}
|
||||
variants={{ enter: { y: 0, opacity: 1 }, from: { y: -10, opacity: 0 } }}
|
||||
className="flex flex-row bg-slate-900 ml-2 align-center"
|
||||
className="flex flex-row bg-neutral-accent ml-2 align-center"
|
||||
>
|
||||
<div className="min-w-[195px] flex flex-row justify-between px-2">
|
||||
<h4>{animatedProperty.label}</h4>
|
||||
<h4 className="text-main/70">{animatedProperty.label}</h4>
|
||||
<ToggleGroup>
|
||||
<ToggleGroupItem
|
||||
onClick={() =>
|
||||
@@ -115,8 +133,33 @@ const TrackAnimatedProperty: FC<{
|
||||
Z
|
||||
</ToggleGroupItem>
|
||||
)}
|
||||
<ToggleGroupItem
|
||||
selected={displayState.selectedAnimatedProperties.includes(index)}
|
||||
onClick={() => {
|
||||
if (displayState.selectedAnimatedProperties.includes(index)) {
|
||||
onDisplayStateUpdate({
|
||||
...displayState,
|
||||
selectedAnimatedProperties:
|
||||
displayState.selectedAnimatedProperties.filter(
|
||||
(index) => index !== index
|
||||
),
|
||||
});
|
||||
} else {
|
||||
onDisplayStateUpdate({
|
||||
...displayState,
|
||||
selectedAnimatedProperties: [
|
||||
...displayState.selectedAnimatedProperties,
|
||||
index,
|
||||
],
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LineChart />
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<TrackAnimatedPropertyKeyframes
|
||||
selectedDimension={
|
||||
@@ -153,30 +196,45 @@ const TrackPropertiesEditor: FC<{
|
||||
|
||||
const parsedEntity = AnimatedEntity.parse(nextValue);
|
||||
|
||||
console.log("reacreated callback");
|
||||
|
||||
entitiesStore.updateEntityById(parsedEntity.id, parsedEntity);
|
||||
},
|
||||
[entity]
|
||||
);
|
||||
|
||||
const [displayState, setDisplayState] = useState<DisplayState>({
|
||||
type: TrackDisplayType.Enum.Default,
|
||||
selectedAnimatedProperties: [],
|
||||
});
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
animate="enter"
|
||||
initial="from"
|
||||
variants={{ enter: {}, from: {} }}
|
||||
transition={{ staggerChildren: 0.05 }}
|
||||
layout
|
||||
className="flex flex-col gap-1"
|
||||
>
|
||||
{animatedProperties.map((animatedProperty, index) => (
|
||||
<TrackAnimatedProperty
|
||||
onUpdate={handleUpdate}
|
||||
<motion.div layout className="flex flex-row">
|
||||
<motion.div
|
||||
animate="enter"
|
||||
initial="from"
|
||||
variants={{ enter: {}, from: {} }}
|
||||
transition={{ staggerChildren: 0.05 }}
|
||||
className="flex flex-col gap-1"
|
||||
>
|
||||
{animatedProperties.map((animatedProperty, index) => (
|
||||
<TrackAnimatedProperty
|
||||
index={index}
|
||||
onDisplayStateUpdate={setDisplayState}
|
||||
displayState={displayState}
|
||||
onUpdate={handleUpdate}
|
||||
animationData={entity.animation_data}
|
||||
key={index}
|
||||
animatedProperty={animatedProperty}
|
||||
/>
|
||||
))}
|
||||
</motion.div>
|
||||
{displayState.selectedAnimatedProperties.length > 0 && (
|
||||
<TrackPropertyGraph
|
||||
animationData={entity.animation_data}
|
||||
key={index}
|
||||
animatedProperty={animatedProperty}
|
||||
animatedProperties={displayState.selectedAnimatedProperties.map(
|
||||
(index) => animatedProperties[index]
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
108
app/src/components/Timeline/TrackDisplay/TrackPropertyGraph.tsx
Normal file
108
app/src/components/Timeline/TrackDisplay/TrackPropertyGraph.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
import { AnimationData } from "primitives/AnimatedEntities";
|
||||
import { AnimatedProperty } from "primitives/AnimatedProperty";
|
||||
import { AnimatedValue, ValueType } from "primitives/Values";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { z } from "zod";
|
||||
import Graph from "./Graph";
|
||||
|
||||
type TrackPropertyPathProps = {
|
||||
animatedProperties: Array<z.input<typeof AnimatedProperty>>;
|
||||
animationData: z.input<typeof AnimationData>;
|
||||
};
|
||||
|
||||
const TrackPropertyGraph: FC<TrackPropertyPathProps> = ({
|
||||
animatedProperties,
|
||||
animationData,
|
||||
}) => {
|
||||
const [values, setValues] = useState<Array<Array<number>>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const tasks: Array<Promise<Array<Array<number>>>> = [];
|
||||
|
||||
animatedProperties.forEach((animatedProperty) => {
|
||||
animatedProperty.animatedValue.type;
|
||||
const animatedValue = animatedProperty.animatedValue;
|
||||
|
||||
const commonValues: {
|
||||
animatedValue: z.input<typeof AnimatedValue>;
|
||||
startFrame: number;
|
||||
endFrame: number;
|
||||
fps: number;
|
||||
animationData: z.input<typeof AnimationData>;
|
||||
} = {
|
||||
animatedValue: AnimatedValue.parse(animatedValue),
|
||||
startFrame: 0,
|
||||
endFrame: 600,
|
||||
fps: 60,
|
||||
animationData: AnimationData.parse(animationData),
|
||||
};
|
||||
|
||||
switch (animatedValue.type) {
|
||||
case ValueType.Enum.Number:
|
||||
tasks.push(
|
||||
invoke(
|
||||
"get_values_at_frame_range_from_animated_float",
|
||||
commonValues
|
||||
).then((data) => {
|
||||
const numbers = data as Array<number>;
|
||||
|
||||
return [numbers];
|
||||
})
|
||||
);
|
||||
break;
|
||||
case ValueType.Enum.Vec2:
|
||||
tasks.push(
|
||||
invoke(
|
||||
"get_values_at_frame_range_from_animated_float_vec2",
|
||||
commonValues
|
||||
).then((data) => {
|
||||
const vectors = data as [Array<number>, Array<number>];
|
||||
|
||||
const xValues = vectors.map((vec2) => vec2[0]);
|
||||
const yValues = vectors.map((vec2) => vec2[1]);
|
||||
|
||||
return [xValues, yValues];
|
||||
})
|
||||
);
|
||||
break;
|
||||
|
||||
case ValueType.Enum.Vec3:
|
||||
tasks.push(
|
||||
invoke(
|
||||
"get_values_at_frame_range_from_animated_float_vec3",
|
||||
commonValues
|
||||
).then((data) => {
|
||||
const vectors = data as [
|
||||
Array<number>,
|
||||
Array<number>,
|
||||
Array<number>
|
||||
];
|
||||
|
||||
const xValues = vectors.map((vec2) => vec2[0]);
|
||||
const yValues = vectors.map((vec2) => vec2[1]);
|
||||
const zValues = vectors.map((vec2) => vec2[2]);
|
||||
|
||||
return [xValues, yValues, zValues];
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all(tasks).then((values) => {
|
||||
const flatValues = values.flat();
|
||||
|
||||
console.log("flattened Values", flatValues);
|
||||
setValues(flatValues);
|
||||
});
|
||||
}, animatedProperties);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Graph values={values} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrackPropertyGraph;
|
||||
@@ -6,6 +6,8 @@ import Timestamp from "./Timestamp";
|
||||
import { PauseIcon, PlayIcon } from "@radix-ui/react-icons";
|
||||
import { useRenderStateStore } from "stores/render-state.store";
|
||||
import Track from "./Track";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import ScrollBar from "components/ScrollArea";
|
||||
|
||||
export type AnimationEntity = {
|
||||
offset: number;
|
||||
@@ -29,35 +31,42 @@ const Timeline: FC<TimelineProps> = () => {
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-row">
|
||||
<button onClick={() => setPlaying(true)} className="w-8 h-8">
|
||||
<PlayIcon color="white" width="100%" height="100%" />
|
||||
<PlayIcon className="text-main" width="100%" height="100%" />
|
||||
</button>
|
||||
<button onClick={() => setPlaying(false)} className="w-8 h-8">
|
||||
<PauseIcon color="white" width="100%" height="100%" />
|
||||
<PauseIcon className="text-main" width="100%" height="100%" />
|
||||
</button>
|
||||
</div>
|
||||
<Timestamp />
|
||||
</div>
|
||||
<div className="gap-1 w-full flex flex-col overflow-x-auto overflow-y-visible">
|
||||
<div className="z-20 flex flex-row gap-2">
|
||||
<div className="flex-shrink-0 min-w-[200px]" />
|
||||
<TimePicker />
|
||||
<ScrollArea.Root>
|
||||
<ScrollArea.Viewport className="w-full h-full">
|
||||
<div className="gap-1 w-full flex flex-col">
|
||||
<div className="z-20 flex flex-row gap-1">
|
||||
<div className="flex-shrink-0 min-w-[200px]" />
|
||||
<TimePicker />
|
||||
</div>
|
||||
<Reorder.Group
|
||||
className="gap-1 flex flex-col"
|
||||
values={entities}
|
||||
onReorder={setEntities}
|
||||
>
|
||||
{entities.map((entity, index) => (
|
||||
<Track
|
||||
entity={entity}
|
||||
key={entity.id}
|
||||
name={entity.type}
|
||||
index={index}
|
||||
animationData={entity.animation_data}
|
||||
/>
|
||||
))}
|
||||
</Reorder.Group>
|
||||
</div>
|
||||
</ScrollArea.Viewport>
|
||||
<div className="h-4 sticky bottom-0">
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</div>
|
||||
<Reorder.Group
|
||||
className="gap-1 flex flex-col"
|
||||
values={entities}
|
||||
onReorder={setEntities}
|
||||
>
|
||||
{entities.map((entity, index) => (
|
||||
<Track
|
||||
entity={entity}
|
||||
key={entity.id}
|
||||
name={entity.type}
|
||||
index={index}
|
||||
animationData={entity.animation_data}
|
||||
/>
|
||||
))}
|
||||
</Reorder.Group>
|
||||
</div>
|
||||
</ScrollArea.Root>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,9 +12,9 @@ const ToggleGroupItem: FC<{
|
||||
data-selected={selected}
|
||||
asChild
|
||||
onClick={onClick}
|
||||
className="hover:bg-indigo-600 text-white data-[selected=true]:bg-indigo-700
|
||||
className="hover:bg-primary/30 text-main data-[selected=true]:bg-primary/60
|
||||
data-[selected=true]:text-indigo-200 flex h-6 w-6
|
||||
items-center justify-center bg-slate-800 text-sm leading-4
|
||||
items-center justify-center bg-neutral text-sm leading-4
|
||||
first:rounded-l last:rounded-r focus:z-10 focus:shadow-[0_0_0_2px] focus:shadow-black
|
||||
focus:outline-none transition-colors"
|
||||
value="left"
|
||||
@@ -29,7 +29,7 @@ const ToggleGroupItem: FC<{
|
||||
|
||||
const ToggleGroup: FC<{ children: ReactNode }> = ({ children }) => (
|
||||
<ToggleGroupComponents.Root
|
||||
className="inline-flex my-auto bg-slate-800 h-fit rounded shadow-[0_2px_10px] shadow-black space-x-px"
|
||||
className="inline-flex my-auto bg-neutral-accent h-fit rounded shadow-[0_2px_10px] shadow-black space-x-px"
|
||||
type="single"
|
||||
defaultValue="center"
|
||||
aria-label="Text alignment"
|
||||
|
||||
@@ -25,9 +25,9 @@ const ToolBarButton: FC<{ children: ReactNode; onClick?: () => void }> = ({
|
||||
onClick={onClick}
|
||||
onMouseOver={() => !didHover && setDidHover(true)}
|
||||
asChild
|
||||
className="text-white p-[10px] bg-gray-900 flex-shrink-0 flex-grow-0
|
||||
className="text-main p-[10px] bg-neutral flex-shrink-0 flex-grow-0
|
||||
basis-auto w-[40px] h-[40px] rounded inline-flex text-[13px] leading-none
|
||||
items-center justify-center outline-none hover:bg-indigo-900
|
||||
items-center justify-center outline-none hover:bg-primary/50
|
||||
transition-colors
|
||||
focus:relative focus:shadow-[0_0_0_2px] focus:shadow-indigo"
|
||||
>
|
||||
@@ -52,7 +52,7 @@ const ToolBar = () => {
|
||||
return (
|
||||
<Toolbar.Root
|
||||
asChild
|
||||
className="bg-gray-800 flex flex-col gap-1 p-1 h-full"
|
||||
className="bg-neutral-accent flex flex-col gap-1 p-1 h-full"
|
||||
orientation="vertical"
|
||||
>
|
||||
<motion.div
|
||||
|
||||
@@ -90,7 +90,7 @@ export class Drawer {
|
||||
|
||||
const parsedAnimatedEntities = AnimatedEntities.parse(animatedEntities);
|
||||
|
||||
const data = await invoke("calculate_timeline_entities_at_frame", {
|
||||
const data = await invoke("calculate_timeline_at_curr_frame", {
|
||||
timeline: {
|
||||
entities: parsedAnimatedEntities,
|
||||
render_state: renderState,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { Interpolation } from "./Interpolation";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
export const Keyframe = z.object({
|
||||
id: z.string().uuid(),
|
||||
|
||||
@@ -5,6 +5,8 @@ import { v4 as uuid } from "uuid";
|
||||
export const Vec2 = z.array(z.number()).length(2);
|
||||
export const Vec3 = z.array(z.number()).length(3);
|
||||
|
||||
|
||||
|
||||
const ValueTypeOptions = ["Vec2", "Vec3", "Number"] as const;
|
||||
|
||||
export const ValueType = z.enum(ValueTypeOptions);
|
||||
|
||||
@@ -13,36 +13,17 @@
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
a,
|
||||
label {
|
||||
@apply dark:text-white;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-blue-600 underline;
|
||||
}
|
||||
|
||||
select,
|
||||
input {
|
||||
@apply box-border bg-gray-900 shadow-indigo-100 hover:shadow-indigo-400
|
||||
focus:shadow-indigo-600 selection:bg-indigo-400 selection:text-black
|
||||
outline-none px-3 py-2 rounded-md shadow-[0_0_0_1px];
|
||||
}
|
||||
|
||||
input {
|
||||
@apply appearance-none items-center justify-center
|
||||
w-full text-base leading-none
|
||||
text-white transition-all;
|
||||
@apply box-border bg-transparent shadow-main/10 hover:shadow-primary/50
|
||||
focus:ring-primary focus:ring-2 focus:ring-offset-1
|
||||
focus:shadow-primary selection:bg-secondary selection:text-black
|
||||
outline-none px-3 py-2 rounded-md shadow-[0_0_0_1px]
|
||||
appearance-none items-center justify-center
|
||||
w-full text-base leading-none transition-all;
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -50,78 +31,39 @@
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 47.4% 11.2%;
|
||||
--color-main: 0 0% 0%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--color-secondary: 0 0% 10%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 47.4% 11.2%;
|
||||
--color-neutral: 0 0% 100%;
|
||||
--color-neutral-accent: 0 0% 90%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 47.4% 11.2%;
|
||||
--color-highlight: 0 0% 0%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 100% 50%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 215 20.2% 65.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
--color-primary: 222.2 47.4% 11.2%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 224 71% 4%;
|
||||
--foreground: 213 31% 91%;
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-main: 0 0% 100%;
|
||||
|
||||
--muted: 223 47% 11%;
|
||||
--muted-foreground: 215.4 16.3% 56.9%;
|
||||
--color-primary: 260 80% 50%;
|
||||
--color-secondary: 260 50% 70%;
|
||||
|
||||
--popover: 224 71% 4%;
|
||||
--popover-foreground: 215 20.2% 65.1%;
|
||||
--color-neutral: 250 30% 8%;
|
||||
--color-neutral-accent: 250 40% 12%;
|
||||
|
||||
--card: 224 71% 4%;
|
||||
--card-foreground: 213 31% 91%;
|
||||
|
||||
--border: 216 34% 17%;
|
||||
--input: 216 34% 17%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 1.2%;
|
||||
|
||||
--secondary: 222.2 47.4% 11.2%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--accent: 216 34% 17%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 63% 31%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--ring: 216 34% 17%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
--color-highlight: 250 20% 15%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
@apply border-highlight;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
@apply bg-neutral text-main;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,53 +3,14 @@
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
main: "hsl(var(--color-main))",
|
||||
secondary: "hsl(var(--color-secondary))",
|
||||
neutral: "hsl(var(--color-neutral))",
|
||||
"neutral-accent": "hsl(var(--color-neutral-accent))",
|
||||
highlight: "hsl(var(--color-highlight))",
|
||||
primary: "hsl(var(--color-primary))",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
|
||||
372
app/yarn.lock
372
app/yarn.lock
@@ -702,6 +702,22 @@
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
|
||||
"@radix-ui/react-scroll-area@^1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.4.tgz#13c36c453b2880aba57df67fb91a1d3f9b18998d"
|
||||
integrity sha512-OIClwBkwPG+FKvC4OMTRaa/3cfD069nkKFFL/TQzRzaO42Ce5ivKU9VMKgT7UU6UIkjcQqKBrDOIzWtPGw6e6w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/number" "1.0.1"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-direction" "1.0.1"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
|
||||
"@radix-ui/react-select@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-1.2.2.tgz#caa981fa0d672cf3c1b2a5240135524e69b32181"
|
||||
@@ -955,6 +971,52 @@
|
||||
resolved "https://packages.unom.io/@tempblade%2fcommon/-/common-2.0.1.tgz"
|
||||
integrity sha512-8uCqsfu2tcQq4O4XODS7Hn7Mj9hZh+Rh+Y0Fsej9Bbemn/WwlIT0WrUSzWGMZLcTspvgl6kz/ljBzCqLAa3Yyw==
|
||||
|
||||
"@types/d3-array@^3.0.5":
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.0.5.tgz#857c1afffd3f51319bbc5b301956aca68acaa7b8"
|
||||
integrity sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A==
|
||||
|
||||
"@types/d3-color@*":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.0.tgz#6594da178ded6c7c3842f3cc0ac84b156f12f2d4"
|
||||
integrity sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==
|
||||
|
||||
"@types/d3-interpolate@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz#e7d17fa4a5830ad56fe22ce3b4fac8541a9572dc"
|
||||
integrity sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==
|
||||
dependencies:
|
||||
"@types/d3-color" "*"
|
||||
|
||||
"@types/d3-path@^1", "@types/d3-path@^1.0.8":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.9.tgz#73526b150d14cd96e701597cbf346cfd1fd4a58c"
|
||||
integrity sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==
|
||||
|
||||
"@types/d3-scale@^4.0.2":
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.3.tgz#7a5780e934e52b6f63ad9c24b105e33dd58102b5"
|
||||
integrity sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==
|
||||
dependencies:
|
||||
"@types/d3-time" "*"
|
||||
|
||||
"@types/d3-shape@^1.3.1":
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.8.tgz#c3c15ec7436b4ce24e38de517586850f1fea8e89"
|
||||
integrity sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==
|
||||
dependencies:
|
||||
"@types/d3-path" "^1"
|
||||
|
||||
"@types/d3-time@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.0.tgz#e1ac0f3e9e195135361fa1a1d62f795d87e6e819"
|
||||
integrity sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==
|
||||
|
||||
"@types/d3-time@^2.0.0":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-2.1.1.tgz#743fdc821c81f86537cbfece07093ac39b4bc342"
|
||||
integrity sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg==
|
||||
|
||||
"@types/lodash.set@^4.3.7":
|
||||
version "4.3.7"
|
||||
resolved "https://registry.npmjs.org/@types/lodash.set/-/lodash.set-4.3.7.tgz"
|
||||
@@ -962,7 +1024,7 @@
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*":
|
||||
"@types/lodash@*", "@types/lodash@^4.14.172":
|
||||
version "4.14.195"
|
||||
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz"
|
||||
integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==
|
||||
@@ -977,6 +1039,13 @@
|
||||
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz"
|
||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||
|
||||
"@types/react-dom@*":
|
||||
version "18.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.6.tgz#ad621fa71a8db29af7c31b41b2ea3d8a6f4144d1"
|
||||
integrity sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-dom@^18.0.6":
|
||||
version "18.2.4"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz"
|
||||
@@ -1010,6 +1079,156 @@
|
||||
dependencies:
|
||||
css-color-converter "^2.0.0"
|
||||
|
||||
"@visx/axis@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/axis/-/axis-3.1.0.tgz#775b221e5abfdec25304c607eeae8cda1a8bade6"
|
||||
integrity sha512-JDj/1VYx0JO0pHFtwoFtYcnqdoZFh/dpHImEl169S5nTslSFlIoNTXA/ekpBP6ELkEZ59gmF1X5k29x6MFBwCA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@visx/group" "3.0.0"
|
||||
"@visx/point" "3.0.1"
|
||||
"@visx/scale" "3.0.0"
|
||||
"@visx/shape" "3.0.0"
|
||||
"@visx/text" "3.0.0"
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
"@visx/bounds@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/bounds/-/bounds-3.0.0.tgz#cf357cbff90a1fe5f95eb9d9288dd8794b744a7f"
|
||||
integrity sha512-YQaSSER9erxlhppzRms6cvYdKqcIwk6eksrGdbJkBoHobhPo1JCIUXlmrA4qgrEnXInPJpueGE+PE5F+Dk12DA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@types/react-dom" "*"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
"@visx/curve@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/curve/-/curve-3.0.0.tgz#c54568472e00a38483c58cf52e4a6ddb2887c2d4"
|
||||
integrity sha512-kvHJDLBeczTQ87ZExSTfRxej06l6o6UiQ0NHf9+xpAin06y6Qk1ThOHHWJTGM6KGzwlu7jEauJGHwZs6nMhDvA==
|
||||
dependencies:
|
||||
"@types/d3-shape" "^1.3.1"
|
||||
d3-shape "^1.0.6"
|
||||
|
||||
"@visx/event@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@visx/event/-/event-3.0.1.tgz#d5358f52ff5ef30036d955bd2b68b96472ff2d6f"
|
||||
integrity sha512-tK1EUYQLLStBuoCMbm8LJ3VbDyCVI8HjT0pMRQxm+C75FSIVWvrThgrfrC9sWOFnEMEYWspZO7hI5zjsPKjLQA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@visx/point" "3.0.1"
|
||||
|
||||
"@visx/glyph@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/glyph/-/glyph-3.0.0.tgz#218a96aa0ccba95dc77e46ab08d26ad89198f3a8"
|
||||
integrity sha512-r1B0IocfWfhTABKjam0qqsWKjxLxZfGwefnwn8IcfELSd9iAUtLbI/46nP4roQRHhB/Wl3RBbgA97fZw8f1MxA==
|
||||
dependencies:
|
||||
"@types/d3-shape" "^1.3.1"
|
||||
"@types/react" "*"
|
||||
"@visx/group" "3.0.0"
|
||||
classnames "^2.3.1"
|
||||
d3-shape "^1.2.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
"@visx/gradient@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/gradient/-/gradient-3.0.0.tgz#39b55dd5a0b34feb219fcb2ea5525388c5ae0c1b"
|
||||
integrity sha512-UoM9R9PIPLO/w7hCW9gFncrLdpKNqh13sLS9/0Iy6b75uP2l05FLG2HX4kXljeyOrj4/XyzRCMYm0HHk/p5iMA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
prop-types "^15.5.7"
|
||||
|
||||
"@visx/grid@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@visx/grid/-/grid-3.0.1.tgz#d91085ed92e7e1c0c2e58710bc33b2f0f33b8e74"
|
||||
integrity sha512-cln5CVvFG58C5Uz1Uf0KRBFmGmgD1NALOQdYDu5yPsTuY2yLzVYPvCIlYBMdUtE0uzfNq972SmkZHfZYs03jxQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@visx/curve" "3.0.0"
|
||||
"@visx/group" "3.0.0"
|
||||
"@visx/point" "3.0.1"
|
||||
"@visx/scale" "3.0.0"
|
||||
"@visx/shape" "3.0.0"
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
"@visx/group@3.0.0", "@visx/group@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/group/-/group-3.0.0.tgz#e7f9752599bcc7e141ff5317a2a9a502577ab8df"
|
||||
integrity sha512-SFjXhTMcsaVAb1/TVL1KM5vn8gQTIVgSx0ATdDl4BJSFp2ym1lO8LY4jpV4SFweaHnWxVwrrfGLTn5QsYnvmjQ==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
"@visx/point@3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@visx/point/-/point-3.0.1.tgz#77587ddaabf6f3023f09f8a0ce33a2c27c9d64c8"
|
||||
integrity sha512-S5WOBMgEP2xHcgs3A2BFB2vwzrk0tMmn3PGZAbQJ+lu4HlnalDP72klUnxLTH8xclNNvpUHtHM5eLIJXyHx6Pw==
|
||||
|
||||
"@visx/responsive@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/responsive/-/responsive-3.0.0.tgz#e183c54ce04cffe756378872d30ac88c66a137ac"
|
||||
integrity sha512-immnxQwOWlrxbnlCIqJWuDpPfrM6tglgMTN1WsyXyGluLMJqhuuxqxllfXaRPkQFS4fcvs66KCEELdazh96U2w==
|
||||
dependencies:
|
||||
"@types/lodash" "^4.14.172"
|
||||
"@types/react" "*"
|
||||
lodash "^4.17.21"
|
||||
prop-types "^15.6.1"
|
||||
|
||||
"@visx/scale@3.0.0", "@visx/scale@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/scale/-/scale-3.0.0.tgz#727123f0c930d3346a4473e926831c45997e0312"
|
||||
integrity sha512-WSf+wrxZEvu5TPGfGTafzzX1MbogbIxfD9ZKM9p7xfw65v23G0dNMy4bqVBUbOJigONoQkIZyqQ+gz5AJ/ioIg==
|
||||
dependencies:
|
||||
"@types/d3-interpolate" "^3.0.1"
|
||||
"@types/d3-scale" "^4.0.2"
|
||||
"@types/d3-time" "^2.0.0"
|
||||
d3-interpolate "^3.0.1"
|
||||
d3-scale "^4.0.2"
|
||||
d3-time "^2.1.1"
|
||||
|
||||
"@visx/shape@3.0.0", "@visx/shape@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/shape/-/shape-3.0.0.tgz#a1d4bd0e12cc94c164252f175997932a09c24652"
|
||||
integrity sha512-t6lpP9bIA1vwChDwiOUWl92ro29XF/M8IVNWRA0pm4LGxGGTACvxG3Agfcdi3JprahUVqPpnRCwuR36PDanq3Q==
|
||||
dependencies:
|
||||
"@types/d3-path" "^1.0.8"
|
||||
"@types/d3-shape" "^1.3.1"
|
||||
"@types/lodash" "^4.14.172"
|
||||
"@types/react" "*"
|
||||
"@visx/curve" "3.0.0"
|
||||
"@visx/group" "3.0.0"
|
||||
"@visx/scale" "3.0.0"
|
||||
classnames "^2.3.1"
|
||||
d3-path "^1.0.5"
|
||||
d3-shape "^1.2.0"
|
||||
lodash "^4.17.21"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
"@visx/text@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@visx/text/-/text-3.0.0.tgz#9099c3605027b9ab4c54bde97518a648136c3629"
|
||||
integrity sha512-LW6v5T/gpd9RGw83/ScXncYc6IlcfzXTpaN8WbbxLRI65gdvSqrykwAMR0cbpQmzoVFuZXljqOf0QslHGnBg1w==
|
||||
dependencies:
|
||||
"@types/lodash" "^4.14.172"
|
||||
"@types/react" "*"
|
||||
classnames "^2.3.1"
|
||||
lodash "^4.17.21"
|
||||
prop-types "^15.7.2"
|
||||
reduce-css-calc "^1.3.0"
|
||||
|
||||
"@visx/tooltip@^3.1.2":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@visx/tooltip/-/tooltip-3.1.2.tgz#6c7bb36a296f4501adb99b59487412e39fe06f44"
|
||||
integrity sha512-p46qztGRNkEDbxzc3V1virahvz3UQ29TzddUjA0oaTIBCrOd9UJuLvv1Tq9OpeUYPdbrO/ZRwaEeri2pbwv04Q==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@visx/bounds" "3.0.0"
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.5.10"
|
||||
react-use-measure "^2.0.4"
|
||||
|
||||
"@vitejs/plugin-react@^3.0.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz"
|
||||
@@ -1065,6 +1284,11 @@ autoprefixer@^10.4.14:
|
||||
picocolors "^1.0.0"
|
||||
postcss-value-parser "^4.2.0"
|
||||
|
||||
balanced-match@^0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
|
||||
integrity sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg==
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
|
||||
@@ -1146,6 +1370,11 @@ class-variance-authority@^0.6.0:
|
||||
dependencies:
|
||||
clsx "1.2.1"
|
||||
|
||||
classnames@^2.3.1:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
|
||||
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||
|
||||
clsx@1.2.1, clsx@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
|
||||
@@ -1212,6 +1441,86 @@ csstype@^3.0.2:
|
||||
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz"
|
||||
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
|
||||
|
||||
d3-array@2:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81"
|
||||
integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==
|
||||
dependencies:
|
||||
internmap "^1.0.0"
|
||||
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3":
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
|
||||
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
||||
dependencies:
|
||||
internmap "1 - 2"
|
||||
|
||||
"d3-color@1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||
|
||||
"d3-format@1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
||||
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||
|
||||
"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
d3-path@1, d3-path@^1.0.5:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf"
|
||||
integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==
|
||||
|
||||
d3-scale@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
|
||||
dependencies:
|
||||
d3-array "2.10.0 - 3"
|
||||
d3-format "1 - 3"
|
||||
d3-interpolate "1.2.0 - 3"
|
||||
d3-time "2.1.1 - 3"
|
||||
d3-time-format "2 - 4"
|
||||
|
||||
d3-shape@^1.0.6, d3-shape@^1.2.0:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7"
|
||||
integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==
|
||||
dependencies:
|
||||
d3-path "1"
|
||||
|
||||
"d3-time-format@2 - 4":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
|
||||
dependencies:
|
||||
d3-time "1 - 3"
|
||||
|
||||
"d3-time@1 - 3", "d3-time@2.1.1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
|
||||
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
|
||||
dependencies:
|
||||
d3-array "2 - 3"
|
||||
|
||||
d3-time@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682"
|
||||
integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ==
|
||||
dependencies:
|
||||
d3-array "2"
|
||||
|
||||
debounce@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
|
||||
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
|
||||
|
||||
debug@^4.1.0, debug@^4.1.1:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||
@@ -1407,6 +1716,16 @@ inherits@2:
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
"internmap@1 - 2":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||
|
||||
internmap@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95"
|
||||
integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==
|
||||
|
||||
invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz"
|
||||
@@ -1480,7 +1799,12 @@ lodash.set@^4.3.2:
|
||||
resolved "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz"
|
||||
integrity sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0:
|
||||
lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
@@ -1506,6 +1830,11 @@ magic-string@^0.27.0:
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.13"
|
||||
|
||||
math-expression-evaluator@^1.2.14:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.4.0.tgz#3d66031117fbb7b9715ea6c9c68c2cd2eebd37e2"
|
||||
integrity sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==
|
||||
|
||||
merge2@^1.3.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
|
||||
@@ -1560,7 +1889,7 @@ normalize-range@^0.1.2:
|
||||
resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz"
|
||||
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
|
||||
|
||||
object-assign@^4.0.1:
|
||||
object-assign@^4.0.1, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
@@ -1660,6 +1989,15 @@ postcss@^8.4.23:
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||
@@ -1673,6 +2011,11 @@ react-dom@^18.2.0:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.0"
|
||||
|
||||
react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-refresh@^0.14.0:
|
||||
version "0.14.0"
|
||||
resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz"
|
||||
@@ -1706,6 +2049,13 @@ react-style-singleton@^2.2.1:
|
||||
invariant "^2.2.4"
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-use-measure@^2.0.4:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.1.1.tgz#5824537f4ee01c9469c45d5f7a8446177c6cc4ba"
|
||||
integrity sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==
|
||||
dependencies:
|
||||
debounce "^1.2.1"
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
|
||||
@@ -1727,6 +2077,22 @@ readdirp@~3.6.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
reduce-css-calc@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
|
||||
integrity sha512-0dVfwYVOlf/LBA2ec4OwQ6p3X9mYxn/wOl2xTcLwjnPYrkgEfPx3VI4eGCH3rQLlPISG5v9I9bkZosKsNRTRKA==
|
||||
dependencies:
|
||||
balanced-match "^0.4.2"
|
||||
math-expression-evaluator "^1.2.14"
|
||||
reduce-function-call "^1.0.1"
|
||||
|
||||
reduce-function-call@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.3.tgz#60350f7fb252c0a67eb10fd4694d16909971300f"
|
||||
integrity sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
regenerator-runtime@^0.13.11:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz"
|
||||
|
||||
1
lib/creator_rs/.gitignore
vendored
Normal file
1
lib/creator_rs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target
|
||||
3834
lib/creator_rs/Cargo.lock
generated
Normal file
3834
lib/creator_rs/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
lib/creator_rs/Cargo.toml
Normal file
31
lib/creator_rs/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "creator_rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Enrico Bühler <buehler@unom.io>"]
|
||||
description = "Motion Design toolkit with spring, eased and linear based value interpolation, timeline functionality and more"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/tempblade/creator"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[dependencies]
|
||||
rayon = { version = "1.7", optional = true }
|
||||
font-kit = { version = "0.11", optional = true }
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
simple-easing = "1.0"
|
||||
tauri = { version = "1.4", optional = true, features = [
|
||||
"dialog-open",
|
||||
"dialog-save",
|
||||
"shell-open",
|
||||
] }
|
||||
uuid = { version = "1.3", features = ["v4", "macro-diagnostics", "js"] }
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
|
||||
[features]
|
||||
tauri = ["dep:tauri"]
|
||||
parallelization = ["dep:rayon"]
|
||||
fonts = ["dep:font-kit"]
|
||||
@@ -4,7 +4,7 @@ use crate::animation::{
|
||||
primitives::{
|
||||
paint::Paint,
|
||||
transform::{AnimatedTransform, Transform},
|
||||
values::{AnimatedFloatVec2, AnimatedValue},
|
||||
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
|
||||
},
|
||||
timeline::Timeline,
|
||||
};
|
||||
@@ -4,7 +4,7 @@ use crate::animation::{
|
||||
primitives::{
|
||||
paint::Paint,
|
||||
transform::{AnimatedTransform, Transform},
|
||||
values::{AnimatedFloatVec2, AnimatedValue},
|
||||
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
|
||||
},
|
||||
timeline::Timeline,
|
||||
};
|
||||
@@ -3,7 +3,7 @@ use crate::animation::{
|
||||
primitives::{
|
||||
paint::TextPaint,
|
||||
transform::{AnimatedTransform, Transform},
|
||||
values::{AnimatedFloatVec2, AnimatedValue},
|
||||
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
|
||||
},
|
||||
timeline::Timeline,
|
||||
};
|
||||
@@ -2,7 +2,7 @@ use crate::animation::{
|
||||
primitives::{
|
||||
paint::TextPaint,
|
||||
transform::{AnimatedTransform, Transform},
|
||||
values::{AnimatedFloatVec2, AnimatedValue},
|
||||
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
|
||||
},
|
||||
timeline::Timeline,
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::{cmp::Ordering, sync::Arc};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -6,16 +6,33 @@ use super::{
|
||||
entities::common::AnimationData,
|
||||
interpolations::{interpolate_rendered_keyframes, InterpolationType},
|
||||
utils::render_keyframe,
|
||||
values::values::Float,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Keyframe {
|
||||
pub value: f32,
|
||||
pub value: Float,
|
||||
pub offset: f32,
|
||||
pub id: String,
|
||||
pub id: Arc<str>,
|
||||
pub interpolation: Option<InterpolationType>,
|
||||
}
|
||||
|
||||
impl Keyframe {
|
||||
pub fn new(
|
||||
value: f32,
|
||||
offset: f32,
|
||||
id: Arc<str>,
|
||||
interpolation: Option<InterpolationType>,
|
||||
) -> Self {
|
||||
Keyframe {
|
||||
value,
|
||||
offset,
|
||||
id,
|
||||
interpolation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RenderedKeyframe {
|
||||
pub absolute_frame: i32,
|
||||
@@ -25,19 +25,19 @@ fn interpolates_the_input() {
|
||||
let keyframes1 = Keyframes {
|
||||
values: vec![
|
||||
Keyframe {
|
||||
id: "1".to_string(),
|
||||
id: "1".into(),
|
||||
value: 0.0,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
},
|
||||
Keyframe {
|
||||
id: "2".to_string(),
|
||||
id: "2".into(),
|
||||
value: 100.0,
|
||||
offset: 1.0,
|
||||
interpolation: None,
|
||||
},
|
||||
Keyframe {
|
||||
id: "3".to_string(),
|
||||
id: "3".into(),
|
||||
value: 300.0,
|
||||
offset: 3.0,
|
||||
interpolation: None,
|
||||
@@ -48,13 +48,13 @@ fn interpolates_the_input() {
|
||||
let keyframes2 = Keyframes {
|
||||
values: vec![
|
||||
Keyframe {
|
||||
id: "4".to_string(),
|
||||
id: "4".into(),
|
||||
value: -100.0,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
},
|
||||
Keyframe {
|
||||
id: "5".to_string(),
|
||||
id: "5".into(),
|
||||
value: 0.0,
|
||||
offset: 1.0,
|
||||
interpolation: None,
|
||||
@@ -150,19 +150,19 @@ fn gets_value_at_frame() {
|
||||
let keyframes = Keyframes {
|
||||
values: vec![
|
||||
Keyframe {
|
||||
id: "1".to_string(),
|
||||
id: "1".into(),
|
||||
value: 0.0,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
},
|
||||
Keyframe {
|
||||
id: "2".to_string(),
|
||||
id: "2".into(),
|
||||
value: 100.0,
|
||||
offset: 1.0,
|
||||
interpolation: None,
|
||||
},
|
||||
Keyframe {
|
||||
id: "3".to_string(),
|
||||
id: "3".into(),
|
||||
value: 300.0,
|
||||
offset: 3.0,
|
||||
interpolation: None,
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
entities::common::AnimationData,
|
||||
values::{AnimatedFloatVec2, AnimatedFloatVec3, AnimatedValue},
|
||||
values::animated_values::{AnimatedFloatVec2, AnimatedFloatVec3, AnimatedValue},
|
||||
};
|
||||
use crate::animation::timeline::Timeline;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -0,0 +1,268 @@
|
||||
use crate::animation::primitives::{
|
||||
entities::common::AnimationData,
|
||||
keyframe::{Keyframe, Keyframes},
|
||||
};
|
||||
#[cfg(feature = "parallelization")]
|
||||
use rayon::prelude::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::values::{Float, FloatVec2, FloatVec3};
|
||||
|
||||
pub trait AnimatedValue<T> {
|
||||
fn sort_keyframes(&mut self);
|
||||
fn get_value_at_frame(&self, curr_frame: i32, animation_data: &AnimationData, fps: i16) -> T;
|
||||
fn get_values_at_frame_range(
|
||||
&self,
|
||||
start_frame: i32,
|
||||
end_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> Vec<T>;
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedFloat {
|
||||
pub keyframes: Keyframes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedFloatVec2 {
|
||||
pub keyframes: (AnimatedFloat, AnimatedFloat),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedFloatVec3 {
|
||||
pub keyframes: (AnimatedFloat, AnimatedFloat, AnimatedFloat),
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||
pub fn get_values_at_frame_range_from_animated_float(
|
||||
animated_value: AnimatedFloat,
|
||||
start_frame: i32,
|
||||
end_frame: i32,
|
||||
animation_data: AnimationData,
|
||||
fps: i16,
|
||||
) -> Vec<Float> {
|
||||
animated_value.get_values_at_frame_range(start_frame, end_frame, &animation_data, fps)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||
pub fn get_values_at_frame_range_from_animated_float_vec2(
|
||||
animated_value: AnimatedFloatVec2,
|
||||
start_frame: i32,
|
||||
end_frame: i32,
|
||||
animation_data: AnimationData,
|
||||
fps: i16,
|
||||
) -> Vec<FloatVec2> {
|
||||
animated_value.get_values_at_frame_range(start_frame, end_frame, &animation_data, fps)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||
pub fn get_values_at_frame_range_from_animated_float_vec3(
|
||||
animated_value: AnimatedFloatVec3,
|
||||
start_frame: i32,
|
||||
end_frame: i32,
|
||||
animation_data: AnimationData,
|
||||
fps: i16,
|
||||
) -> Vec<FloatVec3> {
|
||||
animated_value.get_values_at_frame_range(start_frame, end_frame, &animation_data, fps)
|
||||
}
|
||||
|
||||
impl AnimatedFloat {
|
||||
pub fn new(val: f32) -> AnimatedFloat {
|
||||
AnimatedFloat {
|
||||
keyframes: Keyframes {
|
||||
values: vec![Keyframe {
|
||||
id: Uuid::new_v4().to_string().into(),
|
||||
value: val,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
}],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedFloatVec2 {
|
||||
pub fn new(x: f32, y: f32) -> AnimatedFloatVec2 {
|
||||
AnimatedFloatVec2 {
|
||||
keyframes: (AnimatedFloat::new(x), AnimatedFloat::new(y)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedFloatVec3 {
|
||||
pub fn new(x: f32, y: f32, z: f32) -> AnimatedFloatVec3 {
|
||||
AnimatedFloatVec3 {
|
||||
keyframes: (
|
||||
AnimatedFloat::new(x),
|
||||
AnimatedFloat::new(y),
|
||||
AnimatedFloat::new(z),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedValue<f32> for AnimatedFloat {
|
||||
fn sort_keyframes(&mut self) {
|
||||
self.keyframes.sort();
|
||||
}
|
||||
|
||||
fn get_value_at_frame(&self, curr_frame: i32, animation_data: &AnimationData, fps: i16) -> f32 {
|
||||
self.keyframes
|
||||
.get_value_at_frame(curr_frame, &animation_data, fps)
|
||||
}
|
||||
|
||||
#[cfg(feature = "parallelization")]
|
||||
fn get_values_at_frame_range(
|
||||
&self,
|
||||
start_frame: i32,
|
||||
end_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> Vec<f32> {
|
||||
let values = (start_frame..end_frame)
|
||||
.into_par_iter()
|
||||
.map(|i| self.get_value_at_frame(i, animation_data, fps))
|
||||
.collect();
|
||||
|
||||
values
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "parallelization"))]
|
||||
fn get_values_at_frame_range(
|
||||
&self,
|
||||
start_frame: i32,
|
||||
end_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> Vec<f32> {
|
||||
let values = (start_frame..end_frame)
|
||||
.into_iter()
|
||||
.map(|i| self.get_value_at_frame(i, animation_data, fps))
|
||||
.collect();
|
||||
|
||||
values
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedValue<(f32, f32, f32)> for AnimatedFloatVec3 {
|
||||
fn sort_keyframes(&mut self) {
|
||||
self.keyframes.0.sort_keyframes();
|
||||
self.keyframes.1.sort_keyframes();
|
||||
self.keyframes.2.sort_keyframes();
|
||||
}
|
||||
|
||||
fn get_value_at_frame(
|
||||
&self,
|
||||
curr_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> (f32, f32, f32) {
|
||||
let x = self
|
||||
.keyframes
|
||||
.0
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
let y = self
|
||||
.keyframes
|
||||
.1
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
let z = self
|
||||
.keyframes
|
||||
.2
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
return (x, y, z);
|
||||
}
|
||||
|
||||
fn get_values_at_frame_range(
|
||||
&self,
|
||||
start_frame: i32,
|
||||
end_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> Vec<(f32, f32, f32)> {
|
||||
let x =
|
||||
self.keyframes
|
||||
.0
|
||||
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||
let y =
|
||||
self.keyframes
|
||||
.1
|
||||
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||
let z =
|
||||
self.keyframes
|
||||
.2
|
||||
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||
|
||||
let vectors: Vec<(f32, f32, f32)> = x
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, val_x)| {
|
||||
let val_y: f32 = *y.get(index).unwrap();
|
||||
let val_z: f32 = *z.get(index).unwrap();
|
||||
|
||||
(val_x, val_y, val_z)
|
||||
})
|
||||
.collect();
|
||||
|
||||
vectors
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedValue<(f32, f32)> for AnimatedFloatVec2 {
|
||||
fn sort_keyframes(&mut self) {
|
||||
self.keyframes.0.sort_keyframes();
|
||||
self.keyframes.1.sort_keyframes();
|
||||
}
|
||||
|
||||
fn get_value_at_frame(
|
||||
&self,
|
||||
curr_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> (f32, f32) {
|
||||
let x = self
|
||||
.keyframes
|
||||
.0
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
let y = self
|
||||
.keyframes
|
||||
.1
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
fn get_values_at_frame_range(
|
||||
&self,
|
||||
start_frame: i32,
|
||||
end_frame: i32,
|
||||
animation_data: &AnimationData,
|
||||
fps: i16,
|
||||
) -> Vec<(f32, f32)> {
|
||||
let x =
|
||||
self.keyframes
|
||||
.0
|
||||
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||
let y =
|
||||
self.keyframes
|
||||
.1
|
||||
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||
|
||||
let vectors: Vec<(f32, f32)> = x
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, val_x)| {
|
||||
let val_y: f32 = *y.get(index).unwrap();
|
||||
|
||||
(val_x, val_y)
|
||||
})
|
||||
.collect();
|
||||
|
||||
vectors
|
||||
}
|
||||
}
|
||||
2
lib/creator_rs/src/animation/primitives/values/mod.rs
Normal file
2
lib/creator_rs/src/animation/primitives/values/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod animated_values;
|
||||
pub mod values;
|
||||
4
lib/creator_rs/src/animation/primitives/values/values.rs
Normal file
4
lib/creator_rs/src/animation/primitives/values/values.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub type Float = f32;
|
||||
pub type FloatVec2 = (f32, f32);
|
||||
pub type FloatVec3 = (f32, f32, f32);
|
||||
pub type FloatVec4 = (f32, f32, f32, f32);
|
||||
@@ -1,12 +1,3 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::animation::primitives::{
|
||||
interpolations::{EasingFunction, InterpolationType, SpringProperties},
|
||||
keyframe::{Keyframe, Keyframes},
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::primitives::{
|
||||
entities::{
|
||||
common::{AnimatedEntity, AnimationData, Cache, Entity},
|
||||
@@ -14,8 +5,17 @@ use super::primitives::{
|
||||
text::AnimatedTextEntity,
|
||||
},
|
||||
paint::{Color, FillStyle, Paint, PaintStyle, StrokeStyle, TextAlign, TextPaint},
|
||||
values::{AnimatedFloat, AnimatedFloatVec2},
|
||||
values::animated_values::{AnimatedFloat, AnimatedFloatVec2},
|
||||
};
|
||||
use crate::animation::primitives::{
|
||||
interpolations::{EasingFunction, InterpolationType, SpringProperties},
|
||||
keyframe::{Keyframe, Keyframes},
|
||||
};
|
||||
#[cfg(feature = "parallelization")]
|
||||
use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Input {
|
||||
@@ -38,6 +38,7 @@ pub struct RenderState {
|
||||
}
|
||||
|
||||
impl Timeline {
|
||||
#[cfg(feature = "parallelization")]
|
||||
fn calculate(&self) -> Vec<Entity> {
|
||||
let mut entities = self.entities.clone();
|
||||
|
||||
@@ -50,6 +51,20 @@ impl Timeline {
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "parallelization"))]
|
||||
fn calculate(&self) -> Vec<Entity> {
|
||||
let mut entities = self.entities.clone();
|
||||
|
||||
let entities = entities
|
||||
.iter_mut()
|
||||
.map(|entity| entity.calculate(self))
|
||||
.filter(|entity| entity.is_some())
|
||||
.map(|entity| entity.unwrap())
|
||||
.collect();
|
||||
|
||||
return entities;
|
||||
}
|
||||
}
|
||||
|
||||
fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||
@@ -70,7 +85,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||
keyframes: Keyframes {
|
||||
values: vec![
|
||||
Keyframe {
|
||||
id: "1".to_string(),
|
||||
id: "1".into(),
|
||||
value: (size.0 * -1) as f32,
|
||||
offset: 0.0,
|
||||
interpolation: Some(InterpolationType::EasingFunction(
|
||||
@@ -78,7 +93,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||
)),
|
||||
},
|
||||
Keyframe {
|
||||
id: "2".to_string(),
|
||||
id: "2".into(),
|
||||
value: 0.0,
|
||||
offset: 5.0,
|
||||
interpolation: None,
|
||||
@@ -89,7 +104,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||
AnimatedFloat {
|
||||
keyframes: Keyframes {
|
||||
values: vec![Keyframe {
|
||||
id: "3".to_string(),
|
||||
id: "3".into(),
|
||||
value: 0.0,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
@@ -103,7 +118,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||
AnimatedFloat {
|
||||
keyframes: Keyframes {
|
||||
values: vec![Keyframe {
|
||||
id: "4".to_string(),
|
||||
id: "4".into(),
|
||||
interpolation: None,
|
||||
value: size.0 as f32,
|
||||
offset: 0.0,
|
||||
@@ -113,7 +128,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||
AnimatedFloat {
|
||||
keyframes: Keyframes {
|
||||
values: vec![Keyframe {
|
||||
id: "5".to_string(),
|
||||
id: "5".into(),
|
||||
value: size.1 as f32,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
@@ -126,11 +141,24 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||
return bg_box;
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn calculate_timeline_entities_at_frame(timeline: Timeline) -> Vec<Entity> {
|
||||
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||
pub fn calculate_timeline_at_curr_frame(timeline: Timeline) -> Vec<Entity> {
|
||||
timeline.calculate()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn calculate_timeline_from_json_at_curr_frame(timeline_json: &str) -> String {
|
||||
// TODO: Handle failure instead of unwrap
|
||||
let timeline: Timeline = serde_json::from_str(timeline_json).unwrap();
|
||||
|
||||
let entities = calculate_timeline_at_curr_frame(timeline);
|
||||
|
||||
// TODO: Handle failure instead of unwrap
|
||||
let entities_json = serde_json::to_string(&entities).unwrap();
|
||||
|
||||
entities_json
|
||||
}
|
||||
|
||||
pub fn test_timeline_entities_at_frame(
|
||||
render_state: RenderState,
|
||||
size: (i32, i32),
|
||||
@@ -159,7 +187,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
color: Color::new(0, 0, 0, 1.0),
|
||||
width: 10.0,
|
||||
}),
|
||||
font_name: "Arial".to_string(),
|
||||
font_name: "Arial".into(),
|
||||
align: TextAlign::Center,
|
||||
size: 20.0,
|
||||
};
|
||||
@@ -168,7 +196,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
style: PaintStyle::Fill(FillStyle {
|
||||
color: Color::new(0, 0, 0, 1.0),
|
||||
}),
|
||||
font_name: "Arial".to_string(),
|
||||
font_name: "Arial".into(),
|
||||
align: TextAlign::Center,
|
||||
size: 10.0,
|
||||
};
|
||||
@@ -198,7 +226,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
keyframes: Keyframes {
|
||||
values: vec![
|
||||
Keyframe {
|
||||
id: "1".to_string(),
|
||||
id: "1".into(),
|
||||
value: 0.0,
|
||||
offset: 0.0,
|
||||
interpolation: Some(InterpolationType::Spring(
|
||||
@@ -210,7 +238,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
)),
|
||||
},
|
||||
Keyframe {
|
||||
id: "2".to_string(),
|
||||
id: "2".into(),
|
||||
value: (size.0 / 2) as f32,
|
||||
offset: 2.0,
|
||||
interpolation: None,
|
||||
@@ -221,7 +249,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
AnimatedFloat {
|
||||
keyframes: Keyframes {
|
||||
values: vec![Keyframe {
|
||||
id: "3".to_string(),
|
||||
id: "3".into(),
|
||||
value: (size.1 / 2) as f32,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
@@ -248,7 +276,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
keyframes: Keyframes {
|
||||
values: vec![
|
||||
Keyframe {
|
||||
id: "5".to_string(),
|
||||
id: "5".into(),
|
||||
value: 0.0,
|
||||
offset: 0.0,
|
||||
interpolation: Some(InterpolationType::Spring(
|
||||
@@ -260,7 +288,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
)),
|
||||
},
|
||||
Keyframe {
|
||||
id: "6".to_string(),
|
||||
id: "6".into(),
|
||||
|
||||
value: (size.0 / 2) as f32,
|
||||
offset: 2.0,
|
||||
@@ -272,7 +300,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
AnimatedFloat {
|
||||
keyframes: Keyframes {
|
||||
values: vec![Keyframe {
|
||||
id: "7".to_string(),
|
||||
id: "7".into(),
|
||||
value: ((size.1 / 2) as f32) + 80.0,
|
||||
offset: 0.0,
|
||||
interpolation: None,
|
||||
@@ -1,6 +1,6 @@
|
||||
use font_kit::source::SystemSource;
|
||||
|
||||
#[tauri::command]
|
||||
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||
pub fn get_system_fonts() -> Option<Vec<String>> {
|
||||
let source = SystemSource::new();
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn get_system_fonts() -> Option<Vec<String>> {
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||
pub fn get_system_families() -> Option<Vec<String>> {
|
||||
let source = SystemSource::new();
|
||||
|
||||
@@ -33,7 +33,7 @@ pub fn get_system_families() -> Option<Vec<String>> {
|
||||
found_families.ok()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||
pub fn get_system_font(font_name: String) -> Option<Vec<u8>> {
|
||||
let source = SystemSource::new();
|
||||
|
||||
1
lib/creator_rs/src/fonts/mod.rs
Normal file
1
lib/creator_rs/src/fonts/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod fonts;
|
||||
3
lib/creator_rs/src/lib.rs
Normal file
3
lib/creator_rs/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod animation;
|
||||
#[cfg(feature = "fonts")]
|
||||
pub mod fonts;
|
||||
1
web/.vercel/project.json
Normal file
1
web/.vercel/project.json
Normal file
@@ -0,0 +1 @@
|
||||
{"orgId":"c0DV5ATsGbVZoMVVr9msnlC7","projectId":"prj_6wyilH72l9zcNlIfA9NUAJ0xON0v"}
|
||||
7
web/.vscode/settings.json
vendored
7
web/.vscode/settings.json
vendored
@@ -1,3 +1,8 @@
|
||||
{
|
||||
"typescript.inlayHints.parameterNames.enabled": "all"
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
[
|
||||
"cva\\(([^)]*)\\)",
|
||||
"[\"'`]([^\"'`]*).*?[\"'`]"
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -1,14 +1,55 @@
|
||||
# tempblade Creator
|
||||
# Astro Starter Kit: Basics
|
||||
|
||||
This is a web based motion design creator built on astro.
|
||||
```
|
||||
npm create astro@latest -- --template basics
|
||||
```
|
||||
|
||||
## Commands
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
|
||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||

|
||||
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```
|
||||
/
|
||||
├── public/
|
||||
│ └── favicon.svg
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ └── Card.astro
|
||||
│ ├── layouts/
|
||||
│ │ └── Layout.astro
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :--------------------- | :----------------------------------------------- |
|
||||
| `yarn dev` | Starts local dev server at `localhost:3000` |
|
||||
| `yarn build` | Build your production site to `./dist/` |
|
||||
| `yarn preview` | Preview your build locally, before deploying |
|
||||
| `yarn astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
|
||||
12
web/astro.config.mjs
Normal file
12
web/astro.config.mjs
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [
|
||||
tailwind({
|
||||
applyBaseStyles: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
import UnoCSS from "unocss/astro";
|
||||
|
||||
import react from "@astrojs/react";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [UnoCSS({
|
||||
injectReset: true
|
||||
}), react()]
|
||||
});
|
||||
9
web/deploy.sh
Normal file
9
web/deploy.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
[ -z $TOKEN ] && printf "Token is missing" && exit 1
|
||||
|
||||
if [ -z $IS_PROD ]; then
|
||||
printf "\nTEST DEPLOYMENT\n"
|
||||
else
|
||||
printf "\nPRODUCTION DEPLOYMENT\n" && PROD="--prod"
|
||||
fi
|
||||
|
||||
URL=$(vercel --yes --global-config ./.vercel --token $TOKEN $PROD) && printf "\nDEPLOYMENT SUCCESSFUL\n$URL"
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "creator",
|
||||
"name": "web",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
@@ -10,21 +10,20 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^2.1.1",
|
||||
"@types/react": "^18.0.21",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@astrojs/tailwind": "^3.1.3",
|
||||
"@types/marked": "^5.0.0",
|
||||
"@unom/style": "^0.2.14",
|
||||
"astro": "^2.3.0",
|
||||
"motion": "^10.15.5",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jasikpark/astro-svg-loader": "^0.1.0",
|
||||
"@unocss/postcss": "^0.51.4",
|
||||
"@unocss/preset-mini": "^0.51.4",
|
||||
"@unocss/reset": "^0.51.4",
|
||||
"postcss-preset-env": "^8.3.1",
|
||||
"unocss": "^0.51.4"
|
||||
"astro": "^2.5.0",
|
||||
"astro-icon": "^0.8.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"clsx": "^1.2.1",
|
||||
"marked": "^5.1.0",
|
||||
"motion": "^10.16.2",
|
||||
"postcss": "^8.4.24",
|
||||
"postcss-custom-media": "^9.1.5",
|
||||
"postcss-import": "^15.1.0",
|
||||
"tailwind-merge": "^1.13.2",
|
||||
"tailwindcss": "^3.0.24"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"@unocss/postcss": {},
|
||||
"postcss-preset-env": {
|
||||
features: {
|
||||
"nesting-rules": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
const postcssImport = require("postcss-import");
|
||||
const nesting = require("tailwindcss/nesting");
|
||||
const tailwind = require("tailwindcss");
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const customMedia = require("postcss-custom-media");
|
||||
|
||||
const config = {
|
||||
plugins: [postcssImport, nesting, tailwind, autoprefixer, customMedia],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
44
web/src/components/Button.astro
Normal file
44
web/src/components/Button.astro
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const button = cva(
|
||||
cn(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium",
|
||||
"transition-colors focus-visible:outline-none",
|
||||
"ring-main focus-visible:ring-2 focus-visible:ring-ring",
|
||||
"focus-visible:ring-offset-2 disabled:opacity-50",
|
||||
"disabled:pointer-events-none ring-offset-background"
|
||||
),
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-main text-neutral hocus:bg-main/80",
|
||||
destructive:
|
||||
"bg-error text-destructive-foreground hocus:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-main/10 text-main hocus:bg-main/20",
|
||||
ghost: "hocus:bg-accent hocus:text-accent-foreground",
|
||||
link: "underline-offset-4 hocus:underline text-primary",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 text-base py-2 px-4",
|
||||
sm: "h-9 px-3 rounded-md",
|
||||
lg: "h-11 px-8 rounded-md",
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface Props
|
||||
extends HTMLAttributes<"button">,
|
||||
VariantProps<typeof button> {}
|
||||
|
||||
const { variant = "default", size = "default", ...props } = Astro.props;
|
||||
---
|
||||
|
||||
<button {...props} class={button({ variant, size })}>
|
||||
<slot />
|
||||
</button>
|
||||
36
web/src/components/Card.astro
Normal file
36
web/src/components/Card.astro
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
import { VariantProps, cva } from "class-variance-authority";
|
||||
|
||||
const card = cva(
|
||||
"card rounded-card transition-colors bg-neutral-accent ring-2 ring-main/10",
|
||||
{
|
||||
variants: {
|
||||
interactable: {
|
||||
false: "transition-shadow focus-within:shadow-lg ring-2 ring-neutral",
|
||||
true: "cursor-pointer shadow-sm hocus:bg-neutral-accent/50 \
|
||||
hocus:shadow-lg hocus:ring-2 hocus:outline-none",
|
||||
},
|
||||
padding: {
|
||||
true: "py-6 px-8",
|
||||
false: "p-0",
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface Props
|
||||
extends HTMLAttributes<"div">,
|
||||
VariantProps<typeof card> {}
|
||||
|
||||
const {
|
||||
interactable = false,
|
||||
padding = true,
|
||||
class: _class,
|
||||
...props
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div {...props} class={card({ interactable, padding, class: _class })}>
|
||||
<slot />
|
||||
</div>
|
||||
26
web/src/components/Cards/CardFeature.astro
Normal file
26
web/src/components/Cards/CardFeature.astro
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
import Card from "../Card.astro";
|
||||
import { Icon } from "astro-icon";
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
iconName: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const { title, description, iconName } = Astro.props;
|
||||
---
|
||||
|
||||
<Card class="w-[300px] shrink-0 grow">
|
||||
<div
|
||||
class="text-primary w-12 mb-4 shadow-current drop-shadow-[0px_0px_10px_hsl(var(--color-primary)_/_0.5)]"
|
||||
>
|
||||
<Icon name={iconName} />
|
||||
</div>
|
||||
<h4 class="text-lg font-bold">
|
||||
{title}
|
||||
</h4>
|
||||
<p class="text-secondary">
|
||||
{description}
|
||||
</p>
|
||||
</Card>
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="container">
|
||||
<div class="icon-container mb-8">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<h3 class="mb-2">{title}</h3>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 400px;
|
||||
|
||||
& p {
|
||||
max-width: 300px;
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& .icon-container {
|
||||
& svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
web/src/components/Heading.astro
Normal file
52
web/src/components/Heading.astro
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
import { VariantProps, cva } from "class-variance-authority";
|
||||
|
||||
const heading = cva("max-w-3xl m-[var(--section-heading-margin)]", {
|
||||
variants: {
|
||||
main: {
|
||||
true: "font-bold",
|
||||
false: "font-semibold",
|
||||
},
|
||||
padding: {
|
||||
true: "py-4",
|
||||
false: "p-0",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export interface Props
|
||||
extends HTMLAttributes<"h1" | "h2">,
|
||||
VariantProps<typeof heading> {
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
const { main = false, padding = true, subtitle } = Astro.props;
|
||||
|
||||
const titleClass = cn(
|
||||
heading({ main, padding }),
|
||||
subtitle ? "mb-[calc(var(--section_heading-margin-bottom)_/_2)]" : ""
|
||||
);
|
||||
---
|
||||
|
||||
<div class="z-0 relative">
|
||||
{
|
||||
main ? (
|
||||
<h1 class={titleClass}>
|
||||
<slot />
|
||||
</h1>
|
||||
) : (
|
||||
<h2 class={titleClass}>
|
||||
<slot />
|
||||
</h2>
|
||||
)
|
||||
}
|
||||
{
|
||||
subtitle && (
|
||||
<h3 class="font-normal m-[var(--section-heading-margin)] max-w-3xl">
|
||||
{subtitle}
|
||||
</h3>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
import SVG from "@jasikpark/astro-svg-loader";
|
||||
|
||||
type IconName = "resolution" | "smooth" | "local";
|
||||
|
||||
export interface Props {
|
||||
name: IconName;
|
||||
}
|
||||
|
||||
const { name } = Astro.props;
|
||||
|
||||
const icons: Record<IconName, Promise<typeof import("*.svg")>> = {
|
||||
resolution: import("./Icon_Resolution.svg?raw"),
|
||||
smooth: import("./Icon_Smooth.svg?raw"),
|
||||
local: import("./Icon_Local.svg?raw"),
|
||||
};
|
||||
|
||||
const iconSrc = icons[name];
|
||||
---
|
||||
|
||||
<SVG src={iconSrc} aria-label={name} />
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg width="133" height="133" viewBox="0 0 133 133" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="66.5" cy="66.5" r="66.5" fill="#D9D9D9"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M75 64.8956L90 53V75.3636L66.5 94L43 75.3636V53L56 63.3095L56 0H75L75 64.8956Z" fill="#161616"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 307 B |
@@ -1,23 +0,0 @@
|
||||
<svg width="118" height="118" viewBox="0 0 118 118" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g style="mix-blend-mode:luminosity">
|
||||
<rect x="0.00012207" y="59.0001" width="59" height="59" fill="white"/>
|
||||
<rect x="59.0001" y="6.10352e-05" width="59" height="59" fill="#2D2D2D"/>
|
||||
<rect x="59.0001" y="59.0001" width="59" height="59" fill="#737373"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:luminosity">
|
||||
<rect y="29.5" width="29.5" height="29.5" fill="white"/>
|
||||
<rect x="29.5" width="29.5" height="29.5" fill="#2D2D2D"/>
|
||||
<rect x="29.5" y="29.5" width="29.5" height="29.5" fill="#737373"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:luminosity">
|
||||
<rect y="15" width="15" height="15" fill="white"/>
|
||||
<rect x="15" width="15" height="15" fill="#2D2D2D"/>
|
||||
<rect x="15" y="15" width="15" height="15" fill="#737373"/>
|
||||
</g>
|
||||
<g style="mix-blend-mode:luminosity">
|
||||
<rect y="7.5" width="7.5" height="7.5" fill="white"/>
|
||||
<rect x="7.5" width="7.5" height="7.5" fill="#2D2D2D"/>
|
||||
<rect x="7.5" y="7.5" width="7.5" height="7.5" fill="#737373"/>
|
||||
<rect width="7.5" height="7.5" fill="#828282"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="133" height="133" viewBox="0 0 133 133" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<ellipse cx="66.5" cy="40" rx="39.5" ry="39" fill="#D9D9D9" fill-opacity="0.4"/>
|
||||
<ellipse cx="66.5" cy="94" rx="39.5" ry="39" fill="#D9D9D9" fill-opacity="0.58"/>
|
||||
<circle cx="67" cy="68" r="39" fill="#D9D9D9" fill-opacity="0.4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 337 B |
@@ -1,11 +1,25 @@
|
||||
---
|
||||
type NavigationItem = {
|
||||
path: string;
|
||||
label: string;
|
||||
};
|
||||
import Section from "../Section.astro";
|
||||
|
||||
enum NavigationItemType {
|
||||
Link,
|
||||
Text,
|
||||
}
|
||||
|
||||
type NavigationItem =
|
||||
| {
|
||||
path: string;
|
||||
label: string;
|
||||
type: NavigationItemType.Link;
|
||||
}
|
||||
| {
|
||||
type: NavigationItemType.Text;
|
||||
content: string;
|
||||
};
|
||||
|
||||
type NavigationGroup = {
|
||||
title: string;
|
||||
class?: string;
|
||||
items: Array<NavigationItem>;
|
||||
};
|
||||
|
||||
@@ -15,56 +29,64 @@ const tree: Array<NavigationGroup> = [
|
||||
items: [
|
||||
{
|
||||
path: "/",
|
||||
type: NavigationItemType.Link,
|
||||
label: "Landing",
|
||||
},
|
||||
{
|
||||
path: "/blog",
|
||||
label: "Blog",
|
||||
path: "https://github.com/tempblade/creator",
|
||||
type: NavigationItemType.Link,
|
||||
label: "GitHub",
|
||||
},
|
||||
{
|
||||
path: "/contact",
|
||||
label: "Contact",
|
||||
path: "https://tempblade.com",
|
||||
type: NavigationItemType.Link,
|
||||
label: "tempblade",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Legal",
|
||||
items: [
|
||||
{ path: "/legal/imprint", label: "Imprint" },
|
||||
{
|
||||
path: "/legal/privacy-policy",
|
||||
label: "Privacy Policy",
|
||||
path: "/legal/imprint",
|
||||
label: "Imprint",
|
||||
type: NavigationItemType.Link,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "",
|
||||
class: "ml-auto mr-0 self-end",
|
||||
items: [
|
||||
{
|
||||
type: NavigationItemType.Text,
|
||||
content: "Made with ❤️ in Rottweil",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<footer class="py-4">
|
||||
<div class="m-auto flex flex-row flex-wrap gap-12 inner-container">
|
||||
{
|
||||
tree.map((group) => (
|
||||
<div>
|
||||
<h3 class="mb-2">{group.title}</h3>
|
||||
<div class="flex flex-col">
|
||||
{group.items.map((item) => (
|
||||
<a href={item.path}>{item.label}</a>
|
||||
))}
|
||||
<footer class="bg-neutral-accent">
|
||||
<Section>
|
||||
<div class="flex flex-row flex-wrap gap-12 w-full pb-8">
|
||||
{
|
||||
tree.map((group) => (
|
||||
<div class={group.class}>
|
||||
{group.title && <h3 class="mb-2">{group.title}</h3>}
|
||||
<div class="flex flex-col">
|
||||
{group.items.map((item) => {
|
||||
switch (item.type) {
|
||||
case NavigationItemType.Link:
|
||||
return <a href={item.path}>{item.label}</a>;
|
||||
case NavigationItemType.Text:
|
||||
return <p>{item.content}</p>;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Section>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
background-color: var(--color-neutral-accent);
|
||||
|
||||
& .inner-container {
|
||||
max-width: var(--content-max-width);
|
||||
margin: auto;
|
||||
padding: var(--padding-main);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
import Logo from "components/Logo.astro";
|
||||
---
|
||||
|
||||
<header id="main-header">
|
||||
<div class="inner-container">
|
||||
<Logo />
|
||||
<button id="get-started-button" class="button">Get Started</button>
|
||||
</div>
|
||||
</header>
|
||||
<script>
|
||||
import { animate } from "motion";
|
||||
import { ease } from "@unom/style";
|
||||
|
||||
const getStartedButton = document.getElementById("get-started-button");
|
||||
const header = document.getElementById("main-header");
|
||||
|
||||
if (header) {
|
||||
animate(header, { opacity: [0, 1], y: ["-101%", 0] }, ease.quart(0.6).out);
|
||||
}
|
||||
|
||||
if (getStartedButton) {
|
||||
animate(
|
||||
getStartedButton,
|
||||
{ scale: [0, 1] },
|
||||
{ ...ease.quart(0.6).out, delay: 0 }
|
||||
);
|
||||
}
|
||||
</script>
|
||||
<style lang="postcss">
|
||||
header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 2rem;
|
||||
z-index: 99;
|
||||
|
||||
& .inner-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
border-radius: 100px;
|
||||
margin: auto;
|
||||
max-width: var(--content-max-width);
|
||||
padding: var(--padding-main);
|
||||
background-color: var(--color-neutral-accent);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,42 +0,0 @@
|
||||
<a id="logo" class="block" href="/">
|
||||
<h1 class="text-4xl leading-8 font-bold">
|
||||
<span>p</span><span>l</span><span>a</span><span>y</span>
|
||||
</h1>
|
||||
</a>
|
||||
<style>
|
||||
a {
|
||||
display: block;
|
||||
overflow: visible;
|
||||
}
|
||||
h1 {
|
||||
display: block;
|
||||
padding: 4px;
|
||||
margin-bottom: 0.25rem;
|
||||
background-image: linear-gradient(180deg, white, rgba(255, 255, 255, 0.4));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
width: fit-content;
|
||||
|
||||
&:hover {
|
||||
-webkit-text-stroke: 1px #eee;
|
||||
}
|
||||
|
||||
& span {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { animate, stagger } from "motion";
|
||||
import { ease } from "@unom/style";
|
||||
|
||||
const logoText = document.querySelectorAll("#logo h1")[0];
|
||||
|
||||
console.log(logoText);
|
||||
|
||||
animate(
|
||||
logoText.children as any,
|
||||
{ y: [-30, 0], opacity: [0, 1] },
|
||||
{ ...ease.quint(0.7).out, delay: stagger(0.1) }
|
||||
);
|
||||
</script>
|
||||
27
web/src/components/Section.astro
Normal file
27
web/src/components/Section.astro
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
const section = cva("relative w-full", {
|
||||
variants: {
|
||||
padding: {
|
||||
true: "p-main",
|
||||
false: "p-0",
|
||||
},
|
||||
maxWidth: {
|
||||
true: "max-w-section mx-auto",
|
||||
false: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export interface Props
|
||||
extends HTMLAttributes<"section">,
|
||||
VariantProps<typeof section> {}
|
||||
|
||||
const { padding = true, maxWidth = true, ...props } = Astro.props;
|
||||
---
|
||||
|
||||
<section {...props} class={section({ padding, maxWidth })}>
|
||||
<slot />
|
||||
</section>
|
||||
20
web/src/icons/Extensible.svg
Normal file
20
web/src/icons/Extensible.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<polygon points="455.76 263.72 455.76 291.15 455.76 318.59 455.76 346.02 455.76 373.45 483.19 373.45 483.19 346.02 483.19 318.59 483.19 291.15 483.19 263.72 455.76 263.72"/>
|
||||
<rect x="428.32" y="373.45" width="27.43" height="27.43"/>
|
||||
<rect x="428.32" y="236.28" width="27.43" height="27.43"/>
|
||||
<polygon points="373.45 400.89 346.02 400.89 346.02 428.32 373.45 428.32 400.89 428.32 428.32 428.32 428.32 400.89 400.89 400.89 373.45 400.89"/>
|
||||
<polygon points="400.89 236.28 428.32 236.28 428.32 208.85 400.89 208.85 373.45 208.85 346.02 208.85 346.02 236.28 373.45 236.28 400.89 236.28"/>
|
||||
<rect x="318.59" y="373.45" width="27.43" height="27.43"/>
|
||||
<rect x="318.59" y="236.28" width="27.43" height="27.43"/>
|
||||
<polygon points="318.59 318.59 318.59 291.15 318.59 263.72 291.15 263.72 291.15 291.15 291.15 318.59 291.15 346.02 291.15 373.45 318.59 373.45 318.59 346.02 318.59 318.59"/>
|
||||
<polygon points="291.15 126.55 318.59 126.55 318.59 153.98 346.02 153.98 346.02 181.41 373.45 181.41 373.45 153.98 373.45 126.55 346.02 126.55 346.02 99.11 318.59 99.11 318.59 71.68 291.15 71.68 263.72 71.68 263.72 44.24 236.28 44.24 208.85 44.24 181.41 44.24 181.41 71.68 181.41 99.11 208.85 99.11 208.85 71.68 236.28 71.68 236.28 99.11 263.72 99.11 263.72 126.55 236.28 126.55 236.28 153.98 208.85 153.98 208.85 181.41 208.85 208.85 181.41 208.85 153.98 208.85 153.98 236.28 181.41 236.28 208.85 236.28 208.85 263.72 236.28 263.72 263.72 263.72 263.72 236.28 236.28 236.28 236.28 208.85 236.28 181.41 263.72 181.41 263.72 153.98 291.15 153.98 291.15 126.55"/>
|
||||
<polygon points="236.28 346.02 208.85 346.02 208.85 373.45 208.85 400.89 181.41 400.89 153.98 400.89 126.55 400.89 126.55 428.32 153.98 428.32 181.41 428.32 208.85 428.32 236.28 428.32 236.28 400.89 263.72 400.89 263.72 373.45 236.28 373.45 236.28 346.02"/>
|
||||
<rect x="181.41" y="318.59" width="27.43" height="27.43"/>
|
||||
<rect x="153.98" y="291.15" width="27.43" height="27.43"/>
|
||||
<rect x="126.55" y="263.72" width="27.43" height="27.43"/>
|
||||
<rect x="99.11" y="236.28" width="27.43" height="27.43"/>
|
||||
<polygon points="71.68 428.32 44.24 428.32 44.24 400.89 16.81 400.89 16.81 428.32 16.81 455.76 44.24 455.76 71.68 455.76 99.11 455.76 126.55 455.76 126.55 428.32 99.11 428.32 71.68 428.32"/>
|
||||
<polygon points="99.11 291.15 99.11 263.72 71.68 263.72 71.68 291.15 71.68 318.59 99.11 318.59 99.11 291.15"/>
|
||||
<polygon points="71.68 373.45 71.68 346.02 71.68 318.59 44.24 318.59 44.24 346.02 44.24 373.45 44.24 400.89 71.68 400.89 71.68 373.45"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
6
web/src/icons/Fast.svg
Normal file
6
web/src/icons/Fast.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<polygon points="66.99 184.64 93.14 184.64 119.28 184.64 145.42 184.64 171.57 184.64 197.71 184.64 223.86 184.64 250 184.64 276.14 184.64 302.29 184.64 302.29 158.5 302.29 132.35 302.29 106.21 328.43 106.21 328.43 80.07 302.29 80.07 302.29 53.92 276.14 53.92 276.14 27.78 250 27.78 223.86 27.78 223.86 53.92 223.86 80.07 223.86 106.21 197.71 106.21 171.57 106.21 145.42 106.21 119.28 106.21 93.14 106.21 66.99 106.21 40.85 106.21 40.85 132.35 14.71 132.35 14.71 158.5 14.71 184.64 40.85 184.64 66.99 184.64"/>
|
||||
<polygon points="459.15 236.93 459.15 210.78 433.01 210.78 433.01 184.64 406.86 184.64 406.86 158.5 380.72 158.5 380.72 132.35 354.58 132.35 328.43 132.35 328.43 158.5 328.43 184.64 328.43 210.78 302.29 210.78 276.14 210.78 250 210.78 223.86 210.78 197.71 210.78 171.57 210.78 145.42 210.78 119.28 210.78 93.14 210.78 93.14 236.93 66.99 236.93 66.99 263.07 93.14 263.07 93.14 289.22 119.28 289.22 145.42 289.22 171.57 289.22 197.71 289.22 223.86 289.22 250 289.22 276.14 289.22 302.29 289.22 328.43 289.22 328.43 315.36 328.43 341.5 328.43 367.65 354.58 367.65 380.72 367.65 380.72 341.5 406.86 341.5 406.86 315.36 433.01 315.36 433.01 289.22 459.15 289.22 459.15 263.07 485.29 263.07 485.29 236.93 459.15 236.93"/>
|
||||
<polygon points="302.29 367.65 302.29 341.5 302.29 315.36 276.14 315.36 250 315.36 223.86 315.36 197.71 315.36 171.57 315.36 145.42 315.36 119.28 315.36 93.14 315.36 66.99 315.36 40.85 315.36 14.71 315.36 14.71 341.5 14.71 367.65 40.85 367.65 40.85 393.79 66.99 393.79 93.14 393.79 119.28 393.79 145.42 393.79 171.57 393.79 197.71 393.79 223.86 393.79 223.86 419.93 223.86 446.08 223.86 472.22 250 472.22 276.14 472.22 276.14 446.08 302.29 446.08 302.29 419.93 328.43 419.93 328.43 393.79 302.29 393.79 302.29 367.65"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
32
web/src/icons/Open.svg
Normal file
32
web/src/icons/Open.svg
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<g>
|
||||
<rect x="261.76" y="155.3" width="23.53" height="23.53"/>
|
||||
<rect x="214.71" y="155.3" width="23.53" height="23.53"/>
|
||||
<polygon points="379.41 178.83 379.41 155.3 355.88 155.3 355.88 131.78 332.35 131.78 308.82 131.78 285.29 131.78 285.29 155.3 308.82 155.3 332.35 155.3 332.35 178.83 355.88 178.83 355.88 202.36 379.41 202.36 379.41 225.89 355.88 225.89 355.88 249.42 332.35 249.42 332.35 272.94 308.82 272.94 308.82 296.47 285.29 296.47 285.29 320 261.76 320 261.76 343.53 238.24 343.53 238.24 320 214.71 320 214.71 296.47 191.18 296.47 191.18 272.94 167.65 272.94 167.65 249.42 144.12 249.42 144.12 225.89 120.6 225.89 120.6 202.36 144.12 202.36 144.12 178.83 167.65 178.83 167.65 155.3 191.18 155.3 214.71 155.3 214.71 131.78 191.18 131.78 167.65 131.78 144.12 131.78 144.12 155.3 120.6 155.3 120.6 178.83 97.07 178.83 97.07 202.36 97.07 225.89 97.07 249.42 120.6 249.42 120.6 272.94 144.12 272.94 144.12 296.47 167.65 296.47 167.65 320 191.18 320 191.18 343.53 214.71 343.53 214.71 367.06 238.24 367.06 238.24 390.58 261.76 390.58 261.76 367.06 285.29 367.06 285.29 343.53 308.82 343.53 308.82 320 332.35 320 332.35 296.47 355.88 296.47 355.88 272.94 379.41 272.94 379.41 249.42 402.93 249.42 402.93 225.89 402.93 202.36 402.93 178.83 379.41 178.83"/>
|
||||
<rect x="238.24" y="178.83" width="23.53" height="23.53"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon points="461.26 108.83 437.73 108.83 437.73 132.36 437.73 155.89 461.26 155.89 461.26 132.36 461.26 108.83"/>
|
||||
<rect x="414.2" y="85.3" width="23.53" height="23.53"/>
|
||||
<rect x="390.68" y="61.78" width="23.53" height="23.53"/>
|
||||
<polygon points="367.15 38.25 343.62 38.25 343.62 61.78 367.15 61.78 390.68 61.78 390.68 38.25 367.15 38.25"/>
|
||||
<polygon points="320.09 14.72 296.56 14.72 273.04 14.72 250.49 14.72 249.51 14.72 226.96 14.72 203.44 14.72 179.91 14.72 156.38 14.72 156.38 38.25 179.91 38.25 203.44 38.25 226.96 38.25 250.49 38.25 250.49 38.25 273.04 38.25 296.56 38.25 320.09 38.25 343.62 38.25 343.62 14.72 320.09 14.72"/>
|
||||
<polygon points="296.56 461.75 273.04 461.75 250.49 461.75 249.51 461.75 226.96 461.75 203.44 461.75 179.91 461.75 156.38 461.75 156.38 485.28 179.91 485.28 203.44 485.28 226.96 485.28 250.49 485.28 250.49 485.28 273.04 485.28 296.56 485.28 320.09 485.28 343.62 485.28 343.62 461.75 320.09 461.75 296.56 461.75"/>
|
||||
<polygon points="343.62 438.22 343.62 461.75 367.15 461.75 390.68 461.75 390.68 438.22 367.15 438.22 343.62 438.22"/>
|
||||
<rect x="390.68" y="414.7" width="23.53" height="23.53"/>
|
||||
<rect x="414.2" y="391.17" width="23.53" height="23.53"/>
|
||||
<polygon points="437.73 367.64 437.73 391.17 461.26 391.17 461.26 367.64 461.26 344.11 437.73 344.11 437.73 367.64"/>
|
||||
<polygon points="484.79 250 484.79 226.47 484.79 202.94 484.79 179.42 484.79 155.89 461.26 155.89 461.26 179.42 461.26 202.94 461.26 226.47 461.26 250 461.26 250 461.26 273.53 461.26 297.06 461.26 320.58 461.26 344.11 484.79 344.11 484.79 320.58 484.79 297.06 484.79 273.53 484.79 250 484.79 250"/>
|
||||
<polygon points="38.74 297.06 38.74 273.53 38.74 250 15.21 250 15.21 273.53 15.21 297.06 15.21 320.58 15.21 344.11 38.74 344.11 38.74 320.58 38.74 297.06"/>
|
||||
<polygon points="62.27 344.11 38.74 344.11 38.74 367.64 38.74 391.17 62.27 391.17 62.27 367.64 62.27 344.11"/>
|
||||
<rect x="62.27" y="391.17" width="23.53" height="23.53"/>
|
||||
<rect x="85.8" y="414.7" width="23.53" height="23.53"/>
|
||||
<polygon points="132.85 438.22 109.32 438.22 109.32 461.75 132.85 461.75 156.38 461.75 156.38 438.22 132.85 438.22"/>
|
||||
<polygon points="109.32 38.25 109.32 61.78 132.85 61.78 156.38 61.78 156.38 38.25 132.85 38.25 109.32 38.25"/>
|
||||
<rect x="85.8" y="61.78" width="23.53" height="23.53"/>
|
||||
<rect x="62.27" y="85.3" width="23.53" height="23.53"/>
|
||||
<polygon points="38.74 132.36 38.74 155.89 62.27 155.89 62.27 132.36 62.27 108.83 38.74 108.83 38.74 132.36"/>
|
||||
<polygon points="15.21 179.42 15.21 202.94 15.21 226.47 15.21 250 38.74 250 38.74 226.47 38.74 202.94 38.74 179.42 38.74 155.89 15.21 155.89 15.21 179.42"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1,116 +0,0 @@
|
||||
---
|
||||
import Header from "components/Layout/Header.astro";
|
||||
import Footer from "components/Layout/Footer.astro";
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>tempblade Creator - {title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<Header />
|
||||
<slot />
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
<style is:global>
|
||||
:root {
|
||||
--font-main: "Gilroy", system-ui, sans-serif;
|
||||
|
||||
--font-size-s: 0.8rem;
|
||||
--font-size-m: 1rem;
|
||||
--font-size-l: 1.2rem;
|
||||
--font-size-xl: 2.5rem;
|
||||
--font-size-xxl: 3rem;
|
||||
|
||||
--color-neutral: #222;
|
||||
--color-neutral-accent: #333;
|
||||
--color-main: #eee;
|
||||
|
||||
--padding-main: 24px 48px;
|
||||
|
||||
--content-max-width: 1600px;
|
||||
}
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
color: var(--color-main);
|
||||
background-color: var(--color-neutral);
|
||||
}
|
||||
code {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono,
|
||||
DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 800;
|
||||
font-size: var(--font-size-xxl);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 600;
|
||||
font-size: var(--font-size-l);
|
||||
}
|
||||
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 500;
|
||||
font-size: var(--font-size-m);
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: var(--font-main);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
p,
|
||||
li,
|
||||
button,
|
||||
a {
|
||||
font-family: var(--font-main);
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #563795;
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 30px;
|
||||
transition: filter 0.1s linear;
|
||||
font-weight: 600;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.default-section {
|
||||
max-width: var(--content-max-width);
|
||||
padding: var(--padding-main);
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
30
web/src/layouts/RootLayout.astro
Normal file
30
web/src/layouts/RootLayout.astro
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
import Footer from "@/components/Layout/Footer.astro";
|
||||
import "@/styles/global.css";
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="Astro description" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<script
|
||||
defer
|
||||
data-domain="creator.tempblade.com"
|
||||
src="https://analytics.unom.io/js/plausible.js"></script>
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
6
web/src/lib/utils.ts
Normal file
6
web/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import clsx, { type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -1,78 +1,21 @@
|
||||
---
|
||||
import Layout from "layouts/Layout.astro";
|
||||
import SectionHighlights from "sections/Start/Highlights.astro";
|
||||
import RootLayout from "@/layouts/RootLayout.astro";
|
||||
import Section from "@/components/Section.astro";
|
||||
import Landing from "@/sections/Landing.astro";
|
||||
import Introduction from "@/sections/Introduction.astro";
|
||||
import Features from "@/sections/Features.astro";
|
||||
---
|
||||
|
||||
<Layout title="Free Intro Maker">
|
||||
<main>
|
||||
<section class="default-section" id="header">
|
||||
<div class="heading-container">
|
||||
<h1 class="my-4 font-bold">
|
||||
Motion Graphics made easy, <br /> for free!
|
||||
</h1>
|
||||
<p>
|
||||
Thats right! Create an intro, outro or other motion graphic elements
|
||||
right in your browser for free. How is this possible? Through the
|
||||
power of open source software!
|
||||
</p>
|
||||
</div>
|
||||
<div class="illustration-container">
|
||||
<svg viewBox="0 0 1000 1000">
|
||||
<circle cx="500" cy="500" r="300"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
<SectionHighlights />
|
||||
</main>
|
||||
<script>
|
||||
import { animate, stagger } from "motion";
|
||||
import { ease } from "@unom/style";
|
||||
|
||||
const headingContainer = document.querySelectorAll(
|
||||
"#header .heading-container"
|
||||
)[0];
|
||||
|
||||
console.log(headingContainer);
|
||||
|
||||
animate(
|
||||
headingContainer.children as any,
|
||||
{ y: [50, 0], opacity: [0, 1] },
|
||||
{ ...ease.quart(0.6).out, delay: stagger(0.1) }
|
||||
);
|
||||
window.sessionStorage.setItem("did_animation_run", "1");
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#header {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
& .heading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
& .illustration-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
& svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@screen 2xl {
|
||||
#header {
|
||||
flex-direction: row;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</Layout>
|
||||
<RootLayout title="tempblade - Creator">
|
||||
<main>
|
||||
<Section padding={false}>
|
||||
<Landing />
|
||||
</Section>
|
||||
<Section>
|
||||
<Introduction />
|
||||
</Section>
|
||||
<Section padding={false}>
|
||||
<Features />
|
||||
</Section>
|
||||
</main>
|
||||
</RootLayout>
|
||||
|
||||
27
web/src/pages/legal/imprint.astro
Normal file
27
web/src/pages/legal/imprint.astro
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
import Heading from "@/components/Heading.astro";
|
||||
import Section from "@/components/Section.astro";
|
||||
import RootLayout from "@/layouts/RootLayout.astro";
|
||||
import { marked } from "marked";
|
||||
const response = await fetch("https://api.enrico.buehler.earth/api/imprint", {
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset='utf-8'",
|
||||
},
|
||||
});
|
||||
|
||||
const json = await response.json();
|
||||
const markdownContent = json.data.attributes.content;
|
||||
|
||||
console.log(markdownContent);
|
||||
|
||||
const content = marked.parse(markdownContent);
|
||||
---
|
||||
|
||||
<RootLayout title="Imprint">
|
||||
<Section>
|
||||
<Heading main>Imprint</Heading>
|
||||
</Section>
|
||||
<Section>
|
||||
<article set:html={content} />
|
||||
</Section>
|
||||
</RootLayout>
|
||||
74
web/src/sections/Features.astro
Normal file
74
web/src/sections/Features.astro
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
import CardFeature from "@/components/Cards/CardFeature.astro";
|
||||
import Heading from "@/components/Heading.astro";
|
||||
type Feature = {
|
||||
title: string;
|
||||
iconName: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
const features: Array<Feature> = [
|
||||
{
|
||||
title: "Fast",
|
||||
iconName: "Fast",
|
||||
description:
|
||||
"Thanks to rust with multithreading and skia we're really fast!",
|
||||
},
|
||||
{
|
||||
title: "Extensible",
|
||||
iconName: "Extensible",
|
||||
description:
|
||||
"Modular structured and thanks to our dual language approach you even decide in which language you want to extend!",
|
||||
},
|
||||
{
|
||||
title: "Community driven & Free",
|
||||
iconName: "Open",
|
||||
description:
|
||||
"The project is MIT licensed and we're open to new ideas for further development. Also since this product is not profit driven we won't screw you over!",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<div>
|
||||
<div class="p-main py-0">
|
||||
<Heading>Core Features & Values</Heading>
|
||||
</div>
|
||||
<div
|
||||
id="features-cards-container"
|
||||
class="flex flex-row flex-nowrap overflow-x-auto snap-x snap-mandatory gap-card overflow-y-clip p-main snap-center"
|
||||
>
|
||||
{
|
||||
features.map((feature) => (
|
||||
<CardFeature
|
||||
iconName={feature.iconName}
|
||||
title={feature.title}
|
||||
description={feature.description}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { animate, inView, stagger } from "motion";
|
||||
import { ease } from "@unom/style";
|
||||
|
||||
const cardsContainer = document.getElementById("features-cards-container");
|
||||
|
||||
const cards = document.querySelectorAll("#features-cards-container .card");
|
||||
|
||||
if (cardsContainer) {
|
||||
inView(cardsContainer, () => {
|
||||
animate(
|
||||
cards,
|
||||
{
|
||||
scale: [0.8, 1],
|
||||
opacity: [0, 1],
|
||||
},
|
||||
{ ...ease.quint(0.8).out, delay: stagger(0.1) }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(cardsContainer);
|
||||
</script>
|
||||
30
web/src/sections/Introduction.astro
Normal file
30
web/src/sections/Introduction.astro
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
import Heading from "@/components/Heading.astro";
|
||||
---
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<Heading>
|
||||
The Next Generation Motion Design Tool - Free & Open Source
|
||||
</Heading>
|
||||
<p>
|
||||
We believe tools for expressing yourself should be accessible to
|
||||
everybody. Not constrained to proprietary operating systems or monthly
|
||||
subscriptions. You should own the tools you work with.
|
||||
</p>
|
||||
<p>
|
||||
tempblade Creator aims to become a viable alternative to current motion
|
||||
design tools, and even exceed them in certain aspects like
|
||||
extensibility. This is only possible due to our open source approach.
|
||||
Currently we are in an early alpha stage, and the program as it is right
|
||||
now has to be seen as a proof of concept rather then a finished product.
|
||||
</p>
|
||||
<p>
|
||||
You're a developer, like our idea and want to help? Join our discord or
|
||||
check out the repository!
|
||||
<a href="https://discord.gg/map44Dt6sK">Our Discord</a>
|
||||
</p>
|
||||
<p>
|
||||
You're not a developer but still want to support the work? I've got a
|
||||
patreon!
|
||||
</p>
|
||||
</div>
|
||||
48
web/src/sections/Landing.astro
Normal file
48
web/src/sections/Landing.astro
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
import Button from "@/components/Button.astro";
|
||||
---
|
||||
|
||||
<div class="h-[80vh] relative w-full overflow-x-hidden">
|
||||
<aside
|
||||
style="filter:blur(70px);"
|
||||
class="z-0 absolute top-0 flex items-center w-full h-full justify-center object-center object-contain"
|
||||
id="landing-bg-container"
|
||||
>
|
||||
<canvas width="1920" height="1080" class="w-[800px]" id="landing-bg"
|
||||
></canvas>
|
||||
</aside>
|
||||
<div
|
||||
class="z-10 relative w-full h-full flex items-center flex-col justify-center"
|
||||
>
|
||||
<div class="flex flex-col justify-center items-center gap-2">
|
||||
<h1 class="text-5xl dark:text-neutral text-center font-black">
|
||||
tempblade Creator
|
||||
</h1>
|
||||
<h2 class="text-center dark:text-neutral text-xl font-normal">
|
||||
Rust Based Open Source Motion Design Editor & Toolkit
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { animate, inView } from "motion";
|
||||
import { ease } from "@unom/style";
|
||||
|
||||
const landingBgContainer = document.getElementById("landing-bg");
|
||||
|
||||
if (landingBgContainer) {
|
||||
inView(landingBgContainer, () => {
|
||||
animate(
|
||||
landingBgContainer,
|
||||
{
|
||||
scale: [0.7, 1],
|
||||
opacity: [0, 1],
|
||||
},
|
||||
{ ...ease.quint(2).out }
|
||||
);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="./bg.ts"></script>
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
import HighlightCard from "components/Cards/Highlight.astro";
|
||||
import HighlightIcon from "components/Icons/Highlights/HighlightsIcon.astro";
|
||||
---
|
||||
|
||||
<section class="default-section">
|
||||
<HighlightCard
|
||||
title="Local"
|
||||
description="Unleash the power of any machine rocking a modern browser"
|
||||
>
|
||||
<HighlightIcon slot="icon" name="local" />
|
||||
</HighlightCard>
|
||||
<HighlightCard
|
||||
title="Resolution"
|
||||
description="Experience high fidelity motion graphics like never before"
|
||||
>
|
||||
<HighlightIcon slot="icon" name="resolution" />
|
||||
</HighlightCard>
|
||||
<HighlightCard
|
||||
title="Smooth"
|
||||
description="Our Animations range from 24-60FPS"
|
||||
>
|
||||
<HighlightIcon slot="icon" name="smooth" />
|
||||
</HighlightCard>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
@screen l {
|
||||
section {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
web/src/sections/bg-voronoi.ts
Normal file
95
web/src/sections/bg-voronoi.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface VoronoiCell {
|
||||
site: Point;
|
||||
vertices: Point[];
|
||||
}
|
||||
|
||||
function generateVoronoiPattern(
|
||||
canvas: HTMLCanvasElement,
|
||||
points: Array<Point>
|
||||
) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
// Draw Voronoi regions
|
||||
for (let x = 0; x < canvas.width; x++) {
|
||||
for (let y = 0; y < canvas.height; y++) {
|
||||
let closestPointIndex = 0;
|
||||
let closestDistance = distance(x, y, points[0].x, points[0].y);
|
||||
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const dist = distance(x, y, points[i].x, points[i].y);
|
||||
if (dist < closestDistance) {
|
||||
closestDistance = dist;
|
||||
closestPointIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
const { x: px, y: py, color } = points[closestPointIndex];
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x, y, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function distance(x1: number, y1: number, x2: number, y2: number) {
|
||||
const dx = x2 - x1;
|
||||
const dy = y2 - y1;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
// Get canvas element and generate Voronoi pattern
|
||||
const canvas = document.getElementById("landing-bg") as HTMLCanvasElement;
|
||||
|
||||
|
||||
const initialPoints: Array<Point> = [{
|
||||
x: 200,
|
||||
y: 200,
|
||||
color: "#8AFFAD",
|
||||
},
|
||||
{
|
||||
x: 800,
|
||||
y: 500,
|
||||
color: "#326CCC",
|
||||
},
|
||||
{
|
||||
x: 1100,
|
||||
y: 300,
|
||||
color: "#95B2F5",
|
||||
},
|
||||
{
|
||||
x: 1200,
|
||||
y: 600,
|
||||
color: "#32C3E3",
|
||||
},
|
||||
{
|
||||
x: 300,
|
||||
y: 900,
|
||||
color: "purple",
|
||||
},]
|
||||
|
||||
|
||||
const draw = (time: number) => {
|
||||
const radius = 200;
|
||||
|
||||
const angle = time % 360;
|
||||
|
||||
const radian = angle * (Math.PI / 180);
|
||||
|
||||
const x = Math.cos(radian) * radius;
|
||||
const y = Math.sin(radian) * radius;
|
||||
|
||||
const points = initialPoints.map((p) => ({ ...p, x: p.x + x, y: p.y + y }));
|
||||
|
||||
generateVoronoiPattern(canvas, points);
|
||||
|
||||
//requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
requestAnimationFrame(draw);
|
||||
126
web/src/sections/bg.ts
Normal file
126
web/src/sections/bg.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
color: string;
|
||||
radius: number;
|
||||
}
|
||||
|
||||
const canvas = document.getElementById("landing-bg") as HTMLCanvasElement;
|
||||
|
||||
const initialPoints: Array<Point> = [{
|
||||
x: 500,
|
||||
y: 400,
|
||||
color: "purple",
|
||||
radius: Math.random() * 200
|
||||
},
|
||||
{
|
||||
x: 900,
|
||||
y: 500,
|
||||
color: "#326CCC",
|
||||
radius: Math.random() * 200
|
||||
},
|
||||
{
|
||||
x: 1600,
|
||||
y: 400,
|
||||
color: "#95B2F5",
|
||||
radius: Math.random() * 200
|
||||
},
|
||||
{
|
||||
x: 1200,
|
||||
y: 600,
|
||||
color: "#32C3E3",
|
||||
radius: Math.random() * 200
|
||||
|
||||
},
|
||||
{
|
||||
x: 600,
|
||||
y: 500,
|
||||
color: "#8AFFAD",
|
||||
radius: Math.random() * 200
|
||||
|
||||
},
|
||||
{
|
||||
x: 900,
|
||||
y: 300,
|
||||
color: "purple",
|
||||
radius: Math.random() * 200
|
||||
|
||||
},
|
||||
]
|
||||
|
||||
export class GradientBackground {
|
||||
context: CanvasRenderingContext2D;
|
||||
|
||||
constructor(context: CanvasRenderingContext2D) {
|
||||
this.context = context;
|
||||
this.draw = this.draw.bind(this);
|
||||
|
||||
}
|
||||
|
||||
draw(time: number) {
|
||||
const { width, height } = this.context.canvas;
|
||||
|
||||
this.context.clearRect(0, 0, width, height * 2);
|
||||
|
||||
|
||||
this.context.save();
|
||||
|
||||
this.context.translate(width * 0.5, height * 0.5);
|
||||
|
||||
|
||||
const angle = (time * 0.01) % 360;
|
||||
const radian = angle * (Math.PI / 180);
|
||||
|
||||
this.context.rotate(radian);
|
||||
|
||||
this.context.translate(width * -0.5, height * -0.5);
|
||||
|
||||
|
||||
initialPoints.forEach((point, index) => {
|
||||
const { color, radius } = point;
|
||||
|
||||
|
||||
// Calculate the position/angle on a circle by the time
|
||||
const angle = time * 0.05 + index * 10 % 360;
|
||||
|
||||
// Convert the angle to radian
|
||||
const radian = angle * (Math.PI / 180);
|
||||
|
||||
// Calculate the offset based on the radius
|
||||
const offsetX = Math.cos(radian) * radius;
|
||||
const offsetY = Math.sin(radian) * radius;
|
||||
|
||||
// Calculate the position based on offset and initial position
|
||||
const x = offsetX + point.x;
|
||||
const y = offsetY + point.y;
|
||||
|
||||
// Create the gradient
|
||||
const gradient = this.context.createRadialGradient(x, y, 0, x, y, 500);
|
||||
|
||||
gradient.addColorStop(0, color);
|
||||
gradient.addColorStop(1, "rgba(0,0,0,0)");
|
||||
|
||||
this.context.fillStyle = gradient;
|
||||
this.context.globalCompositeOperation = "lighten";
|
||||
|
||||
|
||||
// Draw a rect with the gradient
|
||||
this.context.fillRect(0, 0, width, height);
|
||||
|
||||
|
||||
})
|
||||
|
||||
this.context.restore();
|
||||
|
||||
return requestAnimationFrame(this.draw);
|
||||
}
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (ctx) {
|
||||
const bg = new GradientBackground(ctx);
|
||||
|
||||
requestAnimationFrame(bg.draw);
|
||||
}
|
||||
|
||||
5
web/src/styles/breakpoints.css
Normal file
5
web/src/styles/breakpoints.css
Normal file
@@ -0,0 +1,5 @@
|
||||
@custom-media --sm-viewport screen and (min-width: 640px);
|
||||
@custom-media --md-viewport screen and (min-width: 768px);
|
||||
@custom-media --lg-viewport screen and (min-width: 1024px);
|
||||
@custom-media --xl-viewport screen and (min-width: 1280px);
|
||||
@custom-media --2xl-viewport screen and (min-width: 1536px);
|
||||
104
web/src/styles/global.css
Normal file
104
web/src/styles/global.css
Normal file
@@ -0,0 +1,104 @@
|
||||
@import "./breakpoints.css";
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--color-main: 0 0% 0%;
|
||||
--color-main: 0 0% 10%;
|
||||
--color-primary: 0 0% 0%;
|
||||
--color-neutral: 0 0% 100%;
|
||||
--color-neutral-accent: 0 0% 93%;
|
||||
--color-highlight: 264 100% 50%;
|
||||
--color-success: 132 100% 78%;
|
||||
--color-error: 335 100% 62%;
|
||||
|
||||
--padding-main: 25px 25px;
|
||||
--padding-card: 1.25rem 1.25rem;
|
||||
|
||||
--spacing-main: 20px;
|
||||
|
||||
--gap-card: 1rem;
|
||||
|
||||
--max-width-section: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-main: 0 0% 100%;
|
||||
--color-secondary: 0 0% 80%;
|
||||
--color-primary: 280 90% 60%;
|
||||
--color-neutral: 240 100% 4%;
|
||||
--color-neutral-accent: 250 70% 6%;
|
||||
--color-highlight: 336 100% 60%;
|
||||
--color-success: 132 100% 78%;
|
||||
--color-error: 335 100% 62%;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
@apply bg-neutral text-main;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
code {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-main;
|
||||
}
|
||||
|
||||
@media (--lg-viewport) {
|
||||
:root {
|
||||
--max-width-section: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (--xl-viewport) {
|
||||
:root {
|
||||
--max-width-section: 1550px;
|
||||
|
||||
--padding-main: 45px 100px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-3xl font-bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-2xl font-semibold;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-xl font-medium;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[astro-icon] {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
article {
|
||||
|
||||
& h1, h2, h3, h4, h5 {
|
||||
@apply mt-6;
|
||||
}
|
||||
|
||||
& h1, h2, h3, h4, h5, p, ol, ul {
|
||||
@apply mb-4;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
@apply max-w-[600px] whitespace-pre-line text-base text-secondary;
|
||||
line-height: 1.5;
|
||||
}
|
||||
59
web/tailwind.config.cjs
Normal file
59
web/tailwind.config.cjs
Normal file
@@ -0,0 +1,59 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
||||
theme: {
|
||||
extend: {
|
||||
screens: { "2xl": "1400px", "3xl": "1850px" },
|
||||
borderRadius: {
|
||||
card: "0.5rem",
|
||||
md: "0.5rem",
|
||||
},
|
||||
colors: {
|
||||
main: "hsl(var(--color-main))",
|
||||
primary: "hsl(var(--color-primary))",
|
||||
secondary: "hsl(var(--color-secondary))",
|
||||
neutral: "hsl(var(--color-neutral))",
|
||||
"neutral-accent": "hsl(var(--color-neutral-accent))",
|
||||
success: "hsl(var(--color-success))",
|
||||
error: "hsl(var(--color-error))",
|
||||
},
|
||||
transitionTimingFunction: {
|
||||
"in-sine": "cubic-bezier(0.47, 0, 0.745, 0.715)",
|
||||
"out-sine": "cubic-bezier(0.39, 0.575, 0.565, 1)",
|
||||
"in-out-sine": "cubic-bezier(0.445, 0.05, 0.55, 0.95)",
|
||||
"in-quad": "cubic-bezier(0.55, 0.085, 0.68, 0.53)",
|
||||
"out-quad": "cubic-bezier(0.25, 0.46, 0.45, 0.94)",
|
||||
"in-out-quad": "cubic-bezier(0.455, 0.03, 0.515, 0.955)",
|
||||
"in-cubic": "cubic-bezier(0.55, 0.055, 0.675, 0.19)",
|
||||
"out-cubic": "cubic-bezier(0.215, 0.61, 0.355, 1)",
|
||||
"in-out-cubic": "cubic-bezier(0.645, 0.045, 0.355, 1)",
|
||||
"in-quart": "cubic-bezier(0.895, 0.03, 0.685, 0.22)",
|
||||
"out-quart": "cubic-bezier(0.165, 0.84, 0.44, 1)",
|
||||
"in-out-quart": "cubic-bezier(0.77, 0, 0.175, 1)",
|
||||
"in-quint": "cubic-bezier(0.755, 0.05, 0.855, 0.06)",
|
||||
"out-quint": "cubic-bezier(0.23, 1, 0.32, 1)",
|
||||
"in-out-quint": "cubic-bezier(0.86, 0, 0.07, 1)",
|
||||
"in-expo": "cubic-bezier(0.95, 0.05, 0.795, 0.035)",
|
||||
"out-expo": "cubic-bezier(0.19, 1, 0.22, 1)",
|
||||
"in-out-expo": "cubic-bezier(1, 0, 0, 1)",
|
||||
"in-circ": "cubic-bezier(0.6, 0.04, 0.98, 0.335)",
|
||||
"out-circ": "cubic-bezier(0.075, 0.82, 0.165, 1)",
|
||||
"in-out-circ": "cubic-bezier(0.785, 0.135, 0.15, 0.86)",
|
||||
},
|
||||
gap: {
|
||||
main: "var(--spacing-main)",
|
||||
card: "var(--gap-card)",
|
||||
},
|
||||
padding: {
|
||||
main: "var(--padding-main)",
|
||||
card: "var(--padding-card)",
|
||||
},
|
||||
|
||||
maxWidth: {
|
||||
section: "var(--max-width-section)",
|
||||
form: "var(--max-width-form)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": "src",
|
||||
"jsxImportSource": "react"
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { defineConfig } from "unocss";
|
||||
import presetMini from "@unocss/preset-mini";
|
||||
|
||||
export default defineConfig({
|
||||
presets: [presetMini()],
|
||||
theme: {
|
||||
breakpoints: {
|
||||
s: "576px",
|
||||
m: "768px",
|
||||
l: "992px",
|
||||
xl: "1200px",
|
||||
"2xl": "1400px",
|
||||
"3xl": "1600px",
|
||||
},
|
||||
},
|
||||
});
|
||||
2761
web/yarn.lock
2761
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user