Compare commits

...

3 Commits

Author SHA1 Message Date
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
56 changed files with 1862 additions and 2303 deletions

View File

@ -2,10 +2,14 @@
# tempblade Creator # tempblade Creator
tempblade creator is a motion design application, build with typescript, skia, rust using tauri. 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 possible to run completly in the browser thanks to wasm. The project is currently in an early alpha stage, and there may be larger design changes to the overall structuring
Its currently in an early alpha stage, and there may be larger design changes to the overall structuring
of the project. 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? ## How does it work?
Currently rust is used for things like keyframe interpolation and overall timeline calculation which then gets passed 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 - Fully typed
- Multithreaded timeline/keyframe interpolation calculation using rayon - Multithreaded timeline/keyframe interpolation calculation using rayon
- Runtime typesafety thanks to zod in typescript - 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 ### 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 - Integration with OpenFX inside rust
- Standalone rust rust rendering using vulkan and or metal by using rust-skia - 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) - 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) - 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)

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,6 +15,7 @@
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-menubar": "^1.0.2", "@radix-ui/react-menubar": "^1.0.2",
"@radix-ui/react-popover": "^1.0.6", "@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-select": "^1.2.2",
"@radix-ui/react-slider": "^1.1.1", "@radix-ui/react-slider": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-toggle-group": "^1.0.4",

View File

