├── .env ├── .gitignore ├── LICENSE.txt ├── README.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── public ├── favicon │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── manifest.json │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ └── ms-icon-70x70.png ├── houdini │ └── smooth-corners.js ├── icon-cycle.svg ├── icon-new.svg ├── index.html ├── og │ └── og.png └── robots.txt ├── src ├── App.tsx ├── components │ ├── ContentEditable.tsx │ └── PaperWallet.tsx ├── lib │ ├── random.js │ └── strings.ts ├── main.tsx ├── static │ ├── icon-cycle.svg │ ├── icon-edit-text.svg │ ├── icon-help.svg │ ├── icon-logo.png │ ├── icon-logo.svg │ ├── icon-new.svg │ └── icon-print.svg └── style │ └── index.css ├── tailwind.config.js └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_VERSION=$npm_package_version -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /dist 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Limerence Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello Wallet 2 | 3 | [Try it.](https://hello-wallet.vercel.app/) 4 | 5 | Most paper wallets look bad. But what if they looked good? Using the latest graphic design technology it is possible to make a better paper wallet. 6 | 7 | ## Development 8 | 9 | This is a `create-react-app` and Tailwind CSS codebase. Start with the following: 10 | 11 | ```bash 12 | pnpm install 13 | pnpm run start 14 | ``` 15 | 16 | ### To Do 17 | 18 | - [ ] Mobile 19 | - [ ] Printing modal with reminder to check `more settings` -> `background graphics` and set margins to `none`. 20 | - [ ] Paper Wallet info, pros + cons 21 | - [ ] Date format selection 22 | - [ ] Write-in mnemonic option 23 | - [ ] Add Eth Icon 24 | 25 | ### Future Ideas 26 | - [ ] Multisig, shards 27 | - [ ] Wallet with password 28 | - [ ] HD Wallet backup key + child key derivation UI 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello Wallet 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-wallet", 3 | "version": "0.2.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "ethers": "^5.7.2", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-scripts": "5.0.1", 13 | "web-vitals": "^2.1.4" 14 | }, 15 | "scripts": { 16 | "dev": "vite", 17 | "build": "vite build" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | }, 31 | "devDependencies": { 32 | "typescript": "^5.3.3", 33 | "vite": "^5.1.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'always', 3 | singleQuote: true, 4 | tabWidth: 2, 5 | semi: false, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /public/favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /public/favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /public/favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /public/favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/apple-icon.png -------------------------------------------------------------------------------- /public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/favicon.ico -------------------------------------------------------------------------------- /public/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /public/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/houdini/smooth-corners.js: -------------------------------------------------------------------------------- 1 | registerPaint('smooth-corners', class { 2 | static get inputProperties() { 3 | return [ 4 | '--border-radius', 5 | '--stroke-width', 6 | '--stroke-color' 7 | ] 8 | } 9 | 10 | paint(ctx, size, styleMap) { 11 | 12 | const lw = parseInt( 13 | styleMap.get('--stroke-width').toString() 14 | ) 15 | 16 | ctx.lineWidth = lw 17 | 18 | ctx.strokeStyle = styleMap.get('--stroke-color').toString() 19 | 20 | const r = parseInt( 21 | styleMap.get('--border-radius').toString() 22 | ) 23 | 24 | const w = size.width - lw 25 | const h = size.height - lw 26 | 27 | const x = 0 + lw / 2 28 | const y = 0 + lw / 2 29 | 30 | ctx.beginPath(); 31 | 32 | ctx.moveTo(x + r, y) 33 | ctx.lineTo(x + w - r, y) 34 | ctx.quadraticCurveTo( 35 | x + w, 36 | y, 37 | x + w, 38 | y + r 39 | ) 40 | ctx.lineTo( 41 | x + w, 42 | y + h - r 43 | ) 44 | ctx.quadraticCurveTo( 45 | x + w, 46 | y + h, 47 | x + w - r, 48 | y + h 49 | ) 50 | ctx.lineTo(x + r, y + h) 51 | ctx.quadraticCurveTo( 52 | x, 53 | y + h, 54 | x, 55 | y + h - r 56 | ) 57 | ctx.lineTo(x, y + r) 58 | ctx.quadraticCurveTo(x, y, x + r, y) 59 | ctx.closePath() 60 | 61 | ctx.stroke() 62 | } 63 | }) -------------------------------------------------------------------------------- /public/icon-cycle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icon-new.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello Wallet 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 52 | 53 | -------------------------------------------------------------------------------- /public/og/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/public/og/og.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo } from 'react' 2 | import * as Ethers from 'ethers' 3 | import { PaperWallet } from './components/PaperWallet' 4 | import { capitalizeFirstLetter } from './lib/strings' 5 | import { randomChoice } from './lib/random' 6 | import words from './lib/words.json' 7 | import pkg from '../package.json' 8 | 9 | const defaults = { 10 | title: '✏️ Hello Wallet', 11 | description: 12 | "✏️ Hello Wallet is a paper wallet you can edit in-browser. Save this wallet in a secure place only you control. Ready to print? Just press ⌘P.", 13 | } 14 | 15 | function generateWallet() { 16 | return Ethers.Wallet.createRandom() 17 | } 18 | 19 | function App() { 20 | const _wallet = useMemo(() => generateWallet(), []) 21 | 22 | const date = new Date().toLocaleDateString('en-US') 23 | const version = pkg.version 24 | const source = 'https://github.com/g-a-v-i-n/hello-wallet' 25 | 26 | const [wallet, setWallet] = useState(_wallet) 27 | 28 | return ( 29 |
30 |
31 | 32 |
33 | {/* Shadow */} 34 |
35 | {/* Start printable area */} 36 | 44 | 47 | 48 |
49 | 50 |
51 | 52 |
53 |

About

54 |

Paper wallets are an offline method to secure cryptocurrencies, involving printing private keys and addresses on paper. They offer high security against online threats but must be carefully stored to avoid loss or damage.

55 |

While highly secure for long-term storage, using the funds requires transferring them to a digital wallet, introducing potential online risks. At one point, they were fairly common but were gradually replaced by somewhat better options like hardware wallets.

56 | 57 | Source Code 58 | 59 |
60 |
61 |
62 | 63 | 64 | ) 65 | } 66 | 67 | export default App 68 | -------------------------------------------------------------------------------- /src/components/ContentEditable.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | type ContentEditableProps = { 4 | as: 'div' | 'span' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' 5 | className?: string 6 | iconClassName?: string 7 | initialValue?: string 8 | } 9 | 10 | export const ContentEditable = (props: ContentEditableProps) => { 11 | const Component = props.as 12 | 13 | const internalProps = { 14 | contentEditable: true, 15 | role:"textbox", 16 | autoComplete: 'off', 17 | autoCorrect: 'off', 18 | autoCapitalize: 'off', 19 | className: props.className, 20 | suppressContentEditableWarning: true, 21 | // Removes rich text styles from clipboard text 22 | onPaste: (e: React.ClipboardEvent) => { 23 | // Cancel paste 24 | e.preventDefault() 25 | // Get text representation of clipboard 26 | const text = e.clipboardData?.getData('text/plain') 27 | // Insert text manually. 28 | document.execCommand('insertHTML', false, text) 29 | }, 30 | 31 | } 32 | 33 | return ( 34 |
35 | {props.initialValue} 36 |
37 | ) 38 | } 39 | 40 | ContentEditable.defaultProps = { 41 | as: 'div', 42 | className: '', 43 | iconClassName: '', 44 | initialValue: '', 45 | } 46 | -------------------------------------------------------------------------------- /src/components/PaperWallet.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ContentEditable } from './ContentEditable' 3 | import { chunk } from '../lib/strings' 4 | 5 | export type PaperWalletProps = { 6 | address: string 7 | mnemonic: string 8 | title: string 9 | description: string 10 | date: string 11 | version?: string 12 | } 13 | 14 | export function PaperWallet(props: PaperWalletProps) { 15 | return ( 16 |
17 |
18 |
19 | 24 | 29 |
30 | 31 |
32 | 33 |

Address

34 |

35 | Send and receive using this sharable public address. 36 |

37 |
38 | 45 |
46 |

0x

47 | 48 | { 49 | /* @ts-ignore */ 50 | chunk(props.address.slice(2), 4).map((chunk, i) => { 51 | return ( 52 |

53 | {chunk} 54 |

55 | ) 56 | })} 57 |
58 |
59 |
60 | 61 |
62 | 63 |

Seed Phrase

64 |

65 | Never share these secret words with anyone. 66 |

67 |
68 | 75 |
76 | {props.mnemonic.split(' ').slice(0, 6).map((word, i) => { 77 | return ( 78 | 79 |
{i+1}
80 |

81 | {word} 82 |

83 |
84 | ) 85 | })} 86 |
87 |
88 | {props.mnemonic.split(' ').slice(6, 12).map((word, i) => { 89 | return ( 90 | 91 |
{i+7}
92 |

93 | {word} 94 |

95 |
96 | ) 97 | })} 98 |
99 |
100 |
101 | 102 |
103 | 104 |

Notes

105 |
106 |

107 | A place for notes after this wallet is printed. 108 |

109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | 125 |
126 |
127 | 128 | 129 | 130 |

Ethereum

131 |
132 |
133 |

Hello Wallet Version

{' '} 134 |
135 | {props.version} 136 |
137 |
138 |

Generated on {props.date}

139 |
140 |
141 |
142 | ) 143 | } 144 | 145 | PaperWallet.defaultProps = { 146 | address: '', 147 | mnemonic: '', 148 | emoji: '', 149 | title: '', 150 | description: '', 151 | date: '', 152 | version: '', 153 | } 154 | -------------------------------------------------------------------------------- /src/lib/random.js: -------------------------------------------------------------------------------- 1 | const randomInt = (min, max) => { 2 | min = Math.ceil(min) 3 | max = Math.floor(max) 4 | return Math.floor(Math.random() * (max - min + 1)) + min 5 | } 6 | 7 | const randomChoice = (array) => { 8 | return array[randomInt(0, array.length - 1)] 9 | } 10 | export { randomInt, randomChoice } 11 | -------------------------------------------------------------------------------- /src/lib/strings.ts: -------------------------------------------------------------------------------- 1 | export function capitalizeFirstLetter(str: string) { 2 | return str.charAt(0).toUpperCase() + str.slice(1) 3 | } 4 | 5 | export function chunk(str: string, size: number) { 6 | return str.match(new RegExp('.{1,' + size + '}', 'g')) 7 | } 8 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import "./style/index.css"; 2 | 3 | import React from "react"; 4 | import ReactDOM from "react-dom/client"; 5 | import App from "./App"; 6 | 7 | ReactDOM.createRoot(document.getElementById("root")!).render( 8 | 9 | 10 | , 11 | ); 12 | -------------------------------------------------------------------------------- /src/static/icon-cycle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icon-edit-text.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/static/icon-help.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icon-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-a-v-i-n/hello-wallet/6010ed00d1f5681fd014b1a015d65772b2d8780c/src/static/icon-logo.png -------------------------------------------------------------------------------- /src/static/icon-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/static/icon-new.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/static/icon-print.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/style/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --shadow-color: 0deg 0% 74%; 7 | --shadow-elevation-low: 8 | 0.5px 0.5px 0.7px hsl(var(--shadow-color) / 0.34), 9 | 0.7px 0.7px 0.9px -1.9px hsl(var(--shadow-color) / 0.26), 10 | 1.7px 1.6px 2.2px -3.7px hsl(var(--shadow-color) / 0.19); 11 | --shadow-elevation-medium: 12 | 0.5px 0.5px 0.7px hsl(var(--shadow-color) / 0.28), 13 | 0.9px 0.9px 1.2px -0.9px hsl(var(--shadow-color) / 0.24), 14 | 1.9px 1.8px 2.5px -1.9px hsl(var(--shadow-color) / 0.21), 15 | 4.1px 4px 5.4px -2.8px hsl(var(--shadow-color) / 0.17), 16 | 8.4px 8.2px 11px -3.7px hsl(var(--shadow-color) / 0.13); 17 | --shadow-elevation-high: 18 | 0.5px 0.5px 0.7px hsl(var(--shadow-color) / 0.29), 19 | 1.2px 1.2px 1.6px -0.5px hsl(var(--shadow-color) / 0.27), 20 | 2.2px 2.1px 2.9px -0.9px hsl(var(--shadow-color) / 0.25), 21 | 3.7px 3.6px 4.8px -1.4px hsl(var(--shadow-color) / 0.23), 22 | 6.2px 6px 8.1px -1.9px hsl(var(--shadow-color) / 0.2), 23 | 10px 9.8px 13.1px -2.3px hsl(var(--shadow-color) / 0.18), 24 | 15.6px 15.2px 20.4px -2.8px hsl(var(--shadow-color) / 0.16), 25 | 23.3px 22.7px 30.5px -3.3px hsl(var(--shadow-color) / 0.14), 26 | 33.5px 32.7px 43.9px -3.7px hsl(var(--shadow-color) / 0.12); 27 | } 28 | 29 | @media print { 30 | .page-margin { 31 | margin: 0 !important; 32 | padding: 0 !important; 33 | } 34 | 35 | .no-print { 36 | display: none !important; 37 | height: 0 !important; 38 | padding: 0 !important; 39 | margin: 0 !important; 40 | } 41 | 42 | html, 43 | body { 44 | background-color: #fff !important; 45 | } 46 | } 47 | 48 | html, 49 | body { 50 | font-family: 'Inter', 'SF Pro Text', system-ui, sans-serif; 51 | @apply bg-[#FAFAFA]; 52 | } 53 | 54 | a { 55 | text-decoration: underline; 56 | } 57 | 58 | .page-margin { 59 | margin-top: 8em; 60 | margin-bottom: 8em; 61 | } 62 | 63 | .page-size { 64 | /* */ 65 | height: 11in; 66 | width: 8.6in; 67 | } 68 | 69 | .page-width { 70 | width: 8.5in; 71 | } 72 | 73 | .origin-top-center { 74 | transform-origin: top center; 75 | } 76 | 77 | .button-shadow { 78 | 79 | 80 | } 81 | 82 | button, 83 | .button { 84 | content: ''; 85 | font-weight: 500; 86 | outline: 1px solid rgba(0,0,0,0.04); 87 | @apply flex shadow-elevation-medium items-center hover:scale-105 active:scale-100 transition-transform rounded-full; 88 | } 89 | 90 | .shadow-elevation-low { 91 | box-shadow: var(--shadow-elevation-low); 92 | } 93 | 94 | .shadow-elevation-medium { 95 | box-shadow: var(--shadow-elevation-medium); 96 | } 97 | 98 | .shadow-elevation-high { 99 | box-shadow: var(--shadow-elevation-high); 100 | } 101 | 102 | 103 | *[contenteditable='true'] { 104 | @apply relative border-transparent transition-colors -m-2 p-2 outline-none rounded-xl; 105 | } 106 | 107 | *[contenteditable='true']:focus { 108 | @apply border-transparent bg-[#EEF7FF]; 109 | } 110 | 111 | *[contenteditable='true']::selection { 112 | @apply bg-gray-300; 113 | } 114 | 115 | *[contenteditable='true']::after { 116 | @apply absolute -left-8; 117 | } 118 | 119 | 120 | h1[contenteditable='true']::after { 121 | top: 14px; 122 | } 123 | 124 | p[contenteditable='true']::after { 125 | top: -1px; 126 | } 127 | 128 | p::selection, a::selection, h3::selection { 129 | color: #008cff !important; 130 | background-color: #CFE5FF !important; 131 | } 132 | /* 133 | .vr { 134 | content: ''; 135 | border-color: black; 136 | border-style: solid; 137 | border-width: 1px; 138 | border-left: none; 139 | border-top: none; 140 | border-bottom: none; 141 | overflow-y: auto; 142 | display: flex; 143 | align-items: stretch; 144 | flex: 1; 145 | } 146 | 147 | .viewfinder { 148 | padding: 0.75em; 149 | position: relative; 150 | } 151 | 152 | .viewfinder:before, 153 | .viewfinder:after, 154 | .viewfinder > :first-child:before, 155 | .viewfinder > :first-child:after { 156 | position: absolute; 157 | content: ' '; 158 | width: 16px; 159 | height: 16px; 160 | border-color: black; 161 | border-style: solid; 162 | } 163 | 164 | .viewfinder:before { 165 | top: 0; 166 | left: 0; 167 | border-width: 1px 0 0 1px; 168 | border-radius: 10px 0 0 0; 169 | } 170 | .viewfinder:after { 171 | top: 0; 172 | right: 0; 173 | border-width: 1px 1px 0 0; 174 | border-radius: 0 10px 0 0; 175 | } 176 | .viewfinder > :first-child:before { 177 | bottom: 0; 178 | right: 0; 179 | border-width: 0 1px 1px 0; 180 | border-radius: 0 0 10px 0; 181 | } 182 | .viewfinder > :first-child:after { 183 | bottom: 0; 184 | left: 0; 185 | border-width: 0 0 1px 1px; 186 | border-radius: 0 0 0 10px; 187 | } 188 | 189 | @supports (mask-image: paint(smooth-corners)) { 190 | .superellipse-sm { 191 | border-radius: 0; 192 | stroke-width: 0; 193 | stroke: none; 194 | --border-radius: 8; 195 | --stroke-width: 1; 196 | --stroke-color: black; 197 | background-image: paint(smooth-corners); 198 | } 199 | 200 | .superellipse { 201 | border-radius: 0; 202 | stroke-width: 0; 203 | stroke: none; 204 | --border-radius: 14; 205 | --stroke-width: 1; 206 | --stroke-color: black; 207 | background-image: paint(smooth-corners); 208 | } 209 | 210 | .superellipse-lg { 211 | border-radius: 0; 212 | stroke-width: 0; 213 | stroke: none; 214 | --border-radius: 20; 215 | --stroke-width: 1; 216 | --stroke-color: black; 217 | background-image: paint(smooth-corners); 218 | } 219 | } 220 | 221 | .intro-transform { 222 | transform: translateY(0); 223 | } */ 224 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./src/**/*.{js,jsx,ts,tsx}", 4 | ], 5 | theme: { 6 | extend: { 7 | screens: { 8 | 'no-print': { 'raw': '(min-height: 800px)' }, 9 | }, 10 | }, 11 | }, 12 | plugins: [], 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | --------------------------------------------------------------------------------