Compare commits

..
40 changed files with 437 additions and 162 deletions
+35 -19
View File
@@ -1,10 +1,15 @@
import { ChainItemType } from "../../types";
import { Dispatch, FunctionComponent, SetStateAction } from "react";
import {
Dispatch,
FunctionComponent,
SetStateAction,
useEffect,
useState,
} from "react";
import { ChainImage } from "./chain-image";
import { Flex1 } from "../../styles/flex-1";
import {
ChainImageContainer,
ChainInfoContainer,
ChainItemContainer,
ChainName,
@@ -13,43 +18,54 @@ import {
import color from "../../styles/color";
import styled from "styled-components";
import { Checkbox } from "../checkbox";
import AllChainsIcon from "../../public/images/svg/all-chains-icon.svg";
interface Props {
allChecked: boolean;
setAllChecked: Dispatch<SetStateAction<boolean>>;
chainItem: ChainItemType;
chainList: ChainItemType[];
checkedItems: Set<unknown>;
setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
}
export const AllChainsItem: FunctionComponent<Props> = (props) => {
const { allChecked, setAllChecked, chainItem } = props;
const { chainList, checkedItems, setCheckedItems } = props;
const [checked, setChecked] = useState(false);
const checkHandler = () => {
setAllChecked(!allChecked);
if (checked) {
setCheckedItems(new Set());
} else if (chainList.length !== checkedItems.size) {
setCheckedItems(new Set(chainList));
}
};
useEffect(() => {
if (chainList.length === checkedItems.size && checkedItems.size !== 0) {
setChecked(true);
} else {
setChecked(false);
}
}, [checkedItems]);
return (
<AllChainsContainer>
<ChainItemContainer
key={chainItem.prefix}
key="all chains"
isLoading={false}
checked={allChecked}
checked={checked}
onClick={checkHandler}
>
<ChainImageContainer width="3rem" height="3rem">
<ChainImage
src={chainItem.chainImageUrl}
fill={true}
alt={`${chainItem.prefix} chain image`}
/>
</ChainImageContainer>
<ChainImage src={AllChainsIcon} fill={true} alt={`all chain images`} />
<ChainInfoContainer>
<ChainName>{`.${chainItem.prefix}`}</ChainName>
<WalletAddress>{chainItem.address}</WalletAddress>
<ChainName>{`.all chains(${chainList.length})`}</ChainName>
<WalletAddress>
{chainList.map((chain) => chain.chainName).join(", ")}
</WalletAddress>
</ChainInfoContainer>
<Flex1 />
<Checkbox checked={allChecked} />
<Checkbox checked={checked} />
</ChainItemContainer>
</AllChainsContainer>
);
+4
View File
@@ -21,6 +21,10 @@ export const ChainImage = ({ src, ...props }: ImageProps) => {
};
const ImageWrapper = styled.div`
position: relative;
width: 3rem;
height: 3rem;
img {
border-radius: 50%;
}
+6 -15
View File
@@ -1,4 +1,4 @@
import { ChainItemType, WidthHeightProps } from "../../types";
import { ChainItemType } from "../../types";
import { FunctionComponent, useEffect, useState } from "react";
import color from "../../styles/color";
@@ -39,13 +39,11 @@ export const ChainItem: FunctionComponent<Props> = (props) => {
checked={checked}
onClick={checkHandler}
>
<ChainImageContainer width="3rem" height="3rem">
<ChainImage
src={chainItem.chainImageUrl}
fill={true}
alt={`${chainItem.prefix} chain image`}
/>
</ChainImageContainer>
<ChainImage
src={chainItem.chainImageUrl}
fill={true}
alt={`${chainItem.prefix} chain image`}
/>
<ChainInfoContainer>
<ChainName>{`.${chainItem.prefix}`}</ChainName>
<WalletAddress>{chainItem.address}</WalletAddress>
@@ -90,13 +88,6 @@ export const ChainItemContainer = styled.div<{
}
`;
export const ChainImageContainer = styled.div<WidthHeightProps>`
width: ${(props) => props.width};
height: ${(props) => props.height};
position: relative;
`;
export const ChainInfoContainer = styled.div`
display: flex;
flex-direction: column;
+1 -26
View File
@@ -5,8 +5,6 @@ import styled from "styled-components";
import { ChainItem } from "./chain-item";
interface Props {
allChecked: boolean;
setAllChecked: Dispatch<SetStateAction<boolean>>;
chainList: ChainItemType[];
disabledChainList: ChainItemType[];
checkedItems: Set<unknown>;
@@ -14,14 +12,7 @@ interface Props {
}
export const ChainList: FunctionComponent<Props> = (props) => {
const {
allChecked,
setAllChecked,
chainList,
disabledChainList,
checkedItems,
setCheckedItems,
} = props;
const { chainList, disabledChainList, checkedItems, setCheckedItems } = props;
const checkedItemHandler = (chainItem: ChainItemType, isChecked: boolean) => {
const tempSet = new Set(checkedItems);
@@ -35,22 +26,6 @@ export const ChainList: FunctionComponent<Props> = (props) => {
setCheckedItems(tempSet);
};
useEffect(() => {
if (allChecked) {
setCheckedItems(new Set(chainList));
} else if (chainList.length === checkedItems.size) {
setCheckedItems(new Set());
}
}, [allChecked]);
useEffect(() => {
if (chainList.length === checkedItems.size && checkedItems.size !== 0) {
setAllChecked(true);
} else {
setAllChecked(false);
}
}, [checkedItems]);
return (
<ChainContainer color={color.grey["900"]}>
{chainList.map((chainItem) => (
+9 -3
View File
@@ -21,7 +21,6 @@ import {
} from "../twitter-profile";
import {
ChainContainer,
ChainImageContainer,
ChainInfoContainer,
ChainItemContainer,
} from "../chain-list";
@@ -60,9 +59,9 @@ export const SkeletonChainList: FunctionComponent = () => (
.map((_, index) => (
<div key={index}>
<ChainItemContainer isLoading={true} isSkeleton={true}>
<ChainImageContainer width="3rem" height="3rem">
<SkeletonImageContainer>
<SkeletonCircle width="3rem" height="3rem" />
</ChainImageContainer>
</SkeletonImageContainer>
<ChainInfoContainer>
<SkeletonText width="4rem" height="1rem" />
<SkeletonText width="12rem" height="1rem" />
@@ -93,6 +92,13 @@ const SkeletonTitle = styled.div`
background-color: ${color.grey["800"]};
`;
const SkeletonImageContainer = styled.div`
width: 3rem;
height: 3rem;
position: relative;
`;
const SkeletonButtonContainer = styled.div`
margin-top: 1.5rem;
`;
+18
View File
@@ -70,3 +70,21 @@ export const makeSetRecordMessage = (
[],
);
};
export const makeRemoveRecordMessage = (
twitterUserName: string,
senderAddress: string,
removeAddress: string,
): CosmwasmExecuteMessageResult => {
return makeCosmwasmExecMsg(
senderAddress,
RESOLVER_ADDRESS,
{
remove_record: {
name: twitterUserName,
bech32_address: removeAddress,
},
},
[],
);
};
+102
View File
@@ -1,4 +1,5 @@
import type { AppProps } from "next/app";
import Head from "next/head";
import { useRouter } from "next/router";
import React, { useMemo } from "react";
import { DefaultTheme, ThemeProvider } from "styled-components";
@@ -23,8 +24,109 @@ export default function App({ Component, pageProps }: AppProps) {
return router.pathname === "/" ? homePageTheme : defaultPageTheme;
}, [router.pathname]);
const origin = typeof window !== "undefined" ? window.location.origin : "";
return (
<ThemeProvider theme={pageTheme}>
<Head>
<title>Interchain Name Service</title>
<meta content="Interchain Name Service" property="og:title" />
<meta content="Interchain Name Service" property="twitter:title" />
<meta
content="Your identity for the Interchain. Claim yours today."
property="og:description"
/>
<meta
content="Your identity for the Interchain. Claim yours today."
property="twitter:description"
/>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta
content={`${origin}/images/og-image.webp`}
property="twitter:image"
/>
<meta content={`${origin}/images/og-image.webp`} property="og:image" />
<meta content="summary_large_image" name="twitter:card" />
<meta property="og:type" content="website" />
{/* generated favicons */}
<link
rel="apple-touch-icon"
sizes="57x57"
href="/images/favicon/apple-icon-57x57.png"
/>
<link
rel="apple-touch-icon"
sizes="60x60"
href="/images/favicon/apple-icon-60x60.png"
/>
<link
rel="apple-touch-icon"
sizes="72x72"
href="/images/favicon/apple-icon-72x72.png"
/>
<link
rel="apple-touch-icon"
sizes="76x76"
href="/images/favicon/apple-icon-76x76.png"
/>
<link
rel="apple-touch-icon"
sizes="114x114"
href="/images/favicon/apple-icon-114x114.png"
/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="/images/favicon/apple-icon-120x120.png"
/>
<link
rel="apple-touch-icon"
sizes="144x144"
href="/images/favicon/apple-icon-144x144.png"
/>
<link
rel="apple-touch-icon"
sizes="152x152"
href="/images/favicon/apple-icon-152x152.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/images/favicon/apple-icon-180x180.png"
/>
<link
rel="icon"
type="image/png"
sizes="192x192"
href="/images/favicon/android-icon-192x192.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/images/favicon/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="96x96"
href="/images/favicon/favicon-96x96.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/images/favicon/favicon-16x16.png"
/>
<link rel="manifest" href="/images/favicon/manifest.json" />
<meta name="msapplication-TileColor" content="#ffffff" />
<meta
name="msapplication-TileImage"
content="/images/favicon/ms-icon-144x144.png"
/>
<meta name="theme-color" content="#ffffff" />
</Head>
<React.Fragment>
<GlobalStyle />
<ErrorBoundary>
+38
View File
@@ -13,6 +13,7 @@ import { ConnectWalletModal } from "../components/connect-wallet-modal";
import MainTitle from "../public/images/svg/main-title.svg";
import MainLogo from "../public/images/svg/main-logo.svg";
import CheckIcon from "../public/images/svg/check-icon.svg";
import StarIcon from "../public/images/svg/bg-asset-3.svg";
import { Logo } from "../components/logo";
import { useEffect, useState } from "react";
import { MINIMUM_OSMO_FEE, SELECTED_WALLET_KEY } from "../constants/wallet";
@@ -48,6 +49,20 @@ export default function Home() {
<MainContainer>
<MainTitleContainer>
<MainTitleImageBackground>
<Image
src={StarIcon}
width={50}
height={50}
alt="Star Icon"
className="starIcon first"
/>
<Image
src={StarIcon}
width={50}
height={50}
alt="Star Icon"
className="starIcon last"
/>
<MainTitleImageContainer>
<Image
src={MainTitle}
@@ -112,6 +127,15 @@ const Container = styled.div`
width: 100vw;
height: 100vh;
background-image: url("/images/svg/bg-asset-1.svg"),
url("/images/svg/bg-asset-2.svg"), url("/images/svg/bg-asset-2.svg"),
url("/images/svg/bg-asset-3.svg"), url("/images/svg/bg-asset-3.svg");
background-size: 5rem 5rem, 5rem 5rem, 5rem 5rem, 3.125rem 3.125rem,
3.125rem 3.125rem;
background-position: 80px 640px, 960px 80px, 1200px 720px, 136px 776px,
1016px 696px;
background-repeat: no-repeat;
`;
const MainContainer = styled.div`
@@ -120,6 +144,20 @@ const MainContainer = styled.div`
margin-top: 15rem;
margin-left: 10rem;
img.starIcon {
position: absolute;
&.first {
left: -24px;
top: -24px;
}
&.last {
right: -23px;
top: -24px;
}
}
`;
const MainTitleContainer = styled.div`
+170 -99
View File
@@ -30,7 +30,6 @@ import {
} from "../../wallets";
import { ChainIdHelper } from "@keplr-wallet/cosmos";
import AllChainsIcon from "../../public/images/svg/all-chains-icon.svg";
import { AllChainsItem } from "../../components/chain-list/all-chains-item";
import { SearchInput } from "../../components/search-input";
import {
@@ -52,8 +51,12 @@ import {
KEPLR_NOT_FOUND_ERROR,
TWITTER_LOGIN_ERROR,
} from "../../constants/error-message";
import { makeClaimMessage, makeSetRecordMessage } from "../../messages";
import Axios, { AxiosError } from "axios";
import {
makeClaimMessage,
makeRemoveRecordMessage,
makeSetRecordMessage,
} from "../../messages";
import Axios from "axios";
import { BackButton } from "../../components/back-button";
export default function VerificationPage() {
@@ -71,9 +74,6 @@ export default function VerificationPage() {
const [registeredChainList, setRegisteredChainList] = useState<
RegisteredAddresses[]
>([]);
const [allChains, setAllChains] = useState<ChainItemType>();
const [allChecked, setAllChecked] = useState(false);
const [checkedItems, setCheckedItems] = useState(new Set());
const [searchValue, setSearchValue] = useState("");
@@ -82,69 +82,16 @@ export default function VerificationPage() {
const [isAgree, setIsAgree] = useState(false);
useEffect(() => {
const init = async () => {
if (window.location.search) {
try {
const { state, code } = checkTwitterAuthQueryParameter(
window.location.search,
);
// Initialize Wallet
const keplrWallet = await initWallet();
// Fetch Twitter Profile
const twitterInfo = await fetchTwitterInfo(state, code);
// contract check registered
const registeredQueryResponse = await queryRegisteredTwitterId(
twitterInfo.id,
);
setTwitterAuthInfo({
...twitterInfo,
isRegistered: "data" in registeredQueryResponse,
});
if ("data" in registeredQueryResponse) {
const ownerOfQueryResponse = await queryOwnerOfTwitterName(
registeredQueryResponse.data.name,
);
const addressesQueryResponse = await queryAddressesFromTwitterName(
registeredQueryResponse.data.name,
);
if (keplrWallet) {
const key = await keplrWallet.getKey(MainChainId);
setIsOwner(ownerOfQueryResponse.data.owner === key.bech32Address);
}
setRegisteredChainList(addressesQueryResponse.data.addresses);
}
} catch (error) {
if (error instanceof Error && error.message === TWITTER_LOGIN_ERROR) {
await router.push("/");
}
console.error(error);
} finally {
setIsLoading(false);
}
}
};
init();
}, []);
useEffect(() => {
setAllChains({
chainId: "all chains",
chainName: "all chains",
prefix: `all chains(${chainList.length})`,
address: chainList.map((chain) => chain.chainName).join(", "),
chainImageUrl: AllChainsIcon,
});
}, [chainList]);
if (wallet) {
window.addEventListener("keplr_keystorechange", async () => {
await init();
});
}
}, [wallet]);
useEffect(() => {
const disabledChainList = chainList.filter((chain) => {
@@ -164,18 +111,67 @@ export default function VerificationPage() {
(chain) => !disabledChainList.includes(chain),
);
setAllChains({
chainId: "all chains",
chainName: "all chains",
prefix: `all chains(${filteredChainList.length})`,
address: filteredChainList.map((chain) => chain.chainName).join(", "),
chainImageUrl: AllChainsIcon,
});
setChainList(filteredChainList);
setDisabledChainList(disabledChainList);
setCheckedItems(new Set(filteredChainList));
}, [registeredChainList]);
useEffect(() => {
setCheckedItems(new Set(chainList));
}, [chainList]);
const init = async () => {
if (window.location.search) {
try {
const { state, code } = checkTwitterAuthQueryParameter(
window.location.search,
);
// Initialize Wallet
const keplrWallet = await initWallet();
// Fetch Twitter Profile
const twitterInfo = await fetchTwitterInfo(state, code);
// contract check registered
const registeredQueryResponse = await queryRegisteredTwitterId(
twitterInfo.id,
);
setTwitterAuthInfo({
...twitterInfo,
isRegistered: "data" in registeredQueryResponse,
});
if ("data" in registeredQueryResponse) {
const ownerOfQueryResponse = await queryOwnerOfTwitterName(
registeredQueryResponse.data.name,
);
const addressesQueryResponse = await queryAddressesFromTwitterName(
registeredQueryResponse.data.name,
);
if (keplrWallet) {
const key = await keplrWallet.getKey(MainChainId);
setIsOwner(ownerOfQueryResponse.data.owner === key.bech32Address);
}
setRegisteredChainList(addressesQueryResponse.data.addresses);
}
} catch (error) {
if (error instanceof Error && error.message === TWITTER_LOGIN_ERROR) {
await router.push("/");
}
console.error(error);
} finally {
setIsLoading(false);
}
}
};
const initWallet = async () => {
const keplr = await getKeplrFromWindow();
@@ -348,10 +344,15 @@ export default function VerificationPage() {
}
};
const isRegisterButtonDisable =
checkedItems.size < 1 ||
(!isOwner && registeredChainList.length > 0) ||
!isAgree;
const isRegisterButtonDisable = (() => {
const hasCheckedItem = checkedItems.size > 0;
if (isOwner) {
return !hasCheckedItem;
} else {
return !(isAgree && hasCheckedItem);
}
})();
return (
<Container>
@@ -363,6 +364,67 @@ export default function VerificationPage() {
) : (
<ContentContainer>
<BackButton />
<div>
<button
onClick={async () => {
if (twitterAuthInfo && wallet) {
const key = await wallet.getKey(MainChainId);
const removeMessages = registeredChainList.map((chain) => {
return makeRemoveRecordMessage(
twitterAuthInfo.username,
key.bech32Address,
chain.address,
);
});
const aminoMsgs = [];
const protoMsgs = [];
for (const msg of removeMessages) {
aminoMsgs.push(msg.amino);
protoMsgs.push(msg.proto);
}
console.log(aminoMsgs);
const chainInfo = {
chainId: MainChainId,
rest: REST_URL,
};
const simulated = await simulateMsgs(
chainInfo,
key.bech32Address,
{
proto: protoMsgs,
},
{
amount: [],
},
);
const txHash = await sendMsgs(
wallet,
chainInfo,
key.bech32Address,
{
amino: aminoMsgs,
proto: protoMsgs,
},
{
amount: [],
gas: Math.floor(simulated.gasUsed * 1.5).toString(),
},
);
console.log(txHash);
}
}}
>
All Remove
</button>
</div>
<TwitterProfile twitterProfileInformation={twitterAuthInfo} />
<ChainListTitleContainer>
@@ -373,17 +435,15 @@ export default function VerificationPage() {
/>
</ChainListTitleContainer>
{allChains && !searchValue ? (
{!searchValue ? (
<AllChainsItem
allChecked={allChecked}
setAllChecked={setAllChecked}
chainItem={allChains}
chainList={chainList}
checkedItems={checkedItems}
setCheckedItems={setCheckedItems}
/>
) : null}
<ChainList
allChecked={allChecked}
setAllChecked={setAllChecked}
chainList={chainList.filter(
(chain) =>
chain.chainId.includes(searchValue) ||
@@ -400,23 +460,27 @@ export default function VerificationPage() {
setCheckedItems={setCheckedItems}
/>
<AgreeContainer
onClick={() => {
setIsAgree(!isAgree);
}}
>
<AgreeCheckBox type="checkbox" checked={isAgree} readOnly />I
check that Osmo is required for this transaction
</AgreeContainer>
<ButtonContainer disabled={isRegisterButtonDisable}>
<PrimaryButton
disabled={isRegisterButtonDisable}
onClick={onClickRegistration}
{!isOwner && (
<AgreeContainer
onClick={() => {
setIsAgree(!isAgree);
}}
>
Register
</PrimaryButton>
</ButtonContainer>
<AgreeCheckBox type="checkbox" checked={isAgree} readOnly />I
check that Osmo is required for this transaction
</AgreeContainer>
)}
{chainList.length > 0 && (
<ButtonContainer disabled={isRegisterButtonDisable}>
<PrimaryButton
disabled={isRegisterButtonDisable}
onClick={onClickRegistration}
>
Register
</PrimaryButton>
</ButtonContainer>
)}
</ContentContainer>
)}
</MainContainer>
@@ -438,6 +502,13 @@ const MainContainer = styled.div`
padding: 2.7rem 0;
color: white;
background-image: url("/images/svg/bg-asset-3.svg"),
url("/images/svg/bg-asset-3.svg"), url("/images/svg/bg-asset-3.svg"),
url("/images/svg/bg-asset-3.svg");
background-size: 3.125rem 3.125rem;
background-position: 296px 536px, 1256px 216px, 376px 776px, 1176px 856px;
background-repeat: no-repeat;
`;
export const ContentContainer = styled.div`
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+41
View File
@@ -0,0 +1,41 @@
{
"name": "App",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0C-9.27438e-07 10.6087 4.21427 20.7828 11.7157 28.2843C19.2172 35.7857 29.3913 40 40 40C50.6087 40 60.7828 35.7857 68.2843 28.2843C75.7857 20.7828 80 10.6087 80 1.30337e-05L0 0Z" fill="#242424"/>
<path d="M0 40C-9.27438e-07 50.6087 4.21427 60.7828 11.7157 68.2843C19.2172 75.7857 29.3913 80 40 80C50.6087 80 60.7828 75.7857 68.2843 68.2843C75.7857 60.7828 80 50.6087 80 40L0 40Z" fill="#242424"/>
</svg>

After

Width:  |  Height:  |  Size: 512 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 80C10.6087 80 20.7828 75.7857 28.2843 68.2843C35.7857 60.7828 40 50.6087 40 40C40 29.3913 35.7857 19.2172 28.2843 11.7157C20.7828 4.21428 10.6087 -4.6372e-07 9.53674e-06 0L0 80Z" fill="#242424"/>
<path d="M40 80C50.6087 80 60.7828 75.7857 68.2843 68.2843C75.7857 60.7828 80 50.6087 80 40C80 29.3913 75.7857 19.2172 68.2843 11.7157C60.7828 4.21428 50.6087 -4.6372e-07 40 0L40 80Z" fill="#242424"/>
</svg>

After

Width:  |  Height:  |  Size: 512 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25 0L25.3504 2.74704C26.8096 14.1848 35.8152 23.1904 47.253 24.6496L50 25L47.253 25.3504C35.8152 26.8096 26.8096 35.8152 25.3504 47.253L25 50L24.6496 47.253C23.1904 35.8152 14.1848 26.8096 2.74704 25.3504L0 25L2.74704 24.6496C14.1848 23.1904 23.1904 14.1848 24.6496 2.74704L25 0Z" fill="#2B2B2B"/>
</svg>

After

Width:  |  Height:  |  Size: 411 B