mirror of
https://github.com/kennethnym/aris.git
synced 2026-04-13 05:11:17 +01:00
Compare commits
1 Commits
feat/pre-c
...
feat/db-tr
| Author | SHA1 | Date | |
|---|---|---|---|
|
d46e6a9c5d
|
@@ -1 +0,0 @@
|
||||
bunx lint-staged
|
||||
@@ -8,5 +8,5 @@
|
||||
"ignoreCase": true,
|
||||
"newlinesBetween": true
|
||||
},
|
||||
"ignorePatterns": [".claude", ".ona", "drizzle", "fixtures"]
|
||||
"ignorePatterns": [".claude", "fixtures"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Hono } from "hono"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
import type { Auth } from "./index.ts"
|
||||
import type { AuthSession, AuthUser } from "./session.ts"
|
||||
|
||||
@@ -47,3 +47,5 @@ export function createFeedEnhancer(config: FeedEnhancerConfig): FeedEnhancer {
|
||||
return mergeEnhancement(items, result, currentTime)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ export function buildPrompt(
|
||||
|
||||
for (const item of items) {
|
||||
const hasUnfilledSlots =
|
||||
item.slots && Object.values(item.slots).some((slot) => slot.content === null)
|
||||
item.slots &&
|
||||
Object.values(item.slots).some((slot) => slot.content === null)
|
||||
|
||||
if (hasUnfilledSlots) {
|
||||
enhanceItems.push({
|
||||
@@ -78,7 +79,9 @@ export function buildPrompt(
|
||||
*/
|
||||
export function hasUnfilledSlots(items: FeedItem[]): boolean {
|
||||
return items.some(
|
||||
(item) => item.slots && Object.values(item.slots).some((slot) => slot.content === null),
|
||||
(item) =>
|
||||
item.slots &&
|
||||
Object.values(item.slots).some((slot) => slot.content === null),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -126,20 +129,7 @@ function extractCalendarEntry(item: FeedItem): CalendarEntry | null {
|
||||
}
|
||||
|
||||
const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const
|
||||
const MONTHS = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
] as const
|
||||
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const
|
||||
|
||||
function pad2(n: number): string {
|
||||
return n.toString().padStart(2, "0")
|
||||
@@ -154,11 +144,7 @@ function formatDayShort(date: Date): string {
|
||||
}
|
||||
|
||||
function formatDayLabel(date: Date, currentTime: Date): string {
|
||||
const currentDay = Date.UTC(
|
||||
currentTime.getUTCFullYear(),
|
||||
currentTime.getUTCMonth(),
|
||||
currentTime.getUTCDate(),
|
||||
)
|
||||
const currentDay = Date.UTC(currentTime.getUTCFullYear(), currentTime.getUTCMonth(), currentTime.getUTCDate())
|
||||
const targetDay = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
|
||||
const diffDays = Math.round((targetDay - currentDay) / (1000 * 60 * 60 * 24))
|
||||
|
||||
|
||||
@@ -135,7 +135,9 @@ describe("schema sync", () => {
|
||||
|
||||
// JSON Schema structure matches
|
||||
const jsonSchema = enhancementResultJsonSchema
|
||||
expect(Object.keys(jsonSchema.properties).sort()).toEqual(Object.keys(payload).sort())
|
||||
expect(Object.keys(jsonSchema.properties).sort()).toEqual(
|
||||
Object.keys(payload).sort(),
|
||||
)
|
||||
expect([...jsonSchema.required].sort()).toEqual(Object.keys(payload).sort())
|
||||
|
||||
// syntheticItems item schema has the right required fields
|
||||
@@ -165,7 +167,11 @@ describe("schema sync", () => {
|
||||
|
||||
// JSON Schema only allows string or null for slot values
|
||||
const slotValueSchema =
|
||||
enhancementResultJsonSchema.properties.slotFills.additionalProperties.additionalProperties
|
||||
expect(slotValueSchema.anyOf).toEqual([{ type: "string" }, { type: "null" }])
|
||||
enhancementResultJsonSchema.properties.slotFills.additionalProperties
|
||||
.additionalProperties
|
||||
expect(slotValueSchema.anyOf).toEqual([
|
||||
{ type: "string" },
|
||||
{ type: "null" },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { randomBytes } from "node:crypto"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
import { CredentialEncryptor } from "./crypto.ts"
|
||||
|
||||
|
||||
@@ -55,112 +55,44 @@
|
||||
"fontFamily": "Inter",
|
||||
"fontDefinitions": [
|
||||
{ "path": "./assets/fonts/Inter_100Thin.ttf", "weight": 100 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_100Thin_Italic.ttf",
|
||||
"weight": 100,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/Inter_100Thin_Italic.ttf", "weight": 100, "style": "italic" },
|
||||
{ "path": "./assets/fonts/Inter_200ExtraLight.ttf", "weight": 200 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_200ExtraLight_Italic.ttf",
|
||||
"weight": 200,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/Inter_200ExtraLight_Italic.ttf", "weight": 200, "style": "italic" },
|
||||
{ "path": "./assets/fonts/Inter_300Light.ttf", "weight": 300 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_300Light_Italic.ttf",
|
||||
"weight": 300,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/Inter_300Light_Italic.ttf", "weight": 300, "style": "italic" },
|
||||
{ "path": "./assets/fonts/Inter_400Regular.ttf", "weight": 400 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_400Regular_Italic.ttf",
|
||||
"weight": 400,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/Inter_400Regular_Italic.ttf", "weight": 400, "style": "italic" },
|
||||
{ "path": "./assets/fonts/Inter_500Medium.ttf", "weight": 500 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_500Medium_Italic.ttf",
|
||||
"weight": 500,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/Inter_500Medium_Italic.ttf", "weight": 500, "style": "italic" },
|
||||
{ "path": "./assets/fonts/Inter_600SemiBold.ttf", "weight": 600 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_600SemiBold_Italic.ttf",
|
||||
"weight": 600,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/Inter_600SemiBold_Italic.ttf", "weight": 600, "style": "italic" },
|
||||
{ "path": "./assets/fonts/Inter_700Bold.ttf", "weight": 700 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_700Bold_Italic.ttf",
|
||||
"weight": 700,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/Inter_700Bold_Italic.ttf", "weight": 700, "style": "italic" },
|
||||
{ "path": "./assets/fonts/Inter_800ExtraBold.ttf", "weight": 800 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_800ExtraBold_Italic.ttf",
|
||||
"weight": 800,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/Inter_800ExtraBold_Italic.ttf", "weight": 800, "style": "italic" },
|
||||
{ "path": "./assets/fonts/Inter_900Black.ttf", "weight": 900 },
|
||||
{
|
||||
"path": "./assets/fonts/Inter_900Black_Italic.ttf",
|
||||
"weight": 900,
|
||||
"style": "italic"
|
||||
}
|
||||
{ "path": "./assets/fonts/Inter_900Black_Italic.ttf", "weight": 900, "style": "italic" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"fontFamily": "Source Serif 4",
|
||||
"fontDefinitions": [
|
||||
{ "path": "./assets/fonts/SourceSerif4_200ExtraLight.ttf", "weight": 200 },
|
||||
{
|
||||
"path": "./assets/fonts/SourceSerif4_200ExtraLight_Italic.ttf",
|
||||
"weight": 200,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/SourceSerif4_200ExtraLight_Italic.ttf", "weight": 200, "style": "italic" },
|
||||
{ "path": "./assets/fonts/SourceSerif4_300Light.ttf", "weight": 300 },
|
||||
{
|
||||
"path": "./assets/fonts/SourceSerif4_300Light_Italic.ttf",
|
||||
"weight": 300,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/SourceSerif4_300Light_Italic.ttf", "weight": 300, "style": "italic" },
|
||||
{ "path": "./assets/fonts/SourceSerif4_400Regular.ttf", "weight": 400 },
|
||||
{
|
||||
"path": "./assets/fonts/SourceSerif4_400Regular_Italic.ttf",
|
||||
"weight": 400,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/SourceSerif4_400Regular_Italic.ttf", "weight": 400, "style": "italic" },
|
||||
{ "path": "./assets/fonts/SourceSerif4_500Medium.ttf", "weight": 500 },
|
||||
{
|
||||
"path": "./assets/fonts/SourceSerif4_500Medium_Italic.ttf",
|
||||
"weight": 500,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/SourceSerif4_500Medium_Italic.ttf", "weight": 500, "style": "italic" },
|
||||
{ "path": "./assets/fonts/SourceSerif4_600SemiBold.ttf", "weight": 600 },
|
||||
{
|
||||
"path": "./assets/fonts/SourceSerif4_600SemiBold_Italic.ttf",
|
||||
"weight": 600,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/SourceSerif4_600SemiBold_Italic.ttf", "weight": 600, "style": "italic" },
|
||||
{ "path": "./assets/fonts/SourceSerif4_700Bold.ttf", "weight": 700 },
|
||||
{
|
||||
"path": "./assets/fonts/SourceSerif4_700Bold_Italic.ttf",
|
||||
"weight": 700,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/SourceSerif4_700Bold_Italic.ttf", "weight": 700, "style": "italic" },
|
||||
{ "path": "./assets/fonts/SourceSerif4_800ExtraBold.ttf", "weight": 800 },
|
||||
{
|
||||
"path": "./assets/fonts/SourceSerif4_800ExtraBold_Italic.ttf",
|
||||
"weight": 800,
|
||||
"style": "italic"
|
||||
},
|
||||
{ "path": "./assets/fonts/SourceSerif4_800ExtraBold_Italic.ttf", "weight": 800, "style": "italic" },
|
||||
{ "path": "./assets/fonts/SourceSerif4_900Black.ttf", "weight": 900 },
|
||||
{
|
||||
"path": "./assets/fonts/SourceSerif4_900Black_Italic.ttf",
|
||||
"weight": 900,
|
||||
"style": "italic"
|
||||
}
|
||||
{ "path": "./assets/fonts/SourceSerif4_900Black_Italic.ttf", "weight": 900, "style": "italic" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -27,8 +27,8 @@ export class ApiClient {
|
||||
(prevInit, middleware) => middleware(url, prevInit),
|
||||
init,
|
||||
)
|
||||
return fetch(this.baseUrl ? new URL(url.toString(), this.baseUrl) : url, finalInit).then(
|
||||
(res) => Promise.all([Promise.resolve(res), res.json()]),
|
||||
return fetch(this.baseUrl ? new URL(url.toString(), this.baseUrl) : url, finalInit).then((res) =>
|
||||
Promise.all([Promise.resolve(res), res.json()]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ import { useEffect } from "react"
|
||||
import { ScrollView, View } from "react-native"
|
||||
import tw from "twrnc"
|
||||
|
||||
import { type Showcase } from "@/components/showcase"
|
||||
import { buttonShowcase } from "@/components/ui/button.showcase"
|
||||
import { feedCardShowcase } from "@/components/ui/feed-card.showcase"
|
||||
import { monospaceTextShowcase } from "@/components/ui/monospace-text.showcase"
|
||||
import { SansSerifText } from "@/components/ui/sans-serif-text"
|
||||
import { sansSerifTextShowcase } from "@/components/ui/sans-serif-text.showcase"
|
||||
import { serifTextShowcase } from "@/components/ui/serif-text.showcase"
|
||||
import { type Showcase } from "@/components/showcase"
|
||||
import { SansSerifText } from "@/components/ui/sans-serif-text"
|
||||
|
||||
const showcases: Record<string, Showcase> = {
|
||||
button: buttonShowcase,
|
||||
@@ -41,10 +41,7 @@ export default function ComponentDetailScreen() {
|
||||
const ShowcaseComponent = showcase.component
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
style={tw`bg-stone-100 dark:bg-stone-900 flex-1`}
|
||||
contentContainerStyle={tw`px-5 pb-10 pt-4 gap-6`}
|
||||
>
|
||||
<ScrollView style={tw`bg-stone-100 dark:bg-stone-900 flex-1`} contentContainerStyle={tw`px-5 pb-10 pt-4 gap-6`}>
|
||||
<ShowcaseComponent />
|
||||
</ScrollView>
|
||||
)
|
||||
|
||||
@@ -15,9 +15,7 @@ const components = [
|
||||
export default function ComponentsScreen() {
|
||||
return (
|
||||
<View style={tw`flex-1`}>
|
||||
<View
|
||||
style={tw`mx-4 mt-4 rounded-xl border border-stone-200 dark:border-stone-800 overflow-hidden`}
|
||||
>
|
||||
<View style={tw`mx-4 mt-4 rounded-xl border border-stone-200 dark:border-stone-800 overflow-hidden`}>
|
||||
<FlatList
|
||||
data={components}
|
||||
keyExtractor={(item) => item.name}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { View } from "react-native"
|
||||
import tw from "twrnc"
|
||||
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
import { Button } from "./button"
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
|
||||
function ButtonShowcase() {
|
||||
return (
|
||||
@@ -11,7 +11,11 @@ function ButtonShowcase() {
|
||||
<Button style={tw`self-start`} label="Press me" />
|
||||
</Section>
|
||||
<Section title="Leading icon">
|
||||
<Button style={tw`self-start`} label="Add item" leadingIcon={<Button.Icon name="plus" />} />
|
||||
<Button
|
||||
style={tw`self-start`}
|
||||
label="Add item"
|
||||
leadingIcon={<Button.Icon name="plus" />}
|
||||
/>
|
||||
</Section>
|
||||
<Section title="Trailing icon">
|
||||
<Button
|
||||
|
||||
@@ -23,11 +23,7 @@ type ButtonProps = Omit<PressableProps, "children"> & {
|
||||
export function Button({ style, label, leadingIcon, trailingIcon, ...props }: ButtonProps) {
|
||||
const hasIcons = leadingIcon != null || trailingIcon != null
|
||||
|
||||
const textElement = (
|
||||
<SansSerifText style={tw`text-stone-100 dark:text-stone-200 font-medium`}>
|
||||
{label}
|
||||
</SansSerifText>
|
||||
)
|
||||
const textElement = <SansSerifText style={tw`text-stone-100 dark:text-stone-200 font-medium`}>{label}</SansSerifText>
|
||||
|
||||
return (
|
||||
<Pressable style={[tw`rounded-full bg-teal-600 px-4 py-3 w-fit`, style]} {...props}>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { View } from "react-native"
|
||||
import tw from "twrnc"
|
||||
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
import { Button } from "./button"
|
||||
import { FeedCard } from "./feed-card"
|
||||
import { SansSerifText } from "./sans-serif-text"
|
||||
import { SerifText } from "./serif-text"
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
|
||||
function FeedCardShowcase() {
|
||||
return (
|
||||
|
||||
@@ -2,10 +2,5 @@ import { View, type ViewProps } from "react-native"
|
||||
import tw from "twrnc"
|
||||
|
||||
export function FeedCard({ style, ...props }: ViewProps) {
|
||||
return (
|
||||
<View
|
||||
style={[tw`border border-stone-200 dark:border-stone-800 rounded-lg`, style]}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return <View style={[tw`border border-stone-200 dark:border-stone-800 rounded-lg`, style]} {...props} />
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { View } from "react-native"
|
||||
import tw from "twrnc"
|
||||
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
import { MonospaceText } from "./monospace-text"
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
|
||||
function MonospaceTextShowcase() {
|
||||
return (
|
||||
|
||||
@@ -3,10 +3,7 @@ import tw from "twrnc"
|
||||
|
||||
export function MonospaceText({ children, style, ...props }: TextProps) {
|
||||
return (
|
||||
<Text
|
||||
style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Menlo" }, style]}
|
||||
{...props}
|
||||
>
|
||||
<Text style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Menlo" }, style]} {...props}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { View } from "react-native"
|
||||
import tw from "twrnc"
|
||||
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
import { SansSerifText } from "./sans-serif-text"
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
|
||||
function SansSerifTextShowcase() {
|
||||
return (
|
||||
|
||||
@@ -3,10 +3,7 @@ import tw from "twrnc"
|
||||
|
||||
export function SansSerifText({ children, style, ...props }: TextProps) {
|
||||
return (
|
||||
<Text
|
||||
style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Inter" }, style]}
|
||||
{...props}
|
||||
>
|
||||
<Text style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Inter" }, style]} {...props}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { View } from "react-native"
|
||||
import tw from "twrnc"
|
||||
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
import { SerifText } from "./serif-text"
|
||||
import { type Showcase, Section } from "../showcase"
|
||||
|
||||
function SerifTextShowcase() {
|
||||
return (
|
||||
|
||||
@@ -3,10 +3,7 @@ import tw from "twrnc"
|
||||
|
||||
export function SerifText({ children, style, ...props }: TextProps) {
|
||||
return (
|
||||
<Text
|
||||
style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Source Serif 4" }, style]}
|
||||
{...props}
|
||||
>
|
||||
<Text style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Source Serif 4" }, style]} {...props}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
|
||||
@@ -30,8 +30,7 @@ export const catalog = defineCatalog(schema, {
|
||||
style: z.string().nullable(),
|
||||
}),
|
||||
slots: ["default"],
|
||||
description:
|
||||
"Bordered card container for feed content. The style prop accepts a twrnc class string.",
|
||||
description: "Bordered card container for feed content. The style prop accepts a twrnc class string.",
|
||||
example: { style: "p-4 gap-2" },
|
||||
},
|
||||
SansSerifText: {
|
||||
|
||||
@@ -14,20 +14,12 @@ type ButtonIconName = React.ComponentProps<typeof Button.Icon>["name"]
|
||||
|
||||
export const { registry } = defineRegistry(catalog, {
|
||||
components: {
|
||||
View: ({ props, children }) => (
|
||||
<View style={props.style ? tw`${props.style}` : undefined}>{children}</View>
|
||||
),
|
||||
View: ({ props, children }) => <View style={props.style ? tw`${props.style}` : undefined}>{children}</View>,
|
||||
Button: ({ props, emit }) => (
|
||||
<Button
|
||||
label={props.label}
|
||||
leadingIcon={
|
||||
props.leadingIcon ? <Button.Icon name={props.leadingIcon as ButtonIconName} /> : undefined
|
||||
}
|
||||
trailingIcon={
|
||||
props.trailingIcon ? (
|
||||
<Button.Icon name={props.trailingIcon as ButtonIconName} />
|
||||
) : undefined
|
||||
}
|
||||
leadingIcon={props.leadingIcon ? <Button.Icon name={props.leadingIcon as ButtonIconName} /> : undefined}
|
||||
trailingIcon={props.trailingIcon ? <Button.Icon name={props.trailingIcon as ButtonIconName} /> : undefined}
|
||||
onPress={() => emit("press")}
|
||||
/>
|
||||
),
|
||||
@@ -35,17 +27,13 @@ export const { registry } = defineRegistry(catalog, {
|
||||
<FeedCard style={props.style ? tw`${props.style}` : undefined}>{children}</FeedCard>
|
||||
),
|
||||
SansSerifText: ({ props }) => (
|
||||
<SansSerifText style={props.style ? tw`${props.style}` : undefined}>
|
||||
{props.text}
|
||||
</SansSerifText>
|
||||
<SansSerifText style={props.style ? tw`${props.style}` : undefined}>{props.text}</SansSerifText>
|
||||
),
|
||||
SerifText: ({ props }) => (
|
||||
<SerifText style={props.style ? tw`${props.style}` : undefined}>{props.text}</SerifText>
|
||||
),
|
||||
MonospaceText: ({ props }) => (
|
||||
<MonospaceText style={props.style ? tw`${props.style}` : undefined}>
|
||||
{props.text}
|
||||
</MonospaceText>
|
||||
<MonospaceText style={props.style ? tw`${props.style}` : undefined}>{props.text}</MonospaceText>
|
||||
),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,131 +1 @@
|
||||
{
|
||||
"fr": 60,
|
||||
"h": 400,
|
||||
"ip": 0,
|
||||
"layers": [
|
||||
{
|
||||
"ind": 3,
|
||||
"ty": 4,
|
||||
"parent": 2,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 7,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 53] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 122] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{ "ty": "el", "p": { "a": 0, "k": [160, 53] }, "s": { "a": 0, "k": [320, 106] } },
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.906, 0.898, 0.894] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 2,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [160, 53] },
|
||||
"p": { "a": 0, "k": [200.5, 200] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [-30], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 6, "s": [-10], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 7,
|
||||
"st": 0
|
||||
},
|
||||
{
|
||||
"ind": 5,
|
||||
"ty": 4,
|
||||
"parent": 4,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 7,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 53] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 122] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{ "ty": "el", "p": { "a": 0, "k": [160, 53] }, "s": { "a": 0, "k": [320, 106] } },
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.906, 0.898, 0.894] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 4,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [160, 53] },
|
||||
"p": { "a": 0, "k": [200.594, 200.176] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [30], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 6, "s": [10], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 7,
|
||||
"st": 0
|
||||
},
|
||||
{ "ind": 1, "ty": 3, "parent": 0, "ks": {}, "ip": 0, "op": 7, "st": 0 },
|
||||
{ "ind": 0, "ty": 3, "ks": {}, "ip": 0, "op": 7, "st": 0 }
|
||||
],
|
||||
"meta": { "g": "https://jitter.video" },
|
||||
"op": 6,
|
||||
"v": "5.7.4",
|
||||
"w": 400
|
||||
}
|
||||
{"fr":60,"h":400,"ip":0,"layers":[{"ind":3,"ty":4,"parent":2,"ks":{},"ip":0,"op":7,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,53]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,122]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":0,"k":[160,53]},"s":{"a":0,"k":[320,106]}},{"ty":"st","c":{"a":0,"k":[0.906,0.898,0.894]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":2,"ty":3,"parent":1,"ks":{"a":{"a":0,"k":[160,53]},"p":{"a":0,"k":[200.5,200]},"r":{"a":1,"k":[{"t":0,"s":[-30],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":6,"s":[-10],"h":1}]}},"ip":0,"op":7,"st":0},{"ind":5,"ty":4,"parent":4,"ks":{},"ip":0,"op":7,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,53]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,122]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":0,"k":[160,53]},"s":{"a":0,"k":[320,106]}},{"ty":"st","c":{"a":0,"k":[0.906,0.898,0.894]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":4,"ty":3,"parent":1,"ks":{"a":{"a":0,"k":[160,53]},"p":{"a":0,"k":[200.594,200.176]},"r":{"a":1,"k":[{"t":0,"s":[30],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":6,"s":[10],"h":1}]}},"ip":0,"op":7,"st":0},{"ind":1,"ty":3,"parent":0,"ks":{},"ip":0,"op":7,"st":0},{"ind":0,"ty":3,"ks":{},"ip":0,"op":7,"st":0}],"meta":{"g":"https://jitter.video"},"op":6,"v":"5.7.4","w":400}
|
||||
@@ -1,131 +1 @@
|
||||
{
|
||||
"fr": 60,
|
||||
"h": 400,
|
||||
"ip": 0,
|
||||
"layers": [
|
||||
{
|
||||
"ind": 3,
|
||||
"ty": 4,
|
||||
"parent": 2,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 7,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 53] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 122] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{ "ty": "el", "p": { "a": 0, "k": [160, 53] }, "s": { "a": 0, "k": [320, 106] } },
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.11, 0.098, 0.09] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 2,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [160, 53] },
|
||||
"p": { "a": 0, "k": [200.5, 200] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [-30], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 6, "s": [-10], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 7,
|
||||
"st": 0
|
||||
},
|
||||
{
|
||||
"ind": 5,
|
||||
"ty": 4,
|
||||
"parent": 4,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 7,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 53] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 122] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{ "ty": "el", "p": { "a": 0, "k": [160, 53] }, "s": { "a": 0, "k": [320, 106] } },
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.11, 0.098, 0.09] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 4,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [160, 53] },
|
||||
"p": { "a": 0, "k": [200.594, 200.176] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [30], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 6, "s": [10], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 7,
|
||||
"st": 0
|
||||
},
|
||||
{ "ind": 1, "ty": 3, "parent": 0, "ks": {}, "ip": 0, "op": 7, "st": 0 },
|
||||
{ "ind": 0, "ty": 3, "ks": {}, "ip": 0, "op": 7, "st": 0 }
|
||||
],
|
||||
"meta": { "g": "https://jitter.video" },
|
||||
"op": 6,
|
||||
"v": "5.7.4",
|
||||
"w": 400
|
||||
}
|
||||
{"fr":60,"h":400,"ip":0,"layers":[{"ind":3,"ty":4,"parent":2,"ks":{},"ip":0,"op":7,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,53]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,122]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":0,"k":[160,53]},"s":{"a":0,"k":[320,106]}},{"ty":"st","c":{"a":0,"k":[0.11,0.098,0.09]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":2,"ty":3,"parent":1,"ks":{"a":{"a":0,"k":[160,53]},"p":{"a":0,"k":[200.5,200]},"r":{"a":1,"k":[{"t":0,"s":[-30],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":6,"s":[-10],"h":1}]}},"ip":0,"op":7,"st":0},{"ind":5,"ty":4,"parent":4,"ks":{},"ip":0,"op":7,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,53]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,122]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":0,"k":[160,53]},"s":{"a":0,"k":[320,106]}},{"ty":"st","c":{"a":0,"k":[0.11,0.098,0.09]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":4,"ty":3,"parent":1,"ks":{"a":{"a":0,"k":[160,53]},"p":{"a":0,"k":[200.594,200.176]},"r":{"a":1,"k":[{"t":0,"s":[30],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":6,"s":[10],"h":1}]}},"ip":0,"op":7,"st":0},{"ind":1,"ty":3,"parent":0,"ks":{},"ip":0,"op":7,"st":0},{"ind":0,"ty":3,"ks":{},"ip":0,"op":7,"st":0}],"meta":{"g":"https://jitter.video"},"op":6,"v":"5.7.4","w":400}
|
||||
@@ -1,281 +1 @@
|
||||
{
|
||||
"fr": 60,
|
||||
"h": 400,
|
||||
"ip": 0,
|
||||
"layers": [
|
||||
{
|
||||
"ind": 3,
|
||||
"ty": 4,
|
||||
"parent": 2,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 61,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 26.75] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 174.5] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "el",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 8.4,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 37.8,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [320, 1],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 8.4,
|
||||
"s": [320, 1],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 37.8,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [320, 1], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.906, 0.898, 0.894] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 2,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 8.4,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 37.8,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"p": { "a": 0, "k": [200, 200.014] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [-90], "h": 1 },
|
||||
{ "t": 8.4, "s": [-90], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 30, "s": [0], "h": 1 },
|
||||
{ "t": 37.8, "s": [0], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 60, "s": [90], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 61,
|
||||
"st": 0
|
||||
},
|
||||
{
|
||||
"ind": 5,
|
||||
"ty": 4,
|
||||
"parent": 4,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 61,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 26.75] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 174.5] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "el",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [320, 1],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [320, 1], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.906, 0.898, 0.894] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 4,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"p": { "a": 0, "k": [200.094, 200.19] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [-90], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 30, "s": [0], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 60, "s": [90], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 61,
|
||||
"st": 0
|
||||
},
|
||||
{ "ind": 1, "ty": 3, "parent": 0, "ks": {}, "ip": 0, "op": 61, "st": 0 },
|
||||
{ "ind": 0, "ty": 3, "ks": {}, "ip": 0, "op": 61, "st": 0 }
|
||||
],
|
||||
"meta": { "g": "https://jitter.video" },
|
||||
"op": 60,
|
||||
"v": "5.7.4",
|
||||
"w": 400
|
||||
}
|
||||
{"fr":60,"h":400,"ip":0,"layers":[{"ind":3,"ty":4,"parent":2,"ks":{},"ip":0,"op":61,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,26.75]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,174.5]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":1,"k":[{"t":0,"s":[160,0.5],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":8.4,"s":[160,0.5],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[160,53],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":37.8,"s":[160,53],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[160,0.5],"h":1}]},"s":{"a":1,"k":[{"t":0,"s":[320,1],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":8.4,"s":[320,1],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[320,106],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":37.8,"s":[320,106],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[320,1],"h":1}]}},{"ty":"st","c":{"a":0,"k":[0.906,0.898,0.894]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":2,"ty":3,"parent":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[160,0.5],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":8.4,"s":[160,0.5],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[160,53],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":37.8,"s":[160,53],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[160,0.5],"h":1}]},"p":{"a":0,"k":[200,200.014]},"r":{"a":1,"k":[{"t":0,"s":[-90],"h":1},{"t":8.4,"s":[-90],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":30,"s":[0],"h":1},{"t":37.8,"s":[0],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":60,"s":[90],"h":1}]}},"ip":0,"op":61,"st":0},{"ind":5,"ty":4,"parent":4,"ks":{},"ip":0,"op":61,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,26.75]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,174.5]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":1,"k":[{"t":0,"s":[160,0.5],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[160,53],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[160,0.5],"h":1}]},"s":{"a":1,"k":[{"t":0,"s":[320,1],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[320,106],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[320,1],"h":1}]}},{"ty":"st","c":{"a":0,"k":[0.906,0.898,0.894]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":4,"ty":3,"parent":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[160,0.5],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[160,53],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[160,0.5],"h":1}]},"p":{"a":0,"k":[200.094,200.19]},"r":{"a":1,"k":[{"t":0,"s":[-90],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":30,"s":[0],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":60,"s":[90],"h":1}]}},"ip":0,"op":61,"st":0},{"ind":1,"ty":3,"parent":0,"ks":{},"ip":0,"op":61,"st":0},{"ind":0,"ty":3,"ks":{},"ip":0,"op":61,"st":0}],"meta":{"g":"https://jitter.video"},"op":60,"v":"5.7.4","w":400}
|
||||
@@ -1,281 +1 @@
|
||||
{
|
||||
"fr": 60,
|
||||
"h": 400,
|
||||
"ip": 0,
|
||||
"layers": [
|
||||
{
|
||||
"ind": 3,
|
||||
"ty": 4,
|
||||
"parent": 2,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 61,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 26.75] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 174.5] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "el",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 8.4,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 37.8,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [320, 1],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 8.4,
|
||||
"s": [320, 1],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 37.8,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [320, 1], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.11, 0.098, 0.09] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 2,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 8.4,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 37.8,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"p": { "a": 0, "k": [200, 200.014] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [-90], "h": 1 },
|
||||
{ "t": 8.4, "s": [-90], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 30, "s": [0], "h": 1 },
|
||||
{ "t": 37.8, "s": [0], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 60, "s": [90], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 61,
|
||||
"st": 0
|
||||
},
|
||||
{
|
||||
"ind": 5,
|
||||
"ty": 4,
|
||||
"parent": 4,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 61,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 26.75] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 174.5] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "el",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [320, 1],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [320, 1], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.11, 0.098, 0.09] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 4,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 0.5],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 30,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 1] },
|
||||
"o": { "x": [0, 0.5], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 60, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"p": { "a": 0, "k": [200.094, 200.19] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [-90], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 30, "s": [0], "i": { "x": 0, "y": 1 }, "o": { "x": 0.5, "y": 0 } },
|
||||
{ "t": 60, "s": [90], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 61,
|
||||
"st": 0
|
||||
},
|
||||
{ "ind": 1, "ty": 3, "parent": 0, "ks": {}, "ip": 0, "op": 61, "st": 0 },
|
||||
{ "ind": 0, "ty": 3, "ks": {}, "ip": 0, "op": 61, "st": 0 }
|
||||
],
|
||||
"meta": { "g": "https://jitter.video" },
|
||||
"op": 60,
|
||||
"v": "5.7.4",
|
||||
"w": 400
|
||||
}
|
||||
{"fr":60,"h":400,"ip":0,"layers":[{"ind":3,"ty":4,"parent":2,"ks":{},"ip":0,"op":61,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,26.75]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,174.5]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":1,"k":[{"t":0,"s":[160,0.5],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":8.4,"s":[160,0.5],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[160,53],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":37.8,"s":[160,53],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[160,0.5],"h":1}]},"s":{"a":1,"k":[{"t":0,"s":[320,1],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":8.4,"s":[320,1],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[320,106],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":37.8,"s":[320,106],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[320,1],"h":1}]}},{"ty":"st","c":{"a":0,"k":[0.11,0.098,0.09]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":2,"ty":3,"parent":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[160,0.5],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":8.4,"s":[160,0.5],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[160,53],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":37.8,"s":[160,53],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[160,0.5],"h":1}]},"p":{"a":0,"k":[200,200.014]},"r":{"a":1,"k":[{"t":0,"s":[-90],"h":1},{"t":8.4,"s":[-90],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":30,"s":[0],"h":1},{"t":37.8,"s":[0],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":60,"s":[90],"h":1}]}},"ip":0,"op":61,"st":0},{"ind":5,"ty":4,"parent":4,"ks":{},"ip":0,"op":61,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,26.75]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,174.5]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":1,"k":[{"t":0,"s":[160,0.5],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[160,53],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[160,0.5],"h":1}]},"s":{"a":1,"k":[{"t":0,"s":[320,1],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[320,106],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[320,1],"h":1}]}},{"ty":"st","c":{"a":0,"k":[0.11,0.098,0.09]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":4,"ty":3,"parent":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[160,0.5],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":30,"s":[160,53],"i":{"x":[1,0],"y":[1,1]},"o":{"x":[0,0.5],"y":[0,0]}},{"t":60,"s":[160,0.5],"h":1}]},"p":{"a":0,"k":[200.094,200.19]},"r":{"a":1,"k":[{"t":0,"s":[-90],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":30,"s":[0],"i":{"x":0,"y":1},"o":{"x":0.5,"y":0}},{"t":60,"s":[90],"h":1}]}},"ip":0,"op":61,"st":0},{"ind":1,"ty":3,"parent":0,"ks":{},"ip":0,"op":61,"st":0},{"ind":0,"ty":3,"ks":{},"ip":0,"op":61,"st":0}],"meta":{"g":"https://jitter.video"},"op":60,"v":"5.7.4","w":400}
|
||||
@@ -1,224 +1 @@
|
||||
{
|
||||
"fr": 60,
|
||||
"h": 400,
|
||||
"ip": 0,
|
||||
"layers": [
|
||||
{
|
||||
"ind": 3,
|
||||
"ty": 4,
|
||||
"parent": 2,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 31,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 26.75] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 174.5] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "el",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 5.28,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0.001], "y": [1, 0.998] },
|
||||
"o": { "x": [0, 0.349], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 5.28,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 0.001], "y": [1, 0.998] },
|
||||
"o": { "x": [0, 0.349], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [320, 1], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.906, 0.898, 0.894] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 2,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 5.28,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0.001], "y": [1, 0.998] },
|
||||
"o": { "x": [0, 0.349], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"p": { "a": 0, "k": [200.5, 200] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [-30], "h": 1 },
|
||||
{ "t": 5.28, "s": [-30], "i": { "x": 0.001, "y": 0.998 }, "o": { "x": 0.349, "y": 0 } },
|
||||
{ "t": 30, "s": [-90], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 31,
|
||||
"st": 0
|
||||
},
|
||||
{
|
||||
"ind": 5,
|
||||
"ty": 4,
|
||||
"parent": 4,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 31,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 26.75] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 174.5] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "el",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 0.999] },
|
||||
"o": { "x": [0, 0.348], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 0], "y": [1, 0.999] },
|
||||
"o": { "x": [0, 0.348], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [320, 1], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.906, 0.898, 0.894] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 4,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 0.999] },
|
||||
"o": { "x": [0, 0.348], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"p": { "a": 0, "k": [200.594, 200.176] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [30], "i": { "x": 0, "y": 0.999 }, "o": { "x": 0.348, "y": 0 } },
|
||||
{ "t": 30, "s": [90], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 31,
|
||||
"st": 0
|
||||
},
|
||||
{ "ind": 1, "ty": 3, "parent": 0, "ks": {}, "ip": 0, "op": 31, "st": 0 },
|
||||
{ "ind": 0, "ty": 3, "ks": {}, "ip": 0, "op": 31, "st": 0 }
|
||||
],
|
||||
"meta": { "g": "https://jitter.video" },
|
||||
"op": 30,
|
||||
"v": "5.7.4",
|
||||
"w": 400
|
||||
}
|
||||
{"fr":60,"h":400,"ip":0,"layers":[{"ind":3,"ty":4,"parent":2,"ks":{},"ip":0,"op":31,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,26.75]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,174.5]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":1,"k":[{"t":0,"s":[160,53],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":5.28,"s":[160,53],"i":{"x":[1,0.001],"y":[1,0.998]},"o":{"x":[0,0.349],"y":[0,0]}},{"t":30,"s":[160,0.5],"h":1}]},"s":{"a":1,"k":[{"t":0,"s":[320,106],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":5.28,"s":[320,106],"i":{"x":[1,0.001],"y":[1,0.998]},"o":{"x":[0,0.349],"y":[0,0]}},{"t":30,"s":[320,1],"h":1}]}},{"ty":"st","c":{"a":0,"k":[0.906,0.898,0.894]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":2,"ty":3,"parent":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[160,53],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":5.28,"s":[160,53],"i":{"x":[1,0.001],"y":[1,0.998]},"o":{"x":[0,0.349],"y":[0,0]}},{"t":30,"s":[160,0.5],"h":1}]},"p":{"a":0,"k":[200.5,200]},"r":{"a":1,"k":[{"t":0,"s":[-30],"h":1},{"t":5.28,"s":[-30],"i":{"x":0.001,"y":0.998},"o":{"x":0.349,"y":0}},{"t":30,"s":[-90],"h":1}]}},"ip":0,"op":31,"st":0},{"ind":5,"ty":4,"parent":4,"ks":{},"ip":0,"op":31,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,26.75]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,174.5]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":1,"k":[{"t":0,"s":[160,53],"i":{"x":[1,0],"y":[1,0.999]},"o":{"x":[0,0.348],"y":[0,0]}},{"t":30,"s":[160,0.5],"h":1}]},"s":{"a":1,"k":[{"t":0,"s":[320,106],"i":{"x":[1,0],"y":[1,0.999]},"o":{"x":[0,0.348],"y":[0,0]}},{"t":30,"s":[320,1],"h":1}]}},{"ty":"st","c":{"a":0,"k":[0.906,0.898,0.894]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":4,"ty":3,"parent":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[160,53],"i":{"x":[1,0],"y":[1,0.999]},"o":{"x":[0,0.348],"y":[0,0]}},{"t":30,"s":[160,0.5],"h":1}]},"p":{"a":0,"k":[200.594,200.176]},"r":{"a":1,"k":[{"t":0,"s":[30],"i":{"x":0,"y":0.999},"o":{"x":0.348,"y":0}},{"t":30,"s":[90],"h":1}]}},"ip":0,"op":31,"st":0},{"ind":1,"ty":3,"parent":0,"ks":{},"ip":0,"op":31,"st":0},{"ind":0,"ty":3,"ks":{},"ip":0,"op":31,"st":0}],"meta":{"g":"https://jitter.video"},"op":30,"v":"5.7.4","w":400}
|
||||
@@ -1,224 +1 @@
|
||||
{
|
||||
"fr": 60,
|
||||
"h": 400,
|
||||
"ip": 0,
|
||||
"layers": [
|
||||
{
|
||||
"ind": 3,
|
||||
"ty": 4,
|
||||
"parent": 2,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 31,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 26.75] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 174.5] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "el",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 5.28,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0.001], "y": [1, 0.998] },
|
||||
"o": { "x": [0, 0.349], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 5.28,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 0.001], "y": [1, 0.998] },
|
||||
"o": { "x": [0, 0.349], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [320, 1], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.11, 0.098, 0.09] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 2,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 1], "y": [1, 1] },
|
||||
"o": { "x": [0, 0], "y": [0, 0] }
|
||||
},
|
||||
{
|
||||
"t": 5.28,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0.001], "y": [1, 0.998] },
|
||||
"o": { "x": [0, 0.349], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"p": { "a": 0, "k": [200.5, 200] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [-30], "h": 1 },
|
||||
{ "t": 5.28, "s": [-30], "i": { "x": 0.001, "y": 0.998 }, "o": { "x": 0.349, "y": 0 } },
|
||||
{ "t": 30, "s": [-90], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 31,
|
||||
"st": 0
|
||||
},
|
||||
{
|
||||
"ind": 5,
|
||||
"ty": 4,
|
||||
"parent": 4,
|
||||
"ks": {},
|
||||
"ip": 0,
|
||||
"op": 31,
|
||||
"st": 0,
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "rc",
|
||||
"p": { "a": 0, "k": [160, 26.75] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"s": { "a": 0, "k": [336, 174.5] }
|
||||
},
|
||||
{ "ty": "fl", "c": { "a": 0, "k": [0, 0, 0, 0] }, "o": { "a": 0, "k": 0 } },
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"it": [
|
||||
{
|
||||
"ty": "el",
|
||||
"p": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 0.999] },
|
||||
"o": { "x": [0, 0.348], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [320, 106],
|
||||
"i": { "x": [1, 0], "y": [1, 0.999] },
|
||||
"o": { "x": [0, 0.348], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [320, 1], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"c": { "a": 0, "k": [0.11, 0.098, 0.09] },
|
||||
"lc": 1,
|
||||
"lj": 1,
|
||||
"ml": 10,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 16 }
|
||||
},
|
||||
{ "ty": "tr", "o": { "a": 0, "k": 100 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ind": 4,
|
||||
"ty": 3,
|
||||
"parent": 1,
|
||||
"ks": {
|
||||
"a": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"t": 0,
|
||||
"s": [160, 53],
|
||||
"i": { "x": [1, 0], "y": [1, 0.999] },
|
||||
"o": { "x": [0, 0.348], "y": [0, 0] }
|
||||
},
|
||||
{ "t": 30, "s": [160, 0.5], "h": 1 }
|
||||
]
|
||||
},
|
||||
"p": { "a": 0, "k": [200.594, 200.176] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "t": 0, "s": [30], "i": { "x": 0, "y": 0.999 }, "o": { "x": 0.348, "y": 0 } },
|
||||
{ "t": 30, "s": [90], "h": 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
"ip": 0,
|
||||
"op": 31,
|
||||
"st": 0
|
||||
},
|
||||
{ "ind": 1, "ty": 3, "parent": 0, "ks": {}, "ip": 0, "op": 31, "st": 0 },
|
||||
{ "ind": 0, "ty": 3, "ks": {}, "ip": 0, "op": 31, "st": 0 }
|
||||
],
|
||||
"meta": { "g": "https://jitter.video" },
|
||||
"op": 30,
|
||||
"v": "5.7.4",
|
||||
"w": 400
|
||||
}
|
||||
{"fr":60,"h":400,"ip":0,"layers":[{"ind":3,"ty":4,"parent":2,"ks":{},"ip":0,"op":31,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,26.75]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,174.5]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":1,"k":[{"t":0,"s":[160,53],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":5.28,"s":[160,53],"i":{"x":[1,0.001],"y":[1,0.998]},"o":{"x":[0,0.349],"y":[0,0]}},{"t":30,"s":[160,0.5],"h":1}]},"s":{"a":1,"k":[{"t":0,"s":[320,106],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":5.28,"s":[320,106],"i":{"x":[1,0.001],"y":[1,0.998]},"o":{"x":[0,0.349],"y":[0,0]}},{"t":30,"s":[320,1],"h":1}]}},{"ty":"st","c":{"a":0,"k":[0.11,0.098,0.09]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":2,"ty":3,"parent":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[160,53],"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"t":5.28,"s":[160,53],"i":{"x":[1,0.001],"y":[1,0.998]},"o":{"x":[0,0.349],"y":[0,0]}},{"t":30,"s":[160,0.5],"h":1}]},"p":{"a":0,"k":[200.5,200]},"r":{"a":1,"k":[{"t":0,"s":[-30],"h":1},{"t":5.28,"s":[-30],"i":{"x":0.001,"y":0.998},"o":{"x":0.349,"y":0}},{"t":30,"s":[-90],"h":1}]}},"ip":0,"op":31,"st":0},{"ind":5,"ty":4,"parent":4,"ks":{},"ip":0,"op":31,"st":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","p":{"a":0,"k":[160,26.75]},"r":{"a":0,"k":0},"s":{"a":0,"k":[336,174.5]}},{"ty":"fl","c":{"a":0,"k":[0,0,0,0]},"o":{"a":0,"k":0}},{"ty":"tr","o":{"a":0,"k":100}}]},{"ty":"gr","it":[{"ty":"el","p":{"a":1,"k":[{"t":0,"s":[160,53],"i":{"x":[1,0],"y":[1,0.999]},"o":{"x":[0,0.348],"y":[0,0]}},{"t":30,"s":[160,0.5],"h":1}]},"s":{"a":1,"k":[{"t":0,"s":[320,106],"i":{"x":[1,0],"y":[1,0.999]},"o":{"x":[0,0.348],"y":[0,0]}},{"t":30,"s":[320,1],"h":1}]}},{"ty":"st","c":{"a":0,"k":[0.11,0.098,0.09]},"lc":1,"lj":1,"ml":10,"o":{"a":0,"k":100},"w":{"a":0,"k":16}},{"ty":"tr","o":{"a":0,"k":100}}]}]},{"ind":4,"ty":3,"parent":1,"ks":{"a":{"a":1,"k":[{"t":0,"s":[160,53],"i":{"x":[1,0],"y":[1,0.999]},"o":{"x":[0,0.348],"y":[0,0]}},{"t":30,"s":[160,0.5],"h":1}]},"p":{"a":0,"k":[200.594,200.176]},"r":{"a":1,"k":[{"t":0,"s":[30],"i":{"x":0,"y":0.999},"o":{"x":0.348,"y":0}},{"t":30,"s":[90],"h":1}]}},"ip":0,"op":31,"st":0},{"ind":1,"ty":3,"parent":0,"ks":{},"ip":0,"op":31,"st":0},{"ind":0,"ty":3,"ks":{},"ip":0,"op":31,"st":0}],"meta":{"g":"https://jitter.video"},"op":30,"v":"5.7.4","w":400}
|
||||
@@ -9,14 +9,14 @@ primary_region = 'lhr'
|
||||
[build]
|
||||
|
||||
[http_service]
|
||||
internal_port = 3000
|
||||
force_https = true
|
||||
auto_stop_machines = 'stop'
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
processes = ['app']
|
||||
internal_port = 3000
|
||||
force_https = true
|
||||
auto_stop_machines = 'stop'
|
||||
auto_start_machines = true
|
||||
min_machines_running = 0
|
||||
processes = ['app']
|
||||
|
||||
[[vm]]
|
||||
memory = '1gb'
|
||||
cpus = 1
|
||||
memory_mb = 1024
|
||||
memory = '1gb'
|
||||
cpus = 1
|
||||
memory_mb = 1024
|
||||
|
||||
62
bun.lock
62
bun.lock
@@ -9,8 +9,6 @@
|
||||
"@nym.sh/jrx": "^0.2.0",
|
||||
"@types/bun": "latest",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20260412.1",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.4.0",
|
||||
"oxfmt": "^0.24.0",
|
||||
"oxlint": "^1.39.0",
|
||||
},
|
||||
@@ -1749,8 +1747,6 @@
|
||||
|
||||
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
|
||||
|
||||
"cli-truncate": ["cli-truncate@5.2.0", "", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="],
|
||||
|
||||
"cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
@@ -1771,8 +1767,6 @@
|
||||
|
||||
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
|
||||
|
||||
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
||||
@@ -1957,8 +1951,6 @@
|
||||
|
||||
"envinfo": ["envinfo@7.11.0", "", { "bin": { "envinfo": "dist/cli.js" } }, "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg=="],
|
||||
|
||||
"environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
|
||||
|
||||
"err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
|
||||
@@ -2033,8 +2025,6 @@
|
||||
|
||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
||||
|
||||
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
|
||||
|
||||
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
||||
@@ -2309,8 +2299,6 @@
|
||||
|
||||
"human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
|
||||
|
||||
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
|
||||
|
||||
"hyperlinker": ["hyperlinker@1.0.0", "", {}, "sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ=="],
|
||||
|
||||
"hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
|
||||
@@ -2565,10 +2553,6 @@
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"lint-staged": ["lint-staged@16.4.0", "", { "dependencies": { "commander": "^14.0.3", "listr2": "^9.0.5", "picomatch": "^4.0.3", "string-argv": "^0.3.2", "tinyexec": "^1.0.4", "yaml": "^2.8.2" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw=="],
|
||||
|
||||
"listr2": ["listr2@9.0.5", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="],
|
||||
@@ -2585,8 +2569,6 @@
|
||||
|
||||
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
|
||||
|
||||
"log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||
@@ -3181,8 +3163,6 @@
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
|
||||
|
||||
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
||||
@@ -3271,7 +3251,7 @@
|
||||
|
||||
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
|
||||
|
||||
"slice-ansi": ["slice-ansi@8.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="],
|
||||
"slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
|
||||
|
||||
"slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="],
|
||||
|
||||
@@ -3323,8 +3303,6 @@
|
||||
|
||||
"strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
@@ -3401,7 +3379,7 @@
|
||||
|
||||
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
@@ -3617,7 +3595,7 @@
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
||||
"yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
@@ -3827,8 +3805,6 @@
|
||||
|
||||
"@expo/steps/joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="],
|
||||
|
||||
"@expo/steps/yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
|
||||
|
||||
"@expo/xcpretty/@babel/code-frame": ["@babel/code-frame@7.23.5", "", { "dependencies": { "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" } }, "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA=="],
|
||||
|
||||
"@formatjs/ecma402-abstract/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
@@ -3993,8 +3969,6 @@
|
||||
|
||||
"cli-progress/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cli-truncate/string-width": ["string-width@8.2.0", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="],
|
||||
|
||||
"cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
@@ -4025,8 +3999,6 @@
|
||||
|
||||
"eas-cli/ora": ["ora@5.1.0", "", { "dependencies": { "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.4.0", "is-interactive": "^1.0.0", "log-symbols": "^4.0.0", "mute-stream": "0.0.8", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w=="],
|
||||
|
||||
"eas-cli/yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
|
||||
|
||||
"eciesjs/@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
|
||||
|
||||
"eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
||||
@@ -4137,16 +4109,8 @@
|
||||
|
||||
"lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"listr2/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"log-symbols/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
||||
|
||||
"log-update/ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
|
||||
|
||||
"log-update/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
|
||||
|
||||
"log-update/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
@@ -4167,6 +4131,8 @@
|
||||
|
||||
"metro-config/metro-runtime": ["metro-runtime@0.83.3", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw=="],
|
||||
|
||||
"metro-config/yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
||||
|
||||
"metro-source-map/metro-symbolicate": ["metro-symbolicate@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.5", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA=="],
|
||||
|
||||
"metro-source-map/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
||||
@@ -4201,8 +4167,6 @@
|
||||
|
||||
"nypm/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
|
||||
"ora/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
|
||||
@@ -4281,10 +4245,6 @@
|
||||
|
||||
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
|
||||
|
||||
"slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
|
||||
|
||||
"stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
||||
|
||||
"stacktrace-parser/type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="],
|
||||
@@ -4491,16 +4451,12 @@
|
||||
|
||||
"@expo/plugin-help/@oclif/core/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
|
||||
|
||||
"@expo/plugin-help/@oclif/core/slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
|
||||
|
||||
"@expo/plugin-help/@oclif/core/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"@expo/plugin-help/@oclif/core/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@expo/plugin-warn-if-update-available/@oclif/core/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
|
||||
|
||||
"@expo/plugin-warn-if-update-available/@oclif/core/slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
|
||||
|
||||
"@expo/plugin-warn-if-update-available/@oclif/core/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"@expo/plugin-warn-if-update-available/@oclif/core/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
@@ -4755,14 +4711,6 @@
|
||||
|
||||
"lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"listr2/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"log-update/slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"log-update/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
|
||||
|
||||
"log-update/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
|
||||
|
||||
"metro-symbolicate/metro-source-map/ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="],
|
||||
|
||||
@@ -41,7 +41,7 @@ Sources → Source Graph → FeedEngine
|
||||
|
||||
### One harness, not many agents
|
||||
|
||||
The "agents" in this doc describe _behaviors_, not separate running processes. A human PA is one person — they don't have a "calendar agent" and a "follow-up agent" in their head. They look at your whole situation and act on whatever matters.
|
||||
The "agents" in this doc describe *behaviors*, not separate running processes. A human PA is one person — they don't have a "calendar agent" and a "follow-up agent" in their head. They look at your whole situation and act on whatever matters.
|
||||
|
||||
AELIS works the same way. One LLM harness receives all feed items, all context, all user memory, and all available tools. It returns a single `FeedEnhancement`. Every behavior (preparation, follow-up, anomaly detection, tone adjustment, cross-source reasoning) is an instruction in the system prompt, not a separate agent.
|
||||
|
||||
@@ -50,21 +50,20 @@ The advantage: the LLM sees everything at once. It doesn't need agent-to-agent c
|
||||
The only separate LLM call is the **Query Agent** — because it's user-initiated and synchronous. But it uses the same system prompt and context. It's the same "person," just responding to a question instead of proactively enhancing the feed.
|
||||
|
||||
Everything else is either:
|
||||
|
||||
- **Rule-based post-processors** — pure functions, no LLM, run on every refresh
|
||||
- **The single LLM harness** — runs periodically, produces cached `FeedEnhancement`
|
||||
- **Background jobs** — daily summary compression, weekly pattern discovery
|
||||
|
||||
### Component categories
|
||||
|
||||
| Component | What it is | Examples |
|
||||
| ------------------------------ | ----------------------------------------- | --------------------------------------------------------------------- |
|
||||
| **FeedSource nodes** | Graph participants that produce items | Briefing, Preparation, Anomaly Detection, Follow-up, Social Awareness |
|
||||
| **Rule-based post-processors** | Pure functions that rerank/filter/group | TimeOfDay, CalendarGrouping, Deduplication, UserAffinity |
|
||||
| **LLM enhancement harness** | Single background LLM call, cached output | Card rewriting, cross-source synthesis, tone, narrative arcs |
|
||||
| **Query interface** | Synchronous LLM call, user-initiated | Conversational Q&A, web search, delegation, actions |
|
||||
| **Background jobs** | Periodic data processing | Daily summary compression, weekly pattern discovery |
|
||||
| **Persistence** | Stored state that feeds into everything | Memory store, affinity model, conversation history, feed snapshots |
|
||||
| Component | What it is | Examples |
|
||||
|---|---|---|
|
||||
| **FeedSource nodes** | Graph participants that produce items | Briefing, Preparation, Anomaly Detection, Follow-up, Social Awareness |
|
||||
| **Rule-based post-processors** | Pure functions that rerank/filter/group | TimeOfDay, CalendarGrouping, Deduplication, UserAffinity |
|
||||
| **LLM enhancement harness** | Single background LLM call, cached output | Card rewriting, cross-source synthesis, tone, narrative arcs |
|
||||
| **Query interface** | Synchronous LLM call, user-initiated | Conversational Q&A, web search, delegation, actions |
|
||||
| **Background jobs** | Periodic data processing | Daily summary compression, weekly pattern discovery |
|
||||
| **Persistence** | Stored state that feeds into everything | Memory store, affinity model, conversation history, feed snapshots |
|
||||
|
||||
### AgentContext
|
||||
|
||||
@@ -72,32 +71,32 @@ The LLM harness and post-processors need a unified view of the user's world: cur
|
||||
|
||||
`AgentContext` is **not** on the engine. The engine's job is source orchestration — running sources in dependency order, accumulating context, collecting items. It shouldn't know about user preferences, conversation history, or feed snapshots. Those are separate concerns.
|
||||
|
||||
`AgentContext` is a separate object that _reads from_ the engine and composes its output with other data stores:
|
||||
`AgentContext` is a separate object that *reads from* the engine and composes its output with other data stores:
|
||||
|
||||
```typescript
|
||||
interface AgentContext {
|
||||
/** Current accumulated context from all sources */
|
||||
context: Context
|
||||
/** Current accumulated context from all sources */
|
||||
context: Context
|
||||
|
||||
/** Recent feed items (last N refreshes or time window) */
|
||||
recentItems: FeedItem[]
|
||||
/** Recent feed items (last N refreshes or time window) */
|
||||
recentItems: FeedItem[]
|
||||
|
||||
/** Query items from a specific source */
|
||||
itemsFrom(sourceId: string): FeedItem[]
|
||||
/** Query items from a specific source */
|
||||
itemsFrom(sourceId: string): FeedItem[]
|
||||
|
||||
/** User preference and memory store */
|
||||
preferences: UserPreferences
|
||||
/** User preference and memory store */
|
||||
preferences: UserPreferences
|
||||
|
||||
/** Conversation history */
|
||||
conversationHistory: ConversationEntry[]
|
||||
/** Conversation history */
|
||||
conversationHistory: ConversationEntry[]
|
||||
}
|
||||
|
||||
// Constructed by composing the engine with persistence layers
|
||||
const agentContext = new AgentContext({
|
||||
engine, // reads current context + items
|
||||
memoryStore, // reads/writes user preferences, discovered patterns
|
||||
snapshotStore, // reads feed history for pattern discovery
|
||||
conversationStore, // reads conversation history
|
||||
engine, // reads current context + items
|
||||
memoryStore, // reads/writes user preferences, discovered patterns
|
||||
snapshotStore, // reads feed history for pattern discovery
|
||||
conversationStore, // reads conversation history
|
||||
})
|
||||
```
|
||||
|
||||
@@ -136,20 +135,20 @@ The enhancement output:
|
||||
|
||||
```typescript
|
||||
interface FeedEnhancement {
|
||||
/** New items to inject (briefings, nudges, suggestions) */
|
||||
syntheticItems: FeedItem[]
|
||||
/** New items to inject (briefings, nudges, suggestions) */
|
||||
syntheticItems: FeedItem[]
|
||||
|
||||
/** Annotations attached to existing items, keyed by item ID */
|
||||
annotations: Record<string, string>
|
||||
/** Annotations attached to existing items, keyed by item ID */
|
||||
annotations: Record<string, string>
|
||||
|
||||
/** Items to group together with a summary card */
|
||||
groups: Array<{ itemIds: string[]; summary: string }>
|
||||
/** Items to group together with a summary card */
|
||||
groups: Array<{ itemIds: string[], summary: string }>
|
||||
|
||||
/** Item IDs to suppress or deprioritize */
|
||||
suppress: string[]
|
||||
/** Item IDs to suppress or deprioritize */
|
||||
suppress: string[]
|
||||
|
||||
/** Ranking hints: item ID → relative importance (0-1) */
|
||||
rankingHints: Record<string, number>
|
||||
/** Ranking hints: item ID → relative importance (0-1) */
|
||||
rankingHints: Record<string, number>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -186,7 +185,6 @@ These run on every refresh. Fast, deterministic, and cover most of the ranking q
|
||||
**Anomaly detection.** Compare event start times against the user's historical distribution. A 6am meeting when the user never has meetings before 9am is a statistical outlier — flag it.
|
||||
|
||||
**User affinity scoring.** Track implicit signals per source type per time-of-day bucket:
|
||||
|
||||
- Dismissals: user swipes away weather cards → decay affinity for weather
|
||||
- Taps: user taps calendar items frequently → boost affinity for calendar
|
||||
- Dwell time: user reads TfL alerts carefully → boost
|
||||
@@ -195,9 +193,9 @@ No LLM needed. A simple decay/boost model:
|
||||
|
||||
```typescript
|
||||
interface UserAffinityModel {
|
||||
affinities: Record<string, Record<TimeBucket, number>>
|
||||
dismissalDecay: number
|
||||
tapBoost: number
|
||||
affinities: Record<string, Record<TimeBucket, number>>
|
||||
dismissalDecay: number
|
||||
tapBoost: number
|
||||
}
|
||||
```
|
||||
|
||||
@@ -311,7 +309,7 @@ There are three layers:
|
||||
|
||||
None of these have `if` statements. The LLM reads the feed, reads the user's memory, and decides what to say. Add a new source (Spotify, email, tasks) and the LLM automatically incorporates it — no new behavior code needed.
|
||||
|
||||
**Infrastructure (plumbing needed, but logic is emergent).** These need tables, APIs, and background jobs. But the _decision-making_ — what to extract, when to surface, how to phrase — is all LLM.
|
||||
**Infrastructure (plumbing needed, but logic is emergent).** These need tables, APIs, and background jobs. But the *decision-making* — what to extract, when to surface, how to phrase — is all LLM.
|
||||
|
||||
- Gentle Follow-up — needs: extraction pipeline after each conversation turn, `commitments` table. The LLM decides what counts as a commitment and when to remind.
|
||||
- Memory — needs: `memories` table, read/write API. The LLM decides what to remember and how to use it.
|
||||
@@ -323,7 +321,7 @@ None of these have `if` statements. The LLM reads the feed, reads the user's mem
|
||||
- Delegation — needs: confirmation flow, write-back infrastructure. The LLM decides what the user wants done.
|
||||
- Financial Awareness — needs: `financial_events` table, email extraction. The LLM decides what financial events matter.
|
||||
|
||||
**Hardcoded rules (fast path, must be deterministic).** These run on every refresh in <10ms. They _should_ be rules because they need to be fast and predictable.
|
||||
**Hardcoded rules (fast path, must be deterministic).** These run on every refresh in <10ms. They *should* be rules because they need to be fast and predictable.
|
||||
|
||||
- User affinity scoring — decay/boost math on tap/dismiss events
|
||||
- Deduplication — title + time matching across sources
|
||||
@@ -417,38 +415,39 @@ One per user, living in the `FeedEngineManager` on the backend:
|
||||
|
||||
```typescript
|
||||
class EnhancementManager {
|
||||
private cache: FeedEnhancement | null = null
|
||||
private lastInputHash: string | null = null
|
||||
private running = false
|
||||
private cache: FeedEnhancement | null = null
|
||||
private lastInputHash: string | null = null
|
||||
private running = false
|
||||
|
||||
async enhance(items: FeedItem[], context: AgentContext): Promise<FeedEnhancement> {
|
||||
const hash = computeHash(items, context)
|
||||
async enhance(
|
||||
items: FeedItem[],
|
||||
context: AgentContext,
|
||||
): Promise<FeedEnhancement> {
|
||||
const hash = computeHash(items, context)
|
||||
|
||||
// Nothing changed — return cache
|
||||
if (hash === this.lastInputHash && this.cache) {
|
||||
return this.cache
|
||||
}
|
||||
// Nothing changed — return cache
|
||||
if (hash === this.lastInputHash && this.cache) {
|
||||
return this.cache
|
||||
}
|
||||
|
||||
// Already running — return stale cache
|
||||
if (this.running) {
|
||||
return this.cache ?? emptyEnhancement()
|
||||
}
|
||||
// Already running — return stale cache
|
||||
if (this.running) {
|
||||
return this.cache ?? emptyEnhancement()
|
||||
}
|
||||
|
||||
// Run in background, update cache when done
|
||||
this.running = true
|
||||
this.runHarness(items, context, hash)
|
||||
.then((enhancement) => {
|
||||
this.cache = enhancement
|
||||
this.lastInputHash = hash
|
||||
this.notifySubscribers(enhancement)
|
||||
})
|
||||
.finally(() => {
|
||||
this.running = false
|
||||
})
|
||||
// Run in background, update cache when done
|
||||
this.running = true
|
||||
this.runHarness(items, context, hash)
|
||||
.then(enhancement => {
|
||||
this.cache = enhancement
|
||||
this.lastInputHash = hash
|
||||
this.notifySubscribers(enhancement)
|
||||
})
|
||||
.finally(() => { this.running = false })
|
||||
|
||||
// Return stale cache immediately
|
||||
return this.cache ?? emptyEnhancement()
|
||||
}
|
||||
// Return stale cache immediately
|
||||
return this.cache ?? emptyEnhancement()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -523,7 +522,7 @@ These are `FeedSource` nodes that depend on calendar, tasks, weather, and other
|
||||
|
||||
#### Anticipatory Logistics
|
||||
|
||||
Works backward from events to tell you what you need to _do_ to be ready.
|
||||
Works backward from events to tell you what you need to *do* to be ready.
|
||||
|
||||
- Flight at 6am → "You need to leave by 4am, which means waking at 3:30. I'd suggest packing tonight."
|
||||
- Dinner at a new restaurant → "It's a 25-minute walk or 8-minute Uber. Street parking is difficult — there's a car park on the next street."
|
||||
@@ -580,7 +579,7 @@ Tracks loose ends — things you said but never wrote down as tasks.
|
||||
- "You told James you'd review his PR — it's been 3 days"
|
||||
- "You promised to call your mom this weekend"
|
||||
|
||||
The key difference from task tracking: this catches things that fell through the cracks _because_ they were never formalized.
|
||||
The key difference from task tracking: this catches things that fell through the cracks *because* they were never formalized.
|
||||
|
||||
**How intent extraction works:**
|
||||
|
||||
@@ -615,14 +614,12 @@ Long-term memory of interactions and preferences. Feeds into every other agent.
|
||||
A persistent profile that builds over time. Not an agent itself — a system that makes every other agent smarter.
|
||||
|
||||
Learns from:
|
||||
|
||||
- Explicit statements: "I prefer morning meetings"
|
||||
- Implicit behavior: user always dismisses evening suggestions
|
||||
- Feedback: user rates suggestions as helpful/not
|
||||
- Cross-source patterns: always books aisle seats, always picks the cheaper option
|
||||
|
||||
Used by:
|
||||
|
||||
- Proactive Agent suggests restaurants the user would actually like
|
||||
- Delegation Agent books the right kind of hotel room
|
||||
- Summary Agent uses the user's preferred level of detail
|
||||
@@ -651,30 +648,27 @@ Passive observation. The patterns aren't hardcoded — the LLM discovers them fr
|
||||
|
||||
```typescript
|
||||
interface DailySummary {
|
||||
date: string
|
||||
feedCheckTimes: string[] // when the user opened the feed
|
||||
itemTypeCounts: Record<string, number> // how many of each type appeared
|
||||
interactions: Array<{
|
||||
// what the user tapped/dismissed
|
||||
itemType: string
|
||||
action: "tap" | "dismiss" | "dwell"
|
||||
time: string
|
||||
}>
|
||||
locations: Array<{
|
||||
// where the user was throughout the day
|
||||
lat: number
|
||||
lng: number
|
||||
time: string
|
||||
}>
|
||||
calendarSummary: Array<{
|
||||
// what events happened
|
||||
title: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
location?: string
|
||||
attendees?: string[]
|
||||
}>
|
||||
weatherConditions: string[] // conditions seen throughout the day
|
||||
date: string
|
||||
feedCheckTimes: string[] // when the user opened the feed
|
||||
itemTypeCounts: Record<string, number> // how many of each type appeared
|
||||
interactions: Array<{ // what the user tapped/dismissed
|
||||
itemType: string
|
||||
action: "tap" | "dismiss" | "dwell"
|
||||
time: string
|
||||
}>
|
||||
locations: Array<{ // where the user was throughout the day
|
||||
lat: number
|
||||
lng: number
|
||||
time: string
|
||||
}>
|
||||
calendarSummary: Array<{ // what events happened
|
||||
title: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
location?: string
|
||||
attendees?: string[]
|
||||
}>
|
||||
weatherConditions: string[] // conditions seen throughout the day
|
||||
}
|
||||
```
|
||||
|
||||
@@ -684,20 +678,20 @@ interface DailySummary {
|
||||
|
||||
```typescript
|
||||
interface DiscoveredPattern {
|
||||
/** What the pattern is, in natural language */
|
||||
description: string
|
||||
/** How confident (0-1) */
|
||||
confidence: number
|
||||
/** When this pattern is relevant */
|
||||
relevance: {
|
||||
daysOfWeek?: number[]
|
||||
timeRange?: { start: string; end: string }
|
||||
conditions?: string[]
|
||||
}
|
||||
/** How this should affect the feed */
|
||||
feedImplication: string
|
||||
/** Suggested card to surface when pattern is relevant */
|
||||
suggestedAction?: string
|
||||
/** What the pattern is, in natural language */
|
||||
description: string
|
||||
/** How confident (0-1) */
|
||||
confidence: number
|
||||
/** When this pattern is relevant */
|
||||
relevance: {
|
||||
daysOfWeek?: number[]
|
||||
timeRange?: { start: string, end: string }
|
||||
conditions?: string[]
|
||||
}
|
||||
/** How this should affect the feed */
|
||||
feedImplication: string
|
||||
/** Suggested card to surface when pattern is relevant */
|
||||
suggestedAction?: string
|
||||
}
|
||||
```
|
||||
|
||||
@@ -723,9 +717,9 @@ Maintains awareness of relationships and surfaces timely nudges.
|
||||
|
||||
Needs: contacts with birthday/anniversary data, calendar history for meeting frequency, email/message signals, optionally social media.
|
||||
|
||||
This is what makes an assistant feel like it _cares_. Most tools are transactional. This one remembers the people in your life.
|
||||
This is what makes an assistant feel like it *cares*. Most tools are transactional. This one remembers the people in your life.
|
||||
|
||||
Beyond frequency, the assistant can understand relationship _dynamics_:
|
||||
Beyond frequency, the assistant can understand relationship *dynamics*:
|
||||
|
||||
- "You and Sarah always have productive meetings. You and Alex tend to go off-track — maybe set a tighter agenda."
|
||||
- "You've cancelled on Tom three times — he might be feeling deprioritized."
|
||||
@@ -791,7 +785,7 @@ This is where the source graph pays off. All the data is already there — the a
|
||||
|
||||
#### Tone & Timing
|
||||
|
||||
Controls _when_ and _how_ information is delivered. The difference between useful and annoying.
|
||||
Controls *when* and *how* information is delivered. The difference between useful and annoying.
|
||||
|
||||
- Bad news before morning coffee? Hold it.
|
||||
- Three notifications in a row? Batch them.
|
||||
@@ -855,7 +849,6 @@ The primary interface. This isn't a feed query tool — it's the person you talk
|
||||
The user should be able to ask AELIS anything they'd ask a knowledgeable friend. Some questions are about their data. Most aren't.
|
||||
|
||||
**About their life (reads from the source graph):**
|
||||
|
||||
- "What's on my calendar tomorrow?"
|
||||
- "When's my next flight?"
|
||||
- "Do I have any conflicts this week?"
|
||||
@@ -863,7 +856,6 @@ The user should be able to ask AELIS anything they'd ask a knowledgeable friend.
|
||||
- "Tell me more about this" (anchored to a feed item)
|
||||
|
||||
**About the world (falls through to web search):**
|
||||
|
||||
- "How do I unclog a drain?"
|
||||
- "What should I make with chicken and broccoli?"
|
||||
- "What's the best way to get from King's Cross to Heathrow?"
|
||||
@@ -872,7 +864,6 @@ The user should be able to ask AELIS anything they'd ask a knowledgeable friend.
|
||||
- "What are some good date night restaurants in Shoreditch?"
|
||||
|
||||
**Contextual blend (graph + web):**
|
||||
|
||||
- "What's the dress code for The Ivy?" (calendar shows dinner there tonight)
|
||||
- "Will I need an umbrella?" (location + weather, but could also web-search venue for indoor/outdoor)
|
||||
- "What should I know before my meeting with Acme Corp?" (calendar + web search for company info)
|
||||
@@ -888,12 +879,10 @@ This is also where intent extraction happens for the Gentle Follow-up Agent. Eve
|
||||
The backbone for general knowledge. Makes AELIS a person you can ask things, not just a dashboard you look at.
|
||||
|
||||
**Reactive (user asks):**
|
||||
|
||||
- Recipe ideas, how-to questions, factual lookups, recommendations
|
||||
- Anything the source graph can't answer
|
||||
|
||||
**Proactive (agents trigger):**
|
||||
|
||||
- Contextual Preparation enriches calendar events: venue info, attendee backgrounds, parking
|
||||
- Feed shows a concert → pre-fetches setlist, venue details
|
||||
- Ambient Context checks for disruptions, closures, news
|
||||
@@ -960,7 +949,7 @@ Handles tasks the user delegates via natural language.
|
||||
|
||||
Requires write access to sources. Confirmation UX for anything destructive or costly.
|
||||
|
||||
**Implementation:** Extends the Query Agent. When the LLM determines the user wants to _do_ something (not just ask), it calls a delegation tool with structured output: `{ action: "create_reminder" | "schedule_meeting" | "add_task", params: {...} }`. The backend maps this to `executeAction()` on the relevant source. For "find a time that works for both me and Sarah," the agent queries both calendars (requires Sarah to be a known contact with calendar access — or the agent asks the user to share availability). All write actions go through a confirmation step: the backend sends a `delegation.confirm` notification with the proposed action, and the client shows a confirmation UI. The user approves or modifies before execution. Store delegation history for the Follow-up Agent.
|
||||
**Implementation:** Extends the Query Agent. When the LLM determines the user wants to *do* something (not just ask), it calls a delegation tool with structured output: `{ action: "create_reminder" | "schedule_meeting" | "add_task", params: {...} }`. The backend maps this to `executeAction()` on the relevant source. For "find a time that works for both me and Sarah," the agent queries both calendars (requires Sarah to be a known contact with calendar access — or the agent asks the user to share availability). All write actions go through a confirmation step: the backend sends a `delegation.confirm` notification with the proposed action, and the client shows a confirmation UI. The user approves or modifies before execution. Store delegation history for the Follow-up Agent.
|
||||
|
||||
#### Actions
|
||||
|
||||
|
||||
@@ -131,19 +131,19 @@ Feed items carry an optional `ui` field containing a json-render tree, and an op
|
||||
|
||||
```typescript
|
||||
interface FeedItem<TType, TData> {
|
||||
id: string
|
||||
type: TType
|
||||
timestamp: Date
|
||||
data: TData
|
||||
ui?: JsonRenderNode
|
||||
slots?: Record<string, Slot>
|
||||
id: string
|
||||
type: TType
|
||||
timestamp: Date
|
||||
data: TData
|
||||
ui?: JsonRenderNode
|
||||
slots?: Record<string, Slot>
|
||||
}
|
||||
|
||||
interface Slot {
|
||||
/** Tells the LLM what this slot wants — the source writes this */
|
||||
description: string
|
||||
/** LLM-filled text content, null until enhanced */
|
||||
content: string | null
|
||||
/** Tells the LLM what this slot wants — the source writes this */
|
||||
description: string
|
||||
/** LLM-filled text content, null until enhanced */
|
||||
content: string | null
|
||||
}
|
||||
```
|
||||
|
||||
@@ -238,23 +238,28 @@ The user never waits for the LLM. They see the feed instantly with the previous
|
||||
The harness serializes items with their unfilled slots into a single prompt. Items without slots are excluded. The LLM sees everything at once and fills whatever slots are relevant.
|
||||
|
||||
```typescript
|
||||
function buildHarnessInput(items: FeedItem[], context: AgentContext): HarnessInput {
|
||||
const itemsWithSlots = items
|
||||
.filter((item) => item.slots && Object.keys(item.slots).length > 0)
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
data: item.data,
|
||||
slots: Object.fromEntries(
|
||||
Object.entries(item.slots!).map(([name, slot]) => [name, slot.description]),
|
||||
),
|
||||
}))
|
||||
function buildHarnessInput(
|
||||
items: FeedItem[],
|
||||
context: AgentContext,
|
||||
): HarnessInput {
|
||||
const itemsWithSlots = items
|
||||
.filter(item => item.slots && Object.keys(item.slots).length > 0)
|
||||
.map(item => ({
|
||||
id: item.id,
|
||||
type: item.type,
|
||||
data: item.data,
|
||||
slots: Object.fromEntries(
|
||||
Object.entries(item.slots!).map(
|
||||
([name, slot]) => [name, slot.description]
|
||||
)
|
||||
),
|
||||
}))
|
||||
|
||||
return {
|
||||
items: itemsWithSlots,
|
||||
userMemory: context.preferences,
|
||||
currentTime: new Date().toISOString(),
|
||||
}
|
||||
return {
|
||||
items: itemsWithSlots,
|
||||
userMemory: context.preferences,
|
||||
currentTime: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -262,33 +267,29 @@ The LLM sees:
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "weather-current-123",
|
||||
"type": "weather-current",
|
||||
"data": { "temperature": 18, "condition": "cloudy" },
|
||||
"slots": {
|
||||
"insight": "A short contextual insight about the current weather and how it affects the user's day",
|
||||
"cross-source": "Connection between weather and the user's calendar events or plans"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "calendar-event-456",
|
||||
"type": "calendar-event",
|
||||
"data": {
|
||||
"title": "Dinner at The Ivy",
|
||||
"startTime": "19:00",
|
||||
"location": "The Ivy, West St"
|
||||
},
|
||||
"slots": {
|
||||
"context": "Background on this event, attendees, or previous meetings with these people",
|
||||
"logistics": "Travel time, parking, directions to the venue",
|
||||
"weather": "Weather conditions relevant to this event's time and location"
|
||||
}
|
||||
}
|
||||
],
|
||||
"userMemory": { "commute": "victoria-line", "preference.walking_distance": "1 mile" },
|
||||
"currentTime": "2025-02-26T14:30:00Z"
|
||||
"items": [
|
||||
{
|
||||
"id": "weather-current-123",
|
||||
"type": "weather-current",
|
||||
"data": { "temperature": 18, "condition": "cloudy" },
|
||||
"slots": {
|
||||
"insight": "A short contextual insight about the current weather and how it affects the user's day",
|
||||
"cross-source": "Connection between weather and the user's calendar events or plans"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "calendar-event-456",
|
||||
"type": "calendar-event",
|
||||
"data": { "title": "Dinner at The Ivy", "startTime": "19:00", "location": "The Ivy, West St" },
|
||||
"slots": {
|
||||
"context": "Background on this event, attendees, or previous meetings with these people",
|
||||
"logistics": "Travel time, parking, directions to the venue",
|
||||
"weather": "Weather conditions relevant to this event's time and location"
|
||||
}
|
||||
}
|
||||
],
|
||||
"userMemory": { "commute": "victoria-line", "preference.walking_distance": "1 mile" },
|
||||
"currentTime": "2025-02-26T14:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -298,30 +299,27 @@ A flat map of item ID → slot name → text content. Slots left null are unfill
|
||||
|
||||
```json
|
||||
{
|
||||
"slotFills": {
|
||||
"weather-current-123": {
|
||||
"insight": "Rain after 3pm — grab a jacket before your walk",
|
||||
"cross-source": "Should be dry by 7pm for your dinner at The Ivy"
|
||||
},
|
||||
"calendar-event-456": {
|
||||
"context": null,
|
||||
"logistics": "20-minute walk from home — leave by 18:40",
|
||||
"weather": "Rain clears by evening, you'll be fine"
|
||||
}
|
||||
},
|
||||
"syntheticItems": [
|
||||
{
|
||||
"id": "briefing-morning",
|
||||
"type": "briefing",
|
||||
"data": {},
|
||||
"ui": {
|
||||
"component": "Text",
|
||||
"props": { "text": "Light afternoon — just your dinner at 7. Rain clears by then." }
|
||||
}
|
||||
}
|
||||
],
|
||||
"suppress": [],
|
||||
"rankingHints": {}
|
||||
"slotFills": {
|
||||
"weather-current-123": {
|
||||
"insight": "Rain after 3pm — grab a jacket before your walk",
|
||||
"cross-source": "Should be dry by 7pm for your dinner at The Ivy"
|
||||
},
|
||||
"calendar-event-456": {
|
||||
"context": null,
|
||||
"logistics": "20-minute walk from home — leave by 18:40",
|
||||
"weather": "Rain clears by evening, you'll be fine"
|
||||
}
|
||||
},
|
||||
"syntheticItems": [
|
||||
{
|
||||
"id": "briefing-morning",
|
||||
"type": "briefing",
|
||||
"data": {},
|
||||
"ui": { "component": "Text", "props": { "text": "Light afternoon — just your dinner at 7. Rain clears by then." } }
|
||||
}
|
||||
],
|
||||
"suppress": [],
|
||||
"rankingHints": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -331,41 +329,42 @@ One per user, living in the `FeedEngineManager` on the backend:
|
||||
|
||||
```typescript
|
||||
class EnhancementManager {
|
||||
private cache: EnhancementResult | null = null
|
||||
private lastInputHash: string | null = null
|
||||
private running = false
|
||||
private cache: EnhancementResult | null = null
|
||||
private lastInputHash: string | null = null
|
||||
private running = false
|
||||
|
||||
async enhance(items: FeedItem[], context: AgentContext): Promise<EnhancementResult> {
|
||||
const hash = computeHash(items, context)
|
||||
async enhance(
|
||||
items: FeedItem[],
|
||||
context: AgentContext,
|
||||
): Promise<EnhancementResult> {
|
||||
const hash = computeHash(items, context)
|
||||
|
||||
if (hash === this.lastInputHash && this.cache) {
|
||||
return this.cache
|
||||
}
|
||||
if (hash === this.lastInputHash && this.cache) {
|
||||
return this.cache
|
||||
}
|
||||
|
||||
if (this.running) {
|
||||
return this.cache ?? emptyResult()
|
||||
}
|
||||
if (this.running) {
|
||||
return this.cache ?? emptyResult()
|
||||
}
|
||||
|
||||
this.running = true
|
||||
this.runHarness(items, context)
|
||||
.then((result) => {
|
||||
this.cache = result
|
||||
this.lastInputHash = hash
|
||||
this.notifySubscribers(result)
|
||||
})
|
||||
.finally(() => {
|
||||
this.running = false
|
||||
})
|
||||
this.running = true
|
||||
this.runHarness(items, context)
|
||||
.then(result => {
|
||||
this.cache = result
|
||||
this.lastInputHash = hash
|
||||
this.notifySubscribers(result)
|
||||
})
|
||||
.finally(() => { this.running = false })
|
||||
|
||||
return this.cache ?? emptyResult()
|
||||
}
|
||||
return this.cache ?? emptyResult()
|
||||
}
|
||||
}
|
||||
|
||||
interface EnhancementResult {
|
||||
slotFills: Record<string, Record<string, string | null>>
|
||||
syntheticItems: FeedItem[]
|
||||
suppress: string[]
|
||||
rankingHints: Record<string, number>
|
||||
slotFills: Record<string, Record<string, string | null>>
|
||||
syntheticItems: FeedItem[]
|
||||
suppress: string[]
|
||||
rankingHints: Record<string, number>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -374,20 +373,23 @@ interface EnhancementResult {
|
||||
After the harness runs, the engine merges slot fills into items:
|
||||
|
||||
```typescript
|
||||
function mergeEnhancement(items: FeedItem[], result: EnhancementResult): FeedItem[] {
|
||||
return items.map((item) => {
|
||||
const fills = result.slotFills[item.id]
|
||||
if (!fills || !item.slots) return item
|
||||
function mergeEnhancement(
|
||||
items: FeedItem[],
|
||||
result: EnhancementResult,
|
||||
): FeedItem[] {
|
||||
return items.map(item => {
|
||||
const fills = result.slotFills[item.id]
|
||||
if (!fills || !item.slots) return item
|
||||
|
||||
const mergedSlots = { ...item.slots }
|
||||
for (const [name, content] of Object.entries(fills)) {
|
||||
if (name in mergedSlots && content !== null) {
|
||||
mergedSlots[name] = { ...mergedSlots[name], content }
|
||||
}
|
||||
}
|
||||
const mergedSlots = { ...item.slots }
|
||||
for (const [name, content] of Object.entries(fills)) {
|
||||
if (name in mergedSlots && content !== null) {
|
||||
mergedSlots[name] = { ...mergedSlots[name], content }
|
||||
}
|
||||
}
|
||||
|
||||
return { ...item, slots: mergedSlots }
|
||||
})
|
||||
return { ...item, slots: mergedSlots }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -24,16 +24,16 @@ The backend uses a raw `pg` Pool for Better Auth and has no ORM. We need a persi
|
||||
|
||||
A `user_sources` table stores per-user source state:
|
||||
|
||||
| Column | Type | Description |
|
||||
| ------------- | --------------------- | -------------------------------------------------------------- |
|
||||
| `id` | `uuid` PK | Row ID |
|
||||
| `user_id` | `text` FK → `user.id` | Owner |
|
||||
| `source_id` | `text` | Source identifier (e.g., `aelis.tfl`, `aelis.weather`) |
|
||||
| `enabled` | `boolean` | Whether this source is active in the user's feed |
|
||||
| `config` | `jsonb` | Source-specific configuration (validated by source at runtime) |
|
||||
| `credentials` | `bytea` | Encrypted OAuth tokens / secrets (AES-256-GCM) |
|
||||
| `created_at` | `timestamp with tz` | Row creation time |
|
||||
| `updated_at` | `timestamp with tz` | Last modification time |
|
||||
| Column | Type | Description |
|
||||
| ------------ | ------------------------ | ------------------------------------------------------------ |
|
||||
| `id` | `uuid` PK | Row ID |
|
||||
| `user_id` | `text` FK → `user.id` | Owner |
|
||||
| `source_id` | `text` | Source identifier (e.g., `aelis.tfl`, `aelis.weather`) |
|
||||
| `enabled` | `boolean` | Whether this source is active in the user's feed |
|
||||
| `config` | `jsonb` | Source-specific configuration (validated by source at runtime)|
|
||||
| `credentials`| `bytea` | Encrypted OAuth tokens / secrets (AES-256-GCM) |
|
||||
| `created_at` | `timestamp with tz` | Row creation time |
|
||||
| `updated_at` | `timestamp with tz` | Last modification time |
|
||||
|
||||
- Unique constraint on `(user_id, source_id)` — one config row per source per user.
|
||||
- `config` is a generic `jsonb` column. Each source package exports an arktype schema; the backend provider validates the JSON at source construction time.
|
||||
@@ -50,11 +50,11 @@ A `user_sources` table stores per-user source state:
|
||||
|
||||
When a new user is created, seed `user_sources` rows for default sources:
|
||||
|
||||
| Source | Default config |
|
||||
| ---------------- | ----------------------------------------------------------- |
|
||||
| `aelis.location` | `{}` |
|
||||
| `aelis.weather` | `{ "units": "metric", "hourlyLimit": 12, "dailyLimit": 7 }` |
|
||||
| `aelis.tfl` | `{ "lines": <all default lines> }` |
|
||||
| Source | Default config |
|
||||
| ------------------ | --------------------------------------------------------------- |
|
||||
| `aelis.location` | `{}` |
|
||||
| `aelis.weather` | `{ "units": "metric", "hourlyLimit": 12, "dailyLimit": 7 }` |
|
||||
| `aelis.tfl` | `{ "lines": <all default lines> }` |
|
||||
|
||||
- Seeding happens via a Better Auth `after` hook on user creation, or via application-level logic after signup.
|
||||
- Sources requiring credentials (Google Calendar, CalDAV) are **not** enabled by default — they require the user to connect an account first.
|
||||
@@ -67,35 +67,29 @@ Each provider receives the Drizzle DB instance and queries `user_sources` intern
|
||||
|
||||
```typescript
|
||||
class TflSourceProvider implements FeedSourceProvider {
|
||||
constructor(
|
||||
private db: DrizzleDb,
|
||||
private apiKey: string,
|
||||
) {}
|
||||
constructor(private db: DrizzleDb, private apiKey: string) {}
|
||||
|
||||
async feedSourceForUser(userId: string): Promise<TflSource> {
|
||||
const row = await this.db
|
||||
.select()
|
||||
.from(userSources)
|
||||
.where(
|
||||
and(
|
||||
eq(userSources.userId, userId),
|
||||
eq(userSources.sourceId, "aelis.tfl"),
|
||||
eq(userSources.enabled, true),
|
||||
),
|
||||
)
|
||||
.limit(1)
|
||||
async feedSourceForUser(userId: string): Promise<TflSource> {
|
||||
const row = await this.db.select()
|
||||
.from(userSources)
|
||||
.where(and(
|
||||
eq(userSources.userId, userId),
|
||||
eq(userSources.sourceId, "aelis.tfl"),
|
||||
eq(userSources.enabled, true),
|
||||
))
|
||||
.limit(1)
|
||||
|
||||
if (!row[0]) {
|
||||
throw new SourceDisabledError("aelis.tfl", userId)
|
||||
}
|
||||
if (!row[0]) {
|
||||
throw new SourceDisabledError("aelis.tfl", userId)
|
||||
}
|
||||
|
||||
const config = tflSourceConfig(row[0].config ?? {})
|
||||
if (config instanceof type.errors) {
|
||||
throw new Error(`Invalid TFL config for user ${userId}: ${config.summary}`)
|
||||
}
|
||||
const config = tflSourceConfig(row[0].config ?? {})
|
||||
if (config instanceof type.errors) {
|
||||
throw new Error(`Invalid TFL config for user ${userId}: ${config.summary}`)
|
||||
}
|
||||
|
||||
return new TflSource({ ...config, apiKey: this.apiKey })
|
||||
}
|
||||
return new TflSource({ ...config, apiKey: this.apiKey })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -216,19 +210,16 @@ _`feed-source-provider.ts`, `user-session-manager.ts`, `engine/http.ts`, and `lo
|
||||
## Dependencies
|
||||
|
||||
**Add:**
|
||||
|
||||
- `drizzle-orm`
|
||||
- `drizzle-kit` (dev)
|
||||
|
||||
**Remove:**
|
||||
|
||||
- `pg`
|
||||
- `@types/pg` (dev)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
**Add to `.env.example`:**
|
||||
|
||||
- `CREDENTIALS_ENCRYPTION_KEY` — 32-byte hex or base64 key for AES-256-GCM
|
||||
|
||||
## Open Questions (Deferred)
|
||||
|
||||
@@ -39,14 +39,14 @@ Source IDs use reverse domain notation. Built-in sources use `aelis.<name>`. Thi
|
||||
|
||||
Action IDs are descriptive verb-noun pairs in kebab-case, scoped to their source. The globally unique form is `<sourceId>/<actionId>`.
|
||||
|
||||
| Source ID | Action IDs |
|
||||
| ---------------- | -------------------------------------------------------------- |
|
||||
| Source ID | Action IDs |
|
||||
| --------------- | -------------------------------------------------------------- |
|
||||
| `aelis.location` | `update-location` (migrated from `pushLocation()`) |
|
||||
| `aelis.tfl` | `set-lines-of-interest` (migrated from `setLinesOfInterest()`) |
|
||||
| `aelis.weather` | _(none)_ |
|
||||
| `com.spotify` | `play-track`, `pause-playback`, `skip-track`, `like-track` |
|
||||
| `com.spotify` | `play-track`, `pause-playback`, `skip-track`, `like-track` |
|
||||
| `aelis.calendar` | `rsvp`, `create-event` |
|
||||
| `com.todoist` | `complete-task`, `snooze-task` |
|
||||
| `com.todoist` | `complete-task`, `snooze-task` |
|
||||
|
||||
This means existing source packages need their `id` updated (e.g., `"location"` → `"aelis.location"`).
|
||||
|
||||
@@ -241,16 +241,8 @@ class SpotifySource implements FeedSource<SpotifyFeedItem> {
|
||||
type: "View",
|
||||
className: "flex-1",
|
||||
children: [
|
||||
{
|
||||
type: "Text",
|
||||
className: "font-semibold text-black dark:text-white",
|
||||
text: track.name,
|
||||
},
|
||||
{
|
||||
type: "Text",
|
||||
className: "text-sm text-gray-500 dark:text-gray-400",
|
||||
text: track.artist,
|
||||
},
|
||||
{ type: "Text", className: "font-semibold text-black dark:text-white", text: track.name },
|
||||
{ type: "Text", className: "text-sm text-gray-500 dark:text-gray-400", text: track.artist },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -269,8 +261,8 @@ class SpotifySource implements FeedSource<SpotifyFeedItem> {
|
||||
4. `FeedSource.listActions()` is a required method returning `Record<string, ActionDefinition>` (empty record if no actions)
|
||||
5. `FeedSource.executeAction()` is a required method (no-op for sources without actions)
|
||||
6. `FeedItem.actions` is an optional readonly array of `ItemAction`
|
||||
6b. `FeedItem.ui` is an optional json-render tree describing server-driven UI
|
||||
6c. `FeedItem.slots` is an optional record of named LLM-fillable slots
|
||||
6b. `FeedItem.ui` is an optional json-render tree describing server-driven UI
|
||||
6c. `FeedItem.slots` is an optional record of named LLM-fillable slots
|
||||
7. `FeedEngine.executeAction()` routes to correct source, returns `ActionResult`
|
||||
8. `FeedEngine.listActions()` aggregates actions from all sources
|
||||
9. Existing tests pass unchanged (all changes are additive)
|
||||
|
||||
@@ -11,23 +11,17 @@
|
||||
"lint": "oxlint .",
|
||||
"lint:fix": "oxlint --fix .",
|
||||
"format": "oxfmt --write .",
|
||||
"format:check": "oxfmt --check .",
|
||||
"prepare": "husky"
|
||||
"format:check": "oxfmt --check ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@json-render/core": "^0.12.1",
|
||||
"@nym.sh/jrx": "^0.2.0",
|
||||
"@types/bun": "latest",
|
||||
"@typescript/native-preview": "^7.0.0-dev.20260412.1",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.4.0",
|
||||
"oxfmt": "^0.24.0",
|
||||
"oxlint": "^1.39.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^6"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx,js,jsx,json,css,md}": "oxfmt --write"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
"scripts": {
|
||||
"test": "bun test ."
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nym.sh/jrx": "*",
|
||||
"@json-render/core": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@json-render/core": "*",
|
||||
"@nym.sh/jrx": "*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import type { Context, FeedEnhancement, FeedItem, FeedPostProcessor } from "@aelis/core"
|
||||
|
||||
import { TimeRelevance } from "@aelis/core"
|
||||
|
||||
import type { CalDavEventData } from "@aelis/source-caldav"
|
||||
import type { CalendarEventData } from "@aelis/source-google-calendar"
|
||||
import type { CurrentWeatherData } from "@aelis/source-weatherkit"
|
||||
|
||||
import { TimeRelevance } from "@aelis/core"
|
||||
import { CalDavFeedItemType } from "@aelis/source-caldav"
|
||||
import { CalendarFeedItemType } from "@aelis/source-google-calendar"
|
||||
import { TflFeedItemType } from "@aelis/source-tfl"
|
||||
import { WeatherFeedItemType } from "@aelis/source-weatherkit"
|
||||
|
||||
|
||||
export const TimePeriod = {
|
||||
Morning: "morning",
|
||||
Afternoon: "afternoon",
|
||||
@@ -25,6 +28,7 @@ export const DayType = {
|
||||
|
||||
export type DayType = (typeof DayType)[keyof typeof DayType]
|
||||
|
||||
|
||||
const PRE_MEETING_WINDOW_MS = 30 * 60 * 1000
|
||||
const TRANSITION_WINDOW_MS = 30 * 60 * 1000
|
||||
|
||||
@@ -140,6 +144,7 @@ export function createTimeOfDayEnhancer(options?: TimeOfDayEnhancerOptions): Fee
|
||||
return timeOfDayEnhancer
|
||||
}
|
||||
|
||||
|
||||
export function getTimePeriod(date: Date): TimePeriod {
|
||||
const hour = date.getHours()
|
||||
if (hour >= 22 || hour < 6) return TimePeriod.Night
|
||||
@@ -177,9 +182,7 @@ function getNextPeriodBoundary(date: Date): { period: TimePeriod; msUntil: numbe
|
||||
* Google Calendar uses `startTime`, CalDAV uses `startDate`.
|
||||
*/
|
||||
function getEventStartTime(data: CalendarEventData | CalDavEventData): Date {
|
||||
return "startTime" in data
|
||||
? (data as CalendarEventData).startTime
|
||||
: (data as CalDavEventData).startDate
|
||||
return "startTime" in data ? (data as CalendarEventData).startTime : (data as CalDavEventData).startDate
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,6 +196,7 @@ function hasPrecipitationOrExtreme(item: FeedItem): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
interface PreMeetingInfo {
|
||||
/** IDs of calendar items starting within the pre-meeting window */
|
||||
upcomingMeetingIds: Set<string>
|
||||
@@ -221,6 +225,7 @@ function detectPreMeetingItems(items: FeedItem[], now: Date): PreMeetingInfo {
|
||||
return { upcomingMeetingIds, hasLocationMeeting }
|
||||
}
|
||||
|
||||
|
||||
function findFirstEventOfDay(items: FeedItem[], now: Date): string | null {
|
||||
let earliest: { id: string; time: number } | null = null
|
||||
|
||||
@@ -247,6 +252,7 @@ function findFirstEventOfDay(items: FeedItem[], now: Date): string | null {
|
||||
return earliest?.id ?? null
|
||||
}
|
||||
|
||||
|
||||
function applyMorningWeekday(
|
||||
items: FeedItem[],
|
||||
boost: Record<string, number>,
|
||||
@@ -409,6 +415,7 @@ function applyNight(items: FeedItem[], boost: Record<string, number>, suppress:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function applyPreMeetingOverrides(
|
||||
items: FeedItem[],
|
||||
preMeeting: PreMeetingInfo,
|
||||
@@ -480,6 +487,7 @@ function applyWindDown(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function applyTransitionLookahead(
|
||||
items: FeedItem[],
|
||||
now: Date,
|
||||
@@ -536,6 +544,7 @@ function getNextPeriodBoostTargets(period: TimePeriod, dayType: DayType): Readon
|
||||
return targets
|
||||
}
|
||||
|
||||
|
||||
function applyWeatherTimeCorrelation(
|
||||
items: FeedItem[],
|
||||
period: TimePeriod,
|
||||
@@ -553,11 +562,7 @@ function applyWeatherTimeCorrelation(
|
||||
break
|
||||
}
|
||||
case WeatherFeedItemType.Current:
|
||||
if (
|
||||
period === TimePeriod.Morning &&
|
||||
dayType === DayType.Weekday &&
|
||||
hasPrecipitationOrExtreme(item)
|
||||
) {
|
||||
if (period === TimePeriod.Morning && dayType === DayType.Weekday && hasPrecipitationOrExtreme(item)) {
|
||||
boost[item.id] = (boost[item.id] ?? 0) + 0.1
|
||||
}
|
||||
if (period === TimePeriod.Evening && hasEveningEventWithLocation) {
|
||||
@@ -586,3 +591,5 @@ function hasEveningCalendarEventWithLocation(items: FeedItem[], now: Date): bool
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
* Writes feed items (with slots) to scripts/.cache/feed-items.json for inspection.
|
||||
*/
|
||||
|
||||
import { Context } from "@aelis/core"
|
||||
import { mkdirSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
|
||||
import { Context } from "@aelis/core"
|
||||
|
||||
import { CalDavSource } from "../src/index.ts"
|
||||
|
||||
const serverUrl = prompt("CalDAV server URL:")
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"fetch-fixtures": "bun run scripts/fetch-fixtures.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aelis/components": "workspace:*",
|
||||
"@aelis/core": "workspace:*",
|
||||
"@aelis/components": "workspace:*",
|
||||
"@aelis/source-location": "workspace:*",
|
||||
"arktype": "^2.1.0"
|
||||
},
|
||||
|
||||
@@ -9,14 +9,15 @@
|
||||
* Usage: bun packages/aelis-source-weatherkit/scripts/query.ts
|
||||
*/
|
||||
|
||||
import { Context } from "@aelis/core"
|
||||
import { LocationKey } from "@aelis/source-location"
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { createInterface } from "node:readline/promises"
|
||||
|
||||
import { WeatherSource, Units } from "../src/weather-source"
|
||||
import { Context } from "@aelis/core"
|
||||
import { LocationKey } from "@aelis/source-location"
|
||||
|
||||
import { DefaultWeatherKitClient } from "../src/weatherkit"
|
||||
import { WeatherSource, Units } from "../src/weather-source"
|
||||
|
||||
const SCRIPT_DIR = import.meta.dirname
|
||||
const CACHE_DIR = join(SCRIPT_DIR, ".cache")
|
||||
|
||||
@@ -4,12 +4,7 @@ import { Context } from "@aelis/core"
|
||||
import { LocationKey } from "@aelis/source-location"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
import type {
|
||||
WeatherKitClient,
|
||||
WeatherKitResponse,
|
||||
HourlyForecast,
|
||||
DailyForecast,
|
||||
} from "./weatherkit"
|
||||
import type { WeatherKitClient, WeatherKitResponse, HourlyForecast, DailyForecast } from "./weatherkit"
|
||||
|
||||
import fixture from "../fixtures/san-francisco.json"
|
||||
import { WeatherFeedItemType, type DailyWeatherData, type HourlyWeatherData } from "./feed-items"
|
||||
|
||||
@@ -3,12 +3,7 @@ import type { ActionDefinition, ContextEntry, FeedItemSignals, FeedSource } from
|
||||
import { Context, TimeRelevance, UnknownActionError } from "@aelis/core"
|
||||
import { LocationKey } from "@aelis/source-location"
|
||||
|
||||
import {
|
||||
WeatherFeedItemType,
|
||||
type DailyWeatherEntry,
|
||||
type HourlyWeatherEntry,
|
||||
type WeatherFeedItem,
|
||||
} from "./feed-items"
|
||||
import { WeatherFeedItemType, type DailyWeatherEntry, type HourlyWeatherEntry, type WeatherFeedItem } from "./feed-items"
|
||||
import currentWeatherInsightPrompt from "./prompts/current-weather-insight.txt"
|
||||
import { WeatherKey, type Weather } from "./weather-context"
|
||||
import {
|
||||
|
||||
Reference in New Issue
Block a user