30 Commits

Author SHA1 Message Date
a8943fc924 update colors
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-30 12:53:10 +02:00
1d9c38e94a remove unneeded import
All checks were successful
continuous-integration/drone/push Build is passing
add tempblade logo
improve styling
improve animations
2023-06-30 12:08:03 +02:00
fa3f7a8403 remove tauri rust src and replace with lib
All checks were successful
continuous-integration/drone/push Build is passing
remove empty files
2023-06-27 16:46:54 +02:00
1d9508f49b remove test lib
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-27 15:07:38 +02:00
6d2ffe6902 move rust code to own library for targeting
All checks were successful
continuous-integration/drone/push Build is passing
multiple environments
like wasm, tauri and more
put non wasm compatible features like rayon multithreading
behind feature flags
2023-06-27 15:05:46 +02:00
c352ba7fd3 add private package secret to tauri build
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-27 00:36:39 +02:00
95087124b5 fix private package installation
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-27 00:33:35 +02:00
1d2f617a4e add github actions
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-27 00:06:22 +02:00
7fc923244e change wording
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-26 23:45:05 +02:00
f237d73016 add graph/visualization for interpolated keyframes
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-26 23:41:07 +02:00
cae187b939 improve bright mode coloring
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-26 10:29:06 +02:00
6f807b4df5 wording/grammar changes
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-26 01:38:38 +02:00
ee6e250277 fix p styling
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-26 01:35:37 +02:00
0dfa43c4ed animate cards
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-26 01:31:02 +02:00
b228f3cd8d update footer
All checks were successful
continuous-integration/drone/push Build is passing
improve landing bg
2023-06-26 01:14:35 +02:00
d78fe7b020 remove non navigateable footer links
All checks were successful
continuous-integration/drone/push Build is passing
add github to footer
add imprint
add marked
2023-06-26 01:02:57 +02:00
d8fedfb379 removed privacy link from footer since theres
All checks were successful
continuous-integration/drone/push Build is passing
no data processing
2023-06-26 00:33:51 +02:00
233442b099 improve mobile layout
All checks were successful
continuous-integration/drone/push Build is passing
update colors
remove landing button
2023-06-26 00:28:46 +02:00
0c7eedaee8 fix drone deploy
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-25 22:01:29 +02:00
7385b7ba41 add ci
All checks were successful
continuous-integration/drone Build is passing
add vercel deploy for web
2023-06-25 21:58:16 +02:00
74f7274a7e add icons
improve styling
improve landing bg
add astro-icon dep
2023-06-25 21:27:37 +02:00
ab7464eb83 Add discord link 2023-06-25 14:48:50 +02:00
512d102bb2 Remove unneeded import 2023-06-25 14:48:44 +02:00
82e0ee9280 add analytics 2023-06-25 14:48:33 +02:00
ba6a8c1308 Update messaging 2023-06-25 14:37:00 +02:00
7d70ad51e5 Remove section for testing design system 2023-06-25 14:36:47 +02:00
bbbae5c8a6 update readme 2023-06-25 13:33:42 +02:00
518b819fe8 update readme
recreate web
- now using tailwind instead of unocss
- created theme/design system
- created first components
- add first sections/content
2023-06-25 13:31:24 +02:00
5791b61a48 add app readme
lots of styling improvements
fix dark/bright mode
2023-06-21 13:10:01 +02:00
e8b6fcdbba extend readme 2023-06-18 18:01:19 +02:00
100 changed files with 7627 additions and 2551 deletions

18
.drone.yml Normal file
View 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
View 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
View 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 }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,32 @@
// 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");

View File

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

View File

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

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

View File

