diff --git a/packages/ui/build/css/source/global.css b/packages/ui/build/css/source/global.css index 7116e19977a..93545e90fee 100644 --- a/packages/ui/build/css/source/global.css +++ b/packages/ui/build/css/source/global.css @@ -1,6 +1,6 @@ /** * Do not edit directly - * Generated on Mon, 17 Jul 2023 15:59:23 GMT + * Generated on Fri, 28 Jul 2023 11:07:13 GMT */ :root { diff --git a/packages/ui/build/css/themes/dark.css b/packages/ui/build/css/themes/dark.css index 3748b607d97..d78986e5532 100644 --- a/packages/ui/build/css/themes/dark.css +++ b/packages/ui/build/css/themes/dark.css @@ -1,13 +1,15 @@ /** * Do not edit directly - * Generated on Mon, 17 Jul 2023 15:59:23 GMT + * Generated on Fri, 28 Jul 2023 11:07:13 GMT */ .dark { --brand-default: 152.9deg 60% 52.9%; - --brand-400: 153.6deg 41% 12%; - --brand-300: 152.7deg 48.5% 26.7%; - --brand-200: 148.5deg 43.5% 82%; + --brand-600: 152.9deg 60% 50%; + --brand-500: 153.5deg 61.8% 21.6%; + --brand-400: 153.3deg 65.2% 13.5%; + --brand-300: 153.8deg 69.6% 9%; + --brand-200: 152.5deg 75% 6.3%; --warning-default: 38.9deg 100% 57.1%; --warning-600: 38.9deg 89.8% 49.8%; --warning-500: 34.8deg 90.9% 21.6%; diff --git a/packages/ui/build/css/themes/light.css b/packages/ui/build/css/themes/light.css index 41927d7bc60..422e4e2e9f9 100644 --- a/packages/ui/build/css/themes/light.css +++ b/packages/ui/build/css/themes/light.css @@ -1,13 +1,15 @@ /** * Do not edit directly - * Generated on Mon, 17 Jul 2023 15:59:23 GMT + * Generated on Fri, 28 Jul 2023 11:07:13 GMT */ .light { - --brand-primary: 152.9deg 60% 52.9%; - --brand-400: 153.7deg 68.7% 83.7%; - --brand-300: 153deg 46% 65.9%; - --brand-200: 148.5deg 43.5% 82%; + --brand-default: 152.9deg 60% 52.9%; + --brand-600: 153.2deg 49.7% 33.5%; + --brand-500: 148.7deg 42.7% 69.2%; + --brand-400: 149.1deg 59.3% 88.4%; + --brand-300: 148.6deg 77.8% 94.7%; + --brand-200: 156deg 71.4% 98.6%; --warning-default: 38.9deg 100% 57.1%; --warning-600: 30.2deg 100% 33.9%; --warning-500: 36.3deg 85.7% 67.1%; diff --git a/packages/ui/build/css/tw-extend/color.js b/packages/ui/build/css/tw-extend/color.js index e9a280e3825..6c17c9aa676 100644 --- a/packages/ui/build/css/tw-extend/color.js +++ b/packages/ui/build/css/tw-extend/color.js @@ -529,15 +529,23 @@ module.exports = { }, "brand-200": { cssVariable: "var(--brand-200)", - value: "hsl(148.5, 43.5%, 82%)" + value: "hsl(152.5, 75%, 6.3%)" }, "brand-300": { cssVariable: "var(--brand-300)", - value: "hsl(152.70000000000005, 48.5%, 26.7%)" + value: "hsl(153.79999999999995, 69.6%, 9%)" }, "brand-400": { cssVariable: "var(--brand-400)", - value: "hsl(153.60000000000002, 41%, 12%)" + value: "hsl(153.29999999999995, 65.2%, 13.5%)" +}, +"brand-500": { + cssVariable: "var(--brand-500)", + value: "hsl(153.5, 61.8%, 21.6%)" +}, +"brand-600": { + cssVariable: "var(--brand-600)", + value: "hsl(152.89999999999998, 60%, 50%)" }, "brand-DEFAULT": { cssVariable: "var(--brand-default)", diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index ae5581d03ea..30a8e333f7f 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -119,6 +119,8 @@ export { AlertDescription as AlertDescription_Shadcn_, } from './src/components/shadcn/ui/alert' +export { Input as Input_Shadcn_ } from './src/components/shadcn/ui/input' + export { useFormField as useFormField_Shadcn_, Form as Form_Shadcn_, diff --git a/packages/ui/src/components/shadcn/ui/input.tsx b/packages/ui/src/components/shadcn/ui/input.tsx index 3455be14a2b..ba78c34aa38 100644 --- a/packages/ui/src/components/shadcn/ui/input.tsx +++ b/packages/ui/src/components/shadcn/ui/input.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { cn } from 'src/lib/utils' +import { cn } from '@ui/lib/utils' export interface InputProps extends React.InputHTMLAttributes {} @@ -12,7 +12,8 @@ const Input = React.forwardRef( { + const [step, setStep] = useState(1) + + useEffect(() => { + const interval = setInterval(() => { + setStep((step) => { + return (step % 5) + 1 + }) + }, 2000) + return () => clearInterval(interval) + }, []) + + return ( +
+
+
+
+
+
+
+
+ ) +} + +export { AiIconAnimation } diff --git a/studio/components/interfaces/SQLEditor/MonacoEditor.tsx b/studio/components/interfaces/SQLEditor/MonacoEditor.tsx index e58bf3f1bdd..1a9e1fccaa4 100644 --- a/studio/components/interfaces/SQLEditor/MonacoEditor.tsx +++ b/studio/components/interfaces/SQLEditor/MonacoEditor.tsx @@ -4,6 +4,7 @@ import { editor } from 'monaco-editor' import { MutableRefObject, useRef, useState } from 'react' import { useSqlEditorStateSnapshot } from 'state/sql-editor' import { IStandaloneCodeEditor } from './SQLEditor.types' +import { cn } from 'ui' export type MonacoEditorProps = { id: string @@ -11,6 +12,7 @@ export type MonacoEditorProps = { isExecuting: boolean autoFocus?: boolean executeQuery: () => void + className?: string } const MonacoEditor = ({ @@ -18,6 +20,7 @@ const MonacoEditor = ({ editorRef, isExecuting, autoFocus = true, + className, executeQuery, }: MonacoEditorProps) => { const snap = useSqlEditorStateSnapshot({ sync: true }) @@ -73,7 +76,7 @@ const MonacoEditor = ({ return ( <> import('./MonacoEditor'), { ssr: false }) @@ -136,6 +139,8 @@ const SQLEditor = () => { const [sqlDiff, setSqlDiff] = useState() const inputRef = useRef(null) + const [aiOpen, setAiOpen] = useState(false) + const [isAISettingsOpen, setIsAISettingsOpen] = useState(false) const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false) @@ -417,248 +422,333 @@ const SQLEditor = () => { executeQuery(true) }} /> -
- - - {!isEditSqlLoading ? ( - - setAiInput(e.currentTarget.value)} - disabled={isDiffOpen} - inputRef={inputRef} - icon={ -
- - - +
+ {aiOpen && ( + +
+ {/*
*/} + {/*
+
+
+
+
+
*/} - {debugSolution && ( -
- {debugSolution} -
- )} -
- } - inputClassName="w-full !border-brand-900 border-none bg-transparent !shadow-none py-4 focus:!ring-0 placeholder:text-scale-900 pr-20" - iconContainerClassName="transition text-scale-800 text-brand-900" - placeholder={!debugSolution ? 'Ask Supabase AI to modify your query' : ''} - className="w-full" - actions={ -
- {isDiffOpen ? ( - <> -
- - diffType !== selectedDiffType) - .map((diffType) => ( - { - setSelectedDiffType(diffType) - switch (diffType) { - case DiffType.Modification: - return compareAsModification() - case DiffType.Addition: - return compareAsAddition() - case DiffType.NewSnippet: - return compareAsNewSnippet() - default: - throw new Error(`Unknown diff type '${diffType}'`) - } - }} - > - {getDiffTypeDropdownLabel(diffType)} - - ))} - > -
- - - ) : ( - <> - - setIsAISettingsOpen(true)} - /> - - )} -
- } - onKeyPress={async (e) => { - if (e.key === 'Enter') { - try { - const prompt = e.currentTarget.value - - if (!prompt) { - return - } - - const sql = editorRef.current?.getValue() - - if (!sql) { - return - } - - const { sql: modifiedSql } = await editSql({ - prompt, - sql: sql.replace(sqlAiDisclaimerComment, '').trim(), - entityDefinitions, - }) - - const formattedSql = - sqlAiDisclaimerComment + - '\n\n' + - format(modifiedSql, { - language: 'postgresql', - keywordCase: 'lower', - }) - - // TODO: show error - if (formattedSql.trim() === sql.trim()) { - ui.setNotification({ - category: 'error', - message: 'Unable to edit SQL. Try adding more details to your prompt.', - }) - return - } - - setSqlDiff({ - original: sql, - modified: formattedSql, - }) - } catch (error: unknown) { - if (isError(error)) { - ui.setNotification({ - category: 'error', - message: error.message, - }) - } - } - } - }} - /> - - ) : ( - - - + {/*
+
+
+
+
+
*/} + {/*
*/} + {/*
+ + - - Thinking... - - - )} - - +
*/} + + + + + {debugSolution && ( +
+ {debugSolution} +
+ )} + {!isEditSqlLoading ? ( + + setAiInput(e.currentTarget.value)} + disabled={isDiffOpen} + ref={inputRef} + className={cn( + '!p-0 bg-transparent border-transparent text-sm text-brand placeholder:text-brand-600 focus:!ring-0', + 'focus-visible:ring-0 focus-visible:ring-offset-0', + 'appearance-none outline-none' + // "after:content-['_↗']", + // 'after:relative after:w-10 after:h-10', + // 'after:placeholder:content-["hello"]' + )} + placeholder={!debugSolution ? 'Ask Supabase AI to modify your query' : ''} + onKeyPress={async (e) => { + if (e.key === 'Enter') { + console.log('entering') + try { + const prompt = e.currentTarget.value + + if (!prompt) { + return + } + + const sql = editorRef.current?.getValue() + + if (!sql) { + return + } + + const { sql: modifiedSql } = await editSql({ + prompt, + sql: sql.replace(sqlAiDisclaimerComment, '').trim(), + entityDefinitions, + }) + + const formattedSql = + sqlAiDisclaimerComment + + '\n\n' + + format(modifiedSql, { + language: 'postgresql', + keywordCase: 'lower', + }) + + // TODO: show error + if (formattedSql.trim() === sql.trim()) { + ui.setNotification({ + category: 'error', + message: + 'Unable to edit SQL. Try adding more details to your prompt.', + }) + return + } + + setSqlDiff({ + original: sql, + modified: formattedSql, + }) + } catch (error: unknown) { + if ( + error && + typeof error === 'object' && + 'message' in error && + typeof error.message === 'string' + ) { + ui.setNotification({ + category: 'error', + message: error.message, + }) + } + } + } + }} + /> + {/*
+ +
*/} +
+ ) : ( + + + Thinking... + + + )} +
+
+ {isDiffOpen ? ( + <> +
+ + diffType !== selectedDiffType) + .map((diffType) => ( + { + setSelectedDiffType(diffType) + switch (diffType) { + case DiffType.Modification: + return compareAsModification() + case DiffType.Addition: + return compareAsAddition() + case DiffType.NewSnippet: + return compareAsNewSnippet() + default: + throw new Error(`Unknown diff type '${diffType}'`) + } + }} + > + {getDiffTypeDropdownLabel(diffType)} + + ))} + > +
+ + + ) : ( + <> +
+ +
+ + + + + )} +
+
+ + )} { collapsed={isUtilityPanelCollapsed ? 1 : undefined} onDragEnd={onDragEnd} > -
+
+ {!aiOpen && ( + + )} + {isLoading ? (
Loading...
) : ( diff --git a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx new file mode 100644 index 00000000000..859a2ab16f0 --- /dev/null +++ b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLQuickstarts.tsx @@ -0,0 +1,284 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { partition } from 'lodash' +import { observer } from 'mobx-react-lite' + +import { useParams, useTelemetryProps } from 'common' +import { + SQL_TEMPLATES, + sqlAiDisclaimerComment, +} from 'components/interfaces/SQLEditor/SQLEditor.constants' +import { useSqlGenerateMutation } from 'data/ai/sql-generate-mutation' +import { SqlSnippet } from 'data/content/sql-snippets-query' +import { useEntityDefinitionsQuery } from 'data/database/entity-definitions-query' +import { isError } from 'data/utils/error-check' +import { motion } from 'framer-motion' +import { + useCheckPermissions, + useLocalStorageQuery, + useSelectedOrganization, + useSelectedProject, + useStore, +} from 'hooks' +import { IS_PLATFORM } from 'lib/constants' +import { uuidv4 } from 'lib/helpers' +import { useProfile } from 'lib/profile' +import Telemetry from 'lib/telemetry' +import { useRouter } from 'next/router' +import { useState } from 'react' +import { format } from 'sql-formatter' +import { useSqlEditorStateSnapshot } from 'state/sql-editor' +import { AiIcon, IconCornerDownLeft, IconSettings, Input } from 'ui' +import AISettingsModal from '../AISettingsModal' +import { createSqlSnippetSkeleton } from '../SQLEditor.utils' +import SQLCard from './SQLCard' + +const SQLQuickstarts = observer(() => { + const { ui } = useStore() + const { ref } = useParams() + const router = useRouter() + const { profile } = useProfile() + const [sql, quickStart] = partition(SQL_TEMPLATES, { type: 'template' }) + const { mutateAsync: generateSql, isLoading: isSqlGenerateLoading } = useSqlGenerateMutation() + const [isAISettingsOpen, setIsAISettingsOpen] = useState(false) + const selectedOrganization = useSelectedOrganization() + const selectedProject = useSelectedProject() + const isOptedInToAI = + selectedOrganization?.opt_in_tags?.includes('AI_SQL_GENERATOR_OPT_IN') ?? false + const [isOptedInToAISchema] = useLocalStorageQuery('supabase_sql-editor-ai-schema', false) + + const includeSchemaMetadata = (isOptedInToAI || !IS_PLATFORM) && isOptedInToAISchema + + const { data } = useEntityDefinitionsQuery( + { + projectRef: selectedProject?.ref, + connectionString: selectedProject?.connectionString, + }, + { enabled: includeSchemaMetadata } + ) + + const entityDefinitions = includeSchemaMetadata ? data?.map((def) => def.sql.trim()) : undefined + + const telemetryProps = useTelemetryProps() + const snap = useSqlEditorStateSnapshot() + const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', { + resource: { type: 'sql', owner_id: profile?.id }, + subject: { id: profile?.id }, + }) + + // [Joshen TODO] Removed optimistic query creation logic for now, need to figure out + // how to do that after using ids as part of the URL + const handleNewQuery = async (sql: string, name: string) => { + if (!ref) return console.error('Project ref is required') + if (!canCreateSQLSnippet) { + return ui.setNotification({ + category: 'info', + message: 'Your queries will not be saved as you do not have sufficient permissions', + }) + } + + try { + const snippet = createSqlSnippetSkeleton({ name, sql, owner_id: profile?.id }) + const data = { ...snippet, id: uuidv4() } + snap.addSnippet(data as SqlSnippet, ref, true) + router.push(`/project/${ref}/sql/${data.id}`) + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to create new query: ${error.message}`, + }) + } + } + + return ( + <> + setIsAISettingsOpen(false)} /> +
+
+ + What do you want to build? + +
+ {!isSqlGenerateLoading ? ( + + { + setTimeout(() => { + inputElement?.focus() + }, 200) + }} + icon={ + + + + } + inputClassName="w-full border-none py-4 focus:!ring-0 pr-20" + iconContainerClassName="transition text-scale-800 text-brand-900" + placeholder="Ask Supabase AI to build a query" + className="w-full" + onKeyPress={async (e) => { + if (e.key === 'Enter') { + try { + const value = e.currentTarget.value + + if (!value) { + return + } + + const { title, sql } = await generateSql({ + prompt: e.currentTarget.value, + entityDefinitions, + }) + + const formattedSql = + sqlAiDisclaimerComment + + '\n\n' + + format(sql, { + language: 'postgresql', + keywordCase: 'lower', + }) + + await handleNewQuery(formattedSql, title) + } catch (error: unknown) { + if (isError(error)) { + ui.setNotification({ + category: 'error', + message: error.message, + }) + } + } + } + }} + actions={ +
+ + setIsAISettingsOpen(true)} + /> +
+ } + /> +
+ ) : ( + + + + + + )} +
+
+
+
+

Quick start

+

+ While we're in beta, we want to offer a quick way to explore Supabase. While we build + importers, check out these simple starters. +

+

+ Click on any script to fill the query box, modify the script, then click + Run. +

+
+
+ {quickStart.map((x) => ( + { + handleNewQuery(sql, title) + Telemetry.sendEvent( + { + category: 'quickstart', + action: 'quickstart_clicked', + label: x.title, + }, + telemetryProps, + router + ) + }} + /> + ))} +
+
+
+ + ) +}) + +export default SQLQuickstarts diff --git a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx index 9a35c2b366a..de51992b05d 100644 --- a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx +++ b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx @@ -273,41 +273,6 @@ const SQLTemplates = observer(() => { ))}
-
-
-

Quick start

-

- While we're in beta, we want to offer a quick way to explore Supabase. While we build - importers, check out these simple starters. -

-

- Click on any script to fill the query box, modify the script, then click - Run. -

-
-
- {quickStart.map((x) => ( - { - handleNewQuery(sql, title) - Telemetry.sendEvent( - { - category: 'quickstart', - action: 'quickstart_clicked', - label: x.title, - }, - telemetryProps, - router - ) - }} - /> - ))} -
-
) diff --git a/studio/components/interfaces/SQLEditor/index.ts b/studio/components/interfaces/SQLEditor/index.ts index 5ce7f8a5bc0..47aa5345512 100644 --- a/studio/components/interfaces/SQLEditor/index.ts +++ b/studio/components/interfaces/SQLEditor/index.ts @@ -1,3 +1,4 @@ import SQLTemplates from './SQLTemplates/SQLTemplates' +import SQLQuickstarts from './SQLTemplates/SQLQuickstarts' -export { SQLTemplates } +export { SQLTemplates, SQLQuickstarts } diff --git a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx index c68b4c0cc83..3eb62ab0572 100644 --- a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx +++ b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx @@ -14,7 +14,7 @@ import { useCheckPermissions, useStore } from 'hooks' import { uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' import { useSnippets, useSqlEditorStateSnapshot } from 'state/sql-editor' -import { IconSearch, IconX, Input, Menu, cn } from 'ui' +import { Button, IconSearch, IconX, Input, Menu, cn } from 'ui' import QueryItem from './QueryItem' const SideBarContent = observer(() => { @@ -98,20 +98,31 @@ const SideBarContent = observer(() => { ) : isSuccess ? (
- - { handleNewQuery() }} - /> + icon={} + > + New query +
+
+ + +
{favorites.length >= 1 && (
@@ -158,52 +169,58 @@ const SideBarContent = observer(() => {
)} - {queries.length >= 1 && ( -
-
- + +
+
+ + +
+ {isSnippetsFilterOpen && ( +
+ } + placeholder="Filter" + disabled={isLoading} + onChange={(e) => setSnippetsFilterString(e.target.value)} + value={snippetsFilterString} + actions={ + snippetsFilterString && ( + setSnippetsFilterString('')} + /> + ) + } />
- {isSnippetsFilterOpen && ( -
- } - placeholder="Filter" - disabled={isLoading} - onChange={(e) => setSnippetsFilterString(e.target.value)} - value={snippetsFilterString} - actions={ - snippetsFilterString && ( - setSnippetsFilterString('')} - /> - ) - } - /> -
- )} + )} + {snippetsTabs.length > 0 ? (
{snippetsTabs.map((tabInfo) => { const { id } = tabInfo || {} return })}
-
- )} + ) : ( +
+ No queries found + +
+ )} +
+ {snippetsFilterString.length > 0 && favouriteTabs.length === 0 && snippetsTabs.length === 0 && ( diff --git a/studio/pages/project/[ref]/sql/quickstarts.tsx b/studio/pages/project/[ref]/sql/quickstarts.tsx new file mode 100644 index 00000000000..62fd8b8337d --- /dev/null +++ b/studio/pages/project/[ref]/sql/quickstarts.tsx @@ -0,0 +1,12 @@ +import { observer } from 'mobx-react-lite' +import { NextPageWithLayout } from 'types' +import { SQLEditorLayout } from 'components/layouts' +import { SQLQuickstarts } from 'components/interfaces/SQLEditor' + +const SqlEditorWelcome: NextPageWithLayout = () => { + return +} + +SqlEditorWelcome.getLayout = (page) => {page} + +export default observer(SqlEditorWelcome) diff --git a/studio/styles/monaco.scss b/studio/styles/monaco.scss index edb6be20dd6..0ef8126a48f 100644 --- a/studio/styles/monaco.scss +++ b/studio/styles/monaco.scss @@ -46,3 +46,618 @@ span.th.tr.td.textarea { background: var(--vscode-quickInput-background); } } + +/* +* STEP ONE +*/ + +@keyframes step1_square1 { + 0% { + transform: rotate(45deg); + top: 10px; + left: 10px; + width: 8px; + height: 8px; + border-radius: 100%; + } + + 100% { + transform: rotate(0deg); + top: 0px; + left: 0px; + width: 16px; + height: 16px; + } +} + +@keyframes step1_square2 { + 0% { + transform: rotate(45deg); + top: 10px; + left: 10px; + width: 8px; + height: 8px; + border-radius: 100%; + } + + 100% { + transform: rotate(0deg); + top: 0px; + left: 8px; + width: 16px; + height: 16px; + } +} + +@keyframes step1_square3 { + 0% { + transform: rotate(45deg); + top: 10px; + left: 10px; + width: 8px; + height: 8px; + border-radius: 100%; + } + + 100% { + transform: rotate(0deg); + top: 8px; + left: 0px; + width: 16px; + height: 16px; + } +} + +@keyframes step1_square4 { + 0% { + transform: rotate(45deg); + top: 10px; + left: 10px; + width: 8px; + height: 8px; + border-radius: 100%; + } + + 100% { + transform: rotate(0deg); + top: 8px; + left: 8px; + width: 16px; + height: 16px; + } +} + +/* +* STEP TWO +*/ + +@keyframes step2_square1 { + 0% { + transform: rotate(0deg); + top: 0px; + left: 0px; + width: 16px; + height: 16px; + } + 100% { + transform: rotate(45deg); + top: 0px; + left: 0px; + width: 8px; + height: 8px; + } +} + +@keyframes step2_square2 { + 0% { + transform: rotate(0deg); + top: 0px; + left: 8px; + width: 16px; + height: 16px; + } + 100% { + transform: rotate(45deg); + top: 0px; + left: 16px; + width: 8px; + height: 8px; + } +} + +@keyframes step2_square3 { + 0% { + transform: rotate(0deg); + top: 8px; + left: 0px; + width: 16px; + height: 16px; + } + 100% { + transform: rotate(45deg); + top: 16px; + left: 0px; + width: 8px; + height: 8px; + } +} + +@keyframes step2_square4 { + 0% { + transform: rotate(0deg); + top: 8px; + left: 8px; + width: 16px; + height: 16px; + } + 100% { + transform: rotate(45deg); + top: 16px; + left: 16px; + width: 8px; + height: 8px; + } +} + +/* +* STEP THREE +*/ + +@keyframes step3_square1 { + 0% { + transform: rotate(45deg); + top: 0px; + left: 0px; + width: 8px; + height: 8px; + } + 100% { + transform: rotate(0deg); + top: 0px; + left: 0px; + width: 6px; + height: 6px; + border-radius: 100%; + } +} + +@keyframes step3_square2 { + 0% { + transform: rotate(45deg); + top: 0px; + left: 16px; + width: 8px; + height: 8px; + } + 100% { + transform: rotate(0deg); + top: 0px; + left: 18px; + width: 6px; + height: 6px; + border-radius: 100%; + } +} + +@keyframes step3_square3 { + 0% { + transform: rotate(45deg); + top: 16px; + left: 0px; + width: 8px; + height: 8px; + } + 100% { + transform: rotate(0deg); + top: 18px; + left: 0px; + width: 6px; + height: 6px; + border-radius: 100%; + } +} + +@keyframes step3_square4 { + 0% { + transform: rotate(45deg); + top: 16px; + left: 16px; + width: 8px; + height: 8px; + } + 100% { + transform: rotate(0deg); + top: 18px; + left: 18px; + width: 6px; + height: 6px; + border-radius: 100%; + } +} + +/* +* STEP FOUR +*/ + +@keyframes step4_square1 { + 0% { + transform: rotate(0deg); + top: 0px; + left: 0px; + width: 6px; + height: 6px; + border-radius: 100%; + } + 100% { + transform: rotate(0deg); + top: 0px; + left: 8px; + width: 8px; + height: 8px; + border-radius: 100%; + } +} + +@keyframes step4_square2 { + 0% { + transform: rotate(0deg); + top: 0px; + left: 18px; + width: 6px; + height: 6px; + border-radius: 100%; + } + 100% { + transform: rotate(0deg); + top: 8px; + left: 16px; + width: 8px; + height: 8px; + border-radius: 100%; + } +} + +@keyframes step4_square3 { + 0% { + transform: rotate(0deg); + top: 18px; + left: 0px; + width: 6px; + height: 6px; + border-radius: 100%; + } + 100% { + transform: rotate(0deg); + top: 8px; + left: 0px; + width: 8px; + height: 8px; + border-radius: 100%; + } +} + +@keyframes step4_square4 { + 0% { + transform: rotate(0deg); + top: 18px; + left: 18px; + width: 6px; + height: 6px; + border-radius: 100%; + } + 100% { + transform: rotate(0deg); + top: 16px; + left: 8px; + width: 8px; + height: 8px; + border-radius: 100%; + } +} + +// STEP FIVE + +@keyframes step5_square1 { + 0% { + transform: rotate(0deg); + top: 0px; + left: 8px; + width: 8px; + height: 8px; + border-radius: 100%; + } + 100% { + transform: rotate(45deg); + top: 8px; + left: 8px; + width: 8px; + height: 8px; + border-radius: 100%; + } +} + +@keyframes step5_square2 { + 0% { + transform: rotate(0deg); + top: 8px; + left: 16px; + width: 8px; + height: 8px; + border-radius: 100%; + } + 100% { + transform: rotate(45deg); + top: 8px; + left: 8px; + width: 8px; + height: 8px; + border-radius: 100%; + } +} + +@keyframes step5_square3 { + 0% { + transform: rotate(0deg); + top: 8px; + left: 0px; + width: 8px; + height: 8px; + border-radius: 100%; + } + 100% { + transform: rotate(45deg); + top: 8px; + left: 8px; + width: 8px; + height: 8px; + border-radius: 100%; + } +} + +@keyframes step5_square4 { + 0% { + transform: rotate(0deg); + top: 16px; + left: 8px; + width: 8px; + height: 8px; + border-radius: 100%; + } + 100% { + transform: rotate(45deg); + top: 8px; + left: 8px; + width: 8px; + height: 8px; + border-radius: 100%; + } +} + +@keyframes spinAiIconContainer { + 0% { + transform: rotate(0deg); + } + 40% { + transform: rotate(-45deg); + } + // 40% { + // transform: rotate(-45deg); + // } + // 60% { + // transform: rotate(90deg); /* 360 degrees * 3 (3 full spins) */ + // } + 80% { + transform: rotate(180deg); /* 360 degrees * 3 (3 full spins) */ + } + 100% { + transform: rotate(180deg); /* 360 degrees * 3 (3 full spins) */ + } + // 90% { + // transform: rotate(360deg); /* 360 degrees * 5 (5 full spins) */ + // } + // 100% { + // transform: rotate(360deg); /* 360 degrees * 5 (5 full spins) */ + // } +} + +.spin-ai-icon-container { + // width: 100%; + // height: 100%; + // background-color: #f00; /* Replace this with your desired background color */ + // border-radius: 50%; + // position: absolute; + // animation: spinAiIconContainer 10s infinite; + // animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); + // animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1); +} + +/* +* STEP SIX +*/ + +// @keyframes step6_square1 { +// 0% { +// transform: rotate(45deg); +// left: 0px; +// top: 0px; +// width: 12px; +// height: 12px; +// } +// 100% { +// transform: rotate(0deg); +// top: 8px; +// left: 8px; +// width: 8px; +// height: 8px; +// } +// } + +// @keyframes step6_square2 { +// 0% { +// transform: rotate(45deg); +// top: 0px; +// left: 8px; +// width: 12px; +// height: 12px; +// } +// 100% { +// transform: rotate(0deg); +// top: 8px; +// left: 8px; +// width: 8px; +// height: 8px; +// } +// } + +// @keyframes step6_square3 { +// 0% { +// transform: rotate(45deg); +// top: 8px; +// left: 0px; +// width: 12px; +// height: 12px; +// } +// 100% { +// transform: rotate(0deg); +// top: 8px; +// left: 8px; +// width: 8px; +// height: 8px; +// } +// } + +// @keyframes step6_square4 { +// 0% { +// transform: rotate(45deg); +// top: 8px; +// left: 8px; +// width: 12px; +// height: 12px; +// } +// 100% { +// transform: rotate(0deg); +// top: 8px; +// left: 8px; +// width: 8px; +// height: 8px; +// } +// } + +.ai-icon__container { + width: 24px; + height: 24px; + // background: red; +} + +.ai-icon__grid { + position: relative; + width: 24px; + height: 24px; + // background: yellow; +} + +.ai-icon__grid__square { + position: absolute; + border: 1px solid hsl(var(--brand-default)); +} + +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-1:nth-child(1) { + animation: step1_square1 700ms forwards; + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); + // animation-delay: 0; +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-1:nth-child(2) { + animation: step1_square2 700ms forwards; + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); + // animation-delay: 200ms; +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-1:nth-child(3) { + animation: step1_square3 700ms forwards; + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); + // animation-delay: 400ms; +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-1:nth-child(4) { + animation: step1_square4 700ms forwards; + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); + // animation-delay: 600ms; +} + +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-2:nth-child(1) { + animation-delay: 200; + animation: step2_square1 0.7s forwards; + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-2:nth-child(2) { + animation-delay: 400ms; + animation: step2_square2 0.7s forwards; + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-2:nth-child(3) { + animation-delay: 600ms; + animation: step2_square3 0.7s forwards; + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-2:nth-child(4) { + animation-delay: 800ms; + animation: step2_square4 0.7s forwards; + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} + +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-3:nth-child(1) { + animation: step3_square1 0.7s forwards; + // animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-3:nth-child(2) { + animation: step3_square2 0.7s forwards; + // animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-3:nth-child(3) { + animation: step3_square3 0.7s forwards; + // animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-3:nth-child(4) { + animation: step3_square4 0.7s forwards; + // animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); + animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} + +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-4:nth-child(1) { + animation: step4_square1 1s forwards; + // animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); + // animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-4:nth-child(2) { + animation: step4_square2 1s forwards; + // animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); + // animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-4:nth-child(3) { + animation: step4_square3 1s forwards; + // animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); + // animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-4:nth-child(4) { + animation: step4_square4 1s forwards; + // animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); + // animation-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); +} + +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-5:nth-child(1) { + animation: step5_square1 3s forwards; + // animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-5:nth-child(2) { + animation: step5_square2 3s forwards; + // animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-5:nth-child(3) { + animation: step5_square3 3s forwards; + // animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +} +.ai-icon__grid .ai-icon__grid__square.ai-icon__grid__square--step-5:nth-child(4) { + animation: step5_square4 3s forwards; + // animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1); +}