├── .eslintignore
├── src
├── components
│ ├── EditSigners.tsx
│ ├── InfoBox.tsx
│ ├── Contexts.ts
│ ├── GenericField.tsx
│ ├── AddressView.tsx
│ ├── NetworkSwitcher.tsx
│ ├── DataActionPreview.tsx
│ ├── SafeInformation.tsx
│ └── SetOwnerModal.tsx
├── vite-env.d.ts
├── app
│ ├── Error.tsx
│ ├── SafeInformationPage.tsx
│ ├── Wrapper.tsx
│ ├── App.tsx
│ ├── ViewSafe.tsx
│ ├── Root.tsx
│ ├── CreateSafe.tsx
│ └── NewSafeProposal.tsx
├── main.tsx
├── utils
│ ├── validators.ts
│ ├── etherFormatting.ts
│ └── get.ts
├── hooks
│ ├── useUpdateProposalViaQuery.ts
│ ├── useSetParamsFromQuery.ts
│ └── useLoadProposalFromQuery.ts
├── schemas
│ └── proposal.ts
├── chains.ts
└── app.css
├── .npmrc
├── .gitignore
├── postcss.config.js
├── index.html
├── vite.config.ts
├── tsconfig.node.json
├── .github
└── workflows
│ ├── build.yml
│ └── publish.yml
├── README.md
├── .eslintrc.cjs
├── tsconfig.json
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/src/components/EditSigners.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers=true
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .parcel-cache
2 | node_modules
3 | dist
4 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export { config as default } from "reshaped/config/postcss.js";
--------------------------------------------------------------------------------
/src/app/Error.tsx:
--------------------------------------------------------------------------------
1 | export const Error = () => {
2 | return
ERR
;
3 | };
4 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Smol Safe
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | define: {
8 | 'process.env': {},
9 | 'global': {}
10 | }
11 | })
12 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on: push
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - name: Checkout 🛎️
11 | uses: actions/checkout@master
12 |
13 | - run: corepack enable
14 | - run: pnpm install --frozen-lockfile
15 |
16 | - name: Build 🏗️
17 | run: pnpm build
18 |
--------------------------------------------------------------------------------
/src/components/InfoBox.tsx:
--------------------------------------------------------------------------------
1 | import { Icon, Tooltip } from "reshaped";
2 | import { Info } from "react-feather";
3 |
4 | export const InfoBox = ({ children }: { children: React.ReactNode }) => {
5 | return (
6 |
7 | {(attributes) => } />}
8 |
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import { Wrapper } from "./app/Wrapper";
4 | import "reshaped/themes/reshaped/theme.css";
5 | import { Buffer } from "buffer";
6 | globalThis.Buffer = Buffer;
7 |
8 | const container = document.getElementById("app");
9 | const root = createRoot(container!);
10 | root.render(
11 |
12 |
13 | ,
14 | );
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | small on-chain front-end for gnosis safe
2 |
3 | [use it ↝](https://ourzora.github.io/smol-safe/)
4 |
5 | this allows for gnosis safe interactions without requiring any specific backend-servers etc. utilizing safe protocol kit and a web3 injected connector. it's especially useful for new chains that may not be fully integrated into the official gnosis safe UI or as a backup user interface. All signatures in this app are created on-chain.
6 |
7 | contributions are welcome. to add a new chain that is not yet in gnosis-deploys, update the chains.ts file.
8 |
--------------------------------------------------------------------------------
/src/utils/validators.ts:
--------------------------------------------------------------------------------
1 | import { isAddress, parseEther } from "viem";
2 | import { string } from "yup";
3 |
4 | export const validateAddress = (address: string) => {
5 | if (!isAddress(address)) {
6 | return "Invalid address";
7 | }
8 | };
9 |
10 | export const validateETH = (value: string) => {
11 | try {
12 | parseEther(value);
13 | } catch (e: any) {
14 | return "ETH Value is Invalid";
15 | }
16 | };
17 |
18 | export const yupAddress = string()
19 | .matches(/^0x[a-fA-F0-9]{40}$/, "Needs to be a valid address")
20 | .required();
21 |
--------------------------------------------------------------------------------
/src/components/Contexts.ts:
--------------------------------------------------------------------------------
1 | import Safe from "@safe-global/protocol-kit";
2 | import { BrowserProvider } from "ethers";
3 | import { Address } from "viem";
4 |
5 | export type SafeInformationType = {
6 | owners: string[];
7 | threshold: number;
8 | chainId: number;
9 | nonce: number;
10 | address: Address;
11 | safeSdk: Safe;
12 | safeSdk2: Safe;
13 | };
14 |
15 | export interface NetworkContext {
16 | walletProvider: BrowserProvider;
17 | currentNetwork: number;
18 | }
19 |
20 | export interface SafeContext {
21 | safeInformation: SafeInformationType;
22 | }
23 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Build and deploy
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout 🛎️
14 | uses: actions/checkout@master
15 |
16 | - run: corepack enable
17 | - run: pnpm install --frozen-lockfile
18 |
19 | - name: Build 🏗️
20 | run: pnpm build
21 |
22 | - name: Deploy to GH Pages 🚀
23 | uses: peaceiris/actions-gh-pages@v3
24 | with:
25 | github_token: ${{ secrets.GITHUB_TOKEN }}
26 | publish_dir: dist
27 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs', 'node_modules'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | "@typescript-eslint/no-explicit-any": "off"
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils/etherFormatting.ts:
--------------------------------------------------------------------------------
1 | import { formatEther, parseEther } from "viem";
2 | import { Proposal } from "../schemas/proposal";
3 |
4 | export function transformValuesToWei(proposal: Proposal): Proposal {
5 | return {
6 | ...proposal,
7 | actions: proposal.actions?.map((action) => ({
8 | ...action,
9 | value: parseEther(action.value).toString(),
10 | })),
11 | };
12 | }
13 |
14 | export function transformValuesFromWei(proposal: Proposal): Proposal {
15 | return {
16 | ...proposal,
17 | actions: proposal.actions?.map((action) => ({
18 | ...action,
19 | value: formatEther(BigInt(action.value)),
20 | })),
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/SafeInformationPage.tsx:
--------------------------------------------------------------------------------
1 | import { Button, View } from "reshaped";
2 | import { SafeInformation } from "../components/SafeInformation";
3 | import { useNavigate, useParams } from "react-router-dom";
4 | import { useUpdateProposalInQuery } from "../hooks/useUpdateProposalViaQuery";
5 |
6 | export const SafeInformationPage = () => {
7 | const { networkId, safeAddress } = useParams();
8 | const navigate = useNavigate();
9 |
10 | const onNewProposalClick = () => {
11 | navigate(`/safe/${networkId}/${safeAddress}/new`);
12 | };
13 |
14 | const { addAction } = useUpdateProposalInQuery({ proposal: undefined });
15 |
16 | return (
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": false,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "types": []
23 | },
24 | "include": ["src"],
25 | "exclude": [
26 | "node_modules"
27 | ],
28 | "references": [{ "path": "./tsconfig.node.json" }]
29 | }
30 |
--------------------------------------------------------------------------------
/src/utils/get.ts:
--------------------------------------------------------------------------------
1 | // WARNING: This is not a drop in replacement solution and
2 | // it might not work for some edge cases. Test your code!
3 | export const get = (obj: any, path: string, defValue?: any) => {
4 | // If path is not defined or it has false value
5 | if (!path) {
6 | return undefined;
7 | }
8 | // Check if path is string or array. Regex : ensure that we do not have '.' and brackets.
9 | // Regex explained: https://regexr.com/58j0k
10 | const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g);
11 | if (!pathArray) {
12 | return undefined;
13 | }
14 | // Find value
15 | const result = pathArray.reduce(
16 | (prevObj, key) => prevObj && prevObj[key],
17 | obj,
18 | );
19 | // If found value is undefined return default value; otherwise return the value
20 | return result === undefined ? defValue : result;
21 | };
22 |
--------------------------------------------------------------------------------
/src/components/GenericField.tsx:
--------------------------------------------------------------------------------
1 | import { FormControl, TextField, View } from "reshaped";
2 | import { get } from "../utils/get";
3 |
4 | export const GenericField = ({
5 | label,
6 | fieldProps,
7 | }: {
8 | label: string;
9 | fieldProps?: any;
10 | }) => {
11 | return ({ field: { name, value, onChange }, form: { errors } }: any) => {
12 | const error = get(errors, name);
13 |
14 | return (
15 |
16 |
17 | {label}
18 | onChange(event)}
22 | {...fieldProps}
23 | />
24 | {error && {error}}
25 |
26 |
27 | );
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/src/hooks/useUpdateProposalViaQuery.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react";
2 | import { Proposal } from "../schemas/proposal";
3 | import { useRedirectToProposalWithNewParams } from "./useSetParamsFromQuery";
4 |
5 | export type AddAction = (
6 | newAction: NonNullable[0],
7 | ) => void;
8 |
9 | export type UpdateProposal = {
10 | addAction: AddAction;
11 | replace: (proposal: Proposal) => void;
12 | };
13 | export const useUpdateProposalInQuery = ({
14 | proposal,
15 | }: {
16 | proposal: Proposal | undefined;
17 | }): UpdateProposal => {
18 | const setParams = useRedirectToProposalWithNewParams();
19 |
20 | const addAction = useCallback(
21 | (newAction: NonNullable[0]) => {
22 | setParams({
23 | actions: [...(proposal?.actions || []), newAction],
24 | nonce: proposal?.nonce,
25 | });
26 | },
27 | [proposal, setParams],
28 | );
29 |
30 | return {
31 | addAction,
32 | replace: setParams,
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/src/schemas/proposal.ts:
--------------------------------------------------------------------------------
1 | import { InferType, array, number, object, string } from "yup";
2 | import { yupAddress } from "../utils/validators";
3 |
4 | export const proposalSchema = object({
5 | nonce: number().nullable(),
6 | actions: array(
7 | object({
8 | to: yupAddress,
9 | value: string()
10 | .default("0")
11 | .matches(
12 | /^[0-9]+(\.[0-9]+)?$/,
13 | "Needs to be a ETH price (0, 1, or 0.23)",
14 | )
15 | .required(),
16 | data: string()
17 | .default("0x")
18 | .matches(
19 | /^0x(?:[0-9A-Za-z][0-9A-Za-z])*$/,
20 | "Data is required to match hex format",
21 | )
22 | .required(),
23 | }),
24 | ),
25 | });
26 |
27 | export interface Proposal extends InferType {
28 | // using interface instead of type generally gives nicer editor feedback
29 | }
30 |
31 | export const DEFAULT_ACTION_ITEM = {
32 | to: "0x",
33 | value: "0",
34 | data: "0x",
35 | };
36 |
37 | export const DEFAULT_PROPOSAL = {
38 | nonce: null,
39 | actions: [DEFAULT_ACTION_ITEM],
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/AddressView.tsx:
--------------------------------------------------------------------------------
1 | import { goerli, mainnet, optimism, zora, zoraTestnet } from "viem/chains";
2 | import { useOutletContext } from "react-router-dom";
3 | import { NetworkContext } from "./Contexts";
4 |
5 | export const networkToExplorer = {
6 | [mainnet.id]: "https://etherscan.io/",
7 | [zora.id]: "https://explorer.zora.energy/",
8 | [goerli.id]: "https://goerli.etherscan.io/",
9 | [zoraTestnet.id]: "https://testnet.explorer.zora.energy/",
10 | [optimism.id]: "https://optimistic.etherscan.io/",
11 | };
12 |
13 | export const AddressView = ({
14 | address,
15 | prettyName,
16 | }: {
17 | address: `0x${string}`;
18 | prettyName?: string;
19 | }) => {
20 | const currentNetwork = useOutletContext().currentNetwork;
21 | // const { data: ensName } = useEns({ address, enabled: !prettyName });
22 | const ensName = null;
23 |
24 | return (
25 |
26 | {prettyName ? prettyName : ensName ? ensName : address}{" "}
27 |
35 | ↗
36 |
37 |
38 | );
39 | };
40 |
--------------------------------------------------------------------------------
/src/components/NetworkSwitcher.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-ts-comment */
2 | import { useCallback, useState } from "react";
3 | import { FormControl, Select } from "reshaped";
4 | import { allowedNetworks } from "../chains";
5 | import { useNavigate } from "react-router-dom";
6 |
7 | export const NetworkSwitcher = ({
8 | currentNetwork,
9 | }: {
10 | currentNetwork: string | undefined;
11 | }) => {
12 | const [currentNetworkValue, setCurrentNetworkValue] =
13 | useState(currentNetwork);
14 | const navigate = useNavigate();
15 | const changeNetwork = useCallback(
16 | (e: { value: string }) => {
17 | const networkId = e.value;
18 | setCurrentNetworkValue(networkId);
19 |
20 | navigate(`/safe/${networkId}`);
21 | },
22 | [navigate],
23 | );
24 |
25 | return (
26 |
27 | Network:
28 |
40 |
41 | );
42 | };
43 |
--------------------------------------------------------------------------------
/src/app/Wrapper.tsx:
--------------------------------------------------------------------------------
1 | import { RouterProvider, createHashRouter } from "react-router-dom";
2 | import { Container, Reshaped } from "reshaped";
3 | import { App } from "./App";
4 | // import { Error } from "./Error";
5 | import { ViewSafe } from "./ViewSafe";
6 | import { Root } from "./Root";
7 | import { CreateSafe } from "./CreateSafe";
8 | import { NewSafeProposal } from "./NewSafeProposal";
9 | import { SafeInformationPage } from "./SafeInformationPage";
10 |
11 | const router = createHashRouter([
12 | {
13 | path: "/",
14 | Component: Root,
15 | children: [
16 | {
17 | path: "/safe/:networkId",
18 | index: true,
19 | Component: App,
20 | },
21 | {
22 | path: "/safe/:networkId/create",
23 | Component: CreateSafe,
24 | },
25 | {
26 | path: "/safe/:networkId/:safeAddress",
27 | Component: ViewSafe,
28 | children: [
29 | {
30 | path: "/safe/:networkId/:safeAddress",
31 | index: true,
32 | Component: SafeInformationPage,
33 | },
34 | {
35 | path: "/safe/:networkId/:safeAddress/new",
36 | index: true,
37 | Component: NewSafeProposal,
38 | },
39 | ],
40 | },
41 | ],
42 | },
43 | ]);
44 |
45 | export const Wrapper = () => (
46 |
47 |
48 |
49 |
50 |
51 | );
52 |
--------------------------------------------------------------------------------
/src/components/DataActionPreview.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from "react";
2 | import { Address, Hex } from "viem";
3 | import {
4 | base,
5 | baseGoerli,
6 | goerli,
7 | mainnet,
8 | optimism,
9 | zora,
10 | zoraTestnet,
11 | } from "viem/chains";
12 | import { useOutletContext } from "react-router-dom";
13 | import { NetworkContext } from "./Contexts";
14 |
15 | const networkToEtherActor: any = {
16 | [mainnet.id]: "mainnet",
17 | [zora.id]: "zora",
18 | [baseGoerli.id]: "base-goerli",
19 | [base.id]: "base",
20 | [goerli.id]: "goerli",
21 | [zoraTestnet.id]: "zora-goerli",
22 | [optimism.id]: "optimism",
23 | };
24 |
25 | export const DataActionPreview = ({ to, data }: { to: Address; data: Hex }) => {
26 | const currentNetwork = useOutletContext().currentNetwork;
27 | const [responseData, setResponseData] = useState();
28 |
29 | const fetchData = useCallback(async () => {
30 | let json;
31 | try {
32 | const response = await fetch(
33 | `https://${networkToEtherActor[Number(currentNetwork)]}.ether.actor/decode/${to}/${data}`,
34 | );
35 | if (!response.ok) {
36 | throw new Error();
37 | }
38 | json = await response.json();
39 | } catch (err: any) {
40 | const response = await fetch(`https://ether.actor/decode/${data}`);
41 | json = await response.json();
42 | }
43 | setResponseData(json);
44 | }, [to, data, setResponseData]);
45 |
46 | useEffect(() => {
47 | fetchData();
48 | }, [to, data]);
49 |
50 | return {JSON.stringify(responseData, null, 2)};
51 | };
52 |
--------------------------------------------------------------------------------
/src/hooks/useSetParamsFromQuery.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from "react";
2 | import {
3 | useLocation,
4 | useNavigate,
5 | useParams,
6 | useSearchParams,
7 | } from "react-router-dom";
8 | import { Proposal } from "../schemas/proposal";
9 | import { queryKeys } from "./useLoadProposalFromQuery";
10 |
11 | export const useRedirectToProposalWithNewParams = () => {
12 | const [, setParams] = useSearchParams();
13 | const { networkId, safeAddress } = useParams();
14 | const navigate = useNavigate();
15 | const { pathname } = useLocation();
16 |
17 | return useCallback(
18 | (proposal: Proposal) => {
19 | if (!proposal.actions?.length) {
20 | return;
21 | }
22 | const params = {
23 | [queryKeys.targets]: proposal
24 | .actions!.map((action) => action.to)
25 | .join("|"),
26 | [queryKeys.calldatas]: proposal
27 | .actions!.map((action) => action.data)
28 | .join("|"),
29 | [queryKeys.values]: proposal
30 | .actions!.map((action) => action.value)
31 | .join("|"),
32 | ...(proposal.nonce
33 | ? {
34 | [queryKeys.nonce]: proposal.nonce.toString(),
35 | }
36 | : {}),
37 | };
38 | // if we are not in a new safe proposal, we need to redirect to new safe proposal
39 | // so that it shows up in the url, and renders correctly
40 | if (!pathname.includes("new")) {
41 | const newPath = `/safe/${networkId}/${safeAddress}/new`;
42 |
43 | navigate(newPath, {
44 | replace: true,
45 | });
46 | }
47 | setParams(params);
48 | },
49 | [setParams, networkId, safeAddress, pathname, navigate],
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/src/hooks/useLoadProposalFromQuery.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useSearchParams } from "react-router-dom";
3 | import { Proposal } from "../schemas/proposal";
4 |
5 | export const queryKeys = {
6 | targets: "targets",
7 | calldatas: "calldatas",
8 | values: "values",
9 | nonce: "nonce",
10 | } as const;
11 |
12 | export const useLoadProposalFromQuery = () => {
13 | const [proposal, setProposal] = useState();
14 | const [params] = useSearchParams();
15 |
16 | useEffect(() => {
17 | const targets = params.get(queryKeys["targets"])?.split("|");
18 | const calldatas = params.get(queryKeys["calldatas"])?.split("|");
19 | const values = params.get(queryKeys["values"])?.split("|");
20 | const nonce = params.get(queryKeys["nonce"]);
21 |
22 | if (targets && calldatas) {
23 | // ensure the 3 lengths are the same. check if values also have the same length if it's not empty
24 | // check the inverse of the above, if inverse is true, return:
25 | if (
26 | targets.length !== calldatas.length ||
27 | (values?.length && values?.length !== targets.length)
28 | ) {
29 | console.log("invalid lengths");
30 | return;
31 | }
32 |
33 | const actions = targets.map((target, index) => ({
34 | to: target,
35 | data: calldatas[index]!,
36 | value: (values && values[index]) || "0",
37 | }));
38 |
39 | console.log({ actions, txt: "setting proposal" });
40 |
41 | const proposal: Proposal = {
42 | actions,
43 | ...(nonce ? { nonce: parseInt(nonce) } : {}),
44 | };
45 |
46 | setProposal(proposal);
47 | }
48 | }, [params, setProposal]);
49 |
50 | return proposal;
51 | };
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "smol-safe",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "type": "module",
6 | "dependencies": {
7 | "@safe-global/protocol-kit": "^6.1.1",
8 | "@safe-global/safe-deployments": "^1.37.14",
9 | "@zoralabs/zorb": "^0.1.0",
10 | "buffer": "^6.0.3",
11 | "ethers": "^6.7.1",
12 | "formik": "^2.4.5",
13 | "lodash": "^4.17.21",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-feather": "^2.0.10",
17 | "react-router-dom": "^6.20.0",
18 | "reshaped": "^2.6.0",
19 | "toastify-js": "^1.12.0",
20 | "viem": "^2.38.2",
21 | "yup": "^1.3.2"
22 | },
23 | "scripts": {
24 | "dev": "vite",
25 | "build": "tsc && vite build --base=/smol-safe/",
26 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
27 | "preview": "vite preview",
28 | "prettier": "prettier src/ --write",
29 | "deploy": "pnpm build && gh-pages -d dist"
30 | },
31 | "devDependencies": {
32 | "@safe-global/safe-core-sdk-types": "^5.1.0",
33 | "@types/react": "^18.2.38",
34 | "@types/react-dom": "^18.2.16",
35 | "@typescript-eslint/eslint-plugin": "^6.12.0",
36 | "@typescript-eslint/parser": "^6.12.0",
37 | "@vitejs/plugin-react-swc": "^3.5.0",
38 | "assert": "^2.0.0",
39 | "eslint": "^8.54.0",
40 | "eslint-plugin-react-hooks": "^4.6.0",
41 | "eslint-plugin-react-refresh": "^0.4.4",
42 | "events": "^3.1.0",
43 | "gh-pages": "^5.0.0",
44 | "os-browserify": "^0.3.0",
45 | "parcel": "^2.9.2",
46 | "path-browserify": "^1.0.0",
47 | "prettier": "^3.2.4",
48 | "stream-browserify": "^3.0.0",
49 | "ts-node": "^10.9.1",
50 | "typescript": "^5.3.2",
51 | "vite": "^5.0.2"
52 | },
53 | "packageManager": "pnpm@10.19.0+sha512.c9fc7236e92adf5c8af42fd5bf1612df99c2ceb62f27047032f4720b33f8eacdde311865e91c411f2774f618d82f320808ecb51718bfa82c060c4ba7c76a32b8"
54 | }
55 |
--------------------------------------------------------------------------------
/src/app/App.tsx:
--------------------------------------------------------------------------------
1 | import { FormControl, Button, TextField, Divider, View, Card } from "reshaped";
2 | import { useFormik } from "formik";
3 | import { isAddress } from "viem";
4 | import { useNavigate, useOutletContext } from "react-router-dom";
5 | import { NetworkContext } from "../components/Contexts";
6 |
7 | function FormAddressInput() {
8 | const navigate = useNavigate();
9 |
10 | const { currentNetwork: network } = useOutletContext();
11 |
12 | const formik = useFormik({
13 | initialValues: {
14 | address: "",
15 | },
16 | validate: (data) => {
17 | const errors = {};
18 | if (!isAddress(data.address)) {
19 | return { address: "Invalid Address" };
20 | }
21 | return errors;
22 | },
23 | onSubmit: (values) => {
24 | navigate(`/safe/${network}/${values.address}`);
25 | },
26 | });
27 |
28 | return (
29 |
49 | );
50 | }
51 |
52 | export function App() {
53 | const navigate = useNavigate();
54 |
55 | const chainId = useOutletContext().currentNetwork;
56 |
57 | return (
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
73 |
74 |
75 |
76 | );
77 | }
78 |
--------------------------------------------------------------------------------
/src/chains.ts:
--------------------------------------------------------------------------------
1 | import { ContractNetworksConfig, ContractNetworkConfig } from "@safe-global/protocol-kit";
2 | import * as chains from "viem/chains";
3 |
4 | const defaultL2Addresses: ContractNetworkConfig = {
5 | multiSendAddress: "0xA238CBeb142c10Ef7Ad8442C6D1f9E89e07e7761",
6 | safeProxyFactoryAddress: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2",
7 | multiSendCallOnlyAddress: "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D",
8 | fallbackHandlerAddress: "0x1AC114C2099aFAf5261731655Dc6c306bFcd4Dbd",
9 | createCallAddress: "0x7cbB62EaA69F79e6873cD1ecB2392971036cFAa4",
10 | signMessageLibAddress: "0xA65387F16B013cf2Af4605Ad8aA5ec25a2cbA3a2",
11 | // renamed from safeMasterCopyAddress
12 | safeSingletonAddress: "0x3E5c63644E683549055b9Be8653de26E0B4CD36E",
13 | simulateTxAccessorAddress: "0x59AD6735bCd8152B84860Cb256dD9e96b85F69Da",
14 | };
15 |
16 | const zkAddresses: ContractNetworkConfig = {
17 | multiSendAddress: "0x0dFcccB95225ffB03c6FBB2559B530C2B7C8A912",
18 | safeProxyFactoryAddress: "0xDAec33641865E4651fB43181C6DB6f7232Ee91c2",
19 | multiSendCallOnlyAddress: "0xf220D3b4DFb23C4ade8C88E526C1353AbAcbC38F",
20 | fallbackHandlerAddress: "0x2f870a80647BbC554F3a0EBD093f11B4d2a7492A",
21 | createCallAddress: "0xcB8e5E438c5c2b45FbE17B02Ca9aF91509a8ad56",
22 | signMessageLibAddress: "0x357147caf9C0cCa67DfA0CF5369318d8193c8407",
23 | // renamed from safeMasterCopyAddress
24 | safeSingletonAddress: "0x1727c2c531cf966f902E5927b98490fDFb3b2b70",
25 | simulateTxAccessorAddress: "0x4191E2e12E8BC5002424CE0c51f9947b02675a44",
26 | };
27 |
28 | // Example how to add new networks before they are merged and released from `safe-global/safe-deployments` package.
29 | export const contractNetworks: ContractNetworksConfig = {
30 | [`${chains.abstractTestnet.id}`]: zkAddresses,
31 | [`${chains.abstract.id}`]: zkAddresses,
32 | [`${chains.zoraSepolia.id}`]: defaultL2Addresses,
33 | [`${chains.blastSepolia.id}`]: defaultL2Addresses,
34 | [`${chains.optimismSepolia.id}`]: defaultL2Addresses,
35 | [`${chains.blast.id}`]: defaultL2Addresses,
36 | [`${chains.unichainSepolia.id}`]: defaultL2Addresses,
37 | [`${chains.zksyncSepoliaTestnet.id}`]: zkAddresses,
38 | [`${chains.zksync.id}`]: zkAddresses,
39 | };
40 |
41 | export const allowedNetworks: { [chainId: number]: chains.Chain } = {
42 | [chains.mainnet.id]: chains.mainnet,
43 | [chains.zora.id]: chains.zora,
44 | [chains.zoraTestnet.id]: chains.zoraTestnet,
45 | [chains.zoraSepolia.id]: chains.zoraSepolia,
46 | [chains.arbitrumGoerli.id]: chains.arbitrumGoerli,
47 | [chains.arbitrumSepolia.id]: chains.arbitrumSepolia,
48 | [chains.arbitrumNova.id]: chains.arbitrumNova,
49 | [chains.arbitrum.id]: chains.arbitrum,
50 | [chains.base.id]: chains.base,
51 | [chains.baseSepolia.id]: chains.baseSepolia,
52 | [chains.sepolia.id]: chains.sepolia,
53 | [chains.optimism.id]: chains.optimism,
54 | [chains.optimismSepolia.id]: chains.optimismSepolia,
55 | [chains.blastSepolia.id]: chains.blastSepolia,
56 | [chains.blast.id]: chains.blast,
57 | [chains.zksync.id]: chains.zkSync,
58 | [chains.zksyncSepoliaTestnet.id]: chains.zksyncSepoliaTestnet,
59 | [chains.unichainSepolia.id]: chains.unichainSepolia,
60 | [chains.abstractTestnet.id]: chains.abstractTestnet,
61 | [chains.abstract.id]: chains.abstract,
62 | };
63 |
64 | Object.keys(contractNetworks).map((network) => {
65 | if (allowedNetworks[+network]) {
66 | // if already exists skip
67 | return;
68 | }
69 | const viemChain = Object.values(chains).find(
70 | (chain) => chain.id.toString() === network
71 | );
72 |
73 | if (!viemChain) {
74 | return;
75 | }
76 | allowedNetworks[+network] = viemChain;
77 | });
78 |
--------------------------------------------------------------------------------
/src/app/ViewSafe.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback, useMemo } from "react";
2 | import { Outlet, useOutletContext, useParams } from "react-router-dom";
3 | import { Signer } from "ethers";
4 | import Safe from "@safe-global/protocol-kit";
5 | import { contractNetworks } from "../chains";
6 | import { Button, View, Text } from "reshaped";
7 | import { Address } from "viem";
8 | import {
9 | NetworkContext,
10 | SafeContext,
11 | SafeInformationType,
12 | } from "../components/Contexts";
13 |
14 | type SafeData = Awaited>;
15 |
16 | async function getSafeSDK(safeAddress: string, signer: Signer) {
17 | const signerAddress = await signer.getAddress();
18 |
19 | const safeSdk: Safe = await Safe.init({
20 | provider: (window as any).ethereum,
21 | signer: signerAddress,
22 | safeAddress,
23 | contractNetworks,
24 | });
25 |
26 | const safeSdk2 = await Safe.init({
27 | provider: (window as any).ethereum,
28 | signer: signerAddress,
29 | safeAddress,
30 | contractNetworks,
31 | });
32 |
33 | return { safeSdk, safeSdk2, signer };
34 | }
35 |
36 | const useLoadSafeInformation = ({
37 | safeData,
38 | }: {
39 | safeData: SafeData | undefined;
40 | }) => {
41 | const [safeInformation, setSafeInformation] = useState();
42 | useEffect(() => {
43 | if (!safeData) return;
44 |
45 | const loadSafeInfo = async () => {
46 | const { safeSdk, safeSdk2 } = safeData;
47 | const owners = await safeSdk.getOwners();
48 | const threshold = await safeSdk.getThreshold();
49 | const chainId = Number(await safeSdk.getChainId());
50 | const nonce = await safeSdk.getNonce();
51 | const address = (await safeSdk.getAddress()) as Address;
52 |
53 | setSafeInformation({
54 | owners,
55 | threshold,
56 | chainId,
57 | nonce,
58 | address,
59 | safeSdk,
60 | safeSdk2,
61 | });
62 | };
63 |
64 | loadSafeInfo();
65 | }, [safeData, setSafeInformation]);
66 |
67 | return safeInformation;
68 | };
69 |
70 | export const ViewSafe = () => {
71 | const params = useParams();
72 | const [safeData, setSafeData] = useState();
73 | const { walletProvider: providerContext } =
74 | useOutletContext();
75 |
76 | const setupSafe = useCallback(async () => {
77 | if (params.safeAddress && providerContext) {
78 | setSafeData(
79 | await getSafeSDK(params.safeAddress, await providerContext.getSigner()),
80 | );
81 | }
82 | }, [params.safeAddress, providerContext]);
83 |
84 | const switchNetwork = useCallback(() => {
85 | if (!params.networkId) return;
86 | providerContext?.send("wallet_switchEthereumChain", [
87 | {
88 | chainId: `0x${parseInt(params.networkId!).toString(16)}`,
89 | },
90 | ]);
91 | }, [params.networkId, providerContext]);
92 |
93 | useEffect(() => {
94 | switchNetwork();
95 | }, [switchNetwork]);
96 |
97 | useEffect(() => {
98 | setupSafe();
99 | }, [setupSafe]);
100 |
101 | const safeInformation = useLoadSafeInformation({ safeData });
102 |
103 | const safeInformationContext: SafeContext | undefined = useMemo(() => {
104 | if (!safeInformation) return;
105 | return {
106 | safeInformation,
107 | };
108 | }, [safeInformation]);
109 |
110 | return (
111 |
112 | View Safe
113 |
114 | {safeInformationContext ? (
115 |
116 | ) : (
117 |
118 | )}
119 |
120 | );
121 | };
122 |
--------------------------------------------------------------------------------
/src/components/SafeInformation.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { Button, Card, Text, View } from "reshaped";
3 | import { allowedNetworks } from "../chains";
4 | import { InfoBox } from "../components/InfoBox";
5 | import { Address } from "viem";
6 | import { AddressView } from "../components/AddressView";
7 | import { OwnerAction, SetOwnerModal } from "../components/SetOwnerModal";
8 | import { useOutletContext } from "react-router-dom";
9 | import { SafeContext } from "./Contexts";
10 | import { AddAction } from "../hooks/useUpdateProposalViaQuery";
11 |
12 | const SafeInformationItem = ({
13 | title,
14 | description,
15 | children,
16 | }: {
17 | title: string;
18 | description: string;
19 | children: React.ReactNode;
20 | }) => (
21 | <>
22 |
23 | {title}: {description}
24 |
25 | {children}
26 | >
27 | );
28 |
29 | export const SafeInformation = ({
30 | children,
31 | addAction,
32 | }: {
33 | children?: React.ReactNode;
34 | addAction: AddAction;
35 | }) => {
36 | const [ownerAction, setOwnerAction] = useState();
37 |
38 | const { safeInformation } = useOutletContext();
39 |
40 | return (
41 |
42 | {ownerAction && (
43 |
{
45 | setOwnerAction(undefined);
46 | }}
47 | action={ownerAction}
48 | addAction={addAction}
49 | />
50 | )}
51 |
52 |
53 |
54 |
58 | {allowedNetworks[safeInformation.chainId]?.name ||
59 | safeInformation.chainId.toString()}
60 |
61 |
62 |
63 |
68 | {safeInformation.threshold}
69 |
70 |
71 |
72 |
76 |
77 | {safeInformation.owners.map((owner) => (
78 |
79 |
80 |
81 |
90 |
91 |
92 | ))}
93 |
94 |
95 |
96 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
114 | {safeInformation.nonce}
115 |
116 |
117 |
118 |
119 | {children}
120 |
121 | );
122 | };
123 |
--------------------------------------------------------------------------------
/src/app/Root.tsx:
--------------------------------------------------------------------------------
1 | import { ethers } from "ethers";
2 | import { useCallback, useEffect, useMemo, useState } from "react";
3 | import { Outlet, useParams } from "react-router-dom";
4 | import { Button, View } from "reshaped";
5 | import { NetworkSwitcher } from "../components/NetworkSwitcher";
6 | import { BrowserProvider } from "ethers";
7 | import { NetworkContext } from "../components/Contexts";
8 | import { allowedNetworks } from "../chains";
9 |
10 | export const Root = () => {
11 | const [provider, setProvider] = useState<
12 | ethers.BrowserProvider | undefined
13 | >();
14 | const networkIdFromRoute = useParams().networkId;
15 |
16 | const [currentNetwork, setCurrentNetwork] = useState(0);
17 | const [multichainSession, setMultichainSession] = useState(false);
18 |
19 | const connectMetamask = useCallback(async () => {
20 | const provider = new BrowserProvider((window as any).ethereum, "any");
21 |
22 | // Check if MetaMask supports multichain API
23 | const isMultichainSupported = typeof (window as any).ethereum?.request === 'function';
24 |
25 | if (isMultichainSupported) {
26 | try {
27 | // Create a multichain session with all allowed networks
28 | const optionalScopes = Object.keys(allowedNetworks).reduce((acc, chainId) => {
29 | acc[`eip155:${chainId}`] = {
30 | methods: [
31 | "eth_sendTransaction",
32 | "eth_signTransaction",
33 | "eth_sign",
34 | "personal_sign",
35 | "eth_signTypedData",
36 | "eth_signTypedData_v4",
37 | "eth_getBalance",
38 | "eth_call"
39 | ],
40 | accounts: []
41 | };
42 | return acc;
43 | }, {} as Record);
44 |
45 | await (window as any).ethereum.request({
46 | method: "wallet_createSession",
47 | params: {
48 | optionalScopes
49 | }
50 | });
51 |
52 | setMultichainSession(true);
53 | console.log("Multichain session created");
54 | } catch (error) {
55 | console.log("Multichain API not available, falling back to standard connection", error);
56 | }
57 | }
58 |
59 | provider.on("accountsChanged", async (accounts) => {
60 | console.log({ accounts });
61 | const newNetwork = await provider.getNetwork();
62 | setCurrentNetwork(Number(newNetwork.chainId));
63 | });
64 | provider.on("disconnect", () => {
65 | setProvider(undefined);
66 | setCurrentNetwork(0);
67 | setMultichainSession(false);
68 | });
69 | provider.on("connect", async () => {
70 | setProvider(provider);
71 | const network = await provider.getNetwork();
72 | setCurrentNetwork(Number(network.chainId));
73 | });
74 | await provider.send("eth_requestAccounts", []);
75 | const signer = await provider.getSigner();
76 | if (provider && signer) {
77 | const network = await provider.getNetwork();
78 | setCurrentNetwork(Number(network.chainId));
79 | setProvider(provider);
80 | }
81 | }, [setProvider, setCurrentNetwork]);
82 | useEffect(() => {
83 | connectMetamask();
84 | }, [connectMetamask]);
85 |
86 | const networkContext: NetworkContext | undefined = useMemo(() => {
87 | if (!provider) return;
88 |
89 | return {
90 | walletProvider: provider,
91 | currentNetwork,
92 | };
93 | }, [provider, currentNetwork]);
94 |
95 | useEffect(() => {
96 | if (!networkIdFromRoute) return;
97 | if (currentNetwork !== Number(networkIdFromRoute)) {
98 | provider?.send("wallet_switchEthereumChain", [
99 | {
100 | chainId: `0x${parseInt(networkIdFromRoute).toString(16)}`,
101 | },
102 | ]);
103 |
104 | setCurrentNetwork(Number(networkIdFromRoute));
105 | }
106 | }, [currentNetwork, networkIdFromRoute, setCurrentNetwork, provider]);
107 |
108 | if (!networkContext) {
109 | return (
110 |
111 |
112 |
113 | );
114 | }
115 |
116 | return (
117 | <>
118 |
119 |
120 |
121 |
122 | >
123 | );
124 | };
125 |
--------------------------------------------------------------------------------
/src/components/SetOwnerModal.tsx:
--------------------------------------------------------------------------------
1 | import { SyntheticEvent } from "react";
2 | import { Button, Modal, Text, View, useToast } from "reshaped";
3 | import { AddressView } from "./AddressView";
4 | import { Address } from "viem";
5 | import { Field, Form, Formik } from "formik";
6 | import { GenericField } from "./GenericField";
7 | import { yupAddress } from "../utils/validators";
8 | import { number, object } from "yup";
9 | import { useOutletContext } from "react-router-dom";
10 | import { SafeContext } from "./Contexts";
11 | import { AddAction } from "../hooks/useUpdateProposalViaQuery";
12 |
13 | export type OwnerAction =
14 | | undefined
15 | | {
16 | type: "add";
17 | }
18 | | {
19 | type: "remove";
20 | address: string;
21 | };
22 |
23 | const ButtonPanel = ({
24 | onClick,
25 | actionDisabled,
26 | onClose,
27 | }: {
28 | onClose: () => void;
29 | onClick?: (button: SyntheticEvent) => void;
30 | actionDisabled?: boolean;
31 | }) => (
32 |
33 |
34 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 |
44 | const AddOwnerModalContent = ({
45 | onClose,
46 | addAction,
47 | }: {
48 | onClose: () => void;
49 | addAction: AddAction;
50 | }) => {
51 | const { safeInformation } = useOutletContext();
52 | const toast = useToast();
53 |
54 | return (
55 | {
59 | if (!safeInformation) {
60 | return;
61 | }
62 |
63 | try {
64 | const addOwnerTx = await safeInformation.safeSdk.createAddOwnerTx({
65 | ownerAddress: address,
66 | threshold: threshold,
67 | });
68 | addAction({
69 | data: addOwnerTx.data.data,
70 | value: "0",
71 | to: safeInformation.address,
72 | });
73 | } catch (err: any) {
74 | toast.show({ title: "Error Updating Safe", text: err.toString() });
75 | }
76 | onClose();
77 | }}
78 | >
79 |
95 |
96 | );
97 | };
98 |
99 | const RemoveOwnerModalContent = ({
100 | onClose,
101 | target,
102 | addAction,
103 | }: {
104 | onClose: () => void;
105 | target: string;
106 | addAction: AddAction;
107 | }) => {
108 | const { safeInformation } = useOutletContext();
109 | const toaster = useToast();
110 |
111 | const onSubmitClick = async ({ threshold }: any) => {
112 | try {
113 | const removeOwnerTx = await safeInformation?.safeSdk2.createRemoveOwnerTx(
114 | {
115 | ownerAddress: target,
116 | threshold: threshold,
117 | },
118 | );
119 | if (!removeOwnerTx || !safeInformation) {
120 | return;
121 | }
122 |
123 | addAction({
124 | data: removeOwnerTx.data.data,
125 | value: "0",
126 | to: safeInformation.address,
127 | });
128 | onClose();
129 | } catch (err: any) {
130 | toaster.show({
131 | title: "Error Removing Owner",
132 | text: `Error setting up transaction: ${err.toString()}`,
133 | });
134 | }
135 | };
136 | return (
137 |
142 |
158 |
159 | );
160 | };
161 |
162 | export const SetOwnerModal = ({
163 | action,
164 | onClose,
165 | addAction,
166 | }: {
167 | action: OwnerAction;
168 | onClose: () => void;
169 | addAction: AddAction;
170 | }) => {
171 | return (
172 |
173 | {action?.type === "remove" && (
174 |
179 | )}
180 | {action?.type === "add" && (
181 |
182 | )}
183 |
184 | );
185 | };
186 |
--------------------------------------------------------------------------------
/src/app/CreateSafe.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useState } from "react";
2 | import { Field, FieldArray, Formik } from "formik";
3 | import { Text, Button, FormControl, TextField, View, useToast } from "reshaped";
4 | import { allowedNetworks, contractNetworks } from "../chains";
5 | import { isAddress } from "viem";
6 | import Safe, { SafeAccountConfig, SafeDeploymentConfig, SafeProvider } from "@safe-global/protocol-kit";
7 | import { useNavigate, useOutletContext } from "react-router-dom";
8 | import { AbstractSigner } from "ethers";
9 | import { BrowserProvider } from "ethers";
10 | import { NetworkContext } from "../components/Contexts";
11 |
12 | function validateAddress(value: string) {
13 | if (!isAddress(value)) {
14 | return "Invalid address";
15 | }
16 | }
17 |
18 | function validateSafeArguments(values: any) {
19 | const errors: any = {};
20 |
21 | if (values.threshold <= 0) {
22 | errors.threshold = "Threshold needs to be at least 1";
23 | }
24 | if (values.threshold > values.addresses.length) {
25 | errors.threshold = "Threshold cannot be more than the number of addresses";
26 | }
27 |
28 | return errors;
29 | }
30 |
31 | export function CreateSafe() {
32 | const { walletProvider: provider } = useOutletContext();
33 | const { currentNetwork: network } = useOutletContext();
34 | const toaster = useToast();
35 | const navigate = useNavigate();
36 | const [signerInfo, setSignerInfo] = useState<
37 | undefined | { signer: AbstractSigner; address: string }
38 | >(undefined);
39 |
40 | const updateSignerInfo = useCallback(
41 | async (provider: BrowserProvider) => {
42 | const signer = await provider.getSigner();
43 | const address = await signer.getAddress();
44 | setSignerInfo({ signer, address });
45 | },
46 | [setSignerInfo],
47 | );
48 |
49 | useEffect(() => {
50 | if (provider) {
51 | updateSignerInfo(provider);
52 | }
53 | }, [provider, updateSignerInfo]);
54 |
55 | const submitCallback = useCallback(
56 | async (data: any) => {
57 | if (!signerInfo) {
58 | return;
59 | }
60 | try {
61 | const safeAccountConfig: SafeAccountConfig = {
62 | owners: data.addresses,
63 | threshold: parseInt(data.threshold, 10),
64 | };
65 |
66 | const safeDeploymentConfig: SafeDeploymentConfig = {
67 | saltNonce: Date.now().toString(),
68 | };
69 |
70 | // Create SafeProvider
71 | const safeProvider = new SafeProvider({
72 | provider: (window as any).ethereum,
73 | signer: await signerInfo.signer.getAddress(),
74 | });
75 |
76 | // Create and deploy the Safe
77 | const safeSdk = await Safe.init({
78 | provider: (window as any).ethereum,
79 | signer: await signerInfo.signer.getAddress(),
80 | predictedSafe: {
81 | safeAccountConfig,
82 | safeDeploymentConfig,
83 | },
84 | contractNetworks,
85 | });
86 |
87 | // Deploy the Safe
88 | const deploymentTransaction = await safeSdk.createSafeDeploymentTransaction();
89 | const txResponse = await signerInfo.signer.sendTransaction({
90 | to: deploymentTransaction.to,
91 | data: deploymentTransaction.data,
92 | value: deploymentTransaction.value,
93 | });
94 |
95 | await txResponse.wait();
96 |
97 | const newAddress = await safeSdk.getAddress();
98 | toaster.show({
99 | title: "Created a new safe!",
100 | text: `Opening safe... The new safe address is ${newAddress}`,
101 | });
102 | navigate(`/safe/${network}/${newAddress}`);
103 | } catch (e: any) {
104 | toaster.show({
105 | title: "Error creating safe",
106 | text: `Message: ${e.message}`,
107 | });
108 | }
109 | },
110 | [navigate, network, signerInfo, toaster],
111 | );
112 |
113 | return (
114 |
115 | Create a new safe
116 |
117 | Network: {allowedNetworks[Number(network)]?.name || "unknown"}
118 |
119 |
127 | {({ handleSubmit, handleChange, isSubmitting, values, errors }) => (
128 |
211 | )}
212 |
213 |
214 | );
215 | }
216 |
--------------------------------------------------------------------------------
/src/app/NewSafeProposal.tsx:
--------------------------------------------------------------------------------
1 | import { Field, FieldArray, Formik } from "formik";
2 | import { SafeInformation } from "../components/SafeInformation";
3 | import { Card, View, Text, Button, useToast } from "reshaped";
4 | import { SyntheticEvent, useCallback, useEffect, useState } from "react";
5 | import { Address, Hex, formatEther } from "viem";
6 | import { validateAddress, validateETH } from "../utils/validators";
7 | import { GenericField } from "../components/GenericField";
8 | import { DataActionPreview } from "../components/DataActionPreview";
9 | import Safe from "@safe-global/protocol-kit";
10 | import {
11 | DEFAULT_ACTION_ITEM,
12 | DEFAULT_PROPOSAL,
13 | Proposal,
14 | proposalSchema,
15 | } from "../schemas/proposal";
16 | import { useLoadProposalFromQuery } from "../hooks/useLoadProposalFromQuery";
17 | import {
18 | transformValuesFromWei,
19 | transformValuesToWei,
20 | } from "../utils/etherFormatting";
21 | import { useOutletContext } from "react-router-dom";
22 | import { NetworkContext, SafeContext } from "../components/Contexts";
23 | import { useUpdateProposalInQuery } from "../hooks/useUpdateProposalViaQuery";
24 |
25 | const FormActionItem = ({
26 | name,
27 | indx,
28 | remove,
29 | }: {
30 | name: string;
31 | indx: number;
32 | remove: (indx: number) => void;
33 | }) => {
34 | return (
35 |
36 |
37 | Action #{indx}
38 |
41 |
42 |
43 | {GenericField({ label: "Destination Contract" })}
44 |
45 |
46 | {GenericField({
47 | label: "Value (in ETH)",
48 | fieldProps: { type: "number" },
49 | })}
50 |
51 | {GenericField({ label: "Data" })}
52 |
53 | );
54 | };
55 |
56 | const createSafeTransaction = async ({
57 | proposal,
58 | safe,
59 | }: {
60 | proposal: Proposal;
61 | safe: Safe;
62 | }) => {
63 | if (!proposal.actions) {
64 | return;
65 | }
66 |
67 | return await safe.createTransaction({
68 | transactions: proposal.actions,
69 | options: { nonce: proposal.nonce || undefined },
70 | });
71 | };
72 |
73 | const signTx = async ({
74 | proposal,
75 | safe,
76 | }: {
77 | proposal: Proposal;
78 | safe: Safe;
79 | }) => {
80 | const txn = await createSafeTransaction({
81 | proposal,
82 | safe,
83 | });
84 | if (!txn) {
85 | throw new Error("No txn");
86 | }
87 | const txHash = await safe.getTransactionHash(txn);
88 | const executedTxn = await safe.approveTransactionHash(txHash);
89 | // In v6, the transaction is already executed when approveTransactionHash returns
90 |
91 | return executedTxn;
92 | };
93 |
94 | const signAndExecuteTx = async ({
95 | proposal,
96 | safe,
97 | }: {
98 | proposal: Proposal;
99 | safe: Safe;
100 | }) => {
101 | const txn = await createSafeTransaction({
102 | proposal,
103 | safe,
104 | });
105 | if (!txn) {
106 | throw new Error("No txn");
107 | }
108 | const executedTxn = await safe.executeTransaction(txn);
109 | // In v6, the transaction is already executed when executeTransaction returns
110 |
111 | return executedTxn;
112 | };
113 |
114 | const useGetSafeTxApprovals = ({ proposal }: { proposal: Proposal }) => {
115 | const { safeInformation } = useOutletContext();
116 |
117 | const safeSdk = safeInformation.safeSdk;
118 | const safeSdk2 = safeInformation.safeSdk2;
119 |
120 | const [approvers, setApprovers] = useState([]);
121 |
122 | const loadApprovers = useCallback(async () => {
123 | if (!safeSdk || !safeSdk2) return;
124 | const txn = await createSafeTransaction({
125 | proposal,
126 | safe: safeSdk,
127 | });
128 |
129 | if (!txn) return;
130 |
131 | const txHash = await safeSdk.getTransactionHash(txn);
132 | const ownersWhoApprovedTx = await safeSdk2.getOwnersWhoApprovedTx(txHash);
133 |
134 | setApprovers(ownersWhoApprovedTx as Address[]);
135 | }, [proposal, safeSdk, safeSdk2]);
136 |
137 | useEffect(() => {
138 | loadApprovers();
139 | }, [loadApprovers]);
140 |
141 | return { approvers, loadApprovers };
142 | };
143 |
144 | const useAccountAddress = () => {
145 | const { walletProvider } = useOutletContext();
146 |
147 | const [address, setAddress] = useState();
148 |
149 | useEffect(() => {
150 | if (!walletProvider) return;
151 |
152 | (async () => {
153 | const signer = await walletProvider.getSigner();
154 | setAddress((await signer.getAddress()) as Address | undefined);
155 | })();
156 | }, [walletProvider]);
157 |
158 | return address;
159 | };
160 |
161 | function determineIfCanExecute({
162 | hasApproved,
163 | totalApprovers,
164 | threshold,
165 | }: {
166 | hasApproved: boolean;
167 | totalApprovers: number;
168 | threshold: number;
169 | }) {
170 | const remainingNeeded = threshold - totalApprovers;
171 |
172 | if (remainingNeeded === 0) {
173 | return true;
174 | }
175 |
176 | // if there is one left, and i haven't signed, i can sign and approve
177 | if (remainingNeeded === 1) {
178 | if (!hasApproved) {
179 | return true;
180 | }
181 | }
182 |
183 | return false;
184 | }
185 |
186 | const ViewProposal = ({
187 | handleEditClicked,
188 | proposal,
189 | }: {
190 | proposal: Proposal;
191 | handleEditClicked: (evt: SyntheticEvent) => void;
192 | }) => {
193 | const { safeInformation } = useOutletContext();
194 | const safe = safeInformation.safeSdk;
195 |
196 | const toaster = useToast();
197 |
198 | const { approvers, loadApprovers } = useGetSafeTxApprovals({ proposal });
199 |
200 | const address = useAccountAddress();
201 |
202 | const signCallback = useCallback(async () => {
203 | if (!safe) return;
204 | try {
205 | const executedTxn = await signTx({
206 | proposal: proposal!,
207 | safe,
208 | });
209 | // call submit
210 | toaster.show({
211 | title: "Approved Txn Hash",
212 | text: `Approved with hash: ${executedTxn.hash}`,
213 | });
214 | loadApprovers();
215 | } catch (e: any) {
216 | toaster.show({
217 | title: "Error creating safe",
218 | text: `Message: ${e.message}`,
219 | });
220 | }
221 | }, [proposal, safe, toaster, loadApprovers]);
222 |
223 | const signAndExecuteCallback = useCallback(async () => {
224 | if (!safe) return;
225 | try {
226 | const executedTxn = await signAndExecuteTx({
227 | proposal: proposal!,
228 | safe,
229 | });
230 | // call submit
231 | toaster.show({
232 | title: "Executed Txn Hash",
233 | text: `Executed with hash: ${executedTxn.hash}`,
234 | });
235 |
236 | loadApprovers();
237 | } catch (e: any) {
238 | toaster.show({
239 | title: "Error creating safe",
240 | text: `Message: ${e.message}`,
241 | });
242 | }
243 | }, [proposal, safe, toaster, loadApprovers]);
244 |
245 | const hasApproved = address ? approvers.includes(address as Address) : false;
246 |
247 | // count others that are needed to sign, taking into account that self must have signed, and if self has signed
248 | // exclude from others needed to sign
249 | const canExecute = determineIfCanExecute({
250 | hasApproved,
251 | totalApprovers: approvers.length,
252 | threshold: safeInformation?.threshold || 0,
253 | });
254 |
255 | return (
256 | <>
257 |
258 | Nonce: {proposal.nonce}
259 | {proposal.actions?.map((action, indx: number) => (
260 | <>
261 | Proposal #{indx}
262 | To: {action.to as Address}
263 | Value: {formatEther(BigInt(action.value))}
264 | {action.data ? (
265 | <>
266 | Data: {action.data}
267 |
268 | Data Actions:{" "}
269 |
270 |
274 |
275 |
276 | >
277 | ) : (
278 | No data
279 | )}
280 | >
281 | ))}
282 |
283 |
284 |
285 | Approvers: ({approvers.length} out of {safeInformation?.threshold}{" "}
286 | signed)
287 |
288 | {approvers.map((approver) => (
289 |
290 | {approver} {approver === address && "(you)"}
291 |
292 | ))}
293 |
294 |
295 |
296 |
299 |
302 |
303 | >
304 | );
305 | };
306 |
307 | const EditProposal = ({
308 | proposal,
309 | setProposal,
310 | }: {
311 | proposal: Proposal | undefined;
312 | setProposal: (proposal: Proposal) => void;
313 | }) => {
314 | const onSubmit = useCallback(
315 | (result: Proposal) => {
316 | const proposal = transformValuesToWei(result);
317 | if (proposal) {
318 | setProposal(proposal);
319 | }
320 | },
321 | [setProposal],
322 | );
323 |
324 | const defaultActions = proposal || DEFAULT_PROPOSAL;
325 |
326 | return (
327 |
328 |
329 |
334 | {({ handleSubmit, values, isValid }) => (
335 |
376 | )}
377 |
378 |
379 |
380 | );
381 | };
382 |
383 | export const NewSafeProposal = () => {
384 | const [isEditing, setIsEditing] = useState(true);
385 |
386 | const proposal = useLoadProposalFromQuery();
387 | const { addAction, replace } = useUpdateProposalInQuery({ proposal });
388 |
389 | useEffect(() => {
390 | if (proposal) {
391 | setIsEditing(false);
392 | }
393 | }, [proposal]);
394 |
395 | const handleEditClicked = useCallback(
396 | (evt: SyntheticEvent) => {
397 | setIsEditing(true);
398 | evt.preventDefault();
399 | },
400 | [setIsEditing],
401 | );
402 |
403 | return (
404 |
405 |
406 | {isEditing && (
407 |
408 | )}
409 | {!isEditing && proposal && (
410 |
414 | )}
415 |
416 |
417 | );
418 | };
419 |
--------------------------------------------------------------------------------
/src/app.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8"; /*!
2 | * Pico CSS v1.5.10 (https://picocss.com)
3 | * Copyright 2019-2023 - Licensed under MIT
4 | */
5 | :root {
6 | --font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu",
7 | "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
8 | "Segoe UI Symbol", "Noto Color Emoji";
9 | --line-height: 1.5;
10 | --font-weight: 400;
11 | --font-size: 16px;
12 | --border-radius: 0.25rem;
13 | --border-width: 1px;
14 | --outline-width: 3px;
15 | --spacing: 1rem;
16 | --typography-spacing-vertical: 1.5rem;
17 | --block-spacing-vertical: calc(var(--spacing) * 2);
18 | --block-spacing-horizontal: var(--spacing);
19 | --grid-spacing-vertical: 0;
20 | --grid-spacing-horizontal: var(--spacing);
21 | --form-element-spacing-vertical: 0.75rem;
22 | --form-element-spacing-horizontal: 1rem;
23 | --nav-element-spacing-vertical: 1rem;
24 | --nav-element-spacing-horizontal: 0.5rem;
25 | --nav-link-spacing-vertical: 0.5rem;
26 | --nav-link-spacing-horizontal: 0.5rem;
27 | --form-label-font-weight: var(--font-weight);
28 | --transition: 0.2s ease-in-out;
29 | --modal-overlay-backdrop-filter: blur(0.25rem);
30 | }
31 | @media (min-width: 576px) {
32 | :root {
33 | --font-size: 17px;
34 | }
35 | }
36 | @media (min-width: 768px) {
37 | :root {
38 | --font-size: 18px;
39 | }
40 | }
41 | @media (min-width: 992px) {
42 | :root {
43 | --font-size: 19px;
44 | }
45 | }
46 | @media (min-width: 1200px) {
47 | :root {
48 | --font-size: 20px;
49 | }
50 | }
51 | @media (min-width: 576px) {
52 | body > footer,
53 | body > header,
54 | body > main,
55 | section {
56 | --block-spacing-vertical: calc(var(--spacing) * 2.5);
57 | }
58 | }
59 | @media (min-width: 768px) {
60 | body > footer,
61 | body > header,
62 | body > main,
63 | section {
64 | --block-spacing-vertical: calc(var(--spacing) * 3);
65 | }
66 | }
67 | @media (min-width: 992px) {
68 | body > footer,
69 | body > header,
70 | body > main,
71 | section {
72 | --block-spacing-vertical: calc(var(--spacing) * 3.5);
73 | }
74 | }
75 | @media (min-width: 1200px) {
76 | body > footer,
77 | body > header,
78 | body > main,
79 | section {
80 | --block-spacing-vertical: calc(var(--spacing) * 4);
81 | }
82 | }
83 | @media (min-width: 576px) {
84 | article {
85 | --block-spacing-horizontal: calc(var(--spacing) * 1.25);
86 | }
87 | }
88 | @media (min-width: 768px) {
89 | article {
90 | --block-spacing-horizontal: calc(var(--spacing) * 1.5);
91 | }
92 | }
93 | @media (min-width: 992px) {
94 | article {
95 | --block-spacing-horizontal: calc(var(--spacing) * 1.75);
96 | }
97 | }
98 | @media (min-width: 1200px) {
99 | article {
100 | --block-spacing-horizontal: calc(var(--spacing) * 2);
101 | }
102 | }
103 | dialog > article {
104 | --block-spacing-vertical: calc(var(--spacing) * 2);
105 | --block-spacing-horizontal: var(--spacing);
106 | }
107 | @media (min-width: 576px) {
108 | dialog > article {
109 | --block-spacing-vertical: calc(var(--spacing) * 2.5);
110 | --block-spacing-horizontal: calc(var(--spacing) * 1.25);
111 | }
112 | }
113 | @media (min-width: 768px) {
114 | dialog > article {
115 | --block-spacing-vertical: calc(var(--spacing) * 3);
116 | --block-spacing-horizontal: calc(var(--spacing) * 1.5);
117 | }
118 | }
119 | a {
120 | --text-decoration: none;
121 | }
122 | a.contrast,
123 | a.secondary {
124 | --text-decoration: underline;
125 | }
126 | small {
127 | --font-size: 0.875em;
128 | }
129 | h1,
130 | h2,
131 | h3,
132 | h4,
133 | h5,
134 | h6 {
135 | --font-weight: 700;
136 | }
137 | h1 {
138 | --font-size: 2rem;
139 | --typography-spacing-vertical: 3rem;
140 | }
141 | h2 {
142 | --font-size: 1.75rem;
143 | --typography-spacing-vertical: 2.625rem;
144 | }
145 | h3 {
146 | --font-size: 1.5rem;
147 | --typography-spacing-vertical: 2.25rem;
148 | }
149 | h4 {
150 | --font-size: 1.25rem;
151 | --typography-spacing-vertical: 1.874rem;
152 | }
153 | h5 {
154 | --font-size: 1.125rem;
155 | --typography-spacing-vertical: 1.6875rem;
156 | }
157 | [type="checkbox"],
158 | [type="radio"] {
159 | --border-width: 2px;
160 | }
161 | [type="checkbox"][role="switch"] {
162 | --border-width: 3px;
163 | }
164 | tfoot td,
165 | tfoot th,
166 | thead td,
167 | thead th {
168 | --border-width: 3px;
169 | }
170 | :not(thead, tfoot) > * > td {
171 | --font-size: 0.875em;
172 | }
173 | code,
174 | kbd,
175 | pre,
176 | samp {
177 | --font-family: "Menlo", "Consolas", "Roboto Mono", "Ubuntu Monospace",
178 | "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace,
179 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
180 | }
181 | kbd {
182 | --font-weight: bolder;
183 | }
184 | :root:not([data-theme="dark"]),
185 | [data-theme="light"] {
186 | --background-color: #fff;
187 | --color: hsl(205, 20%, 32%);
188 | --h1-color: hsl(205, 30%, 15%);
189 | --h2-color: #24333e;
190 | --h3-color: hsl(205, 25%, 23%);
191 | --h4-color: #374956;
192 | --h5-color: hsl(205, 20%, 32%);
193 | --h6-color: #4d606d;
194 | --muted-color: hsl(205, 10%, 50%);
195 | --muted-border-color: hsl(205, 20%, 94%);
196 | --primary: hsl(195, 85%, 41%);
197 | --primary-hover: hsl(195, 90%, 32%);
198 | --primary-focus: rgba(16, 149, 193, 0.125);
199 | --primary-inverse: #fff;
200 | --secondary: hsl(205, 15%, 41%);
201 | --secondary-hover: hsl(205, 20%, 32%);
202 | --secondary-focus: rgba(89, 107, 120, 0.125);
203 | --secondary-inverse: #fff;
204 | --contrast: hsl(205, 30%, 15%);
205 | --contrast-hover: #000;
206 | --contrast-focus: rgba(89, 107, 120, 0.125);
207 | --contrast-inverse: #fff;
208 | --mark-background-color: #fff2ca;
209 | --mark-color: #543a26;
210 | --ins-color: #388e3c;
211 | --del-color: #c62828;
212 | --blockquote-border-color: var(--muted-border-color);
213 | --blockquote-footer-color: var(--muted-color);
214 | --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
215 | --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
216 | --form-element-background-color: transparent;
217 | --form-element-border-color: hsl(205, 14%, 68%);
218 | --form-element-color: var(--color);
219 | --form-element-placeholder-color: var(--muted-color);
220 | --form-element-active-background-color: transparent;
221 | --form-element-active-border-color: var(--primary);
222 | --form-element-focus-color: var(--primary-focus);
223 | --form-element-disabled-background-color: hsl(205, 18%, 86%);
224 | --form-element-disabled-border-color: hsl(205, 14%, 68%);
225 | --form-element-disabled-opacity: 0.5;
226 | --form-element-invalid-border-color: #c62828;
227 | --form-element-invalid-active-border-color: #d32f2f;
228 | --form-element-invalid-focus-color: rgba(211, 47, 47, 0.125);
229 | --form-element-valid-border-color: #388e3c;
230 | --form-element-valid-active-border-color: #43a047;
231 | --form-element-valid-focus-color: rgba(67, 160, 71, 0.125);
232 | --switch-background-color: hsl(205, 16%, 77%);
233 | --switch-color: var(--primary-inverse);
234 | --switch-checked-background-color: var(--primary);
235 | --range-border-color: hsl(205, 18%, 86%);
236 | --range-active-border-color: hsl(205, 16%, 77%);
237 | --range-thumb-border-color: var(--background-color);
238 | --range-thumb-color: var(--secondary);
239 | --range-thumb-hover-color: var(--secondary-hover);
240 | --range-thumb-active-color: var(--primary);
241 | --table-border-color: var(--muted-border-color);
242 | --table-row-stripped-background-color: #f6f8f9;
243 | --code-background-color: hsl(205, 20%, 94%);
244 | --code-color: var(--muted-color);
245 | --code-kbd-background-color: var(--contrast);
246 | --code-kbd-color: var(--contrast-inverse);
247 | --code-tag-color: hsl(330, 40%, 50%);
248 | --code-property-color: hsl(185, 40%, 40%);
249 | --code-value-color: hsl(40, 20%, 50%);
250 | --code-comment-color: hsl(205, 14%, 68%);
251 | --accordion-border-color: var(--muted-border-color);
252 | --accordion-close-summary-color: var(--color);
253 | --accordion-open-summary-color: var(--muted-color);
254 | --card-background-color: var(--background-color);
255 | --card-border-color: var(--muted-border-color);
256 | --card-box-shadow: 0.0145rem 0.029rem 0.174rem rgba(27, 40, 50, 0.01698),
257 | 0.0335rem 0.067rem 0.402rem rgba(27, 40, 50, 0.024),
258 | 0.0625rem 0.125rem 0.75rem rgba(27, 40, 50, 0.03),
259 | 0.1125rem 0.225rem 1.35rem rgba(27, 40, 50, 0.036),
260 | 0.2085rem 0.417rem 2.502rem rgba(27, 40, 50, 0.04302),
261 | 0.5rem 1rem 6rem rgba(27, 40, 50, 0.06),
262 | 0 0 0 0.0625rem rgba(27, 40, 50, 0.015);
263 | --card-sectionning-background-color: #fbfbfc;
264 | --dropdown-background-color: #fbfbfc;
265 | --dropdown-border-color: #e1e6eb;
266 | --dropdown-box-shadow: var(--card-box-shadow);
267 | --dropdown-color: var(--color);
268 | --dropdown-hover-background-color: hsl(205, 20%, 94%);
269 | --modal-overlay-background-color: rgba(213, 220, 226, 0.7);
270 | --progress-background-color: hsl(205, 18%, 86%);
271 | --progress-color: var(--primary);
272 | --loading-spinner-opacity: 0.5;
273 | --tooltip-background-color: var(--contrast);
274 | --tooltip-color: var(--contrast-inverse);
275 | --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
276 | --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
277 | --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
278 | --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
279 | --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
280 | --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");
281 | --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(198, 40, 40)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
282 | --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");
283 | --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
284 | --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");
285 | --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(56, 142, 60)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
286 | color-scheme: light;
287 | }
288 | @media only screen and (prefers-color-scheme: dark) {
289 | :root:not([data-theme]) {
290 | --background-color: #11191f;
291 | --color: hsl(205, 16%, 77%);
292 | --h1-color: hsl(205, 20%, 94%);
293 | --h2-color: #e1e6eb;
294 | --h3-color: hsl(205, 18%, 86%);
295 | --h4-color: #c8d1d8;
296 | --h5-color: hsl(205, 16%, 77%);
297 | --h6-color: #afbbc4;
298 | --muted-color: hsl(205, 10%, 50%);
299 | --muted-border-color: #1f2d38;
300 | --primary: hsl(195, 85%, 41%);
301 | --primary-hover: hsl(195, 80%, 50%);
302 | --primary-focus: rgba(16, 149, 193, 0.25);
303 | --primary-inverse: #fff;
304 | --secondary: hsl(205, 15%, 41%);
305 | --secondary-hover: hsl(205, 10%, 50%);
306 | --secondary-focus: rgba(115, 130, 140, 0.25);
307 | --secondary-inverse: #fff;
308 | --contrast: hsl(205, 20%, 94%);
309 | --contrast-hover: #fff;
310 | --contrast-focus: rgba(115, 130, 140, 0.25);
311 | --contrast-inverse: #000;
312 | --mark-background-color: #d1c284;
313 | --mark-color: #11191f;
314 | --ins-color: #388e3c;
315 | --del-color: #c62828;
316 | --blockquote-border-color: var(--muted-border-color);
317 | --blockquote-footer-color: var(--muted-color);
318 | --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
319 | --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
320 | --form-element-background-color: #11191f;
321 | --form-element-border-color: #374956;
322 | --form-element-color: var(--color);
323 | --form-element-placeholder-color: var(--muted-color);
324 | --form-element-active-background-color: var(
325 | --form-element-background-color
326 | );
327 | --form-element-active-border-color: var(--primary);
328 | --form-element-focus-color: var(--primary-focus);
329 | --form-element-disabled-background-color: hsl(205, 25%, 23%);
330 | --form-element-disabled-border-color: hsl(205, 20%, 32%);
331 | --form-element-disabled-opacity: 0.5;
332 | --form-element-invalid-border-color: #b71c1c;
333 | --form-element-invalid-active-border-color: #c62828;
334 | --form-element-invalid-focus-color: rgba(198, 40, 40, 0.25);
335 | --form-element-valid-border-color: #2e7d32;
336 | --form-element-valid-active-border-color: #388e3c;
337 | --form-element-valid-focus-color: rgba(56, 142, 60, 0.25);
338 | --switch-background-color: #374956;
339 | --switch-color: var(--primary-inverse);
340 | --switch-checked-background-color: var(--primary);
341 | --range-border-color: #24333e;
342 | --range-active-border-color: hsl(205, 25%, 23%);
343 | --range-thumb-border-color: var(--background-color);
344 | --range-thumb-color: var(--secondary);
345 | --range-thumb-hover-color: var(--secondary-hover);
346 | --range-thumb-active-color: var(--primary);
347 | --table-border-color: var(--muted-border-color);
348 | --table-row-stripped-background-color: rgba(115, 130, 140, 0.05);
349 | --code-background-color: #18232c;
350 | --code-color: var(--muted-color);
351 | --code-kbd-background-color: var(--contrast);
352 | --code-kbd-color: var(--contrast-inverse);
353 | --code-tag-color: hsl(330, 30%, 50%);
354 | --code-property-color: hsl(185, 30%, 50%);
355 | --code-value-color: hsl(40, 10%, 50%);
356 | --code-comment-color: #4d606d;
357 | --accordion-border-color: var(--muted-border-color);
358 | --accordion-active-summary-color: var(--primary);
359 | --accordion-close-summary-color: var(--color);
360 | --accordion-open-summary-color: var(--muted-color);
361 | --card-background-color: #141e26;
362 | --card-border-color: var(--card-background-color);
363 | --card-box-shadow: 0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698),
364 | 0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024),
365 | 0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03),
366 | 0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036),
367 | 0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302),
368 | 0.5rem 1rem 6rem rgba(0, 0, 0, 0.06), 0 0 0 0.0625rem rgba(0, 0, 0, 0.015);
369 | --card-sectionning-background-color: #18232c;
370 | --dropdown-background-color: hsl(205, 30%, 15%);
371 | --dropdown-border-color: #24333e;
372 | --dropdown-box-shadow: var(--card-box-shadow);
373 | --dropdown-color: var(--color);
374 | --dropdown-hover-background-color: rgba(36, 51, 62, 0.75);
375 | --modal-overlay-background-color: rgba(36, 51, 62, 0.8);
376 | --progress-background-color: #24333e;
377 | --progress-color: var(--primary);
378 | --loading-spinner-opacity: 0.5;
379 | --tooltip-background-color: var(--contrast);
380 | --tooltip-color: var(--contrast-inverse);
381 | --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
382 | --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
383 | --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
384 | --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
385 | --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
386 | --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");
387 | --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
388 | --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");
389 | --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
390 | --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");
391 | --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
392 | color-scheme: dark;
393 | }
394 | }
395 | [data-theme="dark"] {
396 | --background-color: #11191f;
397 | --color: hsl(205, 16%, 77%);
398 | --h1-color: hsl(205, 20%, 94%);
399 | --h2-color: #e1e6eb;
400 | --h3-color: hsl(205, 18%, 86%);
401 | --h4-color: #c8d1d8;
402 | --h5-color: hsl(205, 16%, 77%);
403 | --h6-color: #afbbc4;
404 | --muted-color: hsl(205, 10%, 50%);
405 | --muted-border-color: #1f2d38;
406 | --primary: hsl(195, 85%, 41%);
407 | --primary-hover: hsl(195, 80%, 50%);
408 | --primary-focus: rgba(16, 149, 193, 0.25);
409 | --primary-inverse: #fff;
410 | --secondary: hsl(205, 15%, 41%);
411 | --secondary-hover: hsl(205, 10%, 50%);
412 | --secondary-focus: rgba(115, 130, 140, 0.25);
413 | --secondary-inverse: #fff;
414 | --contrast: hsl(205, 20%, 94%);
415 | --contrast-hover: #fff;
416 | --contrast-focus: rgba(115, 130, 140, 0.25);
417 | --contrast-inverse: #000;
418 | --mark-background-color: #d1c284;
419 | --mark-color: #11191f;
420 | --ins-color: #388e3c;
421 | --del-color: #c62828;
422 | --blockquote-border-color: var(--muted-border-color);
423 | --blockquote-footer-color: var(--muted-color);
424 | --button-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
425 | --button-hover-box-shadow: 0 0 0 rgba(0, 0, 0, 0);
426 | --form-element-background-color: #11191f;
427 | --form-element-border-color: #374956;
428 | --form-element-color: var(--color);
429 | --form-element-placeholder-color: var(--muted-color);
430 | --form-element-active-background-color: var(--form-element-background-color);
431 | --form-element-active-border-color: var(--primary);
432 | --form-element-focus-color: var(--primary-focus);
433 | --form-element-disabled-background-color: hsl(205, 25%, 23%);
434 | --form-element-disabled-border-color: hsl(205, 20%, 32%);
435 | --form-element-disabled-opacity: 0.5;
436 | --form-element-invalid-border-color: #b71c1c;
437 | --form-element-invalid-active-border-color: #c62828;
438 | --form-element-invalid-focus-color: rgba(198, 40, 40, 0.25);
439 | --form-element-valid-border-color: #2e7d32;
440 | --form-element-valid-active-border-color: #388e3c;
441 | --form-element-valid-focus-color: rgba(56, 142, 60, 0.25);
442 | --switch-background-color: #374956;
443 | --switch-color: var(--primary-inverse);
444 | --switch-checked-background-color: var(--primary);
445 | --range-border-color: #24333e;
446 | --range-active-border-color: hsl(205, 25%, 23%);
447 | --range-thumb-border-color: var(--background-color);
448 | --range-thumb-color: var(--secondary);
449 | --range-thumb-hover-color: var(--secondary-hover);
450 | --range-thumb-active-color: var(--primary);
451 | --table-border-color: var(--muted-border-color);
452 | --table-row-stripped-background-color: rgba(115, 130, 140, 0.05);
453 | --code-background-color: #18232c;
454 | --code-color: var(--muted-color);
455 | --code-kbd-background-color: var(--contrast);
456 | --code-kbd-color: var(--contrast-inverse);
457 | --code-tag-color: hsl(330, 30%, 50%);
458 | --code-property-color: hsl(185, 30%, 50%);
459 | --code-value-color: hsl(40, 10%, 50%);
460 | --code-comment-color: #4d606d;
461 | --accordion-border-color: var(--muted-border-color);
462 | --accordion-active-summary-color: var(--primary);
463 | --accordion-close-summary-color: var(--color);
464 | --accordion-open-summary-color: var(--muted-color);
465 | --card-background-color: #141e26;
466 | --card-border-color: var(--card-background-color);
467 | --card-box-shadow: 0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698),
468 | 0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024),
469 | 0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03),
470 | 0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036),
471 | 0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302),
472 | 0.5rem 1rem 6rem rgba(0, 0, 0, 0.06), 0 0 0 0.0625rem rgba(0, 0, 0, 0.015);
473 | --card-sectionning-background-color: #18232c;
474 | --dropdown-background-color: hsl(205, 30%, 15%);
475 | --dropdown-border-color: #24333e;
476 | --dropdown-box-shadow: var(--card-box-shadow);
477 | --dropdown-color: var(--color);
478 | --dropdown-hover-background-color: rgba(36, 51, 62, 0.75);
479 | --modal-overlay-background-color: rgba(36, 51, 62, 0.8);
480 | --progress-background-color: #24333e;
481 | --progress-color: var(--primary);
482 | --loading-spinner-opacity: 0.5;
483 | --tooltip-background-color: var(--contrast);
484 | --tooltip-color: var(--contrast-inverse);
485 | --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
486 | --icon-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
487 | --icon-chevron-button: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
488 | --icon-chevron-button-inverse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
489 | --icon-close: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
490 | --icon-date: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");
491 | --icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
492 | --icon-minus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");
493 | --icon-search: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");
494 | --icon-time: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");
495 | --icon-valid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
496 | color-scheme: dark;
497 | }
498 | [type="checkbox"],
499 | [type="radio"],
500 | [type="range"],
501 | progress {
502 | accent-color: var(--primary);
503 | }
504 | *,
505 | ::after,
506 | ::before {
507 | box-sizing: border-box;
508 | background-repeat: no-repeat;
509 | }
510 | ::after,
511 | ::before {
512 | text-decoration: inherit;
513 | vertical-align: inherit;
514 | }
515 | :where(:root) {
516 | -webkit-tap-highlight-color: transparent;
517 | -webkit-text-size-adjust: 100%;
518 | -moz-text-size-adjust: 100%;
519 | text-size-adjust: 100%;
520 | background-color: var(--background-color);
521 | color: var(--color);
522 | font-weight: var(--font-weight);
523 | font-size: var(--font-size);
524 | line-height: var(--line-height);
525 | font-family: var(--font-family);
526 | text-rendering: optimizeLegibility;
527 | overflow-wrap: break-word;
528 | cursor: default;
529 | -moz-tab-size: 4;
530 | -o-tab-size: 4;
531 | tab-size: 4;
532 | }
533 | main {
534 | display: block;
535 | }
536 | body {
537 | width: 100%;
538 | margin: 0;
539 | }
540 | body > footer,
541 | body > header,
542 | body > main {
543 | width: 100%;
544 | margin-right: auto;
545 | margin-left: auto;
546 | padding: var(--block-spacing-vertical) 0;
547 | }
548 | .container,
549 | .container-fluid {
550 | width: 100%;
551 | margin-right: auto;
552 | margin-left: auto;
553 | padding-right: var(--spacing);
554 | padding-left: var(--spacing);
555 | }
556 | @media (min-width: 576px) {
557 | .container {
558 | max-width: 510px;
559 | padding-right: 0;
560 | padding-left: 0;
561 | }
562 | }
563 | @media (min-width: 768px) {
564 | .container {
565 | max-width: 700px;
566 | }
567 | }
568 | @media (min-width: 992px) {
569 | .container {
570 | max-width: 920px;
571 | }
572 | }
573 | @media (min-width: 1200px) {
574 | .container {
575 | max-width: 1130px;
576 | }
577 | }
578 | section {
579 | margin-bottom: var(--block-spacing-vertical);
580 | }
581 | .grid {
582 | grid-column-gap: var(--grid-spacing-horizontal);
583 | grid-row-gap: var(--grid-spacing-vertical);
584 | display: grid;
585 | grid-template-columns: 1fr;
586 | margin: 0;
587 | }
588 | @media (min-width: 992px) {
589 | .grid {
590 | grid-template-columns: repeat(auto-fit, minmax(0%, 1fr));
591 | }
592 | }
593 | .grid > * {
594 | min-width: 0;
595 | }
596 | figure {
597 | display: block;
598 | margin: 0;
599 | padding: 0;
600 | overflow-x: auto;
601 | }
602 | figure figcaption {
603 | padding: calc(var(--spacing) * 0.5) 0;
604 | color: var(--muted-color);
605 | }
606 | b,
607 | strong {
608 | font-weight: bolder;
609 | }
610 | sub,
611 | sup {
612 | position: relative;
613 | font-size: 0.75em;
614 | line-height: 0;
615 | vertical-align: baseline;
616 | }
617 | sub {
618 | bottom: -0.25em;
619 | }
620 | sup {
621 | top: -0.5em;
622 | }
623 | address,
624 | blockquote,
625 | dl,
626 | figure,
627 | form,
628 | ol,
629 | p,
630 | pre,
631 | table,
632 | ul {
633 | margin-top: 0;
634 | margin-bottom: var(--typography-spacing-vertical);
635 | color: var(--color);
636 | font-style: normal;
637 | font-weight: var(--font-weight);
638 | font-size: var(--font-size);
639 | }
640 | [role="link"],
641 | a {
642 | --color: var(--primary);
643 | --background-color: transparent;
644 | outline: 0;
645 | background-color: var(--background-color);
646 | color: var(--color);
647 | -webkit-text-decoration: var(--text-decoration);
648 | text-decoration: var(--text-decoration);
649 | transition:
650 | background-color var(--transition),
651 | color var(--transition),
652 | box-shadow var(--transition),
653 | -webkit-text-decoration var(--transition);
654 | transition:
655 | background-color var(--transition),
656 | color var(--transition),
657 | text-decoration var(--transition),
658 | box-shadow var(--transition);
659 | transition:
660 | background-color var(--transition),
661 | color var(--transition),
662 | text-decoration var(--transition),
663 | box-shadow var(--transition),
664 | -webkit-text-decoration var(--transition);
665 | }
666 | [role="link"]:is([aria-current], :hover, :active, :focus),
667 | a:is([aria-current], :hover, :active, :focus) {
668 | --color: var(--primary-hover);
669 | --text-decoration: underline;
670 | }
671 | [role="link"]:focus,
672 | a:focus {
673 | --background-color: var(--primary-focus);
674 | }
675 | [role="link"].secondary,
676 | a.secondary {
677 | --color: var(--secondary);
678 | }
679 | [role="link"].secondary:is([aria-current], :hover, :active, :focus),
680 | a.secondary:is([aria-current], :hover, :active, :focus) {
681 | --color: var(--secondary-hover);
682 | }
683 | [role="link"].secondary:focus,
684 | a.secondary:focus {
685 | --background-color: var(--secondary-focus);
686 | }
687 | [role="link"].contrast,
688 | a.contrast {
689 | --color: var(--contrast);
690 | }
691 | [role="link"].contrast:is([aria-current], :hover, :active, :focus),
692 | a.contrast:is([aria-current], :hover, :active, :focus) {
693 | --color: var(--contrast-hover);
694 | }
695 | [role="link"].contrast:focus,
696 | a.contrast:focus {
697 | --background-color: var(--contrast-focus);
698 | }
699 | h1,
700 | h2,
701 | h3,
702 | h4,
703 | h5,
704 | h6 {
705 | margin-top: 0;
706 | margin-bottom: var(--typography-spacing-vertical);
707 | color: var(--color);
708 | font-weight: var(--font-weight);
709 | font-size: var(--font-size);
710 | font-family: var(--font-family);
711 | }
712 | h1 {
713 | --color: var(--h1-color);
714 | }
715 | h2 {
716 | --color: var(--h2-color);
717 | }
718 | h3 {
719 | --color: var(--h3-color);
720 | }
721 | h4 {
722 | --color: var(--h4-color);
723 | }
724 | h5 {
725 | --color: var(--h5-color);
726 | }
727 | h6 {
728 | --color: var(--h6-color);
729 | }
730 | :where(address, blockquote, dl, figure, form, ol, p, pre, table, ul)
731 | ~ :is(h1, h2, h3, h4, h5, h6) {
732 | margin-top: var(--typography-spacing-vertical);
733 | }
734 | .headings,
735 | hgroup {
736 | margin-bottom: var(--typography-spacing-vertical);
737 | }
738 | .headings > *,
739 | hgroup > * {
740 | margin-bottom: 0;
741 | }
742 | .headings > :last-child,
743 | hgroup > :last-child {
744 | --color: var(--muted-color);
745 | --font-weight: unset;
746 | font-size: 1rem;
747 | font-family: unset;
748 | }
749 | p {
750 | margin-bottom: var(--typography-spacing-vertical);
751 | }
752 | small {
753 | font-size: var(--font-size);
754 | }
755 | :where(dl, ol, ul) {
756 | padding-right: 0;
757 | padding-left: var(--spacing);
758 | -webkit-padding-start: var(--spacing);
759 | padding-inline-start: var(--spacing);
760 | -webkit-padding-end: 0;
761 | padding-inline-end: 0;
762 | }
763 | :where(dl, ol, ul) li {
764 | margin-bottom: calc(var(--typography-spacing-vertical) * 0.25);
765 | }
766 | :where(dl, ol, ul) :is(dl, ol, ul) {
767 | margin: 0;
768 | margin-top: calc(var(--typography-spacing-vertical) * 0.25);
769 | }
770 | ul li {
771 | list-style: square;
772 | }
773 | mark {
774 | padding: 0.125rem 0.25rem;
775 | background-color: var(--mark-background-color);
776 | color: var(--mark-color);
777 | vertical-align: baseline;
778 | }
779 | blockquote {
780 | display: block;
781 | margin: var(--typography-spacing-vertical) 0;
782 | padding: var(--spacing);
783 | border-right: none;
784 | border-left: 0.25rem solid var(--blockquote-border-color);
785 | -webkit-border-start: 0.25rem solid var(--blockquote-border-color);
786 | border-inline-start: 0.25rem solid var(--blockquote-border-color);
787 | -webkit-border-end: none;
788 | border-inline-end: none;
789 | }
790 | blockquote footer {
791 | margin-top: calc(var(--typography-spacing-vertical) * 0.5);
792 | color: var(--blockquote-footer-color);
793 | }
794 | abbr[title] {
795 | border-bottom: 1px dotted;
796 | text-decoration: none;
797 | cursor: help;
798 | }
799 | ins {
800 | color: var(--ins-color);
801 | text-decoration: none;
802 | }
803 | del {
804 | color: var(--del-color);
805 | }
806 | ::-moz-selection {
807 | background-color: var(--primary-focus);
808 | }
809 | ::selection {
810 | background-color: var(--primary-focus);
811 | }
812 | :where(audio, canvas, iframe, img, svg, video) {
813 | vertical-align: middle;
814 | }
815 | audio,
816 | video {
817 | display: inline-block;
818 | }
819 | audio:not([controls]) {
820 | display: none;
821 | height: 0;
822 | }
823 | :where(iframe) {
824 | border-style: none;
825 | }
826 | img {
827 | max-width: 100%;
828 | height: auto;
829 | border-style: none;
830 | }
831 | :where(svg:not([fill])) {
832 | fill: currentColor;
833 | }
834 | svg:not(:root) {
835 | overflow: hidden;
836 | }
837 | button {
838 | margin: 0;
839 | overflow: visible;
840 | font-family: inherit;
841 | text-transform: none;
842 | }
843 | [type="button"],
844 | [type="reset"],
845 | [type="submit"],
846 | button {
847 | -webkit-appearance: button;
848 | }
849 | button {
850 | display: block;
851 | width: 100%;
852 | margin-bottom: var(--spacing);
853 | }
854 | [role="button"] {
855 | display: inline-block;
856 | text-decoration: none;
857 | }
858 | [role="button"],
859 | button,
860 | input[type="button"],
861 | input[type="reset"],
862 | input[type="submit"] {
863 | --background-color: var(--primary);
864 | --border-color: var(--primary);
865 | --color: var(--primary-inverse);
866 | --box-shadow: var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));
867 | padding: var(--form-element-spacing-vertical)
868 | var(--form-element-spacing-horizontal);
869 | border: var(--border-width) solid var(--border-color);
870 | border-radius: var(--border-radius);
871 | outline: 0;
872 | background-color: var(--background-color);
873 | box-shadow: var(--box-shadow);
874 | color: var(--color);
875 | font-weight: var(--font-weight);
876 | font-size: 1rem;
877 | line-height: var(--line-height);
878 | text-align: center;
879 | cursor: pointer;
880 | transition:
881 | background-color var(--transition),
882 | border-color var(--transition),
883 | color var(--transition),
884 | box-shadow var(--transition);
885 | }
886 | [role="button"]:is([aria-current], :hover, :active, :focus),
887 | button:is([aria-current], :hover, :active, :focus),
888 | input[type="button"]:is([aria-current], :hover, :active, :focus),
889 | input[type="reset"]:is([aria-current], :hover, :active, :focus),
890 | input[type="submit"]:is([aria-current], :hover, :active, :focus) {
891 | --background-color: var(--primary-hover);
892 | --border-color: var(--primary-hover);
893 | --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));
894 | --color: var(--primary-inverse);
895 | }
896 | [role="button"]:focus,
897 | button:focus,
898 | input[type="button"]:focus,
899 | input[type="reset"]:focus,
900 | input[type="submit"]:focus {
901 | --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
902 | 0 0 0 var(--outline-width) var(--primary-focus);
903 | }
904 | :is(
905 | button,
906 | input[type="submit"],
907 | input[type="button"],
908 | [role="button"]
909 | ).secondary,
910 | input[type="reset"] {
911 | --background-color: var(--secondary);
912 | --border-color: var(--secondary);
913 | --color: var(--secondary-inverse);
914 | cursor: pointer;
915 | }
916 | :is(
917 | button,
918 | input[type="submit"],
919 | input[type="button"],
920 | [role="button"]
921 | ).secondary:is([aria-current], :hover, :active, :focus),
922 | input[type="reset"]:is([aria-current], :hover, :active, :focus) {
923 | --background-color: var(--secondary-hover);
924 | --border-color: var(--secondary-hover);
925 | --color: var(--secondary-inverse);
926 | }
927 | :is(
928 | button,
929 | input[type="submit"],
930 | input[type="button"],
931 | [role="button"]
932 | ).secondary:focus,
933 | input[type="reset"]:focus {
934 | --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
935 | 0 0 0 var(--outline-width) var(--secondary-focus);
936 | }
937 | :is(
938 | button,
939 | input[type="submit"],
940 | input[type="button"],
941 | [role="button"]
942 | ).contrast {
943 | --background-color: var(--contrast);
944 | --border-color: var(--contrast);
945 | --color: var(--contrast-inverse);
946 | }
947 | :is(
948 | button,
949 | input[type="submit"],
950 | input[type="button"],
951 | [role="button"]
952 | ).contrast:is([aria-current], :hover, :active, :focus) {
953 | --background-color: var(--contrast-hover);
954 | --border-color: var(--contrast-hover);
955 | --color: var(--contrast-inverse);
956 | }
957 | :is(
958 | button,
959 | input[type="submit"],
960 | input[type="button"],
961 | [role="button"]
962 | ).contrast:focus {
963 | --box-shadow: var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),
964 | 0 0 0 var(--outline-width) var(--contrast-focus);
965 | }
966 | :is(
967 | button,
968 | input[type="submit"],
969 | input[type="button"],
970 | [role="button"]
971 | ).outline,
972 | input[type="reset"].outline {
973 | --background-color: transparent;
974 | --color: var(--primary);
975 | }
976 | :is(
977 | button,
978 | input[type="submit"],
979 | input[type="button"],
980 | [role="button"]
981 | ).outline:is([aria-current], :hover, :active, :focus),
982 | input[type="reset"].outline:is([aria-current], :hover, :active, :focus) {
983 | --background-color: transparent;
984 | --color: var(--primary-hover);
985 | }
986 | :is(
987 | button,
988 | input[type="submit"],
989 | input[type="button"],
990 | [role="button"]
991 | ).outline.secondary,
992 | input[type="reset"].outline {
993 | --color: var(--secondary);
994 | }
995 | :is(
996 | button,
997 | input[type="submit"],
998 | input[type="button"],
999 | [role="button"]
1000 | ).outline.secondary:is([aria-current], :hover, :active, :focus),
1001 | input[type="reset"].outline:is([aria-current], :hover, :active, :focus) {
1002 | --color: var(--secondary-hover);
1003 | }
1004 | :is(
1005 | button,
1006 | input[type="submit"],
1007 | input[type="button"],
1008 | [role="button"]
1009 | ).outline.contrast {
1010 | --color: var(--contrast);
1011 | }
1012 | :is(
1013 | button,
1014 | input[type="submit"],
1015 | input[type="button"],
1016 | [role="button"]
1017 | ).outline.contrast:is([aria-current], :hover, :active, :focus) {
1018 | --color: var(--contrast-hover);
1019 | }
1020 | :where(
1021 | button,
1022 | [type="submit"],
1023 | [type="button"],
1024 | [type="reset"],
1025 | [role="button"]
1026 | )[disabled],
1027 | :where(fieldset[disabled])
1028 | :is(
1029 | button,
1030 | [type="submit"],
1031 | [type="button"],
1032 | [type="reset"],
1033 | [role="button"]
1034 | ),
1035 | a[role="button"]:not([href]) {
1036 | opacity: 0.5;
1037 | pointer-events: none;
1038 | }
1039 | input,
1040 | optgroup,
1041 | select,
1042 | textarea {
1043 | margin: 0;
1044 | font-size: 1rem;
1045 | line-height: var(--line-height);
1046 | font-family: inherit;
1047 | letter-spacing: inherit;
1048 | }
1049 | input {
1050 | overflow: visible;
1051 | }
1052 | select {
1053 | text-transform: none;
1054 | }
1055 | legend {
1056 | max-width: 100%;
1057 | padding: 0;
1058 | color: inherit;
1059 | white-space: normal;
1060 | }
1061 | textarea {
1062 | overflow: auto;
1063 | }
1064 | [type="checkbox"],
1065 | [type="radio"] {
1066 | padding: 0;
1067 | }
1068 | ::-webkit-inner-spin-button,
1069 | ::-webkit-outer-spin-button {
1070 | height: auto;
1071 | }
1072 | [type="search"] {
1073 | -webkit-appearance: textfield;
1074 | outline-offset: -2px;
1075 | }
1076 | [type="search"]::-webkit-search-decoration {
1077 | -webkit-appearance: none;
1078 | }
1079 | ::-webkit-file-upload-button {
1080 | -webkit-appearance: button;
1081 | font: inherit;
1082 | }
1083 | ::-moz-focus-inner {
1084 | padding: 0;
1085 | border-style: none;
1086 | }
1087 | :-moz-focusring {
1088 | outline: 0;
1089 | }
1090 | :-moz-ui-invalid {
1091 | box-shadow: none;
1092 | }
1093 | ::-ms-expand {
1094 | display: none;
1095 | }
1096 | [type="file"],
1097 | [type="range"] {
1098 | padding: 0;
1099 | border-width: 0;
1100 | }
1101 | input:not([type="checkbox"], [type="radio"], [type="range"]) {
1102 | height: calc(
1103 | 1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 +
1104 | var(--border-width) * 2
1105 | );
1106 | }
1107 | fieldset {
1108 | margin: 0;
1109 | margin-bottom: var(--spacing);
1110 | padding: 0;
1111 | border: 0;
1112 | }
1113 | fieldset legend,
1114 | label {
1115 | display: block;
1116 | margin-bottom: calc(var(--spacing) * 0.25);
1117 | font-weight: var(--form-label-font-weight, var(--font-weight));
1118 | }
1119 | input:not([type="checkbox"], [type="radio"]),
1120 | select,
1121 | textarea {
1122 | width: 100%;
1123 | }
1124 | input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"]),
1125 | select,
1126 | textarea {
1127 | -webkit-appearance: none;
1128 | -moz-appearance: none;
1129 | appearance: none;
1130 | padding: var(--form-element-spacing-vertical)
1131 | var(--form-element-spacing-horizontal);
1132 | }
1133 | input,
1134 | select,
1135 | textarea {
1136 | --background-color: var(--form-element-background-color);
1137 | --border-color: var(--form-element-border-color);
1138 | --color: var(--form-element-color);
1139 | --box-shadow: none;
1140 | border: var(--border-width) solid var(--border-color);
1141 | border-radius: var(--border-radius);
1142 | outline: 0;
1143 | background-color: var(--background-color);
1144 | box-shadow: var(--box-shadow);
1145 | color: var(--color);
1146 | font-weight: var(--font-weight);
1147 | transition:
1148 | background-color var(--transition),
1149 | border-color var(--transition),
1150 | color var(--transition),
1151 | box-shadow var(--transition);
1152 | }
1153 | :where(select, textarea):is(:active, :focus),
1154 | input:not(
1155 | [type="submit"],
1156 | [type="button"],
1157 | [type="reset"],
1158 | [type="checkbox"],
1159 | [type="radio"],
1160 | [readonly]
1161 | ):is(:active, :focus) {
1162 | --background-color: var(--form-element-active-background-color);
1163 | }
1164 | :where(select, textarea):is(:active, :focus),
1165 | input:not(
1166 | [type="submit"],
1167 | [type="button"],
1168 | [type="reset"],
1169 | [role="switch"],
1170 | [readonly]
1171 | ):is(:active, :focus) {
1172 | --border-color: var(--form-element-active-border-color);
1173 | }
1174 | input:not(
1175 | [type="submit"],
1176 | [type="button"],
1177 | [type="reset"],
1178 | [type="range"],
1179 | [type="file"],
1180 | [readonly]
1181 | ):focus,
1182 | select:focus,
1183 | textarea:focus {
1184 | --box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color);
1185 | }
1186 | :where(fieldset[disabled])
1187 | :is(
1188 | input:not([type="submit"], [type="button"], [type="reset"]),
1189 | select,
1190 | textarea
1191 | ),
1192 | input:not([type="submit"], [type="button"], [type="reset"])[disabled],
1193 | select[disabled],
1194 | textarea[disabled] {
1195 | --background-color: var(--form-element-disabled-background-color);
1196 | --border-color: var(--form-element-disabled-border-color);
1197 | opacity: var(--form-element-disabled-opacity);
1198 | pointer-events: none;
1199 | }
1200 | :where(input, select, textarea):not(
1201 | [type="checkbox"],
1202 | [type="radio"],
1203 | [type="date"],
1204 | [type="datetime-local"],
1205 | [type="month"],
1206 | [type="time"],
1207 | [type="week"]
1208 | )[aria-invalid] {
1209 | padding-right: calc(
1210 | var(--form-element-spacing-horizontal) + 1.5rem
1211 | ) !important;
1212 | padding-left: var(--form-element-spacing-horizontal);
1213 | -webkit-padding-start: var(--form-element-spacing-horizontal) !important;
1214 | padding-inline-start: var(--form-element-spacing-horizontal) !important;
1215 | -webkit-padding-end: calc(
1216 | var(--form-element-spacing-horizontal) + 1.5rem
1217 | ) !important;
1218 | padding-inline-end: calc(
1219 | var(--form-element-spacing-horizontal) + 1.5rem
1220 | ) !important;
1221 | background-position: center right 0.75rem;
1222 | background-size: 1rem auto;
1223 | background-repeat: no-repeat;
1224 | }
1225 | :where(input, select, textarea):not(
1226 | [type="checkbox"],
1227 | [type="radio"],
1228 | [type="date"],
1229 | [type="datetime-local"],
1230 | [type="month"],
1231 | [type="time"],
1232 | [type="week"]
1233 | )[aria-invalid="false"] {
1234 | background-image: var(--icon-valid);
1235 | }
1236 | :where(input, select, textarea):not(
1237 | [type="checkbox"],
1238 | [type="radio"],
1239 | [type="date"],
1240 | [type="datetime-local"],
1241 | [type="month"],
1242 | [type="time"],
1243 | [type="week"]
1244 | )[aria-invalid="true"] {
1245 | background-image: var(--icon-invalid);
1246 | }
1247 | :where(input, select, textarea)[aria-invalid="false"] {
1248 | --border-color: var(--form-element-valid-border-color);
1249 | }
1250 | :where(input, select, textarea)[aria-invalid="false"]:is(:active, :focus) {
1251 | --border-color: var(--form-element-valid-active-border-color) !important;
1252 | --box-shadow: 0 0 0 var(--outline-width) var(--form-element-valid-focus-color) !important;
1253 | }
1254 | :where(input, select, textarea)[aria-invalid="true"] {
1255 | --border-color: var(--form-element-invalid-border-color);
1256 | }
1257 | :where(input, select, textarea)[aria-invalid="true"]:is(:active, :focus) {
1258 | --border-color: var(--form-element-invalid-active-border-color) !important;
1259 | --box-shadow: 0 0 0 var(--outline-width)
1260 | var(--form-element-invalid-focus-color) !important;
1261 | }
1262 | [dir="rtl"]
1263 | :where(input, select, textarea):not([type="checkbox"], [type="radio"]):is(
1264 | [aria-invalid],
1265 | [aria-invalid="true"],
1266 | [aria-invalid="false"]
1267 | ) {
1268 | background-position: center left 0.75rem;
1269 | }
1270 | input::-webkit-input-placeholder,
1271 | input::placeholder,
1272 | select:invalid,
1273 | textarea::-webkit-input-placeholder,
1274 | textarea::placeholder {
1275 | color: var(--form-element-placeholder-color);
1276 | opacity: 1;
1277 | }
1278 | input:not([type="checkbox"], [type="radio"]),
1279 | select,
1280 | textarea {
1281 | margin-bottom: var(--spacing);
1282 | }
1283 | select::-ms-expand {
1284 | border: 0;
1285 | background-color: transparent;
1286 | }
1287 | select:not([multiple], [size]) {
1288 | padding-right: calc(var(--form-element-spacing-horizontal) + 1.5rem);
1289 | padding-left: var(--form-element-spacing-horizontal);
1290 | -webkit-padding-start: var(--form-element-spacing-horizontal);
1291 | padding-inline-start: var(--form-element-spacing-horizontal);
1292 | -webkit-padding-end: calc(var(--form-element-spacing-horizontal) + 1.5rem);
1293 | padding-inline-end: calc(var(--form-element-spacing-horizontal) + 1.5rem);
1294 | background-image: var(--icon-chevron);
1295 | background-position: center right 0.75rem;
1296 | background-size: 1rem auto;
1297 | background-repeat: no-repeat;
1298 | }
1299 | [dir="rtl"] select:not([multiple], [size]) {
1300 | background-position: center left 0.75rem;
1301 | }
1302 | :where(input, select, textarea, .grid) + small {
1303 | display: block;
1304 | width: 100%;
1305 | margin-top: calc(var(--spacing) * -0.75);
1306 | margin-bottom: var(--spacing);
1307 | color: var(--muted-color);
1308 | }
1309 | label > :where(input, select, textarea) {
1310 | margin-top: calc(var(--spacing) * 0.25);
1311 | }
1312 | [type="checkbox"],
1313 | [type="radio"] {
1314 | -webkit-appearance: none;
1315 | -moz-appearance: none;
1316 | appearance: none;
1317 | width: 1.25em;
1318 | height: 1.25em;
1319 | margin-top: -0.125em;
1320 | margin-right: 0.375em;
1321 | margin-left: 0;
1322 | -webkit-margin-start: 0;
1323 | margin-inline-start: 0;
1324 | -webkit-margin-end: 0.375em;
1325 | margin-inline-end: 0.375em;
1326 | border-width: var(--border-width);
1327 | font-size: inherit;
1328 | vertical-align: middle;
1329 | cursor: pointer;
1330 | }
1331 | [type="checkbox"]::-ms-check,
1332 | [type="radio"]::-ms-check {
1333 | display: none;
1334 | }
1335 | [type="checkbox"]:checked,
1336 | [type="checkbox"]:checked:active,
1337 | [type="checkbox"]:checked:focus,
1338 | [type="radio"]:checked,
1339 | [type="radio"]:checked:active,
1340 | [type="radio"]:checked:focus {
1341 | --background-color: var(--primary);
1342 | --border-color: var(--primary);
1343 | background-image: var(--icon-checkbox);
1344 | background-position: center;
1345 | background-size: 0.75em auto;
1346 | background-repeat: no-repeat;
1347 | }
1348 | [type="checkbox"] ~ label,
1349 | [type="radio"] ~ label {
1350 | display: inline-block;
1351 | margin-right: 0.375em;
1352 | margin-bottom: 0;
1353 | cursor: pointer;
1354 | }
1355 | [type="checkbox"]:indeterminate {
1356 | --background-color: var(--primary);
1357 | --border-color: var(--primary);
1358 | background-image: var(--icon-minus);
1359 | background-position: center;
1360 | background-size: 0.75em auto;
1361 | background-repeat: no-repeat;
1362 | }
1363 | [type="radio"] {
1364 | border-radius: 50%;
1365 | }
1366 | [type="radio"]:checked,
1367 | [type="radio"]:checked:active,
1368 | [type="radio"]:checked:focus {
1369 | --background-color: var(--primary-inverse);
1370 | border-width: 0.35em;
1371 | background-image: none;
1372 | }
1373 | [type="checkbox"][role="switch"] {
1374 | --background-color: var(--switch-background-color);
1375 | --border-color: var(--switch-background-color);
1376 | --color: var(--switch-color);
1377 | width: 2.25em;
1378 | height: 1.25em;
1379 | border: var(--border-width) solid var(--border-color);
1380 | border-radius: 1.25em;
1381 | background-color: var(--background-color);
1382 | line-height: 1.25em;
1383 | }
1384 | [type="checkbox"][role="switch"]:focus {
1385 | --background-color: var(--switch-background-color);
1386 | --border-color: var(--switch-background-color);
1387 | }
1388 | [type="checkbox"][role="switch"]:checked {
1389 | --background-color: var(--switch-checked-background-color);
1390 | --border-color: var(--switch-checked-background-color);
1391 | }
1392 | [type="checkbox"][role="switch"]:before {
1393 | display: block;
1394 | width: calc(1.25em - (var(--border-width) * 2));
1395 | height: 100%;
1396 | border-radius: 50%;
1397 | background-color: var(--color);
1398 | content: "";
1399 | transition: margin 0.1s ease-in-out;
1400 | }
1401 | [type="checkbox"][role="switch"]:checked {
1402 | background-image: none;
1403 | }
1404 | [type="checkbox"][role="switch"]:checked::before {
1405 | margin-left: calc(1.125em - var(--border-width));
1406 | -webkit-margin-start: calc(1.125em - var(--border-width));
1407 | margin-inline-start: calc(1.125em - var(--border-width));
1408 | }
1409 | [type="checkbox"]:checked[aria-invalid="false"],
1410 | [type="checkbox"][aria-invalid="false"],
1411 | [type="checkbox"][role="switch"]:checked[aria-invalid="false"],
1412 | [type="checkbox"][role="switch"][aria-invalid="false"],
1413 | [type="radio"]:checked[aria-invalid="false"],
1414 | [type="radio"][aria-invalid="false"] {
1415 | --border-color: var(--form-element-valid-border-color);
1416 | }
1417 | [type="checkbox"]:checked[aria-invalid="true"],
1418 | [type="checkbox"][aria-invalid="true"],
1419 | [type="checkbox"][role="switch"]:checked[aria-invalid="true"],
1420 | [type="checkbox"][role="switch"][aria-invalid="true"],
1421 | [type="radio"]:checked[aria-invalid="true"],
1422 | [type="radio"][aria-invalid="true"] {
1423 | --border-color: var(--form-element-invalid-border-color);
1424 | }
1425 | [type="color"]::-webkit-color-swatch-wrapper {
1426 | padding: 0;
1427 | }
1428 | [type="color"]::-moz-focus-inner {
1429 | padding: 0;
1430 | }
1431 | [type="color"]::-webkit-color-swatch {
1432 | border: 0;
1433 | border-radius: calc(var(--border-radius) * 0.5);
1434 | }
1435 | [type="color"]::-moz-color-swatch {
1436 | border: 0;
1437 | border-radius: calc(var(--border-radius) * 0.5);
1438 | }
1439 | input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"]):is(
1440 | [type="date"],
1441 | [type="datetime-local"],
1442 | [type="month"],
1443 | [type="time"],
1444 | [type="week"]
1445 | ) {
1446 | --icon-position: 0.75rem;
1447 | --icon-width: 1rem;
1448 | padding-right: calc(var(--icon-width) + var(--icon-position));
1449 | background-image: var(--icon-date);
1450 | background-position: center right var(--icon-position);
1451 | background-size: var(--icon-width) auto;
1452 | background-repeat: no-repeat;
1453 | }
1454 | input:not(
1455 | [type="checkbox"],
1456 | [type="radio"],
1457 | [type="range"],
1458 | [type="file"]
1459 | )[type="time"] {
1460 | background-image: var(--icon-time);
1461 | }
1462 | [type="date"]::-webkit-calendar-picker-indicator,
1463 | [type="datetime-local"]::-webkit-calendar-picker-indicator,
1464 | [type="month"]::-webkit-calendar-picker-indicator,
1465 | [type="time"]::-webkit-calendar-picker-indicator,
1466 | [type="week"]::-webkit-calendar-picker-indicator {
1467 | width: var(--icon-width);
1468 | margin-right: calc(var(--icon-width) * -1);
1469 | margin-left: var(--icon-position);
1470 | opacity: 0;
1471 | }
1472 | [dir="rtl"]
1473 | :is(
1474 | [type="date"],
1475 | [type="datetime-local"],
1476 | [type="month"],
1477 | [type="time"],
1478 | [type="week"]
1479 | ) {
1480 | text-align: right;
1481 | }
1482 | @-moz-document url-prefix() {
1483 | [type="date"],
1484 | [type="datetime-local"],
1485 | [type="month"],
1486 | [type="time"],
1487 | [type="week"] {
1488 | padding-right: var(--form-element-spacing-horizontal) !important;
1489 | background-image: none !important;
1490 | }
1491 | }
1492 | [type="file"] {
1493 | --color: var(--muted-color);
1494 | padding: calc(var(--form-element-spacing-vertical) * 0.5) 0;
1495 | border: 0;
1496 | border-radius: 0;
1497 | background: 0 0;
1498 | }
1499 | [type="file"]::file-selector-button {
1500 | --background-color: var(--secondary);
1501 | --border-color: var(--secondary);
1502 | --color: var(--secondary-inverse);
1503 | margin-right: calc(var(--spacing) / 2);
1504 | margin-left: 0;
1505 | -webkit-margin-start: 0;
1506 | margin-inline-start: 0;
1507 | -webkit-margin-end: calc(var(--spacing) / 2);
1508 | margin-inline-end: calc(var(--spacing) / 2);
1509 | padding: calc(var(--form-element-spacing-vertical) * 0.5)
1510 | calc(var(--form-element-spacing-horizontal) * 0.5);
1511 | border: var(--border-width) solid var(--border-color);
1512 | border-radius: var(--border-radius);
1513 | outline: 0;
1514 | background-color: var(--background-color);
1515 | box-shadow: var(--box-shadow);
1516 | color: var(--color);
1517 | font-weight: var(--font-weight);
1518 | font-size: 1rem;
1519 | line-height: var(--line-height);
1520 | text-align: center;
1521 | cursor: pointer;
1522 | transition:
1523 | background-color var(--transition),
1524 | border-color var(--transition),
1525 | color var(--transition),
1526 | box-shadow var(--transition);
1527 | }
1528 | [type="file"]::file-selector-button:is(:hover, :active, :focus) {
1529 | --background-color: var(--secondary-hover);
1530 | --border-color: var(--secondary-hover);
1531 | }
1532 | [type="file"]::-webkit-file-upload-button {
1533 | --background-color: var(--secondary);
1534 | --border-color: var(--secondary);
1535 | --color: var(--secondary-inverse);
1536 | margin-right: calc(var(--spacing) / 2);
1537 | margin-left: 0;
1538 | -webkit-margin-start: 0;
1539 | margin-inline-start: 0;
1540 | -webkit-margin-end: calc(var(--spacing) / 2);
1541 | margin-inline-end: calc(var(--spacing) / 2);
1542 | padding: calc(var(--form-element-spacing-vertical) * 0.5)
1543 | calc(var(--form-element-spacing-horizontal) * 0.5);
1544 | border: var(--border-width) solid var(--border-color);
1545 | border-radius: var(--border-radius);
1546 | outline: 0;
1547 | background-color: var(--background-color);
1548 | box-shadow: var(--box-shadow);
1549 | color: var(--color);
1550 | font-weight: var(--font-weight);
1551 | font-size: 1rem;
1552 | line-height: var(--line-height);
1553 | text-align: center;
1554 | cursor: pointer;
1555 | -webkit-transition:
1556 | background-color var(--transition),
1557 | border-color var(--transition),
1558 | color var(--transition),
1559 | box-shadow var(--transition);
1560 | transition:
1561 | background-color var(--transition),
1562 | border-color var(--transition),
1563 | color var(--transition),
1564 | box-shadow var(--transition);
1565 | }
1566 | [type="file"]::-webkit-file-upload-button:is(:hover, :active, :focus) {
1567 | --background-color: var(--secondary-hover);
1568 | --border-color: var(--secondary-hover);
1569 | }
1570 | [type="file"]::-ms-browse {
1571 | --background-color: var(--secondary);
1572 | --border-color: var(--secondary);
1573 | --color: var(--secondary-inverse);
1574 | margin-right: calc(var(--spacing) / 2);
1575 | margin-left: 0;
1576 | margin-inline-start: 0;
1577 | margin-inline-end: calc(var(--spacing) / 2);
1578 | padding: calc(var(--form-element-spacing-vertical) * 0.5)
1579 | calc(var(--form-element-spacing-horizontal) * 0.5);
1580 | border: var(--border-width) solid var(--border-color);
1581 | border-radius: var(--border-radius);
1582 | outline: 0;
1583 | background-color: var(--background-color);
1584 | box-shadow: var(--box-shadow);
1585 | color: var(--color);
1586 | font-weight: var(--font-weight);
1587 | font-size: 1rem;
1588 | line-height: var(--line-height);
1589 | text-align: center;
1590 | cursor: pointer;
1591 | -ms-transition:
1592 | background-color var(--transition),
1593 | border-color var(--transition),
1594 | color var(--transition),
1595 | box-shadow var(--transition);
1596 | transition:
1597 | background-color var(--transition),
1598 | border-color var(--transition),
1599 | color var(--transition),
1600 | box-shadow var(--transition);
1601 | }
1602 | [type="file"]::-ms-browse:is(:hover, :active, :focus) {
1603 | --background-color: var(--secondary-hover);
1604 | --border-color: var(--secondary-hover);
1605 | }
1606 | [type="range"] {
1607 | -webkit-appearance: none;
1608 | -moz-appearance: none;
1609 | appearance: none;
1610 | width: 100%;
1611 | height: 1.25rem;
1612 | background: 0 0;
1613 | }
1614 | [type="range"]::-webkit-slider-runnable-track {
1615 | width: 100%;
1616 | height: 0.25rem;
1617 | border-radius: var(--border-radius);
1618 | background-color: var(--range-border-color);
1619 | -webkit-transition:
1620 | background-color var(--transition),
1621 | box-shadow var(--transition);
1622 | transition:
1623 | background-color var(--transition),
1624 | box-shadow var(--transition);
1625 | }
1626 | [type="range"]::-moz-range-track {
1627 | width: 100%;
1628 | height: 0.25rem;
1629 | border-radius: var(--border-radius);
1630 | background-color: var(--range-border-color);
1631 | -moz-transition:
1632 | background-color var(--transition),
1633 | box-shadow var(--transition);
1634 | transition:
1635 | background-color var(--transition),
1636 | box-shadow var(--transition);
1637 | }
1638 | [type="range"]::-ms-track {
1639 | width: 100%;
1640 | height: 0.25rem;
1641 | border-radius: var(--border-radius);
1642 | background-color: var(--range-border-color);
1643 | -ms-transition:
1644 | background-color var(--transition),
1645 | box-shadow var(--transition);
1646 | transition:
1647 | background-color var(--transition),
1648 | box-shadow var(--transition);
1649 | }
1650 | [type="range"]::-webkit-slider-thumb {
1651 | -webkit-appearance: none;
1652 | width: 1.25rem;
1653 | height: 1.25rem;
1654 | margin-top: -0.5rem;
1655 | border: 2px solid var(--range-thumb-border-color);
1656 | border-radius: 50%;
1657 | background-color: var(--range-thumb-color);
1658 | cursor: pointer;
1659 | -webkit-transition:
1660 | background-color var(--transition),
1661 | transform var(--transition);
1662 | transition:
1663 | background-color var(--transition),
1664 | transform var(--transition);
1665 | }
1666 | [type="range"]::-moz-range-thumb {
1667 | -webkit-appearance: none;
1668 | width: 1.25rem;
1669 | height: 1.25rem;
1670 | margin-top: -0.5rem;
1671 | border: 2px solid var(--range-thumb-border-color);
1672 | border-radius: 50%;
1673 | background-color: var(--range-thumb-color);
1674 | cursor: pointer;
1675 | -moz-transition:
1676 | background-color var(--transition),
1677 | transform var(--transition);
1678 | transition:
1679 | background-color var(--transition),
1680 | transform var(--transition);
1681 | }
1682 | [type="range"]::-ms-thumb {
1683 | -webkit-appearance: none;
1684 | width: 1.25rem;
1685 | height: 1.25rem;
1686 | margin-top: -0.5rem;
1687 | border: 2px solid var(--range-thumb-border-color);
1688 | border-radius: 50%;
1689 | background-color: var(--range-thumb-color);
1690 | cursor: pointer;
1691 | -ms-transition:
1692 | background-color var(--transition),
1693 | transform var(--transition);
1694 | transition:
1695 | background-color var(--transition),
1696 | transform var(--transition);
1697 | }
1698 | [type="range"]:focus,
1699 | [type="range"]:hover {
1700 | --range-border-color: var(--range-active-border-color);
1701 | --range-thumb-color: var(--range-thumb-hover-color);
1702 | }
1703 | [type="range"]:active {
1704 | --range-thumb-color: var(--range-thumb-active-color);
1705 | }
1706 | [type="range"]:active::-webkit-slider-thumb {
1707 | transform: scale(1.25);
1708 | }
1709 | [type="range"]:active::-moz-range-thumb {
1710 | transform: scale(1.25);
1711 | }
1712 | [type="range"]:active::-ms-thumb {
1713 | transform: scale(1.25);
1714 | }
1715 | input:not(
1716 | [type="checkbox"],
1717 | [type="radio"],
1718 | [type="range"],
1719 | [type="file"]
1720 | )[type="search"] {
1721 | -webkit-padding-start: calc(var(--form-element-spacing-horizontal) + 1.75rem);
1722 | padding-inline-start: calc(var(--form-element-spacing-horizontal) + 1.75rem);
1723 | border-radius: 5rem;
1724 | background-image: var(--icon-search);
1725 | background-position: center left 1.125rem;
1726 | background-size: 1rem auto;
1727 | background-repeat: no-repeat;
1728 | }
1729 | input:not(
1730 | [type="checkbox"],
1731 | [type="radio"],
1732 | [type="range"],
1733 | [type="file"]
1734 | )[type="search"][aria-invalid] {
1735 | -webkit-padding-start: calc(
1736 | var(--form-element-spacing-horizontal) + 1.75rem
1737 | ) !important;
1738 | padding-inline-start: calc(
1739 | var(--form-element-spacing-horizontal) + 1.75rem
1740 | ) !important;
1741 | background-position:
1742 | center left 1.125rem,
1743 | center right 0.75rem;
1744 | }
1745 | input:not(
1746 | [type="checkbox"],
1747 | [type="radio"],
1748 | [type="range"],
1749 | [type="file"]
1750 | )[type="search"][aria-invalid="false"] {
1751 | background-image: var(--icon-search), var(--icon-valid);
1752 | }
1753 | input:not(
1754 | [type="checkbox"],
1755 | [type="radio"],
1756 | [type="range"],
1757 | [type="file"]
1758 | )[type="search"][aria-invalid="true"] {
1759 | background-image: var(--icon-search), var(--icon-invalid);
1760 | }
1761 | [type="search"]::-webkit-search-cancel-button {
1762 | -webkit-appearance: none;
1763 | display: none;
1764 | }
1765 | [dir="rtl"]
1766 | :where(input):not(
1767 | [type="checkbox"],
1768 | [type="radio"],
1769 | [type="range"],
1770 | [type="file"]
1771 | )[type="search"] {
1772 | background-position: center right 1.125rem;
1773 | }
1774 | [dir="rtl"]
1775 | :where(input):not(
1776 | [type="checkbox"],
1777 | [type="radio"],
1778 | [type="range"],
1779 | [type="file"]
1780 | )[type="search"][aria-invalid] {
1781 | background-position:
1782 | center right 1.125rem,
1783 | center left 0.75rem;
1784 | }
1785 | :where(table) {
1786 | width: 100%;
1787 | border-collapse: collapse;
1788 | border-spacing: 0;
1789 | text-indent: 0;
1790 | }
1791 | td,
1792 | th {
1793 | padding: calc(var(--spacing) / 2) var(--spacing);
1794 | border-bottom: var(--border-width) solid var(--table-border-color);
1795 | color: var(--color);
1796 | font-weight: var(--font-weight);
1797 | font-size: var(--font-size);
1798 | text-align: left;
1799 | text-align: start;
1800 | }
1801 | tfoot td,
1802 | tfoot th {
1803 | border-top: var(--border-width) solid var(--table-border-color);
1804 | border-bottom: 0;
1805 | }
1806 | table[role="grid"] tbody tr:nth-child(odd) {
1807 | background-color: var(--table-row-stripped-background-color);
1808 | }
1809 | code,
1810 | kbd,
1811 | pre,
1812 | samp {
1813 | font-size: 0.875em;
1814 | font-family: var(--font-family);
1815 | }
1816 | pre {
1817 | -ms-overflow-style: scrollbar;
1818 | overflow: auto;
1819 | }
1820 | code,
1821 | kbd,
1822 | pre {
1823 | border-radius: var(--border-radius);
1824 | background: var(--code-background-color);
1825 | color: var(--code-color);
1826 | font-weight: var(--font-weight);
1827 | line-height: initial;
1828 | }
1829 | code,
1830 | kbd {
1831 | display: inline-block;
1832 | padding: 0.375rem 0.5rem;
1833 | }
1834 | pre {
1835 | display: block;
1836 | margin-bottom: var(--spacing);
1837 | overflow-x: auto;
1838 | }
1839 | pre > code {
1840 | display: block;
1841 | padding: var(--spacing);
1842 | background: 0 0;
1843 | font-size: 14px;
1844 | line-height: var(--line-height);
1845 | }
1846 | code b {
1847 | color: var(--code-tag-color);
1848 | font-weight: var(--font-weight);
1849 | }
1850 | code i {
1851 | color: var(--code-property-color);
1852 | font-style: normal;
1853 | }
1854 | code u {
1855 | color: var(--code-value-color);
1856 | text-decoration: none;
1857 | }
1858 | code em {
1859 | color: var(--code-comment-color);
1860 | font-style: normal;
1861 | }
1862 | kbd {
1863 | background-color: var(--code-kbd-background-color);
1864 | color: var(--code-kbd-color);
1865 | vertical-align: baseline;
1866 | }
1867 | hr {
1868 | height: 0;
1869 | border: 0;
1870 | border-top: 1px solid var(--muted-border-color);
1871 | color: inherit;
1872 | }
1873 | [hidden],
1874 | template {
1875 | display: none !important;
1876 | }
1877 | canvas {
1878 | display: inline-block;
1879 | }
1880 | details {
1881 | display: block;
1882 | margin-bottom: var(--spacing);
1883 | padding-bottom: var(--spacing);
1884 | border-bottom: var(--border-width) solid var(--accordion-border-color);
1885 | }
1886 | details summary {
1887 | line-height: 1rem;
1888 | list-style-type: none;
1889 | cursor: pointer;
1890 | transition: color var(--transition);
1891 | }
1892 | details summary:not([role]) {
1893 | color: var(--accordion-close-summary-color);
1894 | }
1895 | details summary::-webkit-details-marker {
1896 | display: none;
1897 | }
1898 | details summary::marker {
1899 | display: none;
1900 | }
1901 | details summary::-moz-list-bullet {
1902 | list-style-type: none;
1903 | }
1904 | details summary::after {
1905 | display: block;
1906 | width: 1rem;
1907 | height: 1rem;
1908 | -webkit-margin-start: calc(var(--spacing, 1rem) * 0.5);
1909 | margin-inline-start: calc(var(--spacing, 1rem) * 0.5);
1910 | float: right;
1911 | transform: rotate(-90deg);
1912 | background-image: var(--icon-chevron);
1913 | background-position: right center;
1914 | background-size: 1rem auto;
1915 | background-repeat: no-repeat;
1916 | content: "";
1917 | transition: transform var(--transition);
1918 | }
1919 | details summary:focus {
1920 | outline: 0;
1921 | }
1922 | details summary:focus:not([role="button"]) {
1923 | color: var(--accordion-active-summary-color);
1924 | }
1925 | details summary[role="button"] {
1926 | width: 100%;
1927 | text-align: left;
1928 | }
1929 | details summary[role="button"]::after {
1930 | height: calc(1rem * var(--line-height, 1.5));
1931 | background-image: var(--icon-chevron-button);
1932 | }
1933 | details summary[role="button"]:not(.outline).contrast::after {
1934 | background-image: var(--icon-chevron-button-inverse);
1935 | }
1936 | details[open] > summary {
1937 | margin-bottom: calc(var(--spacing));
1938 | }
1939 | details[open] > summary:not([role]):not(:focus) {
1940 | color: var(--accordion-open-summary-color);
1941 | }
1942 | details[open] > summary::after {
1943 | transform: rotate(0);
1944 | }
1945 | [dir="rtl"] details summary {
1946 | text-align: right;
1947 | }
1948 | [dir="rtl"] details summary::after {
1949 | float: left;
1950 | background-position: left center;
1951 | }
1952 | article {
1953 | margin: var(--block-spacing-vertical) 0;
1954 | padding: var(--block-spacing-vertical) var(--block-spacing-horizontal);
1955 | border-radius: var(--border-radius);
1956 | background: var(--card-background-color);
1957 | box-shadow: var(--card-box-shadow);
1958 | }
1959 | article > footer,
1960 | article > header {
1961 | margin-right: calc(var(--block-spacing-horizontal) * -1);
1962 | margin-left: calc(var(--block-spacing-horizontal) * -1);
1963 | padding: calc(var(--block-spacing-vertical) * 0.66)
1964 | var(--block-spacing-horizontal);
1965 | background-color: var(--card-sectionning-background-color);
1966 | }
1967 | article > header {
1968 | margin-top: calc(var(--block-spacing-vertical) * -1);
1969 | margin-bottom: var(--block-spacing-vertical);
1970 | border-bottom: var(--border-width) solid var(--card-border-color);
1971 | border-top-right-radius: var(--border-radius);
1972 | border-top-left-radius: var(--border-radius);
1973 | }
1974 | article > footer {
1975 | margin-top: var(--block-spacing-vertical);
1976 | margin-bottom: calc(var(--block-spacing-vertical) * -1);
1977 | border-top: var(--border-width) solid var(--card-border-color);
1978 | border-bottom-right-radius: var(--border-radius);
1979 | border-bottom-left-radius: var(--border-radius);
1980 | }
1981 | :root {
1982 | --scrollbar-width: 0px;
1983 | }
1984 | dialog {
1985 | display: flex;
1986 | z-index: 999;
1987 | position: fixed;
1988 | top: 0;
1989 | right: 0;
1990 | bottom: 0;
1991 | left: 0;
1992 | align-items: center;
1993 | justify-content: center;
1994 | width: inherit;
1995 | min-width: 100%;
1996 | height: inherit;
1997 | min-height: 100%;
1998 | padding: var(--spacing);
1999 | border: 0;
2000 | -webkit-backdrop-filter: var(--modal-overlay-backdrop-filter);
2001 | backdrop-filter: var(--modal-overlay-backdrop-filter);
2002 | background-color: var(--modal-overlay-background-color);
2003 | color: var(--color);
2004 | }
2005 | dialog article {
2006 | max-height: calc(100vh - var(--spacing) * 2);
2007 | overflow: auto;
2008 | }
2009 | @media (min-width: 576px) {
2010 | dialog article {
2011 | max-width: 510px;
2012 | }
2013 | }
2014 | @media (min-width: 768px) {
2015 | dialog article {
2016 | max-width: 700px;
2017 | }
2018 | }
2019 | dialog article > footer,
2020 | dialog article > header {
2021 | padding: calc(var(--block-spacing-vertical) * 0.5)
2022 | var(--block-spacing-horizontal);
2023 | }
2024 | dialog article > header .close {
2025 | margin: 0;
2026 | margin-left: var(--spacing);
2027 | float: right;
2028 | }
2029 | dialog article > footer {
2030 | text-align: right;
2031 | }
2032 | dialog article > footer [role="button"] {
2033 | margin-bottom: 0;
2034 | }
2035 | dialog article > footer [role="button"]:not(:first-of-type) {
2036 | margin-left: calc(var(--spacing) * 0.5);
2037 | }
2038 | dialog article p:last-of-type {
2039 | margin: 0;
2040 | }
2041 | dialog article .close {
2042 | display: block;
2043 | width: 1rem;
2044 | height: 1rem;
2045 | margin-top: calc(var(--block-spacing-vertical) * -0.5);
2046 | margin-bottom: var(--typography-spacing-vertical);
2047 | margin-left: auto;
2048 | background-image: var(--icon-close);
2049 | background-position: center;
2050 | background-size: auto 1rem;
2051 | background-repeat: no-repeat;
2052 | opacity: 0.5;
2053 | transition: opacity var(--transition);
2054 | }
2055 | dialog article .close:is([aria-current], :hover, :active, :focus) {
2056 | opacity: 1;
2057 | }
2058 | dialog:not([open]),
2059 | dialog[open="false"] {
2060 | display: none;
2061 | }
2062 | .modal-is-open {
2063 | padding-right: var(--scrollbar-width, 0);
2064 | overflow: hidden;
2065 | pointer-events: none;
2066 | touch-action: none;
2067 | }
2068 | .modal-is-open dialog {
2069 | pointer-events: auto;
2070 | }
2071 | :where(.modal-is-opening, .modal-is-closing) dialog,
2072 | :where(.modal-is-opening, .modal-is-closing) dialog > article {
2073 | animation-duration: 0.2s;
2074 | animation-timing-function: ease-in-out;
2075 | animation-fill-mode: both;
2076 | }
2077 | :where(.modal-is-opening, .modal-is-closing) dialog {
2078 | animation-duration: 0.8s;
2079 | animation-name: modal-overlay;
2080 | }
2081 | :where(.modal-is-opening, .modal-is-closing) dialog > article {
2082 | animation-delay: 0.2s;
2083 | animation-name: modal;
2084 | }
2085 | .modal-is-closing dialog,
2086 | .modal-is-closing dialog > article {
2087 | animation-delay: 0s;
2088 | animation-direction: reverse;
2089 | }
2090 | @keyframes modal-overlay {
2091 | from {
2092 | -webkit-backdrop-filter: none;
2093 | backdrop-filter: none;
2094 | background-color: transparent;
2095 | }
2096 | }
2097 | @keyframes modal {
2098 | from {
2099 | transform: translateY(-100%);
2100 | opacity: 0;
2101 | }
2102 | }
2103 | :where(nav li)::before {
2104 | float: left;
2105 | content: "";
2106 | }
2107 | nav,
2108 | nav ul {
2109 | display: flex;
2110 | }
2111 | nav {
2112 | justify-content: space-between;
2113 | }
2114 | nav ol,
2115 | nav ul {
2116 | align-items: center;
2117 | margin-bottom: 0;
2118 | padding: 0;
2119 | list-style: none;
2120 | }
2121 | nav ol:first-of-type,
2122 | nav ul:first-of-type {
2123 | margin-left: calc(var(--nav-element-spacing-horizontal) * -1);
2124 | }
2125 | nav ol:last-of-type,
2126 | nav ul:last-of-type {
2127 | margin-right: calc(var(--nav-element-spacing-horizontal) * -1);
2128 | }
2129 | nav li {
2130 | display: inline-block;
2131 | margin: 0;
2132 | padding: var(--nav-element-spacing-vertical)
2133 | var(--nav-element-spacing-horizontal);
2134 | }
2135 | nav li > * {
2136 | --spacing: 0;
2137 | }
2138 | nav :where(a, [role="link"]) {
2139 | display: inline-block;
2140 | margin: calc(var(--nav-link-spacing-vertical) * -1)
2141 | calc(var(--nav-link-spacing-horizontal) * -1);
2142 | padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);
2143 | border-radius: var(--border-radius);
2144 | text-decoration: none;
2145 | }
2146 | nav :where(a, [role="link"]):is([aria-current], :hover, :active, :focus) {
2147 | text-decoration: none;
2148 | }
2149 | nav[aria-label="breadcrumb"] {
2150 | align-items: center;
2151 | justify-content: start;
2152 | }
2153 | nav[aria-label="breadcrumb"] ul li:not(:first-child) {
2154 | -webkit-margin-start: var(--nav-link-spacing-horizontal);
2155 | margin-inline-start: var(--nav-link-spacing-horizontal);
2156 | }
2157 | nav[aria-label="breadcrumb"] ul li:not(:last-child) ::after {
2158 | position: absolute;
2159 | width: calc(var(--nav-link-spacing-horizontal) * 2);
2160 | -webkit-margin-start: calc(var(--nav-link-spacing-horizontal) / 2);
2161 | margin-inline-start: calc(var(--nav-link-spacing-horizontal) / 2);
2162 | content: "/";
2163 | color: var(--muted-color);
2164 | text-align: center;
2165 | }
2166 | nav[aria-label="breadcrumb"] a[aria-current] {
2167 | background-color: transparent;
2168 | color: inherit;
2169 | text-decoration: none;
2170 | pointer-events: none;
2171 | }
2172 | nav [role="button"] {
2173 | margin-right: inherit;
2174 | margin-left: inherit;
2175 | padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);
2176 | }
2177 | aside li,
2178 | aside nav,
2179 | aside ol,
2180 | aside ul {
2181 | display: block;
2182 | }
2183 | aside li {
2184 | padding: calc(var(--nav-element-spacing-vertical) * 0.5)
2185 | var(--nav-element-spacing-horizontal);
2186 | }
2187 | aside li a {
2188 | display: block;
2189 | }
2190 | aside li [role="button"] {
2191 | margin: inherit;
2192 | }
2193 | [dir="rtl"] nav[aria-label="breadcrumb"] ul li:not(:last-child) ::after {
2194 | content: "\\";
2195 | }
2196 | progress {
2197 | display: inline-block;
2198 | vertical-align: baseline;
2199 | }
2200 | progress {
2201 | -webkit-appearance: none;
2202 | -moz-appearance: none;
2203 | display: inline-block;
2204 | appearance: none;
2205 | width: 100%;
2206 | height: 0.5rem;
2207 | margin-bottom: calc(var(--spacing) * 0.5);
2208 | overflow: hidden;
2209 | border: 0;
2210 | border-radius: var(--border-radius);
2211 | background-color: var(--progress-background-color);
2212 | color: var(--progress-color);
2213 | }
2214 | progress::-webkit-progress-bar {
2215 | border-radius: var(--border-radius);
2216 | background: 0 0;
2217 | }
2218 | progress[value]::-webkit-progress-value {
2219 | background-color: var(--progress-color);
2220 | }
2221 | progress::-moz-progress-bar {
2222 | background-color: var(--progress-color);
2223 | }
2224 | @media (prefers-reduced-motion: no-preference) {
2225 | progress:indeterminate {
2226 | background: var(--progress-background-color)
2227 | linear-gradient(
2228 | to right,
2229 | var(--progress-color) 30%,
2230 | var(--progress-background-color) 30%
2231 | )
2232 | top left/150% 150% no-repeat;
2233 | animation: progress-indeterminate 1s linear infinite;
2234 | }
2235 | progress:indeterminate[value]::-webkit-progress-value {
2236 | background-color: transparent;
2237 | }
2238 | progress:indeterminate::-moz-progress-bar {
2239 | background-color: transparent;
2240 | }
2241 | }
2242 | @media (prefers-reduced-motion: no-preference) {
2243 | [dir="rtl"] progress:indeterminate {
2244 | animation-direction: reverse;
2245 | }
2246 | }
2247 | @keyframes progress-indeterminate {
2248 | 0% {
2249 | background-position: 200% 0;
2250 | }
2251 | 100% {
2252 | background-position: -200% 0;
2253 | }
2254 | }
2255 | details[role="list"],
2256 | li[role="list"] {
2257 | position: relative;
2258 | }
2259 | details[role="list"] summary + ul,
2260 | li[role="list"] > ul {
2261 | display: flex;
2262 | z-index: 99;
2263 | position: absolute;
2264 | top: auto;
2265 | right: 0;
2266 | left: 0;
2267 | flex-direction: column;
2268 | margin: 0;
2269 | padding: 0;
2270 | border: var(--border-width) solid var(--dropdown-border-color);
2271 | border-radius: var(--border-radius);
2272 | border-top-right-radius: 0;
2273 | border-top-left-radius: 0;
2274 | background-color: var(--dropdown-background-color);
2275 | box-shadow: var(--card-box-shadow);
2276 | color: var(--dropdown-color);
2277 | white-space: nowrap;
2278 | }
2279 | details[role="list"] summary + ul li,
2280 | li[role="list"] > ul li {
2281 | width: 100%;
2282 | margin-bottom: 0;
2283 | padding: calc(var(--form-element-spacing-vertical) * 0.5)
2284 | var(--form-element-spacing-horizontal);
2285 | list-style: none;
2286 | }
2287 | details[role="list"] summary + ul li:first-of-type,
2288 | li[role="list"] > ul li:first-of-type {
2289 | margin-top: calc(var(--form-element-spacing-vertical) * 0.5);
2290 | }
2291 | details[role="list"] summary + ul li:last-of-type,
2292 | li[role="list"] > ul li:last-of-type {
2293 | margin-bottom: calc(var(--form-element-spacing-vertical) * 0.5);
2294 | }
2295 | details[role="list"] summary + ul li a,
2296 | li[role="list"] > ul li a {
2297 | display: block;
2298 | margin: calc(var(--form-element-spacing-vertical) * -0.5)
2299 | calc(var(--form-element-spacing-horizontal) * -1);
2300 | padding: calc(var(--form-element-spacing-vertical) * 0.5)
2301 | var(--form-element-spacing-horizontal);
2302 | overflow: hidden;
2303 | color: var(--dropdown-color);
2304 | text-decoration: none;
2305 | text-overflow: ellipsis;
2306 | }
2307 | details[role="list"] summary + ul li a:hover,
2308 | li[role="list"] > ul li a:hover {
2309 | background-color: var(--dropdown-hover-background-color);
2310 | }
2311 | details[role="list"] summary::after,
2312 | li[role="list"] > a::after {
2313 | display: block;
2314 | width: 1rem;
2315 | height: calc(1rem * var(--line-height, 1.5));
2316 | -webkit-margin-start: 0.5rem;
2317 | margin-inline-start: 0.5rem;
2318 | float: right;
2319 | transform: rotate(0);
2320 | background-position: right center;
2321 | background-size: 1rem auto;
2322 | background-repeat: no-repeat;
2323 | content: "";
2324 | }
2325 | details[role="list"] {
2326 | padding: 0;
2327 | border-bottom: none;
2328 | }
2329 | details[role="list"] summary {
2330 | margin-bottom: 0;
2331 | }
2332 | details[role="list"] summary:not([role]) {
2333 | height: calc(
2334 | 1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 +
2335 | var(--border-width) * 2
2336 | );
2337 | padding: var(--form-element-spacing-vertical)
2338 | var(--form-element-spacing-horizontal);
2339 | border: var(--border-width) solid var(--form-element-border-color);
2340 | border-radius: var(--border-radius);
2341 | background-color: var(--form-element-background-color);
2342 | color: var(--form-element-placeholder-color);
2343 | line-height: inherit;
2344 | cursor: pointer;
2345 | transition:
2346 | background-color var(--transition),
2347 | border-color var(--transition),
2348 | color var(--transition),
2349 | box-shadow var(--transition);
2350 | }
2351 | details[role="list"] summary:not([role]):active,
2352 | details[role="list"] summary:not([role]):focus {
2353 | border-color: var(--form-element-active-border-color);
2354 | background-color: var(--form-element-active-background-color);
2355 | }
2356 | details[role="list"] summary:not([role]):focus {
2357 | box-shadow: 0 0 0 var(--outline-width) var(--form-element-focus-color);
2358 | }
2359 | details[role="list"][open] summary {
2360 | border-bottom-right-radius: 0;
2361 | border-bottom-left-radius: 0;
2362 | }
2363 | details[role="list"][open] summary::before {
2364 | display: block;
2365 | z-index: 1;
2366 | position: fixed;
2367 | top: 0;
2368 | right: 0;
2369 | bottom: 0;
2370 | left: 0;
2371 | background: 0 0;
2372 | content: "";
2373 | cursor: default;
2374 | }
2375 | nav details[role="list"] summary,
2376 | nav li[role="list"] a {
2377 | display: flex;
2378 | direction: ltr;
2379 | }
2380 | nav details[role="list"] summary + ul,
2381 | nav li[role="list"] > ul {
2382 | min-width: -moz-fit-content;
2383 | min-width: fit-content;
2384 | border-radius: var(--border-radius);
2385 | }
2386 | nav details[role="list"] summary + ul li a,
2387 | nav li[role="list"] > ul li a {
2388 | border-radius: 0;
2389 | }
2390 | nav details[role="list"] summary,
2391 | nav details[role="list"] summary:not([role]) {
2392 | height: auto;
2393 | padding: var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);
2394 | }
2395 | nav details[role="list"][open] summary {
2396 | border-radius: var(--border-radius);
2397 | }
2398 | nav details[role="list"] summary + ul {
2399 | margin-top: var(--outline-width);
2400 | -webkit-margin-start: 0;
2401 | margin-inline-start: 0;
2402 | }
2403 | nav details[role="list"] summary[role="link"] {
2404 | margin-bottom: calc(var(--nav-link-spacing-vertical) * -1);
2405 | line-height: var(--line-height);
2406 | }
2407 | nav details[role="list"] summary[role="link"] + ul {
2408 | margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width));
2409 | -webkit-margin-start: calc(var(--nav-link-spacing-horizontal) * -1);
2410 | margin-inline-start: calc(var(--nav-link-spacing-horizontal) * -1);
2411 | }
2412 | li[role="list"] a:active ~ ul,
2413 | li[role="list"] a:focus ~ ul,
2414 | li[role="list"]:hover > ul {
2415 | display: flex;
2416 | }
2417 | li[role="list"] > ul {
2418 | display: none;
2419 | margin-top: calc(var(--nav-link-spacing-vertical) + var(--outline-width));
2420 | -webkit-margin-start: calc(
2421 | var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal)
2422 | );
2423 | margin-inline-start: calc(
2424 | var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal)
2425 | );
2426 | }
2427 | li[role="list"] > a::after {
2428 | background-image: var(--icon-chevron);
2429 | }
2430 | label > details[role="list"] {
2431 | margin-top: calc(var(--spacing) * 0.25);
2432 | margin-bottom: var(--spacing);
2433 | }
2434 | [aria-busy="true"] {
2435 | cursor: progress;
2436 | }
2437 | [aria-busy="true"]:not(input, select, textarea, html)::before {
2438 | display: inline-block;
2439 | width: 1em;
2440 | height: 1em;
2441 | border: 0.1875em solid currentColor;
2442 | border-radius: 1em;
2443 | border-right-color: transparent;
2444 | content: "";
2445 | vertical-align: text-bottom;
2446 | vertical-align: -0.125em;
2447 | animation: spinner 0.75s linear infinite;
2448 | opacity: var(--loading-spinner-opacity);
2449 | }
2450 | [aria-busy="true"]:not(input, select, textarea, html):not(:empty)::before {
2451 | margin-right: calc(var(--spacing) * 0.5);
2452 | margin-left: 0;
2453 | -webkit-margin-start: 0;
2454 | margin-inline-start: 0;
2455 | -webkit-margin-end: calc(var(--spacing) * 0.5);
2456 | margin-inline-end: calc(var(--spacing) * 0.5);
2457 | }
2458 | [aria-busy="true"]:not(input, select, textarea, html):empty {
2459 | text-align: center;
2460 | }
2461 | a[aria-busy="true"],
2462 | button[aria-busy="true"],
2463 | input[type="button"][aria-busy="true"],
2464 | input[type="reset"][aria-busy="true"],
2465 | input[type="submit"][aria-busy="true"] {
2466 | pointer-events: none;
2467 | }
2468 | @keyframes spinner {
2469 | to {
2470 | transform: rotate(360deg);
2471 | }
2472 | }
2473 | [data-tooltip] {
2474 | position: relative;
2475 | }
2476 | [data-tooltip]:not(a, button, input) {
2477 | border-bottom: 1px dotted;
2478 | text-decoration: none;
2479 | cursor: help;
2480 | }
2481 | [data-tooltip]::after,
2482 | [data-tooltip]::before,
2483 | [data-tooltip][data-placement="top"]::after,
2484 | [data-tooltip][data-placement="top"]::before {
2485 | display: block;
2486 | z-index: 99;
2487 | position: absolute;
2488 | bottom: 100%;
2489 | left: 50%;
2490 | padding: 0.25rem 0.5rem;
2491 | overflow: hidden;
2492 | transform: translate(-50%, -0.25rem);
2493 | border-radius: var(--border-radius);
2494 | background: var(--tooltip-background-color);
2495 | content: attr(data-tooltip);
2496 | color: var(--tooltip-color);
2497 | font-style: normal;
2498 | font-weight: var(--font-weight);
2499 | font-size: 0.875rem;
2500 | text-decoration: none;
2501 | text-overflow: ellipsis;
2502 | white-space: nowrap;
2503 | opacity: 0;
2504 | pointer-events: none;
2505 | }
2506 | [data-tooltip]::after,
2507 | [data-tooltip][data-placement="top"]::after {
2508 | padding: 0;
2509 | transform: translate(-50%, 0);
2510 | border-top: 0.3rem solid;
2511 | border-right: 0.3rem solid transparent;
2512 | border-left: 0.3rem solid transparent;
2513 | border-radius: 0;
2514 | background-color: transparent;
2515 | content: "";
2516 | color: var(--tooltip-background-color);
2517 | }
2518 | [data-tooltip][data-placement="bottom"]::after,
2519 | [data-tooltip][data-placement="bottom"]::before {
2520 | top: 100%;
2521 | bottom: auto;
2522 | transform: translate(-50%, 0.25rem);
2523 | }
2524 | [data-tooltip][data-placement="bottom"]:after {
2525 | transform: translate(-50%, -0.3rem);
2526 | border: 0.3rem solid transparent;
2527 | border-bottom: 0.3rem solid;
2528 | }
2529 | [data-tooltip][data-placement="left"]::after,
2530 | [data-tooltip][data-placement="left"]::before {
2531 | top: 50%;
2532 | right: 100%;
2533 | bottom: auto;
2534 | left: auto;
2535 | transform: translate(-0.25rem, -50%);
2536 | }
2537 | [data-tooltip][data-placement="left"]:after {
2538 | transform: translate(0.3rem, -50%);
2539 | border: 0.3rem solid transparent;
2540 | border-left: 0.3rem solid;
2541 | }
2542 | [data-tooltip][data-placement="right"]::after,
2543 | [data-tooltip][data-placement="right"]::before {
2544 | top: 50%;
2545 | right: auto;
2546 | bottom: auto;
2547 | left: 100%;
2548 | transform: translate(0.25rem, -50%);
2549 | }
2550 | [data-tooltip][data-placement="right"]:after {
2551 | transform: translate(-0.3rem, -50%);
2552 | border: 0.3rem solid transparent;
2553 | border-right: 0.3rem solid;
2554 | }
2555 | [data-tooltip]:focus::after,
2556 | [data-tooltip]:focus::before,
2557 | [data-tooltip]:hover::after,
2558 | [data-tooltip]:hover::before {
2559 | opacity: 1;
2560 | }
2561 | @media (hover: hover) and (pointer: fine) {
2562 | [data-tooltip]:hover::after,
2563 | [data-tooltip]:hover::before,
2564 | [data-tooltip][data-placement="bottom"]:focus::after,
2565 | [data-tooltip][data-placement="bottom"]:focus::before,
2566 | [data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::after,
2567 | [data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::before {
2568 | animation-duration: 0.2s;
2569 | animation-name: tooltip-slide-top;
2570 | }
2571 | [data-tooltip]:hover::after,
2572 | [data-tooltip][data-placement="bottom"]:focus::after,
2573 | [data-tooltip][data-placement="bottom"]:hover [data-tooltip]:focus::after {
2574 | animation-name: tooltip-caret-slide-top;
2575 | }
2576 | [data-tooltip][data-placement="bottom"]:focus::after,
2577 | [data-tooltip][data-placement="bottom"]:focus::before,
2578 | [data-tooltip][data-placement="bottom"]:hover::after,
2579 | [data-tooltip][data-placement="bottom"]:hover::before {
2580 | animation-duration: 0.2s;
2581 | animation-name: tooltip-slide-bottom;
2582 | }
2583 | [data-tooltip][data-placement="bottom"]:focus::after,
2584 | [data-tooltip][data-placement="bottom"]:hover::after {
2585 | animation-name: tooltip-caret-slide-bottom;
2586 | }
2587 | [data-tooltip][data-placement="left"]:focus::after,
2588 | [data-tooltip][data-placement="left"]:focus::before,
2589 | [data-tooltip][data-placement="left"]:hover::after,
2590 | [data-tooltip][data-placement="left"]:hover::before {
2591 | animation-duration: 0.2s;
2592 | animation-name: tooltip-slide-left;
2593 | }
2594 | [data-tooltip][data-placement="left"]:focus::after,
2595 | [data-tooltip][data-placement="left"]:hover::after {
2596 | animation-name: tooltip-caret-slide-left;
2597 | }
2598 | [data-tooltip][data-placement="right"]:focus::after,
2599 | [data-tooltip][data-placement="right"]:focus::before,
2600 | [data-tooltip][data-placement="right"]:hover::after,
2601 | [data-tooltip][data-placement="right"]:hover::before {
2602 | animation-duration: 0.2s;
2603 | animation-name: tooltip-slide-right;
2604 | }
2605 | [data-tooltip][data-placement="right"]:focus::after,
2606 | [data-tooltip][data-placement="right"]:hover::after {
2607 | animation-name: tooltip-caret-slide-right;
2608 | }
2609 | }
2610 | @keyframes tooltip-slide-top {
2611 | from {
2612 | transform: translate(-50%, 0.75rem);
2613 | opacity: 0;
2614 | }
2615 | to {
2616 | transform: translate(-50%, -0.25rem);
2617 | opacity: 1;
2618 | }
2619 | }
2620 | @keyframes tooltip-caret-slide-top {
2621 | from {
2622 | opacity: 0;
2623 | }
2624 | 50% {
2625 | transform: translate(-50%, -0.25rem);
2626 | opacity: 0;
2627 | }
2628 | to {
2629 | transform: translate(-50%, 0);
2630 | opacity: 1;
2631 | }
2632 | }
2633 | @keyframes tooltip-slide-bottom {
2634 | from {
2635 | transform: translate(-50%, -0.75rem);
2636 | opacity: 0;
2637 | }
2638 | to {
2639 | transform: translate(-50%, 0.25rem);
2640 | opacity: 1;
2641 | }
2642 | }
2643 | @keyframes tooltip-caret-slide-bottom {
2644 | from {
2645 | opacity: 0;
2646 | }
2647 | 50% {
2648 | transform: translate(-50%, -0.5rem);
2649 | opacity: 0;
2650 | }
2651 | to {
2652 | transform: translate(-50%, -0.3rem);
2653 | opacity: 1;
2654 | }
2655 | }
2656 | @keyframes tooltip-slide-left {
2657 | from {
2658 | transform: translate(0.75rem, -50%);
2659 | opacity: 0;
2660 | }
2661 | to {
2662 | transform: translate(-0.25rem, -50%);
2663 | opacity: 1;
2664 | }
2665 | }
2666 | @keyframes tooltip-caret-slide-left {
2667 | from {
2668 | opacity: 0;
2669 | }
2670 | 50% {
2671 | transform: translate(0.05rem, -50%);
2672 | opacity: 0;
2673 | }
2674 | to {
2675 | transform: translate(0.3rem, -50%);
2676 | opacity: 1;
2677 | }
2678 | }
2679 | @keyframes tooltip-slide-right {
2680 | from {
2681 | transform: translate(-0.75rem, -50%);
2682 | opacity: 0;
2683 | }
2684 | to {
2685 | transform: translate(0.25rem, -50%);
2686 | opacity: 1;
2687 | }
2688 | }
2689 | @keyframes tooltip-caret-slide-right {
2690 | from {
2691 | opacity: 0;
2692 | }
2693 | 50% {
2694 | transform: translate(-0.05rem, -50%);
2695 | opacity: 0;
2696 | }
2697 | to {
2698 | transform: translate(-0.3rem, -50%);
2699 | opacity: 1;
2700 | }
2701 | }
2702 | [aria-controls] {
2703 | cursor: pointer;
2704 | }
2705 | [aria-disabled="true"],
2706 | [disabled] {
2707 | cursor: not-allowed;
2708 | }
2709 | [aria-hidden="false"][hidden] {
2710 | display: initial;
2711 | }
2712 | [aria-hidden="false"][hidden]:not(:focus) {
2713 | clip: rect(0, 0, 0, 0);
2714 | position: absolute;
2715 | }
2716 | [tabindex],
2717 | a,
2718 | area,
2719 | button,
2720 | input,
2721 | label,
2722 | select,
2723 | summary,
2724 | textarea {
2725 | -ms-touch-action: manipulation;
2726 | }
2727 | [dir="rtl"] {
2728 | direction: rtl;
2729 | }
2730 | @media (prefers-reduced-motion: reduce) {
2731 | :not([aria-busy="true"]),
2732 | :not([aria-busy="true"])::after,
2733 | :not([aria-busy="true"])::before {
2734 | background-attachment: initial !important;
2735 | animation-duration: 1ms !important;
2736 | animation-delay: -1ms !important;
2737 | animation-iteration-count: 1 !important;
2738 | scroll-behavior: auto !important;
2739 | transition-delay: 0s !important;
2740 | transition-duration: 0s !important;
2741 | }
2742 | }
2743 | /*# sourceMappingURL=pico.min.css.map */
2744 |
--------------------------------------------------------------------------------