├── .gitignore ├── public ├── _redirects ├── robots.txt ├── favicon.png ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── library │ ├── .gitignore │ ├── src │ │ ├── index.ts │ │ ├── BubblyContainer.tsx │ │ ├── index.d.ts │ │ ├── BubblyBubbles.tsx │ │ ├── styles.css │ │ └── BubblyLink.tsx │ ├── package.json │ └── README.md ├── react-app-env.d.ts ├── shared │ ├── Title.tsx │ └── Nav.tsx ├── routes │ ├── About.tsx │ ├── Docs.tsx │ └── Contact.tsx ├── index.tsx ├── App.tsx └── index.css ├── .vscode └── settings.json ├── .DS_Store ├── tsconfig.json ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /src/library/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/HEAD/.DS_Store -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frontend-joe/react-bubbly-transitions/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/library/src/index.ts: -------------------------------------------------------------------------------- 1 | export { BubblyContainer } from "./BubblyContainer"; 2 | export { BubblyLink } from "./BubblyLink"; 3 | -------------------------------------------------------------------------------- /src/library/src/BubblyContainer.tsx: -------------------------------------------------------------------------------- 1 | export const BubblyContainer = () => ( 2 |
3 | ); 4 | -------------------------------------------------------------------------------- /src/library/src/index.d.ts: -------------------------------------------------------------------------------- 1 | export { BubblyContainer } from "./BubblyContainer"; 2 | export { BubblyLink, BubblyLinkProps } from "./BubblyLink"; 3 | -------------------------------------------------------------------------------- /src/shared/Title.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from "react"; 2 | 3 | export const Title: FC = ({ children }) => ( 4 |

5 | {children} 6 |

7 | ); 8 | -------------------------------------------------------------------------------- /src/routes/About.tsx: -------------------------------------------------------------------------------- 1 | import { Nav } from "../shared/Nav"; 2 | import { Title } from "../shared/Title"; 3 | 4 | const Wrapper = () => ( 5 | <> 6 |
7 | About 8 |
10 | 11 | ); 12 | 13 | export default Wrapper; 14 | -------------------------------------------------------------------------------- /src/routes/Docs.tsx: -------------------------------------------------------------------------------- 1 | import { Nav } from "../shared/Nav"; 2 | import { Title } from "../shared/Title"; 3 | 4 | const Wrapper = () => ( 5 | <> 6 |
7 | Docs 8 |
10 | 11 | ); 12 | 13 | export default Wrapper; 14 | -------------------------------------------------------------------------------- /src/routes/Contact.tsx: -------------------------------------------------------------------------------- 1 | import { Nav } from "../shared/Nav"; 2 | import { Title } from "../shared/Title"; 3 | 4 | const Wrapper = () => ( 5 | <> 6 |
7 | Contact 8 |
10 | 11 | ); 12 | 13 | export default Wrapper; 14 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById("root") as HTMLElement 8 | ); 9 | root.render( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/shared/Nav.tsx: -------------------------------------------------------------------------------- 1 | import { BubblyLink } from "../library/src"; 2 | 3 | const MyBubblyLink = ({ to = "", text = "" }) => ( 4 | 5 | {text} 6 | 7 | ); 8 | 9 | export const Nav = () => ( 10 | 15 | ); 16 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "react-wavy-transitions", 3 | "name": "react-wavy-transitions", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "favicon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "favicon.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/library/src/BubblyBubbles.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import "./styles.css"; 3 | 4 | type Props = { 5 | colorStart: string; 6 | colorEnd: string; 7 | duration: number; 8 | }; 9 | 10 | export const BubblyBubbles: FC = ({ 11 | colorStart, 12 | colorEnd, 13 | duration, 14 | }) => { 15 | return ( 16 |
17 |
21 |
25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bubbly-transitions-examples", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/node": "^16.11.64", 7 | "@types/react": "^18.0.21", 8 | "@types/react-dom": "^18.0.6", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-router-dom": "^6.4.2", 12 | "react-scripts": "5.0.1", 13 | "typescript": "^4.8.4" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build" 18 | }, 19 | "eslintConfig": { 20 | "extends": [ 21 | "react-app" 22 | ] 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"; 3 | 4 | import About from "./routes/About"; 5 | import Docs from "./routes/Docs"; 6 | import Contact from "./routes/Contact"; 7 | 8 | import { BubblyContainer } from "./library/src"; 9 | 10 | function App() { 11 | return ( 12 | 13 | 14 | 15 | }> 16 | } /> 17 | ...}> 21 | 22 | 23 | } 24 | /> 25 | ...}> 29 | 30 | 31 | } 32 | /> 33 | No Match} /> 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | export default App; 41 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 64px 15% 0; 4 | font-family: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", 5 | "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", 6 | "Helvetica Neue", sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | header { 12 | display: flex; 13 | align-items: center; 14 | justify-content: space-between; 15 | } 16 | 17 | nav { 18 | display: flex; 19 | gap: 10px; 20 | } 21 | 22 | @keyframes in-keyframes { 23 | 0% { 24 | opacity: 0; 25 | translate: 0 100%; 26 | } 27 | 100% { 28 | opacity: 1; 29 | translate: 0 0; 30 | } 31 | } 32 | 33 | .animate-in { 34 | animation-name: in-keyframes; 35 | animation-duration: 0.5s; 36 | animation-fill-mode: both; 37 | } 38 | 39 | /* wave link styles */ 40 | body .react-bubbly-transitions__bubbly-link { 41 | padding: 0; 42 | outline: none; 43 | } 44 | 45 | body .react-bubbly-transitions__bubbly-link.active { 46 | position: relative; 47 | color: #8f44fd; 48 | } 49 | 50 | body .react-bubbly-transitions__bubbly-link.active::after { 51 | content: ""; 52 | position: absolute; 53 | left: 0; 54 | bottom: 0; 55 | width: 100%; 56 | height: 2px; 57 | border-radius: 3px; 58 | background: #8f44fd; 59 | } 60 | -------------------------------------------------------------------------------- /src/library/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | #react-bubbly-transitions__bubbles { 6 | --size: 200vw; 7 | } 8 | 9 | @media only screen and (min-width: 768px) { 10 | #react-bubbly-transitions__bubbles { 11 | --size: 125vw; 12 | } 13 | } 14 | 15 | .react-bubbly-transitions__first, 16 | .react-bubbly-transitions__second { 17 | position: fixed; 18 | z-index: 9999; 19 | top: 0; 20 | left: 50%; 21 | translate: -50% 100%; 22 | width: var(--size); 23 | height: var(--size); 24 | border-radius: var(--size); 25 | animation-timing-function: ease-in-out; 26 | } 27 | 28 | .react-bubbly-transitions__first { 29 | animation-name: bubble-move; 30 | } 31 | 32 | .react-bubbly-transitions__second { 33 | animation-name: bubble-second-move; 34 | } 35 | 36 | @keyframes bubble-move { 37 | 20% { 38 | border-radius: var(--size); 39 | } 40 | 50%, 41 | 100% { 42 | translate: -50% 0; 43 | border-radius: 0; 44 | } 45 | } 46 | 47 | @keyframes bubble-second-move { 48 | 30% { 49 | translate: -50% 100%; 50 | } 51 | 50% { 52 | border-radius: var(--size); 53 | } 54 | 100% { 55 | translate: -50% 0; 56 | border-radius: 0; 57 | } 58 | } 59 | 60 | /* bubbly Link */ 61 | .react-bubbly-transitions__bubbly-link { 62 | background: transparent; 63 | border: 0; 64 | color: inherit; 65 | font-family: inherit; 66 | font-size: 1rem; 67 | cursor: pointer; 68 | } 69 | -------------------------------------------------------------------------------- /src/library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bubbly-transitions", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist/*.*" 8 | ], 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "publish:npm": "rm -Rf dist && mkdir dist && babel --extensions .ts,.tsx src --ignore 'src/index.d.ts' -d dist --copy-files" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/frontend-joe/react-bubbly-transitions.git" 16 | }, 17 | "author": "@frontendjoe", 18 | "license": "MIT", 19 | "types": "./dist/index.d.ts", 20 | "bugs": { 21 | "url": "https://github.com/frontend-joe/react-bubbly-transitions/issues" 22 | }, 23 | "homepage": "https://github.com/frontend-joe/react-bubbly-transitions#readme", 24 | "dependencies": { 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0", 27 | "react-router-dom": "^6.4.2" 28 | }, 29 | "devDependencies": { 30 | "@babel/cli": "^7.18.10", 31 | "@babel/preset-env": "^7.19.1", 32 | "@babel/preset-react": "^7.18.6", 33 | "@babel/preset-typescript": "^7.18.6" 34 | }, 35 | "babel": { 36 | "presets": [ 37 | [ 38 | "@babel/react", 39 | { 40 | "runtime": "automatic" 41 | } 42 | ], 43 | [ 44 | "@babel/preset-typescript" 45 | ], 46 | [ 47 | "@babel/preset-env" 48 | ] 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/library/src/BubblyLink.tsx: -------------------------------------------------------------------------------- 1 | import { FC, MouseEvent, ReactNode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { useNavigate } from "react-router-dom"; 4 | import { BubblyBubbles } from "./BubblyBubbles"; 5 | 6 | export type BubblyLinkProps = { 7 | to: string; 8 | children: ReactNode; 9 | colorStart?: string; 10 | colorEnd?: string; 11 | duration?: number; 12 | }; 13 | 14 | export const BubblyLink: FC = ({ 15 | to, 16 | children, 17 | colorStart = "#8f44fd", 18 | colorEnd = "#ffffff", 19 | duration = 1250, 20 | }) => { 21 | const navigate = useNavigate(); 22 | 23 | const handleClick = (e: MouseEvent | undefined) => { 24 | e?.preventDefault(); 25 | 26 | if ( 27 | !document.getElementById("react-bubbly-transitions__bubbles") && 28 | window.location.pathname !== to 29 | ) { 30 | // change the url in address bar 31 | window.history.pushState("", "", to); 32 | 33 | // get access to wave container 34 | const container = createRoot( 35 | document.getElementById("react-bubbly-transitions__container")! 36 | ); 37 | 38 | // show the waves 39 | container.render( 40 | 45 | ); 46 | 47 | // do the route change 48 | setTimeout(() => navigate(to), duration / 2); // half total animation 49 | 50 | // hide the waves 51 | setTimeout(() => container.unmount(), duration); // total animation 52 | } 53 | }; 54 | 55 | return ( 56 | 65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | react-bubbly-transitions 28 | 29 | 30 | 34 | 35 | 36 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-bubbly-transitions 2 | 3 | Show bubbly transitions between route changes, in your React 18 apps. 4 | 5 | Click [here for a demo](https://bubbles.frontendjoe.com/). 6 | 7 | Or [check out the npm package here](https://www.npmjs.com/package/react-bubbly-transitions). 8 | 9 | ## Installation 10 | 11 | Just a few quick steps to get started: 12 | 13 | #### 1. Create a React app (optional) 14 | 15 | If you are adding the transitions to an existing app, you can skip this step. 16 | 17 | ```sh 18 | npx create-react-app my-bubbly-app 19 | cd my-bubbly-app 20 | ``` 21 | 22 | #### 2. Install dependencies 23 | 24 | Our project depends upon React's router library 25 | 26 | ```sh 27 | npm i react-bubbly-transitions react-router-dom 28 | ``` 29 | 30 | #### 3. Add components 31 | 32 | The package relies on two components being present. 33 | 34 | ##### BubblyContainer 35 | 36 | This is what houses our wave transition between route changes and does not require any props. 37 | 38 | ##### BubblyLink 39 | 40 | This button can be declared anywhere inside your Router component. 41 | 42 | It takes the following props: 43 | 44 | | Prop | Description | Example | type | required | default | 45 | | ---------- | -------------------------------------------------------------------------------------------- | ------- | ------------------ | -------- | ------- | 46 | | children | The content inside the link | About | String / Component | true | | 47 | | to | The route that the link will take you to | /about | String | true | | 48 | | colorStart | The background color of the bubble shape that appears first. Must be a hexcode or rgba value | #8f44fd | String | false | #8f44fd | 49 | | colorEnd | The background color of the bubble shape that appears last. Must be a hexcode or rgba value | #ffffff | String | false | #ffffff | 50 | 51 | Be careful with the duration (too fast/slow can ruin the effect) - my recommended duration is between 1000ms and 1600ms. 52 | 53 | ##### Example App.tsx 54 | 55 | Copy this whole code snippet into your App.tsx for a basic example: 56 | 57 | ```typescript 58 | import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"; 59 | 60 | import { BubblyContainer, BubblyLink } from "react-bubbly-transitions"; 61 | 62 | const Home = () =>
Home
; 63 | const About = () =>
About
; 64 | const Contact = () =>
Contact
; 65 | 66 | function App() { 67 | return ( 68 | 69 | 70 | 71 | 75 | Home 76 | About 77 | Contact 78 | 79 | 80 | } 81 | > 82 | } /> 83 | } /> 84 | } /> 85 | No Match} /> 86 | 87 | 88 | 89 | ); 90 | } 91 | 92 | export default App; 93 | ``` 94 | 95 | ### 4. Styling 96 | 97 | To style the BubblyLink component you can target it via css (just be more specific than me 😄): 98 | 99 | ```css 100 | body .react-bubbly-transitions__bubbly-link { 101 | color: #af44fd; 102 | } 103 | ``` 104 | 105 | To style the active state of the BubblyLink, just target the .active class and again be more specific. 106 | 107 | ```css 108 | body .react-bubbly-transitions__bubbly-link.active { 109 | text-decoration: underline; 110 | } 111 | ``` 112 | 113 | ### 5. DRY (Don't Repeat Yourself) 114 | 115 | To avoid repeating certain BubblyLink props, I recommend creating your own generic link component that sets the props here by default. 116 | 117 | ```typescript 118 | import { FC, ReactNode } from "react"; 119 | import { BubblyLink } from "react-bubbly-transitions"; 120 | 121 | type Props = { 122 | to: string; 123 | children: ReactNode; 124 | }; 125 | 126 | export const MyBubblyLink: FC = ({ to, children }) => ( 127 | 128 | {children} 129 | 130 | ); 131 | ``` 132 | 133 | ### 6. Have fun with it! 134 | 135 | Please hit me up on [My Instagram page](https://instagram.com/frontendjoe) for any support or suggestions 🙂 136 | -------------------------------------------------------------------------------- /src/library/README.md: -------------------------------------------------------------------------------- 1 | # react-bubbly-transitions 2 | 3 | Show bubbly transitions between route changes, in your React 18 apps. 4 | 5 | Click [here for a demo](https://bubbles.frontendjoe.com/). 6 | 7 | Or [check out the npm package here](https://www.npmjs.com/package/react-bubbly-transitions). 8 | 9 | ## Installation 10 | 11 | Just a few quick steps to get started: 12 | 13 | #### 1. Create a React app (optional) 14 | 15 | If you are adding the transitions to an existing app, you can skip this step. 16 | 17 | ```sh 18 | npx create-react-app my-bubbly-app 19 | cd my-bubbly-app 20 | ``` 21 | 22 | #### 2. Install dependencies 23 | 24 | Our project depends upon React's router library 25 | 26 | ```sh 27 | npm i react-bubbly-transitions react-router-dom 28 | ``` 29 | 30 | #### 3. Add components 31 | 32 | The package relies on two components being present. 33 | 34 | ##### BubblyContainer 35 | 36 | This is what houses our wave transition between route changes and does not require any props. 37 | 38 | ##### BubblyLink 39 | 40 | This button can be declared anywhere inside your Router component. 41 | 42 | It takes the following props: 43 | 44 | | Prop | Description | Example | type | required | default | 45 | | ---------- | -------------------------------------------------------------------------------------------- | ------- | ------------------ | -------- | ------- | 46 | | children | The content inside the link | About | String / Component | true | | 47 | | to | The route that the link will take you to | /about | String | true | | 48 | | colorStart | The background color of the bubble shape that appears first. Must be a hexcode or rgba value | #8f44fd | String | false | #8f44fd | 49 | | colorEnd | The background color of the bubble shape that appears last. Must be a hexcode or rgba value | #ffffff | String | false | #ffffff | 50 | 51 | Be careful with the duration (too fast/slow can ruin the effect) - my recommended duration is between 1000ms and 1600ms. 52 | 53 | ##### Example App.tsx 54 | 55 | Copy this whole code snippet into your App.tsx for a basic example: 56 | 57 | ```typescript 58 | import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom"; 59 | 60 | import { BubblyContainer, BubblyLink } from "react-bubbly-transitions"; 61 | 62 | const Home = () =>
Home
; 63 | const About = () =>
About
; 64 | const Contact = () =>
Contact
; 65 | 66 | function App() { 67 | return ( 68 | 69 | 70 | 71 | 75 | Home 76 | About 77 | Contact 78 | 79 | 80 | } 81 | > 82 | } /> 83 | } /> 84 | } /> 85 | No Match} /> 86 | 87 | 88 | 89 | ); 90 | } 91 | 92 | export default App; 93 | ``` 94 | 95 | ### 4. Styling 96 | 97 | To style the BubblyLink component you can target it via css (just be more specific than me 😄): 98 | 99 | ```css 100 | body .react-bubbly-transitions__bubbly-link { 101 | color: #af44fd; 102 | } 103 | ``` 104 | 105 | To style the active state of the BubblyLink, just target the .active class and again be more specific. 106 | 107 | ```css 108 | body .react-bubbly-transitions__bubbly-link.active { 109 | text-decoration: underline; 110 | } 111 | ``` 112 | 113 | ### 5. DRY (Don't Repeat Yourself) 114 | 115 | To avoid repeating certain BubblyLink props, I recommend creating your own generic link component that sets the props here by default. 116 | 117 | ```typescript 118 | import { FC, ReactNode } from "react"; 119 | import { BubblyLink } from "react-bubbly-transitions"; 120 | 121 | type Props = { 122 | to: string; 123 | children: ReactNode; 124 | }; 125 | 126 | export const MyBubblyLink: FC = ({ to, children }) => ( 127 | 128 | {children} 129 | 130 | ); 131 | ``` 132 | 133 | ### 6. Have fun with it! 134 | 135 | Please hit me up on [My Instagram page](https://instagram.com/frontendjoe) for any support or suggestions 🙂 136 | --------------------------------------------------------------------------------