Improves password prompt UI and features
This commit is contained in:
@@ -7,9 +7,10 @@
|
|||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
|
- 🔗 **短链生成**:生成附有访问者统计信息的短链接 (支持密码保护)
|
||||||
|
- 📮 **临时邮箱**:创建多个临时邮箱接收和发送邮件
|
||||||
- 🌐 **多租户支持**:无缝管理多个 DNS 记录
|
- 🌐 **多租户支持**:无缝管理多个 DNS 记录
|
||||||
- ⚡ **即时记录创建**:快速设置 CNAME、A 等记录
|
- ⚡ **即时记录创建**:快速设置 CNAME、A 等记录
|
||||||
- 🔗 **短链生成**:生成附有访问者统计信息的短链接
|
|
||||||
- 📸 **截图 API**:访问截图 API
|
- 📸 **截图 API**:访问截图 API
|
||||||
- <20> **元数据抓取 API**:访问元数据抓取 API
|
- <20> **元数据抓取 API**:访问元数据抓取 API
|
||||||
- <20>😀 **权限管理**:方便审核的管理员面板
|
- <20>😀 **权限管理**:方便审核的管理员面板
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- 🔗 **URL Shortening:** Generate short links with visitor analytic and password
|
||||||
- 📮 **Email Support:** Receive emails and send emails
|
- 📮 **Email Support:** Receive emails and send emails
|
||||||
- 🌐 **Multi-Tenant Support:** Manage multiple DNS records seamlessly
|
- 🌐 **Multi-Tenant Support:** Manage multiple DNS records seamlessly
|
||||||
- ⚡ **Instant Record Creation:** Set up CNAME, A, and other records quickly
|
- ⚡ **Instant Record Creation:** Set up CNAME, A, and other records quickly
|
||||||
- 🔗 **URL Shortening:** Generate short links with visitor statistics attached
|
|
||||||
- 📸 **Screenshot API:** Access to screenshot API.
|
- 📸 **Screenshot API:** Access to screenshot API.
|
||||||
- 💯 **Meta Scraping API:** Access to meta scraping API.
|
- 💯 **Meta Scraping API:** Access to meta scraping API.
|
||||||
- 😀 **Permission Management:** A convenient admin panel for auditing
|
- 😀 **Permission Management:** A convenient admin panel for auditing
|
||||||
|
|||||||
174
app/password-prompt/card.tsx
Normal file
174
app/password-prompt/card.tsx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState, useTransition } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
|
||||||
|
import { siteConfig } from "@/config/site";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Spotlight } from "@/components/ui/spotlight";
|
||||||
|
import { Icons } from "@/components/shared/icons";
|
||||||
|
|
||||||
|
export default function PasswordPrompt() {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const slug = searchParams.get("slug");
|
||||||
|
const initialPassword = searchParams.get("password") || "";
|
||||||
|
const isError = searchParams.get("error") === "1";
|
||||||
|
const [password, setPassword] = useState(["", "", "", "", "", ""]);
|
||||||
|
const [isHidden, setIsHidden] = useState(true);
|
||||||
|
const [isPending, startTransition] = useTransition();
|
||||||
|
const inputRefs = useRef<(HTMLInputElement | null)[]>(Array(6).fill(null));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialPassword) {
|
||||||
|
const paddedPassword = initialPassword
|
||||||
|
.padEnd(6, "")
|
||||||
|
.split("")
|
||||||
|
.slice(0, 6);
|
||||||
|
setPassword(paddedPassword);
|
||||||
|
handleSubmit(new Event("submit") as any);
|
||||||
|
}
|
||||||
|
}, [initialPassword]);
|
||||||
|
|
||||||
|
const handleChange = (index: number, value: string) => {
|
||||||
|
if (value.length > 1) return;
|
||||||
|
const newPassword = [...password];
|
||||||
|
newPassword[index] = value;
|
||||||
|
setPassword(newPassword);
|
||||||
|
|
||||||
|
if (value && index < 5) {
|
||||||
|
inputRefs.current[index + 1]?.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (index: number, e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === "Backspace" && !password[index] && index > 0) {
|
||||||
|
inputRefs.current[index - 1]?.focus();
|
||||||
|
} else if (e.key === "Enter") {
|
||||||
|
handleSubmit(e as any);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
startTransition(async () => {
|
||||||
|
e.preventDefault();
|
||||||
|
const fullPassword = password.join("");
|
||||||
|
if (slug && !isPending && fullPassword.length === 6) {
|
||||||
|
router.push(`/s/${slug}?password=${encodeURIComponent(fullPassword)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleVisibility = () => {
|
||||||
|
setIsHidden(!isHidden);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative flex min-h-screen flex-col bg-neutral-900">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none absolute inset-0 select-none [background-size:40px_40px]",
|
||||||
|
"[background-image:linear-gradient(to_right,#1e1e1e_1px,transparent_1px),linear-gradient(to_bottom,#1e1e1e_1px,transparent_1px)]",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Spotlight
|
||||||
|
className="-top-40 left-0 md:-top-20 md:left-60"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-1 items-center justify-center">
|
||||||
|
<div className="mx-3 w-full max-w-md rounded-lg bg-black/70 px-6 py-6 shadow-md shadow-neutral-900 backdrop-blur-xl md:px-[50px]">
|
||||||
|
<h1 className="mb-4 flex items-center justify-center gap-2 text-center text-2xl font-bold text-neutral-50">
|
||||||
|
Protected Link
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div className="mb-4 break-all text-left text-sm text-neutral-400">
|
||||||
|
<p>
|
||||||
|
You are attempting to access a password-protected link.{" "}
|
||||||
|
<strong>Please contact the owner to get the password.</strong>{" "}
|
||||||
|
Learn more about this from our{" "}
|
||||||
|
<Link
|
||||||
|
className="underline"
|
||||||
|
target="_blank"
|
||||||
|
href="/docs/short-urls#password"
|
||||||
|
>
|
||||||
|
docs
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
|
<div className="flex justify-between gap-2">
|
||||||
|
{password.map((char, index) => (
|
||||||
|
<Input
|
||||||
|
key={index}
|
||||||
|
type={isHidden ? "password" : "text"}
|
||||||
|
value={char}
|
||||||
|
onChange={(e) => handleChange(index, e.target.value)}
|
||||||
|
onKeyDown={(e) => handleKeyDown(index, e)}
|
||||||
|
ref={(el) => (inputRefs.current[index] = el as any)}
|
||||||
|
maxLength={1}
|
||||||
|
autoFocus={index === 0}
|
||||||
|
className="h-12 w-12 rounded-md border border-gray-300 text-center text-lg font-medium text-neutral-100 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isError && (
|
||||||
|
<p className="mb-2 animate-fade-in text-left text-sm text-red-500">
|
||||||
|
Incorrect password. Please try again.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggleVisibility}
|
||||||
|
className="flex items-center gap-1 text-neutral-400 transition-colors hover:text-neutral-800"
|
||||||
|
>
|
||||||
|
{isHidden ? (
|
||||||
|
<EyeOff className="size-4" />
|
||||||
|
) : (
|
||||||
|
<Eye className="size-4" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant={"default"}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
disabled={
|
||||||
|
!(slug && !isPending && password.join("").length === 6)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isPending ? (
|
||||||
|
<Icons.spinner className="size-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Icons.unLock className="size-4" />
|
||||||
|
)}
|
||||||
|
{isPending ? "Unlocking..." : "Unlock"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer className="py-4 text-center text-sm text-muted-foreground">
|
||||||
|
Powered by{" "}
|
||||||
|
<Link
|
||||||
|
className="hover:underline"
|
||||||
|
href={"https://wr.do"}
|
||||||
|
target="_blank"
|
||||||
|
style={{ fontFamily: "Bahamas Bold" }}
|
||||||
|
>
|
||||||
|
{siteConfig.name}
|
||||||
|
</Link>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,29 +5,9 @@ interface ProtectedLayoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function Password({ children }: ProtectedLayoutProps) {
|
export default async function Password({ children }: ProtectedLayoutProps) {
|
||||||
// const filteredLinks = sidebarLinks.map((section) => ({
|
|
||||||
// ...section,
|
|
||||||
// items: section.items.filter(
|
|
||||||
// ({ authorizeOnly }) => !authorizeOnly || authorizeOnly === user.role,
|
|
||||||
// ),
|
|
||||||
// }));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen w-full overflow-hidden">
|
<div className="relative flex h-screen w-full overflow-hidden">
|
||||||
<div className="flex flex-1 flex-col">
|
<div className="flex flex-1 flex-col">
|
||||||
{/* <header className="sticky top-0 z-50 flex h-14 border-b bg-background px-4 lg:h-[60px] xl:px-8">
|
|
||||||
<MaxWidthWrapper className="flex max-w-7xl items-center gap-x-3 px-0">
|
|
||||||
<MobileSheetSidebar links={filteredLinks} />
|
|
||||||
|
|
||||||
<div className="w-full flex-1">
|
|
||||||
<SearchCommand links={filteredLinks} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ModeToggle />
|
|
||||||
<UserAccountNav />
|
|
||||||
</MaxWidthWrapper>
|
|
||||||
</header> */}
|
|
||||||
|
|
||||||
<main className="flex-1">
|
<main className="flex-1">
|
||||||
<MaxWidthWrapper className="flex max-h-screen max-w-full flex-col gap-4 px-0 lg:gap-6">
|
<MaxWidthWrapper className="flex max-h-screen max-w-full flex-col gap-4 px-0 lg:gap-6">
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,163 +1,12 @@
|
|||||||
"use client";
|
import { constructMetadata } from "@/lib/utils";
|
||||||
|
|
||||||
import { useEffect, useRef, useState, useTransition } from "react";
|
import PasswordPrompt from "./card";
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
|
||||||
|
|
||||||
import { siteConfig } from "@/config/site";
|
export const metadata = constructMetadata({
|
||||||
import { Button } from "@/components/ui/button";
|
title: "Password Required",
|
||||||
import { Input } from "@/components/ui/input";
|
description: "Short link with password",
|
||||||
import { Icons } from "@/components/shared/icons";
|
});
|
||||||
|
|
||||||
export default function PasswordPrompt() {
|
export default async function Page() {
|
||||||
const router = useRouter();
|
return <PasswordPrompt />;
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const slug = searchParams.get("slug");
|
|
||||||
const initialPassword = searchParams.get("password") || "";
|
|
||||||
const isError = searchParams.get("error") === "1";
|
|
||||||
const [password, setPassword] = useState(["", "", "", "", "", ""]);
|
|
||||||
const [isHidden, setIsHidden] = useState(true);
|
|
||||||
const [isPending, startTransition] = useTransition();
|
|
||||||
const inputRefs = useRef<(HTMLInputElement | null)[]>(Array(6).fill(null));
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (initialPassword) {
|
|
||||||
const paddedPassword = initialPassword
|
|
||||||
.padEnd(6, "")
|
|
||||||
.split("")
|
|
||||||
.slice(0, 6);
|
|
||||||
setPassword(paddedPassword);
|
|
||||||
handleSubmit(new Event("submit") as any);
|
|
||||||
}
|
|
||||||
}, [initialPassword]);
|
|
||||||
|
|
||||||
const handleChange = (index: number, value: string) => {
|
|
||||||
if (value.length > 1) return;
|
|
||||||
const newPassword = [...password];
|
|
||||||
newPassword[index] = value;
|
|
||||||
setPassword(newPassword);
|
|
||||||
|
|
||||||
if (value && index < 5) {
|
|
||||||
inputRefs.current[index + 1]?.focus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = (index: number, e: React.KeyboardEvent) => {
|
|
||||||
if (e.key === "Backspace" && !password[index] && index > 0) {
|
|
||||||
inputRefs.current[index - 1]?.focus();
|
|
||||||
} else if (e.key === "Enter") {
|
|
||||||
handleSubmit(e as any);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
|
||||||
startTransition(async () => {
|
|
||||||
e.preventDefault();
|
|
||||||
const fullPassword = password.join("");
|
|
||||||
if (slug && !isPending && fullPassword.length === 6) {
|
|
||||||
router.push(`/s/${slug}?password=${encodeURIComponent(fullPassword)}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleVisibility = () => {
|
|
||||||
setIsHidden(!isHidden);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grids grids-dark flex min-h-screen flex-col bg-neutral-100 dark:bg-neutral-900">
|
|
||||||
<div className="flex flex-1 items-center justify-center">
|
|
||||||
<div className="mx-3 w-full max-w-md rounded-lg bg-background/60 px-6 py-6 shadow-md backdrop-blur-xl md:px-[50px]">
|
|
||||||
<h1 className="mb-4 text-center text-2xl font-bold text-neutral-800 dark:text-neutral-50">
|
|
||||||
Protected Link
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div className="mb-4 break-all text-left text-sm text-neutral-600 dark:text-neutral-400">
|
|
||||||
<p>
|
|
||||||
You are attempting to access a password-protected link.{" "}
|
|
||||||
<strong>Please contact the owner to get the password.</strong>{" "}
|
|
||||||
Learn more about this from our{" "}
|
|
||||||
<Link
|
|
||||||
className="underline"
|
|
||||||
target="_blank"
|
|
||||||
href="/docs/short-urls#password"
|
|
||||||
>
|
|
||||||
docs
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-5">
|
|
||||||
<div className="flex justify-between gap-2">
|
|
||||||
{password.map((char, index) => (
|
|
||||||
<Input
|
|
||||||
key={index}
|
|
||||||
type={isHidden ? "password" : "text"}
|
|
||||||
value={char}
|
|
||||||
onChange={(e) => handleChange(index, e.target.value)}
|
|
||||||
onKeyDown={(e) => handleKeyDown(index, e)}
|
|
||||||
ref={(el) => (inputRefs.current[index] = el as any)}
|
|
||||||
maxLength={1}
|
|
||||||
autoFocus={index === 0}
|
|
||||||
className="h-12 w-12 rounded-md border border-gray-300 text-center text-lg font-medium focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isError && (
|
|
||||||
<p className="mb-2 animate-fade-in text-left text-sm text-red-500">
|
|
||||||
Incorrect password. Please try again.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant={"ghost"}
|
|
||||||
onClick={toggleVisibility}
|
|
||||||
className="flex items-center gap-1 text-neutral-600 transition-colors hover:text-neutral-800 dark:text-neutral-400"
|
|
||||||
>
|
|
||||||
{isHidden ? (
|
|
||||||
<EyeOff className="size-4" />
|
|
||||||
) : (
|
|
||||||
<Eye className="size-4" />
|
|
||||||
)}
|
|
||||||
<span>{isHidden ? "Show" : "Hide"}</span>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant={"default"}
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
disabled={
|
|
||||||
!(slug && !isPending && password.join("").length === 6)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{isPending ? (
|
|
||||||
<Icons.spinner className="size-4 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<Icons.unLock className="size-4" />
|
|
||||||
)}
|
|
||||||
{isPending ? "Unlocking..." : "Unlock"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer className="py-4 text-center text-sm text-muted-foreground">
|
|
||||||
Powered by{" "}
|
|
||||||
<Link
|
|
||||||
className="hover:underline"
|
|
||||||
href={"https://wr.do"}
|
|
||||||
target="_blank"
|
|
||||||
style={{ fontFamily: "Bahamas Bold" }}
|
|
||||||
>
|
|
||||||
{siteConfig.name}
|
|
||||||
</Link>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,55 @@ export const Icons = {
|
|||||||
calendar: Calendar,
|
calendar: Calendar,
|
||||||
lock: LockKeyhole,
|
lock: LockKeyhole,
|
||||||
unLock: LockKeyholeOpen,
|
unLock: LockKeyholeOpen,
|
||||||
|
pwdKey: ({ ...props }: LucideProps) => (
|
||||||
|
<svg
|
||||||
|
height="18"
|
||||||
|
width="18"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<g fill="currentColor">
|
||||||
|
<path
|
||||||
|
d="M7.75,13.25H3.75c-1.105,0-2-.895-2-2V6.75c0-1.105,.895-2,2-2H14.25c1.105,0,2,.895,2,2v.25"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M12.25,12.25v-2c0-.828,.672-1.5,1.5-1.5h0c.828,0,1.5,.672,1.5,1.5v2"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
></path>
|
||||||
|
<circle
|
||||||
|
cx="5.5"
|
||||||
|
cy="9"
|
||||||
|
fill="currentColor"
|
||||||
|
r="1"
|
||||||
|
stroke="none"
|
||||||
|
></circle>
|
||||||
|
<circle cx="9" cy="9" fill="currentColor" r="1" stroke="none"></circle>
|
||||||
|
<rect
|
||||||
|
height="4"
|
||||||
|
width="6"
|
||||||
|
fill="none"
|
||||||
|
rx="1"
|
||||||
|
ry="1"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
x="10.75"
|
||||||
|
y="12.25"
|
||||||
|
></rect>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
fileText: FileText,
|
fileText: FileText,
|
||||||
dashboard: LayoutPanelLeft,
|
dashboard: LayoutPanelLeft,
|
||||||
download: Download,
|
download: Download,
|
||||||
|
|||||||
57
components/ui/spotlight.tsx
Normal file
57
components/ui/spotlight.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type SpotlightProps = {
|
||||||
|
className?: string;
|
||||||
|
fill?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Spotlight = ({ className, fill }: SpotlightProps) => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={cn(
|
||||||
|
"animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] opacity-0 lg:w-[84%]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 3787 2842"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<g filter="url(#filter)">
|
||||||
|
<ellipse
|
||||||
|
cx="1924.71"
|
||||||
|
cy="273.501"
|
||||||
|
rx="1924.71"
|
||||||
|
ry="273.501"
|
||||||
|
transform="matrix(-0.822377 -0.568943 -0.568943 0.822377 3631.88 2291.09)"
|
||||||
|
fill={fill || "white"}
|
||||||
|
fillOpacity="0.21"
|
||||||
|
></ellipse>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter
|
||||||
|
id="filter"
|
||||||
|
x="0.860352"
|
||||||
|
y="0.838989"
|
||||||
|
width="3785.16"
|
||||||
|
height="2840.26"
|
||||||
|
filterUnits="userSpaceOnUse"
|
||||||
|
colorInterpolationFilters="sRGB"
|
||||||
|
>
|
||||||
|
<feFlood floodOpacity="0" result="BackgroundImageFix"></feFlood>
|
||||||
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="BackgroundImageFix"
|
||||||
|
result="shape"
|
||||||
|
></feBlend>
|
||||||
|
<feGaussianBlur
|
||||||
|
stdDeviation="151"
|
||||||
|
result="effect1_foregroundBlur_1065_8"
|
||||||
|
></feGaussianBlur>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
File diff suppressed because one or more lines are too long
@@ -178,6 +178,16 @@ const config = {
|
|||||||
transform: "translateX(0px)",
|
transform: "translateX(0px)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
spotlight: {
|
||||||
|
"0%": {
|
||||||
|
opacity: "0",
|
||||||
|
transform: "translate(-72%, -62%) scale(0.5)",
|
||||||
|
},
|
||||||
|
"100%": {
|
||||||
|
opacity: "1",
|
||||||
|
transform: "translate(-50%,-40%) scale(1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
@@ -196,6 +206,8 @@ const config = {
|
|||||||
"fade-in-right": "fade-in-right 0.4s",
|
"fade-in-right": "fade-in-right 0.4s",
|
||||||
"fade-out-left": "fade-out-left 0.4s",
|
"fade-out-left": "fade-out-left 0.4s",
|
||||||
"fade-out-right": "fade-out-right 0.4s",
|
"fade-out-right": "fade-out-right 0.4s",
|
||||||
|
|
||||||
|
spotlight: "spotlight 2s ease .75s 1 forwards",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user