@@ -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) => {

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
import { Timeline } from "primitives/Timeline";
// TODO: publish package maybe provide wrapper etc.
import * as creatorWasm from "../../../lib/creator_rs/pkg";
import { z } from "zod";
import { useTimelineStore } from "stores/timeline.store";
import { useEntitiesStore } from "stores/entities.store";
import { useRenderStateStore } from "stores/render-state.store";
export class RenderService {
render() {
}
calculate() {
const timelineStore = useTimelineStore.getState();
const entitiesStore = useEntitiesStore.getState();
const renderStateStore = useRenderStateStore.getState();
let timeline: z.input<typeof Timeline> = {
...timelineStore,
entities: entitiesStore.entities,
render_state: renderStateStore.renderState
}
timeline = Timeline.parse(timeline);
const renderedEntities = creatorWasm.calculate_timeline_from_json_at_curr_frame(JSON.stringify(timeline));
console.log(renderedEntities);
}
}

View File

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

View File

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

View File

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

@@ -0,0 +1 @@
target

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
View 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"]

View File

@@ -4,7 +4,7 @@ use crate::animation::{
primitives::{
paint::Paint,
transform::{AnimatedTransform, Transform},
values::{AnimatedFloatVec2, AnimatedValue},
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
},
timeline::Timeline,
};

View File

@@ -4,7 +4,7 @@ use crate::animation::{
primitives::{
paint::Paint,
transform::{AnimatedTransform, Transform},
values::{AnimatedFloatVec2, AnimatedValue},
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
},
timeline::Timeline,
};

View File

@@ -3,7 +3,7 @@ use crate::animation::{
primitives::{
paint::TextPaint,
transform::{AnimatedTransform, Transform},
values::{AnimatedFloatVec2, AnimatedValue},
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
},
timeline::Timeline,
};

View File

@@ -2,7 +2,7 @@ use crate::animation::{
primitives::{
paint::TextPaint,
transform::{AnimatedTransform, Transform},
values::{AnimatedFloatVec2, AnimatedValue},
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
},
timeline::Timeline,
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
pub mod animated_values;
pub mod values;

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
pub mod fonts;

View File

@@ -0,0 +1,3 @@
pub mod animation;
#[cfg(feature = "fonts")]
pub mod fonts;

1
web/.vercel/project.json Normal file
View File

@@ -0,0 +1 @@
{"orgId":"c0DV5ATsGbVZoMVVr9msnlC7","projectId":"prj_6wyilH72l9zcNlIfA9NUAJ0xON0v"}

View File

@@ -1,3 +1,8 @@
{
"typescript.inlayHints.parameterNames.enabled": "all"
"tailwindCSS.experimental.classRegex": [
[
"cva\\(([^)]*)\\)",
"[\"'`]([^\"'`]*).*?[\"'`]"
]
]
}

View File

@@ -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
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![basics](https://user-images.githubusercontent.com/4677417/186188965-73453154-fdec-4d6b-9c34-cb35c248ae5b.png)
## 🚀 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
View 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,
}),
],
});

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
---
import type { HTMLAttributes } from "astro/types";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
export 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",
primary:
"bg-primary dark:bg-primary/50 text-neutral dark:text-main hocus:bg-primary/80 dark:hocus:bg-primary/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>

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,48 +1,18 @@
---
import Logo from "components/Logo.astro";
import Button, { button } from "../Button.astro";
import TempbladeLogo from "components/Logo/tempblade.astro";
---
<header id="main-header">
<div class="inner-container">
<Logo />
<button id="get-started-button" class="button">Get Started</button>
</div>
<header class="fixed top-0 w-full h-16 z-50">
<div
class="flex flex-row justify-between h-full w-full max-w-section m-auto p-main"
>
<div class="w-16 h-8 flex">
<TempbladeLogo />
</div>
<a
class={button({ variant: "primary", size: "default" })}
href="https://github.com/tempblade/creator/releases">Download</a
>
</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>

View File

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

View File