@ -17,7 +17,7 @@ tauri-build = { version = "1.3", features = [] }
uuid = { version = "1.3.3", features = ["v4", "fast-rng", "macro-diagnostics"] } uuid = { version = "1.3.3", features = ["v4", "fast-rng", "macro-diagnostics"] }
tauri = { version = "1.3", features = ["dialog-open", "dialog-save", "shell-open"] } tauri = { version = "1.3", features = ["dialog-open", "dialog-save", "shell-open"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0" serde_json = "1.0"
tint = "1.0.0" tint = "1.0.0"
simple-easing = "1.0.1" simple-easing = "1.0.1"

View File

@ -1,4 +1,4 @@
use std::cmp::Ordering; use std::{cmp::Ordering, sync::Arc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -12,10 +12,26 @@ use super::{
pub struct Keyframe { pub struct Keyframe {
pub value: f32, pub value: f32,
pub offset: f32, pub offset: f32,
pub id: String, pub id: Arc<str>,
pub interpolation: Option<InterpolationType>, 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)] #[derive(Debug, Clone)]
pub struct RenderedKeyframe { pub struct RenderedKeyframe {
pub absolute_frame: i32, pub absolute_frame: i32,

View File

@ -25,19 +25,19 @@ fn interpolates_the_input() {
let keyframes1 = Keyframes { let keyframes1 = Keyframes {
values: vec![ values: vec![
Keyframe { Keyframe {
id: "1".to_string(), id: "1".into(),
value: 0.0, value: 0.0,
offset: 0.0, offset: 0.0,
interpolation: None, interpolation: None,
}, },
Keyframe { Keyframe {
id: "2".to_string(), id: "2".into(),
value: 100.0, value: 100.0,
offset: 1.0, offset: 1.0,
interpolation: None, interpolation: None,
}, },
Keyframe { Keyframe {
id: "3".to_string(), id: "3".into(),
value: 300.0, value: 300.0,
offset: 3.0, offset: 3.0,
interpolation: None, interpolation: None,
@ -48,13 +48,13 @@ fn interpolates_the_input() {
let keyframes2 = Keyframes { let keyframes2 = Keyframes {
values: vec![ values: vec![
Keyframe { Keyframe {
id: "4".to_string(), id: "4".into(),
value: -100.0, value: -100.0,
offset: 0.0, offset: 0.0,
interpolation: None, interpolation: None,
}, },
Keyframe { Keyframe {
id: "5".to_string(), id: "5".into(),
value: 0.0, value: 0.0,
offset: 1.0, offset: 1.0,
interpolation: None, interpolation: None,
@ -150,19 +150,19 @@ fn gets_value_at_frame() {
let keyframes = Keyframes { let keyframes = Keyframes {
values: vec![ values: vec![
Keyframe { Keyframe {
id: "1".to_string(), id: "1".into(),
value: 0.0, value: 0.0,
offset: 0.0, offset: 0.0,
interpolation: None, interpolation: None,
}, },
Keyframe { Keyframe {
id: "2".to_string(), id: "2".into(),
value: 100.0, value: 100.0,
offset: 1.0, offset: 1.0,
interpolation: None, interpolation: None,
}, },
Keyframe { Keyframe {
id: "3".to_string(), id: "3".into(),
value: 300.0, value: 300.0,
offset: 3.0, offset: 3.0,
interpolation: None, interpolation: None,

View File

@ -29,7 +29,7 @@ impl AnimatedFloat {
AnimatedFloat { AnimatedFloat {
keyframes: Keyframes { keyframes: Keyframes {
values: vec![Keyframe { values: vec![Keyframe {
id: Uuid::new_v4().to_string(), id: Uuid::new_v4().to_string().into(),
value: val, value: val,
offset: 0.0, offset: 0.0,
interpolation: None, interpolation: None,

View File

@ -70,7 +70,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
keyframes: Keyframes { keyframes: Keyframes {
values: vec![ values: vec![
Keyframe { Keyframe {
id: "1".to_string(), id: "1".into(),
value: (size.0 * -1) as f32, value: (size.0 * -1) as f32,
offset: 0.0, offset: 0.0,
interpolation: Some(InterpolationType::EasingFunction( interpolation: Some(InterpolationType::EasingFunction(
@ -78,7 +78,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
)), )),
}, },
Keyframe { Keyframe {
id: "2".to_string(), id: "2".into(),
value: 0.0, value: 0.0,
offset: 5.0, offset: 5.0,
interpolation: None, interpolation: None,
@ -89,7 +89,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
AnimatedFloat { AnimatedFloat {
keyframes: Keyframes { keyframes: Keyframes {
values: vec![Keyframe { values: vec![Keyframe {
id: "3".to_string(), id: "3".into(),
value: 0.0, value: 0.0,
offset: 0.0, offset: 0.0,
interpolation: None, interpolation: None,
@ -103,7 +103,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
AnimatedFloat { AnimatedFloat {
keyframes: Keyframes { keyframes: Keyframes {
values: vec![Keyframe { values: vec![Keyframe {
id: "4".to_string(), id: "4".into(),
interpolation: None, interpolation: None,
value: size.0 as f32, value: size.0 as f32,
offset: 0.0, offset: 0.0,
@ -113,7 +113,7 @@ fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
AnimatedFloat { AnimatedFloat {
keyframes: Keyframes { keyframes: Keyframes {
values: vec![Keyframe { values: vec![Keyframe {
id: "5".to_string(), id: "5".into(),
value: size.1 as f32, value: size.1 as f32,
offset: 0.0, offset: 0.0,
interpolation: None, interpolation: None,
@ -159,7 +159,7 @@ pub fn test_timeline_entities_at_frame(
color: Color::new(0, 0, 0, 1.0), color: Color::new(0, 0, 0, 1.0),
width: 10.0, width: 10.0,
}), }),
font_name: "Arial".to_string(), font_name: "Arial".into(),
align: TextAlign::Center, align: TextAlign::Center,
size: 20.0, size: 20.0,
}; };
@ -168,7 +168,7 @@ pub fn test_timeline_entities_at_frame(
style: PaintStyle::Fill(FillStyle { style: PaintStyle::Fill(FillStyle {
color: Color::new(0, 0, 0, 1.0), color: Color::new(0, 0, 0, 1.0),
}), }),
font_name: "Arial".to_string(), font_name: "Arial".into(),
align: TextAlign::Center, align: TextAlign::Center,
size: 10.0, size: 10.0,
}; };
@ -198,7 +198,7 @@ pub fn test_timeline_entities_at_frame(
keyframes: Keyframes { keyframes: Keyframes {
values: vec![ values: vec![
Keyframe { Keyframe {
id: "1".to_string(), id: "1".into(),
value: 0.0, value: 0.0,
offset: 0.0, offset: 0.0,
interpolation: Some(InterpolationType::Spring( interpolation: Some(InterpolationType::Spring(
@ -210,7 +210,7 @@ pub fn test_timeline_entities_at_frame(
)), )),
}, },
Keyframe { Keyframe {
id: "2".to_string(), id: "2".into(),
value: (size.0 / 2) as f32, value: (size.0 / 2) as f32,
offset: 2.0, offset: 2.0,
interpolation: None, interpolation: None,
@ -221,7 +221,7 @@ pub fn test_timeline_entities_at_frame(
AnimatedFloat { AnimatedFloat {
keyframes: Keyframes { keyframes: Keyframes {
values: vec![Keyframe { values: vec![Keyframe {
id: "3".to_string(), id: "3".into(),
value: (size.1 / 2) as f32, value: (size.1 / 2) as f32,
offset: 0.0, offset: 0.0,
interpolation: None, interpolation: None,
@ -248,7 +248,7 @@ pub fn test_timeline_entities_at_frame(
keyframes: Keyframes { keyframes: Keyframes {
values: vec![ values: vec![
Keyframe { Keyframe {
id: "5".to_string(), id: "5".into(),
value: 0.0, value: 0.0,
offset: 0.0, offset: 0.0,
interpolation: Some(InterpolationType::Spring( interpolation: Some(InterpolationType::Spring(
@ -260,7 +260,7 @@ pub fn test_timeline_entities_at_frame(
)), )),
}, },
Keyframe { Keyframe {
id: "6".to_string(), id: "6".into(),
value: (size.0 / 2) as f32, value: (size.0 / 2) as f32,
offset: 2.0, offset: 2.0,
@ -272,7 +272,7 @@ pub fn test_timeline_entities_at_frame(
AnimatedFloat { AnimatedFloat {
keyframes: Keyframes { keyframes: Keyframes {
values: vec![Keyframe { values: vec![Keyframe {
id: "7".to_string(), id: "7".into(),
value: ((size.1 / 2) as f32) + 80.0, value: ((size.1 / 2) as f32) + 80.0,
offset: 0.0, offset: 0.0,
interpolation: None, interpolation: None,

View File

@ -2,10 +2,11 @@ import "./App.css";
import Timeline from "./components/Timeline"; import Timeline from "./components/Timeline";
import Canvas from "./components/Canvas"; import Canvas from "./components/Canvas";
import Properties, { PropertiesContainer } from "components/Properties"; import Properties, { PropertiesContainer } from "components/Properties";
import MenuBar from "components/MenuBar";
import ToolBar from "components/ToolBar"; import ToolBar from "components/ToolBar";
import useKeyControls from "hooks/useKeyControls"; import useKeyControls from "hooks/useKeyControls";
import { useFontsStore } from "stores/fonts.store"; import { useFontsStore } from "stores/fonts.store";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import ScrollBar from "components/ScrollArea";
export default function App() { export default function App() {
const fontsStoreDidInit = useFontsStore((store) => store.didInit); const fontsStoreDidInit = useFontsStore((store) => store.didInit);
@ -13,26 +14,27 @@ export default function App() {
useKeyControls(); useKeyControls();
return ( return (
<div className="bg-gray-950 h-full w-full flex flex-col overflow-hidden"> <div className="bg-neutral h-full w-full flex flex-col overflow-hidden">
<MenuBar /> {/* <MenuBar /> */}
<div className="flex flex-row flex-[1] overflow-hidden"> <div className="flex flex-row flex-[1] overflow-hidden">
<ToolBar /> <ToolBar />
{fontsStoreDidInit && ( {fontsStoreDidInit && (
<div className="flex flex-col pl-4 gap-4 pr-4 overflow-x-hidden overflow-y-auto"> <ScrollArea.Root className="w-full">
<div className="flex w-full gap-4 flex-col lg:flex-row justify-center items-center"> <ScrollArea.Viewport className="w-full h-full">
<Canvas /> <div className="flex w-full flex-col pl-4 gap-4 pr-4 overflow-x-hidden overflow-y-auto">
<PropertiesContainer> <div className="flex w-full gap-4 flex-col lg:flex-row justify-center items-center mt-4">
<Properties /> <Canvas />
</PropertiesContainer> <PropertiesContainer>
</div> <Properties />
<Timeline /> </PropertiesContainer>
</div> </div>
<Timeline />
</div>
</ScrollArea.Viewport>
<ScrollBar />
</ScrollArea.Root>
)} )}
</div> </div>
</div> </div>
); );
} }
{
/* */
}

View File

@ -17,7 +17,8 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( 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", "flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm",
"placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}
@ -38,7 +39,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( 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-popover-foreground shadow-md animate-in fade-in-80",
className className
)} )}
{...props} {...props}
@ -70,7 +71,8 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} ref={ref}
className={cn( 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-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...props} {...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 ( return (
<div> <div>
<fieldset>
<label htmlFor="staggered-text-letter-font">Font</label>
</fieldset>
<fieldset> <fieldset>
<label htmlFor="paint-style-type">PaintStyle</label> <label htmlFor="paint-style-type">PaintStyle</label>
<Select <Select
defaultValue={entity.style.type} defaultValue={entity.style.type}
onValueChange={(value) => { 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={{ whileTap={{
scale: 1.6, scale: 1.6,
transition: {
scale: {
type: "spring",
stiffness: 200,
damping: 10,
mass: 1,
},
},
}} }}
animate={{ animate={{
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 2, x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 2,
@ -97,8 +105,8 @@ const KeyframeIndicator: FC<{
> >
<motion.span <motion.span
data-selected={selected} data-selected={selected}
className="bg-gray-200 className="bg-secondary
data-[selected=true]:bg-indigo-600 data-[selected=true]:bg-secondary
h-full transition-colors" h-full transition-colors"
style={{ style={{
width: 10, width: 10,
@ -108,7 +116,7 @@ const KeyframeIndicator: FC<{
/> />
</motion.div> </motion.div>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-80 backdrop-blur-md bg-slate-700/50"> <PopoverContent className="w-80 backdrop-blur-md bg-neutral/50">
<KeyframePopover <KeyframePopover
onClose={() => deselectKeyframe()} onClose={() => deselectKeyframe()}
onUpdate={handleValueUpdate} onUpdate={handleValueUpdate}

View File

@ -21,11 +21,11 @@ const TimePicker: FC<TimePickerProps> = () => {
step={1} step={1}
aria-label="Current Frame" aria-label="Current Frame"
> >
<Slider.Track className="bg-blackA10 relative grow rounded-full h-[3px]"> <Slider.Track className="bg-neutral-accent relative grow rounded-full h-[3px]">
<Slider.Range className="absolute bg-white rounded-full h-full" /> <Slider.Range className="absolute bg-main rounded-full h-full" />
</Slider.Track> </Slider.Track>
<Slider.Thumb <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" aria-label="Volume"
/> />
</Slider.Root> </Slider.Root>

View File

@ -45,7 +45,7 @@ const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
dragListener={false} dragListener={false}
dragControls={controls} dragControls={controls}
onMouseDown={(e) => e.preventDefault()} 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 <motion.div
layout layout
@ -56,7 +56,7 @@ const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
onPointerDown={(e) => controls.start(e)} onPointerDown={(e) => controls.start(e)}
className={`h-full transition-all rounded-sm min-w-[200px] p-1 px-2 flex flex-col ${ 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" : "bg-neutral-accent"
}`} }`}
> >
<div className="flex flex-row"> <div className="flex flex-row">
@ -77,17 +77,18 @@ const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
? deselectEntity() ? deselectEntity()
: selectEntity(index) : selectEntity(index)
} }
className="text-white-800 select-none cursor-pointer" className="text-white-800 h-2 text-base leading-loose font-semibold select-none cursor-pointer"
> >
{name} {name}
</h3> </h3>
</div> </div>
</div> </div>
<div <div className="flex h-full w-full flex-row relative rounded-sm bg-neutral-accent/50 select-none shrink-0">
style={{ width: TIMELINE_SCALE * 10 }} <div
className="flex h-full flex-row relative bg-gray-900 select-none shrink-0" className="absolute top-0 h-full bg-neutral-accent"
> style={{ width: TIMELINE_SCALE * 10 }}
/>
{!isExpanded && {!isExpanded &&
flattenedKeyframes.map((keyframe, index) => ( flattenedKeyframes.map((keyframe, index) => (
<KeyframeIndicator <KeyframeIndicator
@ -132,10 +133,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/80 h-full top-0 absolute rounded-md select-none cursor-w-resize"
/> />
<motion.div <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/80 h-full top-0 absolute rounded-md select-none cursor-e-resize"
onMouseDown={(e) => e.preventDefault()} onMouseDown={(e) => e.preventDefault()}
drag="x" drag="x"
animate={{ animate={{
@ -194,7 +195,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> ></motion.div>
</div> </div>
</motion.div> </motion.div>

View File

@ -78,10 +78,10 @@ const TrackAnimatedProperty: FC<{
<motion.div <motion.div
transition={ease.quint(0.8).out} transition={ease.quint(0.8).out}
variants={{ enter: { y: 0, opacity: 1 }, from: { y: -10, opacity: 0 } }} 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"> <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> <ToggleGroup>
<ToggleGroupItem <ToggleGroupItem
onClick={() => onClick={() =>

View File

@ -6,6 +6,8 @@ import Timestamp from "./Timestamp";
import { PauseIcon, PlayIcon } from "@radix-ui/react-icons"; import { PauseIcon, PlayIcon } from "@radix-ui/react-icons";
import { useRenderStateStore } from "stores/render-state.store"; import { useRenderStateStore } from "stores/render-state.store";
import Track from "./Track"; import Track from "./Track";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import ScrollBar from "components/ScrollArea";
export type AnimationEntity = { export type AnimationEntity = {
offset: number; offset: number;
@ -37,27 +39,34 @@ const Timeline: FC<TimelineProps> = () => {
</div> </div>
<Timestamp /> <Timestamp />
</div> </div>
<div className="gap-1 w-full flex flex-col overflow-x-auto overflow-y-visible"> <ScrollArea.Root>
<div className="z-20 flex flex-row gap-2"> <ScrollArea.Viewport className="w-full h-full">
<div className="flex-shrink-0 min-w-[200px]" /> <div className="gap-1 w-full flex flex-col">
<TimePicker /> <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> </div>
<Reorder.Group </ScrollArea.Root>
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>
</div> </div>
); );
}; };

View File

@ -12,9 +12,9 @@ const ToggleGroupItem: FC<{
data-selected={selected} data-selected={selected}
asChild asChild
onClick={onClick} 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 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 first:rounded-l last:rounded-r focus:z-10 focus:shadow-[0_0_0_2px] focus:shadow-black
focus:outline-none transition-colors" focus:outline-none transition-colors"
value="left" value="left"
@ -29,7 +29,7 @@ const ToggleGroupItem: FC<{
const ToggleGroup: FC<{ children: ReactNode }> = ({ children }) => ( const ToggleGroup: FC<{ children: ReactNode }> = ({ children }) => (
<ToggleGroupComponents.Root <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" type="single"
defaultValue="center" defaultValue="center"
aria-label="Text alignment" aria-label="Text alignment"

View File

@ -25,9 +25,9 @@ const ToolBarButton: FC<{ children: ReactNode; onClick?: () => void }> = ({
onClick={onClick} onClick={onClick}
onMouseOver={() => !didHover && setDidHover(true)} onMouseOver={() => !didHover && setDidHover(true)}
asChild 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 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 transition-colors
focus:relative focus:shadow-[0_0_0_2px] focus:shadow-indigo" focus:relative focus:shadow-[0_0_0_2px] focus:shadow-indigo"
> >
@ -52,7 +52,7 @@ const ToolBar = () => {
return ( return (
<Toolbar.Root <Toolbar.Root
asChild 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" orientation="vertical"
> >
<motion.div <motion.div

View File

@ -17,32 +17,21 @@
@apply text-white; @apply text-white;
} }
h1,
h2,
h3,
h4,
h5,
h6,
a,
label {
@apply dark:text-white;
}
a { a {
@apply text-blue-600 underline; @apply text-blue-600 underline;
} }
select, select,
input { input {
@apply box-border bg-gray-900 shadow-indigo-100 hover:shadow-indigo-400 @apply box-border bg-transparent shadow-main/10 hover:shadow-primary/50
focus:shadow-indigo-600 selection:bg-indigo-400 selection:text-black focus:ring-primary focus:ring-2 focus:ring-offset-2
focus:shadow-primary selection:bg-secondary selection:text-black
outline-none px-3 py-2 rounded-md shadow-[0_0_0_1px]; outline-none px-3 py-2 rounded-md shadow-[0_0_0_1px];
} }
input { input {
@apply appearance-none items-center justify-center @apply appearance-none items-center justify-center
w-full text-base leading-none w-full text-base leading-none transition-all;
text-white transition-all;
} }
select { select {
@ -50,78 +39,39 @@
} }
:root { :root {
--background: 0 0% 100%; --color-main: 0 0% 0%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%; --color-secondary: 0 0% 10%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%; --color-neutral: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%; --color-neutral-accent: 0 0% 90%;
--card: 0 0% 100%; --color-highlight: 0 0% 0%;
--card-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%; --color-primary: 222.2 47.4% 11.2%;
--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;
} }
.dark { @media (prefers-color-scheme: dark) {
--background: 224 71% 4%; :root {
--foreground: 213 31% 91%; --color-main: 0 0% 100%;
--muted: 223 47% 11%; --color-primary: 260 80% 50%;
--muted-foreground: 215.4 16.3% 56.9%; --color-secondary: 260 50% 70%;
--popover: 224 71% 4%; --color-neutral: 250 30% 8%;
--popover-foreground: 215 20.2% 65.1%; --color-neutral-accent: 250 40% 12%;
--card: 224 71% 4%; --color-highlight: 250 20% 15%;
--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;
} }
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-highlight;
} }
body { body {
@apply bg-background text-foreground; @apply bg-neutral text-main;
font-feature-settings: "rlig" 1, "calt" 1; font-feature-settings: "rlig" 1, "calt" 1;
} }
} }

View File

@ -3,53 +3,14 @@
export default { export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: { theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: { extend: {
colors: { colors: {
border: "hsl(var(--border))", main: "hsl(var(--color-main))",
input: "hsl(var(--input))", secondary: "hsl(var(--color-secondary))",
ring: "hsl(var(--ring))", neutral: "hsl(var(--color-neutral))",
background: "hsl(var(--background))", "neutral-accent": "hsl(var(--color-neutral-accent))",
foreground: "hsl(var(--foreground))", highlight: "hsl(var(--color-highlight))",
primary: { primary: "hsl(var(--color-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)",
}, },
keyframes: { keyframes: {
"accordion-down": { "accordion-down": {

View File

@ -702,6 +702,22 @@
"@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-callback-ref" "1.0.1"
"@radix-ui/react-use-controllable-state" "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": "@radix-ui/react-select@^1.2.2":
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-1.2.2.tgz#caa981fa0d672cf3c1b2a5240135524e69b32181" resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-1.2.2.tgz#caa981fa0d672cf3c1b2a5240135524e69b32181"

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: All commands are run from the root of the project, from a terminal:
| Command | Action | | Command | Action |
| :--------------------- | :----------------------------------------------- | | :------------------------ | :----------------------------------------------- |
| `yarn dev` | Starts local dev server at `localhost:3000` | | `npm install` | Installs dependencies |
| `yarn build` | Build your production site to `./dist/` | | `npm run dev` | Starts local dev server at `localhost:3000` |
| `yarn preview` | Preview your build locally, before deploying | | `npm run build` | Build your production site to `./dist/` |
| `yarn astro ...` | Run CLI commands like `astro add`, `astro check` | | `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()]
});

View File

@ -1,5 +1,5 @@
{ {
"name": "creator", "name": "web",
"type": "module", "type": "module",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
@ -10,21 +10,17 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/react": "^2.1.1", "@astrojs/tailwind": "^3.1.3",
"@types/react": "^18.0.21",
"@types/react-dom": "^18.0.6",
"@unom/style": "^0.2.14", "@unom/style": "^0.2.14",
"astro": "^2.3.0", "astro": "^2.5.0",
"motion": "^10.15.5", "autoprefixer": "^10.4.14",
"react": "^18.0.0", "class-variance-authority": "^0.6.0",
"react-dom": "^18.0.0" "clsx": "^1.2.1",
}, "motion": "^10.16.2",
"devDependencies": { "postcss": "^8.4.24",
"@jasikpark/astro-svg-loader": "^0.1.0", "postcss-custom-media": "^9.1.5",
"@unocss/postcss": "^0.51.4", "postcss-import": "^15.1.0",
"@unocss/preset-mini": "^0.51.4", "tailwind-merge": "^1.13.2",
"@unocss/reset": "^0.51.4", "tailwindcss": "^3.0.24"
"postcss-preset-env": "^8.3.1",
"unocss": "^0.51.4"
} }
} }

View File

@ -1,10 +1,11 @@
module.exports = { const postcssImport = require("postcss-import");
plugins: { const nesting = require("tailwindcss/nesting");
"@unocss/postcss": {}, const tailwind = require("tailwindcss");
"postcss-preset-env": { const autoprefixer = require("autoprefixer");
features: { const customMedia = require("postcss-custom-media");
"nesting-rules": true,
}, const config = {
}, plugins: [postcssImport, nesting, tailwind, autoprefixer, customMedia],
},
}; };
module.exports = config;

View File

@ -0,0 +1,44 @@
---
import type { HTMLAttributes } from "astro/types";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const button = cva(
cn(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
"transition-colors focus-visible:outline-none",
"ring-main focus-visible:ring-2 focus-visible:ring-ring",
"focus-visible:ring-offset-2 disabled:opacity-50",
"disabled:pointer-events-none ring-offset-background"
),
{
variants: {
variant: {
default: "bg-main text-neutral hocus:bg-main/80",
destructive:
"bg-error text-destructive-foreground hocus:bg-destructive/90",
outline:
"border border-input hover:bg-accent hover:text-accent-foreground",
secondary: "bg-main/10 text-main hocus:bg-main/20",
ghost: "hocus:bg-accent hocus:text-accent-foreground",
link: "underline-offset-4 hocus:underline text-primary",
},
size: {
default: "h-10 text-base py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
},
},
}
);
export interface Props
extends HTMLAttributes<"button">,
VariantProps<typeof button> {}
const { variant = "default", size = "default", ...props } = Astro.props;
---
<button {...props} class={button({ variant, size })}>
<slot />
</button>

View File

@ -0,0 +1,31 @@
---
import type { HTMLAttributes } from "astro/types";
import { VariantProps, cva } from "class-variance-authority";
const card = cva(
"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: "p-card",
false: "p-0",
},
},
}
);
export interface Props
extends HTMLAttributes<"div">,
VariantProps<typeof card> {}
const { interactable = false, padding = true, ...props } = Astro.props;
---
<div {...props} class={card({ interactable, padding })}>
<slot />
</div>

View File

@ -0,0 +1,18 @@
---
import Card from "../Card.astro";
export interface Props {
title: string;
description: string;
}
const { title, description } = Astro.props;
---
<Card>
<h4>
{title}
</h4>
<p>
{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

@ -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,4 +1,6 @@
--- ---
import Section from "../Section.astro";
type NavigationItem = { type NavigationItem = {
path: string; path: string;
label: string; label: string;
@ -40,31 +42,21 @@ const tree: Array<NavigationGroup> = [
]; ];
--- ---
<footer class="py-4"> <footer class="bg-neutral-accent">
<div class="m-auto flex flex-row flex-wrap gap-12 inner-container"> <Section>
{ <div class="flex flex-row flex-wrap gap-12">
tree.map((group) => ( {
<div> tree.map((group) => (
<h3 class="mb-2">{group.title}</h3> <div>
<div class="flex flex-col"> <h3 class="mb-2">{group.title}</h3>
{group.items.map((item) => ( <div class="flex flex-col">
<a href={item.path}>{item.label}</a> {group.items.map((item) => (
))} <a href={item.path}>{item.label}</a>
))}
</div>
</div> </div>
</div> ))
)) }
} </div>
</div> </Section>
</footer> </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 +0,0 @@
---
import Logo from "components/Logo.astro";
---
<header id="main-header">
<div class="inner-container">
<Logo />
<button id="get-started-button" class="button">Get Started</button>
</div>
</header>
<script>
import { animate } from "motion";
import { ease } from "@unom/style";
const getStartedButton = document.getElementById("get-started-button");
const header = document.getElementById("main-header");
if (header) {
animate(header, { opacity: [0, 1], y: ["-101%", 0] }, ease.quart(0.6).out);
}
if (getStartedButton) {
animate(
getStartedButton,
{ scale: [0, 1] },
{ ...ease.quart(0.6).out, delay: 0 }
);
}
</script>
<style lang="postcss">
header {
position: fixed;
width: 100%;
top: 2rem;
z-index: 99;
& .inner-container {
display: flex;
justify-content: space-between;
flex-direction: row;
border-radius: 100px;
margin: auto;
max-width: var(--content-max-width);
padding: var(--padding-main);
background-color: var(--color-neutral-accent);
}
}
</style>

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

@ -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,26 @@
---
import Footer from "@/components/Layout/Footer.astro";
import "@/styles/global.css";
export interface Props {
title: string;
}
const { title } = Astro.props;
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body>
<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,43 @@
--- ---
import Layout from "layouts/Layout.astro"; import RootLayout from "@/layouts/RootLayout.astro";
import SectionHighlights from "sections/Start/Highlights.astro"; import Section from "@/components/Section.astro";
import Button from "@/components/Button.astro";
import Landing from "@/sections/Landing.astro";
import Introduction from "@/sections/Introduction.astro";
import Features from "@/sections/Features.astro";
--- ---
<Layout title="Free Intro Maker"> <RootLayout title="tempblade - Creator">
<main> <main>
<section class="default-section" id="header"> <Section>
<div class="heading-container"> <Landing />
<h1 class="my-4 font-bold"> </Section>
Motion Graphics made easy, <br /> for free! <Section>
</h1> <Introduction />
<p> </Section>
Thats right! Create an intro, outro or other motion graphic elements <Section>
right in your browser for free. How is this possible? Through the <Features />
power of open source software! </Section>
</p> <Section id="test">
</div> <h1>Hallo!</h1>
<div class="illustration-container"> <h2>Wie gehts</h2>
<svg viewBox="0 0 1000 1000"> <h3>Wie gehts</h3>
<circle cx="500" cy="500" r="300"></circle> <h4>Wie gehts</h4>
</svg> <h5>Wie gehts</h5>
</div> <p>
</section> cssnano is powered by PostCSS, a tool for transforming styles
<SectionHighlights /> with JavaScript. Specifically, its plugin architecture allows us
</main> to compose cssnano out of small modules with limited
<script> responsibilities. It also allows you to easily insert cssnano
import { animate, stagger } from "motion"; into your build step, along with other processors that can lint
import { ease } from "@unom/style"; your CSS for errors, or transpile future syntax.
</p>
const headingContainer = document.querySelectorAll( <Button>Hallo</Button>
"#header .heading-container" <Button variant="secondary">Hallo</Button>
)[0]; <Button variant="ghost">Hallo</Button>
<Button variant="destructive">Hallo</Button>
console.log(headingContainer); <Button variant="outline">Hallo</Button>
<Button variant="link">Hallo</Button>
animate( </Section>
headingContainer.children as any, </main>
{ y: [50, 0], opacity: [0, 1] }, </RootLayout>
{ ...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>

View File

@ -0,0 +1,26 @@
---
import CardFeature from "@/components/Cards/CardFeature.astro";
type Feature = {
title: string;
description: string;
};
const features: Array<Feature> = [
{
title: "Fast",
description:
"Thanks to rust with multithreading and skia we're really fast!",
},
];
---
<div class="flex flex-row">
{
features.map((feature) => (
<CardFeature
title={feature.title}
description={feature.description}
/>
))
}
</div>

View File

@ -0,0 +1,23 @@
<div class="flex flex-col gap-4">
<h2>The next generation motion design tool</h2>
<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 get a feasable 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 a early alpha stage, and the program 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!
</p>
<p>
You're not a developer but still want to support the work? I've got a
patreon!
</p>
</div>

View File

@ -0,0 +1,50 @@
---
import Button from "@/components/Button.astro";
---
<div class="h-[80vh] relative">
<div
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"
style="filter:blur(100px)"
class="w-[800px]"
id="landing-bg"></canvas>
</div>
<div
class="z-10 relative w-full h-full flex items-center flex-col justify-center"
>
<div class="flex flex-col justify-center items-center gap-2">
<h1 class="text-5xl text-center font-black">tempblade Creator</h1>
<h2 class="text-center text-xl font-normal">
Rust Based Open Source Motion Design Editor & Toolkit
</h2>
<Button>Explore now!</Button>
</div>
</div>
</div>
<script>
import { animate, inView } from "motion";
import { ease } from "@unom/style";
const landingBgContainer = document.getElementById("landing-bg");
if (landingBgContainer) {
inView(landingBgContainer, () => {
animate(
landingBgContainer,
{
scale: [0.7, 1],
opacity: [0, 1],
},
{ ...ease.quint(2).out }
);
});
}
</script>
<script src="./bg.ts"></script>

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>

75
web/src/sections/bg.ts Normal file
View File

@ -0,0 +1,75 @@
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;
generateVoronoiPattern(canvas, [
{
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",
},
]);

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

80
web/src/styles/global.css Normal file
View File

@ -0,0 +1,80 @@
@import "./breakpoints.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--color-main: 0 0% 0%;
--color-primary: 0 0% 0%;
--color-neutral: 0 0% 100%;
--color-neutral-accent: 0 0% 93%;
--color-highlight: 264 100% 50%;
--color-success: 132 100% 78%;
--color-error: 335 100% 62%;
--padding-main: 25px 25px;
--padding-card: 1.25rem 1.25rem;
--spacing-main: 20px;
--max-width-section: 100%;
}
@media (prefers-color-scheme: dark) {
:root {
--color-main: 0 0% 100%;
--color-primary: 0 0% 0%;
--color-neutral: 264 100% 6%;
--color-neutral-accent: 271 100% 10%;
--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;
}
p {
@apply max-w-[600px];
}

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

@ -0,0 +1,59 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
theme: {
extend: {
screens: { "2xl": "1400px", "3xl": "1850px" },
borderRadius: {
card: "0.5rem",
md: "0.5rem",
},
colors: {
main: "hsl(var(--color-main))",
primary: "hsl(var(--color-primary))",
secondary: "hsl(var(--color-secondary))",
neutral: "hsl(var(--color-neutral))",
"neutral-accent": "hsl(var(--color-neutral-accent))",
success: "hsl(var(--color-success))",
error: "hsl(var(--color-error))",
},
transitionTimingFunction: {
"in-sine": "cubic-bezier(0.47, 0, 0.745, 0.715)",
"out-sine": "cubic-bezier(0.39, 0.575, 0.565, 1)",
"in-out-sine": "cubic-bezier(0.445, 0.05, 0.55, 0.95)",
"in-quad": "cubic-bezier(0.55, 0.085, 0.68, 0.53)",
"out-quad": "cubic-bezier(0.25, 0.46, 0.45, 0.94)",
"in-out-quad": "cubic-bezier(0.455, 0.03, 0.515, 0.955)",
"in-cubic": "cubic-bezier(0.55, 0.055, 0.675, 0.19)",
"out-cubic": "cubic-bezier(0.215, 0.61, 0.355, 1)",
"in-out-cubic": "cubic-bezier(0.645, 0.045, 0.355, 1)",
"in-quart": "cubic-bezier(0.895, 0.03, 0.685, 0.22)",
"out-quart": "cubic-bezier(0.165, 0.84, 0.44, 1)",
"in-out-quart": "cubic-bezier(0.77, 0, 0.175, 1)",
"in-quint": "cubic-bezier(0.755, 0.05, 0.855, 0.06)",
"out-quint": "cubic-bezier(0.23, 1, 0.32, 1)",
"in-out-quint": "cubic-bezier(0.86, 0, 0.07, 1)",
"in-expo": "cubic-bezier(0.95, 0.05, 0.795, 0.035)",
"out-expo": "cubic-bezier(0.19, 1, 0.22, 1)",
"in-out-expo": "cubic-bezier(1, 0, 0, 1)",
"in-circ": "cubic-bezier(0.6, 0.04, 0.98, 0.335)",
"out-circ": "cubic-bezier(0.075, 0.82, 0.165, 1)",
"in-out-circ": "cubic-bezier(0.785, 0.135, 0.15, 0.86)",
},
gap: {
main: "var(--spacing-main)",
card: "var(--gap-card)",
},
padding: {
main: "var(--padding-main)",
card: "var(--padding-card)",
},
maxWidth: {
section: "var(--max-width-section)",
form: "var(--max-width-form)",
},
},
},
plugins: [],
};

View File

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

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