diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 562913a..72e8b75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,164 +2,39 @@ name: ci on: push: - branches: [main, nautilus, nexus, injective, trump, ousdt] + branches: [main, testnet] pull_request: - branches: [main, nautilus, nexus, injective, trump, ousdt] - merge_group: workflow_dispatch: -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v6 - with: - node-version-file: '.nvmrc' - - - name: Get pnpm store directory - id: pnpm-store - shell: bash - run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - - - name: Cache pnpm store - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: | - pnpm install --frozen-lockfile - CHANGES=$(git status -s) - if [[ ! -z $CHANGES ]]; then - echo "Changes found: $CHANGES" - git diff - exit 1 - fi - - - name: build - run: pnpm run build - env: - NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.NEXT_PUBLIC_WALLET_CONNECT_ID }} +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true - typecheck: +jobs: + ci: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' + cache: pnpm - - name: Get pnpm store directory - id: pnpm-store - shell: bash - run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - - - name: Cache pnpm store - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies + - name: Install run: pnpm install --frozen-lockfile - - name: typecheck + - name: Typecheck run: pnpm run typecheck - prettier: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v6 - with: - node-version-file: '.nvmrc' - - - name: Get pnpm store directory - id: pnpm-store - shell: bash - run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - - - name: Cache pnpm store - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: prettier - run: | - pnpm run prettier - CHANGES=$(git status -s) - if [[ ! -z $CHANGES ]]; then - echo "Changes found: $CHANGES" - exit 1 - fi - - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v6 - with: - node-version-file: '.nvmrc' - - - name: Get pnpm store directory - id: pnpm-store - shell: bash - run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - - - name: Cache pnpm store - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: lint + - name: Lint run: pnpm run lint - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v6 - with: - node-version-file: '.nvmrc' - - - name: Get pnpm store directory - id: pnpm-store - shell: bash - run: echo "path=$(pnpm store path)" >> $GITHUB_OUTPUT - - - name: Cache pnpm store - uses: actions/cache@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: test + - name: Test run: pnpm run test + + - name: Build + run: pnpm run build + env: + NEXT_PUBLIC_WALLET_CONNECT_ID: ${{ secrets.NEXT_PUBLIC_WALLET_CONNECT_ID }} diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index f484059..897e4cb 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -41,8 +41,15 @@ jobs: pull-requests: write issues: write id-token: write + env: + HAS_ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }} steps: + - name: Skip when ANTHROPIC_API_KEY is not configured + if: env.HAS_ANTHROPIC_KEY != 'true' + run: echo "::notice::ANTHROPIC_API_KEY secret is not set; skipping Claude review." + - name: Get PR SHA + if: env.HAS_ANTHROPIC_KEY == 'true' id: pr-sha uses: actions/github-script@v7 with: @@ -59,12 +66,14 @@ jobs: } - name: Checkout repository + if: env.HAS_ANTHROPIC_KEY == 'true' uses: actions/checkout@v6 with: ref: ${{ steps.pr-sha.outputs.head_sha }} fetch-depth: 0 - name: Run Claude Code Review + if: env.HAS_ANTHROPIC_KEY == 'true' uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} @@ -99,8 +108,15 @@ jobs: pull-requests: write issues: write id-token: write + env: + HAS_ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }} steps: + - name: Skip when ANTHROPIC_API_KEY is not configured + if: env.HAS_ANTHROPIC_KEY != 'true' + run: echo "::notice::ANTHROPIC_API_KEY secret is not set; skipping Claude review." + - name: Get PR SHA + if: env.HAS_ANTHROPIC_KEY == 'true' id: pr-sha uses: actions/github-script@v7 with: @@ -117,12 +133,14 @@ jobs: } - name: Checkout repository + if: env.HAS_ANTHROPIC_KEY == 'true' uses: actions/checkout@v6 with: ref: ${{ steps.pr-sha.outputs.head_sha }} fetch-depth: 2 - name: Run Claude Security Review + if: env.HAS_ANTHROPIC_KEY == 'true' uses: anthropics/claude-code-security-review@25e460eb0a12077f0c6a1934d5dbae2f50785dda with: claude-api-key: ${{ secrets.ANTHROPIC_API_KEY }} @@ -165,8 +183,15 @@ jobs: pull-requests: write issues: write id-token: write + env: + HAS_ANTHROPIC_KEY: ${{ secrets.ANTHROPIC_API_KEY != '' }} steps: + - name: Skip when ANTHROPIC_API_KEY is not configured + if: env.HAS_ANTHROPIC_KEY != 'true' + run: echo "::notice::ANTHROPIC_API_KEY secret is not set; skipping Claude review." + - name: Get PR SHA + if: env.HAS_ANTHROPIC_KEY == 'true' id: pr-sha uses: actions/github-script@v7 with: @@ -187,12 +212,14 @@ jobs: } - name: Checkout repository + if: env.HAS_ANTHROPIC_KEY == 'true' uses: actions/checkout@v6 with: ref: ${{ steps.pr-sha.outputs.head_sha || github.sha }} fetch-depth: 0 - name: Run Claude Code Assistant + if: env.HAS_ANTHROPIC_KEY == 'true' uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/.github/workflows/create-merge-prs.yaml b/.github/workflows/create-merge-prs.yaml deleted file mode 100644 index 529dde6..0000000 --- a/.github/workflows/create-merge-prs.yaml +++ /dev/null @@ -1,107 +0,0 @@ -name: Merge Main to Other Branches - -on: - push: - branches: - - main - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - create-merge-prs: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - strategy: - fail-fast: false - matrix: - branch: [injective, nexus, trump, ousdt] - steps: - - name: Generate GitHub App Token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.HYPER_GONK_APP_ID }} - private-key: ${{ secrets.HYPER_GONK_PRIVATE_KEY }} - - - name: Checkout repository - uses: actions/checkout@v5 - with: - fetch-depth: 0 - token: ${{ steps.generate-token.outputs.token }} - - - name: Get GitHub App User ID - id: get-user-id - run: echo "user-id=$(gh api /users/${{ steps.generate-token.outputs.app-slug }}[bot] --jq .id)" >> "$GITHUB_OUTPUT" - env: - GH_TOKEN: ${{ steps.generate-token.outputs.token }} - - - name: Configure Git for Hyper Gonk - run: | - git config user.name "${{ steps.generate-token.outputs.app-slug }}[bot]" - git config user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com" - - - name: Check if merge is needed - id: check-merge - run: | - git fetch origin ${{ matrix.branch }} - if git merge-base --is-ancestor origin/main origin/${{ matrix.branch }}; then - echo "Branch ${{ matrix.branch }} is already up-to-date with main" - echo "needs_merge=false" >> $GITHUB_OUTPUT - else - echo "Branch ${{ matrix.branch }} needs updates from main" - echo "needs_merge=true" >> $GITHUB_OUTPUT - fi - - - name: Create and push merge branch - if: steps.check-merge.outputs.needs_merge == 'true' - id: merge - run: | - git checkout ${{ matrix.branch }} - - # Attempt merge, capturing if there are conflicts - if git merge origin/main --no-edit; then - echo "has_conflicts=false" >> $GITHUB_OUTPUT - else - echo "::warning::Merge conflict detected for ${{ matrix.branch }}" - echo "has_conflicts=true" >> $GITHUB_OUTPUT - # Stage all files including conflicts to allow branch creation - git add -A - git commit --no-edit || true - fi - - git branch -M main-to-${{ matrix.branch }} - git push -fu origin main-to-${{ matrix.branch }} - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - - - name: Create or update PR - if: steps.check-merge.outputs.needs_merge == 'true' - run: | - PR_EXISTS=$(gh pr list --base ${{ matrix.branch }} --head main-to-${{ matrix.branch }} --json number --jq '.[0].number') - - if [ "${{ steps.merge.outputs.has_conflicts }}" == "true" ]; then - BODY="⚠️ **This PR has merge conflicts that need to be resolved manually.** - - This PR was automatically created to merge changes from \`main\` into \`${{ matrix.branch }}\`." - else - BODY="This PR was automatically created to merge changes from \`main\` into \`${{ matrix.branch }}\`." - fi - - if [ -z "$PR_EXISTS" ]; then - gh pr create \ - --base ${{ matrix.branch }} \ - --head main-to-${{ matrix.branch }} \ - --title "chore: merge main into ${{ matrix.branch }}" \ - --body "$BODY" \ - --draft - echo "Created new PR for ${{ matrix.branch }}" - else - echo "PR #$PR_EXISTS already exists for ${{ matrix.branch }}" - fi - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} diff --git a/.github/workflows/update-hyperlane-deps.yml b/.github/workflows/update-hyperlane-deps.yml deleted file mode 100644 index e9471eb..0000000 --- a/.github/workflows/update-hyperlane-deps.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: Update Hyperlane Dependencies - -on: - schedule: - # Run weekly on Mondays at 9 AM UTC - - cron: '0 9 * * 1' - workflow_dispatch: # Allow manual triggering - -jobs: - update-dependencies: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Generate GitHub App Token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.HYPER_GONK_APP_ID }} - private-key: ${{ secrets.HYPER_GONK_PRIVATE_KEY }} - - - name: Checkout repository - uses: actions/checkout@v6 - with: - ref: main - token: ${{ steps.generate-token.outputs.token }} - - - name: Get GitHub App User ID - id: get-user-id - run: echo "user-id=$(gh api /users/${{ steps.generate-token.outputs.app-slug }}[bot] --jq .id)" >> "$GITHUB_OUTPUT" - env: - GH_TOKEN: ${{ steps.generate-token.outputs.token }} - - - name: Configure Git - run: | - git config user.name "${{ steps.generate-token.outputs.app-slug }}[bot]" - git config user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.generate-token.outputs.app-slug }}[bot]@users.noreply.github.com" - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '22' - - - name: Get latest SDK, Utils, Registry, and Widgets versions - id: get-versions - run: | - LATEST_SDK=$(pnpm view @hyperlane-xyz/sdk version) - LATEST_UTILS=$(pnpm view @hyperlane-xyz/utils version) - LATEST_REGISTRY=$(pnpm view @hyperlane-xyz/registry version) - LATEST_WIDGETS=$(pnpm view @hyperlane-xyz/widgets version) - echo "sdk=$LATEST_SDK" >> $GITHUB_OUTPUT - echo "utils=$LATEST_UTILS" >> $GITHUB_OUTPUT - echo "registry=$LATEST_REGISTRY" >> $GITHUB_OUTPUT - echo "widgets=$LATEST_WIDGETS" >> $GITHUB_OUTPUT - echo "Latest SDK: $LATEST_SDK" - echo "Latest Utils: $LATEST_UTILS" - echo "Latest Registry: $LATEST_REGISTRY" - echo "Latest Widgets: $LATEST_WIDGETS" - - - name: Update package.json with latest versions - run: | - npm pkg set dependencies.@hyperlane-xyz/sdk=${{ steps.get-versions.outputs.sdk }} - npm pkg set dependencies.@hyperlane-xyz/utils=${{ steps.get-versions.outputs.utils }} - npm pkg set dependencies.@hyperlane-xyz/registry=${{ steps.get-versions.outputs.registry }} - npm pkg set dependencies.@hyperlane-xyz/widgets=${{ steps.get-versions.outputs.widgets }} - - - name: Install dependencies - run: pnpm install --no-frozen-lockfile - - - name: Check for changes and create PR - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - run: | - if ! git diff --quiet; then - git checkout -b ci/update-hl-deps - git add -A - git commit -m "chore: update Hyperlane deps to SDK ${{ steps.get-versions.outputs.sdk }} and Registry ${{ steps.get-versions.outputs.registry }} - - - Update @hyperlane-xyz/sdk to ${{ steps.get-versions.outputs.sdk }} - - Update @hyperlane-xyz/utils to ${{ steps.get-versions.outputs.utils }} - - Update @hyperlane-xyz/registry to ${{ steps.get-versions.outputs.registry }} - - Update @hyperlane-xyz/widgets to ${{ steps.get-versions.outputs.widgets }}" - - git push -fu origin ci/update-hl-deps - - PR_TITLE="chore: update Hyperlane deps" - PR_BODY="## Automated Dependency Update - - This PR updates the Hyperlane dependencies to their latest versions. - - **Updated versions:** - - \`@hyperlane-xyz/sdk\`: \`${{ steps.get-versions.outputs.sdk }}\` - - \`@hyperlane-xyz/utils\`: \`${{ steps.get-versions.outputs.utils }}\` - - \`@hyperlane-xyz/registry\`: \`${{ steps.get-versions.outputs.registry }}\` - - \`@hyperlane-xyz/widgets\`: \`${{ steps.get-versions.outputs.widgets }}\` - - **Changes include:** - - Updated \`package.json\` with latest Hyperlane package versions - - Updated \`pnpm-lock.yaml\` via \`pnpm install\` - - --- - 🤖 This PR was automatically generated by the [update-hyperlane-deps workflow](.github/workflows/update-hyperlane-deps.yml)" - - PR_EXISTS=$(gh pr list --base main --head ci/update-hl-deps --json number --jq length) - if [ "$PR_EXISTS" -eq "0" ]; then - gh pr create \ - --base main \ - --head ci/update-hl-deps \ - --title "$PR_TITLE" \ - --body "$PR_BODY" - else - echo "Pull request already exists. Updating title and description..." - gh pr edit ci/update-hl-deps \ - --title "$PR_TITLE" \ - --body "$PR_BODY" - fi - else - echo "No changes detected. Skipping PR creation." - fi diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz deleted file mode 100644 index 049d614..0000000 Binary files a/.yarn/install-state.gz and /dev/null differ diff --git a/.yarnrc.yml b/.yarnrc.yml deleted file mode 100644 index 18fdf78..0000000 --- a/.yarnrc.yml +++ /dev/null @@ -1,13 +0,0 @@ -compressionLevel: mixed - -enableGlobalCache: false - -enableScripts: false - -nodeLinker: node-modules - -plugins: - - path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs - spec: "https://mskelton.dev/yarn-outdated/v3" - -yarnPath: .yarn/releases/yarn-4.5.0.cjs diff --git a/src/components/banner/RecipientWarningBanner.tsx b/src/components/banner/RecipientWarningBanner.tsx index b35f6db..55fca4c 100644 --- a/src/components/banner/RecipientWarningBanner.tsx +++ b/src/components/banner/RecipientWarningBanner.tsx @@ -13,12 +13,12 @@ export function RecipientWarningBanner({
-