@@ -0,0 +1,43 @@
<svg
width="100%"
height="100%"
viewBox="0 0 372 306"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-miterlimit:1.5;"
>
<g>
<g id="Tb">
<path
id="T"
d="M250.532,6.303L250.532,56.321L157.88,56.321L157.88,233.339L100.536,233.339L100.536,56.321L6.186,56.321L6.186,6.303L250.532,6.303Z"
style="fill:url(#_Linear2);fill-rule:nonzero;"></path>
<path
id="b"
class="stroke-main"
d="M133.511,6.303L133.511,41.339L172.011,41.339L172.171,248.131L204.414,248.131L204.414,280.374L317.268,280.374L317.268,248.131L349.511,248.131L349.511,151.4L317.268,151.4L317.268,119.156L235.046,119.156L235.046,151.4"
style="fill:none;stroke-width:30.45px;"></path>
</g>
</g>
<defs>
<linearGradient
id="_Linear2"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(244.346,0,0,227.036,6.18639,119.821)"
><stop
offset="0"
class="text-main"
style="stop-color:currentColor;stop-opacity:1"></stop>
<stop
offset="1"
class="text-main/50"
style="stop-color:currentColor;stop-opacity:1"></stop>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

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

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

View File

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

View File

@@ -0,0 +1,32 @@
---
import Footer from "@/components/Layout/Footer.astro";
import Header from "@/components/Layout/Header.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>
<Header />
<slot />
<Footer />
</body>
</html>

6
web/src/lib/utils.ts Normal file
View 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));
}

View File

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

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

View File

@@ -0,0 +1,72 @@
---
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) }
);
});
}
</script>

View File

@@ -0,0 +1,53 @@
---
import Heading from "@/components/Heading.astro";
---
<div id="introduction-content-container" 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>
<script>
import { ease } from "@unom/style";
import { animate, inView, stagger } from "motion";
const contentContainer = document.getElementById(
"introduction-content-container"
);
const children = document.querySelectorAll(
"#introduction-content-container *"
);
if (contentContainer) {
inView(contentContainer, () => {
animate(
children,
{ y: [20, 0], opacity: [0, 1] },
{ ...ease.quint(0.8).out, delay: stagger(0.1) }
);
});
}
</script>

View File

@@ -0,0 +1,77 @@
---
import TempbladeLogo from "@/components/Logo/tempblade.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-[900px]" id="landing-bg"
></canvas>
</aside>
<div
class="z-10 relative w-full h-full flex items-center flex-col justify-center"
>
<div
id="landing-content-container"
class="flex flex-col justify-center items-center gap-2 p-main dark:text-neutral"
>
<!-- <div data-animate class="w-12 h-12">
<TempbladeLogo />
</div> -->
<h1
data-animate
class="text-6xl lg:text-8xl text-center font-black text-current"
>
Creator
</h1>
<h2 class="text-center text-xl font-normal max-w-xs">
<span class="inline-block" data-animate>
Rust Based Open Source</span
><br />
<span class="inline-block" data-animate>
Motion Design Editor & Toolkit</span
>
</h2>
</div>
</div>
</div>
<script>
import { animate, inView, stagger } from "motion";
import { ease } from "@unom/style";
const landingBgContainer = document.getElementById("landing-bg");
const children = document.querySelectorAll(
"#landing-content-container [data-animate]"
);
if (landingBgContainer) {
inView(landingBgContainer, () => {
animate(
landingBgContainer,
{
scale: [0.7, 1],
opacity: [0, 1],
},
{ ...ease.quint(2).out }
);
animate(
children,
{ y: [50, 0], opacity: [0, 1] },
{
opacity: {
easing: "linear",
},
...ease.quint(0.8).out,
delay: stagger(0.1),
}
);
});
}
</script>
<script src="./bg.ts"></script>

View File

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

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

View 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
View 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: 280 80% 50%;
--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;
}

65
web/tailwind.config.cjs Normal file
View File

@@ -0,0 +1,65 @@
const plugin = require("tailwindcss/plugin");
/** @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: [
plugin(function ({ addVariant }) {
addVariant("hocus", ["&:hover", "&:focus"]);
}),
],
};

View File

@@ -1,8 +1,11 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "react-jsx",
"baseUrl": "src",
"jsxImportSource": "react"
"baseUrl": "./src",
"paths": {
"@/*": [
"./*"
]
}
}
}

View File

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

File diff suppressed because it is too large Load Diff