+

The recipient address is the same as the currently connected smart contract wallet,{' '} but it does not exist as a smart contract on {destinationChain}.

This may result in losing access to your bridged tokens.

-

+

Only proceed if you are certain you have control over this address on {destinationChain} @@ -33,7 +33,7 @@ export function RecipientWarningBanner({ /> diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx index 61542d1..e1a25c0 100644 --- a/src/components/footer/Footer.tsx +++ b/src/components/footer/Footer.tsx @@ -6,7 +6,6 @@ import FooterBottomAnimated from './FooterBottomAnimated'; import SingleFooter from './props/SingleFooter'; import type { RawFooterConfig } from './types/types'; - type Props = { rawFooter: RawFooterConfig; }; @@ -52,10 +51,9 @@ const Footer = ({ rawFooter }: Props) => {

-
- +
- + diff --git a/src/components/footer/FooterBottomAnimated.tsx b/src/components/footer/FooterBottomAnimated.tsx index b4e5243..ea25681 100644 --- a/src/components/footer/FooterBottomAnimated.tsx +++ b/src/components/footer/FooterBottomAnimated.tsx @@ -1,9 +1,9 @@ -"use client"; +'use client'; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import PixelBlast from "../ui/PixelBlast"; -import TextPressure from "../ui/TextPressure"; +import Link from 'next/link'; +import { useEffect, useState } from 'react'; +import PixelBlast from '../ui/PixelBlast'; +import TextPressure from '../ui/TextPressure'; type TitleScale = { letterSpacing: number; minFontSize: number }; @@ -26,8 +26,8 @@ function useFooterTitleScale(): TitleScale { } }; sync(); - window.addEventListener("resize", sync); - return () => window.removeEventListener("resize", sync); + window.addEventListener('resize', sync); + return () => window.removeEventListener('resize', sync); }, []); return scale; @@ -37,16 +37,16 @@ export default function FooterBottomAnimated() { const { letterSpacing, minFontSize } = useFooterTitleScale(); return ( -
-
-

- Copyright © 2026{" "} +

+
+

+ Copyright © 2026{' '} Lightchain Protocol

-
+
buildCandidates(token, chainId, size), - [token, chainId, size], - ); + const candidates = useMemo(() => buildCandidates(token, chainId, size), [token, chainId, size]); const tokenKey = token ? `${token.chainName}::${token.addressOrDenom}` : ''; const [attempt, setAttempt] = useState(0); diff --git a/src/components/input/SearchInput.tsx b/src/components/input/SearchInput.tsx index 4ebba81..6ad5490 100644 --- a/src/components/input/SearchInput.tsx +++ b/src/components/input/SearchInput.tsx @@ -37,7 +37,7 @@ export function SearchInput({ type="button" aria-label="Clear search" onClick={() => onChange('')} - className="absolute right-2.5 top-1/2 -translate-y-1/2 rounded-full p-0.5 text-content-gray hover:text-contentBody [&_path]:fill-content-gray hover:[&_path]:fill-contentBody transition-colors" + className="absolute right-2.5 top-1/2 -translate-y-1/2 rounded-full p-0.5 text-content-gray transition-colors hover:text-contentBody [&_path]:fill-content-gray hover:[&_path]:fill-contentBody" > diff --git a/src/components/layout/Card.tsx b/src/components/layout/Card.tsx index 3e88892..c74997c 100644 --- a/src/components/layout/Card.tsx +++ b/src/components/layout/Card.tsx @@ -5,11 +5,5 @@ interface Props { } export function Card({ className, children }: PropsWithChildren) { - return ( -
- {children} -
- ); + return
{children}
; } diff --git a/src/components/layout/ModalHeader.tsx b/src/components/layout/ModalHeader.tsx index 2a4e855..77e8fa2 100644 --- a/src/components/layout/ModalHeader.tsx +++ b/src/components/layout/ModalHeader.tsx @@ -5,7 +5,9 @@ export function ModalHeader({ children, className }: { children?: ReactNode; cla return (
{children && ( -

{children}

+

+ {children} +

)}
); diff --git a/src/components/nav/Header.tsx b/src/components/nav/Header.tsx index 8e96ad2..26a9f69 100644 --- a/src/components/nav/Header.tsx +++ b/src/components/nav/Header.tsx @@ -18,7 +18,7 @@ export function Header(props: HeaderProps) { const [activeMobileMenu, setActiveMobileMenu] = useState(true); return ( <> -
+
logo @@ -27,7 +27,7 @@ export function Header(props: HeaderProps) {
-
+
diff --git a/src/components/ui/DottedLineBackground.tsx b/src/components/ui/DottedLineBackground.tsx index 025da7b..ce79087 100644 --- a/src/components/ui/DottedLineBackground.tsx +++ b/src/components/ui/DottedLineBackground.tsx @@ -6,23 +6,20 @@ type DottedLineBackgroundProps = { export default function DottedLineBackground({ lineCount = 5, - className = "", + className = '', lineClassName, }: DottedLineBackgroundProps) { const totalLines = Math.max(1, lineCount); const defaultLineClassName = - "h-full w-px border-l border-dashed border-white/55 opacity-[0.18] z-0 [border-image:repeating-linear-gradient(to_bottom,rgba(255,255,255,0.7)_0_7px,transparent_7px_14px)_1] motion-safe:animate-[dotted-line-flow_1.2s_linear_infinite]"; + 'h-full w-px border-l border-dashed border-white/55 opacity-[0.18] z-0 [border-image:repeating-linear-gradient(to_bottom,rgba(255,255,255,0.7)_0_7px,transparent_7px_14px)_1] motion-safe:animate-[dotted-line-flow_1.2s_linear_infinite]'; return (