├── bun.lockb
├── static
├── css
│ ├── input.css
│ └── output.css
└── js
│ └── htpl.js
├── tailwind.config.js
├── package.json
├── README.md
├── jsconfig.json
├── index.jsx
└── .gitignore
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Phillip-England/receipt-tracker/main/bun.lockb
--------------------------------------------------------------------------------
/static/css/input.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./src/**.js",
5 | "./src/**.jsx",
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "receipts",
3 | "version": "0.0.1",
4 | "author": "Phillip England",
5 | "module": "index.jsx",
6 | "devDependencies": {
7 | "@types/bun": "latest"
8 | },
9 | "peerDependencies": {
10 | "typescript": "^5.0.0"
11 | },
12 | "description": "tiny http server for bun",
13 | "scripts": {
14 | "dev": "bun --hot run index.jsx",
15 | "tw": "tailwindcss -i './static/css/input.css' -o './static/css/output.css' --watch"
16 | },
17 | "type": "module",
18 | "dependencies": {
19 | "server": "github:react-dom/server",
20 | "xerus": "^0.0.18"
21 | }
22 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # receipts
2 |
3 | ## Env Vars
4 |
5 | ```bash
6 | USERNAME=username
7 | PASSWORD=password
8 | URL_SECRET_TOKEN=some_token
9 | ```
10 |
11 | ## Todo
12 |
13 | 1. Update HTPL to enable whitelisting of approved file types for uploads
14 |
15 | 2. Update Xerus to better handle network timeouts, especially in regards to trying to access a null value in XerusContext which triggers a timeout when one has not occured.
16 |
17 | 3. Update HTPL to enable _active-link to allow a "flex" option where hrefs do not have to match exactly, but can be close enough. For example, if _active-links href has parameters within it, it may not indicate as active due to not exactly matching. So enable to ability for some flexibility here.
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | // Enable latest features
4 | "lib": ["ESNext", "DOM"],
5 | "target": "ESNext",
6 | "module": "ESNext",
7 | "moduleDetection": "force",
8 | "jsx": "react-jsx",
9 | "allowJs": true,
10 |
11 | // Bundler mode
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": true,
15 | "noEmit": true,
16 |
17 | // Best practices
18 | "strict": true,
19 | "skipLibCheck": true,
20 | "noFallthroughCasesInSwitch": true,
21 |
22 | // Some stricter flags (disabled by default)
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "noPropertyAccessFromIndexSignature": false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/index.jsx:
--------------------------------------------------------------------------------
1 | import { Xerus, logger, timeout } from "xerus";
2 |
3 | const app = new Xerus();
4 |
5 | app.use("*", logger, timeout);
6 |
7 | function Layout(props) {
8 | return (
9 |
10 |
11 |
12 | {props.title}
13 |
14 | {props.children}
15 |
16 | );
17 | }
18 |
19 | function LoginForm(props) {
20 | return (
21 |
28 | );
29 | }
30 |
31 | function Nav(props) {
32 | return (
33 | ,
41 | );
42 | }
43 |
44 | app.get("/", async (c) => {
45 | c.jsx(
46 |
47 |
48 | ,
49 | );
50 | });
51 |
52 | app.post("/form/login", async (c) => {
53 | let data = await c.form();
54 | let username = data.get("username");
55 | let password = data.get("password");
56 | if (username == Bun.env.USERNAME && password == Bun.env.PASSWORD) {
57 | c.redirect("/app");
58 | return;
59 | }
60 | c.redirect("/?loginErr=invalid credentials");
61 | });
62 |
63 | app.get("/logout", async (c) => {
64 | c.redirect("/");
65 | });
66 |
67 | app.get("/app", async (c) => {
68 | c.jsx(
69 |
70 |
71 | ,
72 | );
73 | });
74 |
75 | await app.run(8080);
76 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
2 |
3 | *.db
4 | *.sqlite
5 | *.sqlite-wal
6 | *.sqlite-shm
7 |
8 | # Logs
9 |
10 | logs
11 | _.log
12 | npm-debug.log_
13 | yarn-debug.log*
14 | yarn-error.log*
15 | lerna-debug.log*
16 | .pnpm-debug.log*
17 |
18 | # Caches
19 |
20 | .cache
21 |
22 | # Diagnostic reports (https://nodejs.org/api/report.html)
23 |
24 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
25 |
26 | # Runtime data
27 |
28 | pids
29 | _.pid
30 | _.seed
31 | *.pid.lock
32 |
33 | # Directory for instrumented libs generated by jscoverage/JSCover
34 |
35 | lib-cov
36 |
37 | # Coverage directory used by tools like istanbul
38 |
39 | coverage
40 | *.lcov
41 |
42 | # nyc test coverage
43 |
44 | .nyc_output
45 |
46 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
47 |
48 | .grunt
49 |
50 | # Bower dependency directory (https://bower.io/)
51 |
52 | bower_components
53 |
54 | # node-waf configuration
55 |
56 | .lock-wscript
57 |
58 | # Compiled binary addons (https://nodejs.org/api/addons.html)
59 |
60 | build/Release
61 |
62 | # Dependency directories
63 |
64 | node_modules/
65 | jspm_packages/
66 |
67 | # Snowpack dependency directory (https://snowpack.dev/)
68 |
69 | web_modules/
70 |
71 | # TypeScript cache
72 |
73 | *.tsbuildinfo
74 |
75 | # Optional npm cache directory
76 |
77 | .npm
78 |
79 | # Optional eslint cache
80 |
81 | .eslintcache
82 |
83 | # Optional stylelint cache
84 |
85 | .stylelintcache
86 |
87 | # Microbundle cache
88 |
89 | .rpt2_cache/
90 | .rts2_cache_cjs/
91 | .rts2_cache_es/
92 | .rts2_cache_umd/
93 |
94 | # Optional REPL history
95 |
96 | .node_repl_history
97 |
98 | # Output of 'npm pack'
99 |
100 | *.tgz
101 |
102 | # Yarn Integrity file
103 |
104 | .yarn-integrity
105 |
106 | # dotenv environment variable files
107 |
108 | .env
109 | .env.development.local
110 | .env.test.local
111 | .env.production.local
112 | .env.local
113 |
114 | # parcel-bundler cache (https://parceljs.org/)
115 |
116 | .parcel-cache
117 |
118 | # Next.js build output
119 |
120 | .next
121 | out
122 |
123 | # Nuxt.js build / generate output
124 |
125 | .nuxt
126 | dist
127 |
128 | # Gatsby files
129 |
130 | # Comment in the public line in if your project uses Gatsby and not Next.js
131 |
132 | # https://nextjs.org/blog/next-9-1#public-directory-support
133 |
134 | # public
135 |
136 | # vuepress build output
137 |
138 | .vuepress/dist
139 |
140 | # vuepress v2.x temp and cache directory
141 |
142 | .temp
143 |
144 | # Docusaurus cache and generated files
145 |
146 | .docusaurus
147 |
148 | # Serverless directories
149 |
150 | .serverless/
151 |
152 | # FuseBox cache
153 |
154 | .fusebox/
155 |
156 | # DynamoDB Local files
157 |
158 | .dynamodb/
159 |
160 | # TernJS port file
161 |
162 | .tern-port
163 |
164 | # Stores VSCode versions used for testing VSCode extensions
165 |
166 | .vscode-test
167 |
168 | # yarn v2
169 |
170 | .yarn/cache
171 | .yarn/unplugged
172 | .yarn/build-state.yml
173 | .yarn/install-state.gz
174 | .pnp.*
175 |
176 | # IntelliJ based IDEs
177 | .idea
178 |
179 | # Finder (MacOS) folder config
180 | .DS_Store
181 |
--------------------------------------------------------------------------------
/static/js/htpl.js:
--------------------------------------------------------------------------------
1 | class Htpl {
2 |
3 | constructor() {
4 | this.attributes = {}
5 | }
6 |
7 |
8 | add(attribute, fn) {
9 | this.attributes[attribute] = fn
10 | }
11 |
12 |
13 | hook() {
14 | for (let i = 0; i < Object.keys(this.attributes).length; i++) {
15 | let key = Object.keys(this.attributes)[i]
16 | let allElements = document.querySelectorAll('*')
17 | for (let i2 = 0; i2 < allElements.length; i2++) {
18 | let element = allElements[i2]
19 | let attributes = element.attributes
20 | for (const attrKey in attributes) {
21 | let attr = attributes[attrKey].name
22 | if (typeof(attr) == 'string') {
23 | if (attr.startsWith(key)) {
24 | this.attributes[key](element, element.getAttribute(attr))
25 | }
26 | }
27 | }
28 | }
29 | }
30 | }
31 | }
32 |
33 | const htpl = new Htpl()
34 |
35 | htpl.add("_click-proxy", (element, attr) => {
36 | let target = document.querySelector(attr)
37 | element.addEventListener('click', () => {
38 | target.click()
39 | })
40 | })
41 |
42 | htpl.add('_mass-toggle', (element, attr) => {
43 | let parts = attr.split(":")
44 | let eventType = parts[0]
45 | let targets = parts[1].split(' ')
46 | let classes = parts[2].split(' ')
47 | element.addEventListener(eventType, () => {
48 | for (let i = 0; i < targets.length; i++) {
49 | let target = document.querySelector(targets[i])
50 | for (let i2 = 0; i2 < classes.length; i2++) {
51 | let cls = classes[i2]
52 | target.classList.toggle(cls)
53 | }
54 | }
55 | })
56 | })
57 |
58 | htpl.add('_multi-photo-form', (element, attr) => {
59 |
60 | // extracting attr data
61 | let parts = attr.split(':')
62 | let divClasses = parts[4]
63 | let fileInput = document.querySelector(parts[0])
64 | let photoContainer = document.querySelector(parts[1])
65 | let imgWidth = parts[3]
66 | let undoIcon = document.querySelector(parts[2])
67 |
68 | // classes are optional, all other inputs are not
69 | if (divClasses) {
70 | divClasses = divClasses.split(' ')
71 | }
72 |
73 | // used to update the fileInputs files by extracting from elements in photo container
74 | function updateFiles(fileInput, photoContainer) {
75 | let updatedFiles = new DataTransfer()
76 | let allDivs = photoContainer.querySelectorAll('div')
77 | for (let i = 0; i < allDivs.length; i++) {
78 | let currentDiv = allDivs[i]
79 | updatedFiles.items.add(currentDiv.file)
80 | }
81 | fileInput.files = updatedFiles.files
82 | }
83 |
84 | // event handler for removing a file
85 | undoIcon.addEventListener('click', () => {
86 | let photoContainerElements = photoContainer.children
87 | if (photoContainerElements.length == 0) {
88 | return
89 | }
90 | photoContainerElements[photoContainerElements.length-1].remove()
91 | updateFiles(fileInput, photoContainer)
92 | })
93 |
94 | // event handler for uploading a new file
95 | fileInput.addEventListener('change', (e) => {
96 | e.preventDefault()
97 | for (let i = 0; i < fileInput.files.length; i++) {
98 | let reader = new FileReader()
99 | let file = fileInput.files[i]
100 | reader.onload = function(event) {
101 |
102 | let img = new Image()
103 | img.onload = function() {
104 |
105 | // getting our elements and context
106 | let div = document.createElement('div')
107 | let canvas = document.createElement('canvas')
108 | let ctx = canvas.getContext('2d')
109 |
110 | // adding classes to the div
111 | for (let i = 0; i < divClasses.length; i++) { div.classList.add(divClasses[i]) }
112 |
113 | // sizing up our image and canvas
114 | let imgHeight = (img.height / img.width) * imgWidth
115 | canvas.height = imgHeight
116 | canvas.width = imgWidth
117 |
118 | // drawing on our canvas and inserting in the DOM
119 | ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
120 | div.appendChild(canvas)
121 | photoContainer.appendChild(div)
122 | div.file = file
123 |
124 | // updating files
125 | updateFiles(fileInput, photoContainer)
126 |
127 |
128 | }
129 | img.src = event.target.result
130 | }
131 | reader.readAsDataURL(file)
132 | }
133 | })
134 |
135 | })
136 |
137 |
138 | htpl.add('_form-file-limit', (element, attr) => {
139 |
140 | let parts = attr.split(':');
141 | let input = document.querySelector(parts[0]);
142 | let err = document.querySelector(parts[1]);
143 | let maxSize = parseFloat(parts[2])
144 |
145 | element.addEventListener('submit', (e) => {
146 | let files = input.files;
147 | let size = 0;
148 | for (let i = 0; i < files.length; i++) {
149 | let file = files[i];
150 | size += file.size;
151 | }
152 | let sizeInMB = size / (1024 * 1024);
153 | if (sizeInMB > maxSize) {
154 | e.preventDefault();
155 | err.textContent = `exceeded max upload size of ${maxSize} MB`;
156 | } else {
157 | err.textContent = '';
158 | }
159 | });
160 | });
161 |
162 | htpl.add('_match-height', (element, attr) => {
163 | let targetElement = document.querySelector(attr)
164 | element.style.height = targetElement.offsetHeight
165 | })
166 |
167 |
168 | htpl.add('_height-diff', (element, attr) => {
169 | let parts = attr.split(':')
170 | let e1 = document.querySelector(parts[0])
171 | let e2 = document.querySelector(parts[1])
172 | let newHeight = e1.offsetHeight - e2.offsetHeight
173 | console.log(newHeight)
174 | element.style.height = newHeight
175 | })
176 |
177 | htpl.add('_active-link', (element, attr) => {
178 | if (element.getAttribute('href') == window.location.pathname) {
179 | let parts = attr.split(' ')
180 | for (let i = 0; i < parts.length; i++) {
181 | let className = parts[i]
182 | element.classList.add(className)
183 | }
184 | }
185 | })
186 |
187 | window.addEventListener('DOMContentLoaded', () => {
188 | htpl.hook()
189 | })
--------------------------------------------------------------------------------
/static/css/output.css:
--------------------------------------------------------------------------------
1 | /*
2 | ! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com
3 | */
4 |
5 | /*
6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
8 | */
9 |
10 | *,
11 | ::before,
12 | ::after {
13 | box-sizing: border-box;
14 | /* 1 */
15 | border-width: 0;
16 | /* 2 */
17 | border-style: solid;
18 | /* 2 */
19 | border-color: #e5e7eb;
20 | /* 2 */
21 | }
22 |
23 | ::before,
24 | ::after {
25 | --tw-content: '';
26 | }
27 |
28 | /*
29 | 1. Use a consistent sensible line-height in all browsers.
30 | 2. Prevent adjustments of font size after orientation changes in iOS.
31 | 3. Use a more readable tab size.
32 | 4. Use the user's configured `sans` font-family by default.
33 | 5. Use the user's configured `sans` font-feature-settings by default.
34 | 6. Use the user's configured `sans` font-variation-settings by default.
35 | */
36 |
37 | html {
38 | line-height: 1.5;
39 | /* 1 */
40 | -webkit-text-size-adjust: 100%;
41 | /* 2 */
42 | -moz-tab-size: 4;
43 | /* 3 */
44 | -o-tab-size: 4;
45 | tab-size: 4;
46 | /* 3 */
47 | font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
48 | /* 4 */
49 | font-feature-settings: normal;
50 | /* 5 */
51 | font-variation-settings: normal;
52 | /* 6 */
53 | }
54 |
55 | /*
56 | 1. Remove the margin in all browsers.
57 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
58 | */
59 |
60 | body {
61 | margin: 0;
62 | /* 1 */
63 | line-height: inherit;
64 | /* 2 */
65 | }
66 |
67 | /*
68 | 1. Add the correct height in Firefox.
69 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
70 | 3. Ensure horizontal rules are visible by default.
71 | */
72 |
73 | hr {
74 | height: 0;
75 | /* 1 */
76 | color: inherit;
77 | /* 2 */
78 | border-top-width: 1px;
79 | /* 3 */
80 | }
81 |
82 | /*
83 | Add the correct text decoration in Chrome, Edge, and Safari.
84 | */
85 |
86 | abbr:where([title]) {
87 | -webkit-text-decoration: underline dotted;
88 | text-decoration: underline dotted;
89 | }
90 |
91 | /*
92 | Remove the default font size and weight for headings.
93 | */
94 |
95 | h1,
96 | h2,
97 | h3,
98 | h4,
99 | h5,
100 | h6 {
101 | font-size: inherit;
102 | font-weight: inherit;
103 | }
104 |
105 | /*
106 | Reset links to optimize for opt-in styling instead of opt-out.
107 | */
108 |
109 | a {
110 | color: inherit;
111 | text-decoration: inherit;
112 | }
113 |
114 | /*
115 | Add the correct font weight in Edge and Safari.
116 | */
117 |
118 | b,
119 | strong {
120 | font-weight: bolder;
121 | }
122 |
123 | /*
124 | 1. Use the user's configured `mono` font family by default.
125 | 2. Correct the odd `em` font sizing in all browsers.
126 | */
127 |
128 | code,
129 | kbd,
130 | samp,
131 | pre {
132 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
133 | /* 1 */
134 | font-size: 1em;
135 | /* 2 */
136 | }
137 |
138 | /*
139 | Add the correct font size in all browsers.
140 | */
141 |
142 | small {
143 | font-size: 80%;
144 | }
145 |
146 | /*
147 | Prevent `sub` and `sup` elements from affecting the line height in all browsers.
148 | */
149 |
150 | sub,
151 | sup {
152 | font-size: 75%;
153 | line-height: 0;
154 | position: relative;
155 | vertical-align: baseline;
156 | }
157 |
158 | sub {
159 | bottom: -0.25em;
160 | }
161 |
162 | sup {
163 | top: -0.5em;
164 | }
165 |
166 | /*
167 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
168 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
169 | 3. Remove gaps between table borders by default.
170 | */
171 |
172 | table {
173 | text-indent: 0;
174 | /* 1 */
175 | border-color: inherit;
176 | /* 2 */
177 | border-collapse: collapse;
178 | /* 3 */
179 | }
180 |
181 | /*
182 | 1. Change the font styles in all browsers.
183 | 2. Remove the margin in Firefox and Safari.
184 | 3. Remove default padding in all browsers.
185 | */
186 |
187 | button,
188 | input,
189 | optgroup,
190 | select,
191 | textarea {
192 | font-family: inherit;
193 | /* 1 */
194 | font-feature-settings: inherit;
195 | /* 1 */
196 | font-variation-settings: inherit;
197 | /* 1 */
198 | font-size: 100%;
199 | /* 1 */
200 | font-weight: inherit;
201 | /* 1 */
202 | line-height: inherit;
203 | /* 1 */
204 | color: inherit;
205 | /* 1 */
206 | margin: 0;
207 | /* 2 */
208 | padding: 0;
209 | /* 3 */
210 | }
211 |
212 | /*
213 | Remove the inheritance of text transform in Edge and Firefox.
214 | */
215 |
216 | button,
217 | select {
218 | text-transform: none;
219 | }
220 |
221 | /*
222 | 1. Correct the inability to style clickable types in iOS and Safari.
223 | 2. Remove default button styles.
224 | */
225 |
226 | button,
227 | [type='button'],
228 | [type='reset'],
229 | [type='submit'] {
230 | -webkit-appearance: button;
231 | /* 1 */
232 | background-color: transparent;
233 | /* 2 */
234 | background-image: none;
235 | /* 2 */
236 | }
237 |
238 | /*
239 | Use the modern Firefox focus style for all focusable elements.
240 | */
241 |
242 | :-moz-focusring {
243 | outline: auto;
244 | }
245 |
246 | /*
247 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
248 | */
249 |
250 | :-moz-ui-invalid {
251 | box-shadow: none;
252 | }
253 |
254 | /*
255 | Add the correct vertical alignment in Chrome and Firefox.
256 | */
257 |
258 | progress {
259 | vertical-align: baseline;
260 | }
261 |
262 | /*
263 | Correct the cursor style of increment and decrement buttons in Safari.
264 | */
265 |
266 | ::-webkit-inner-spin-button,
267 | ::-webkit-outer-spin-button {
268 | height: auto;
269 | }
270 |
271 | /*
272 | 1. Correct the odd appearance in Chrome and Safari.
273 | 2. Correct the outline style in Safari.
274 | */
275 |
276 | [type='search'] {
277 | -webkit-appearance: textfield;
278 | /* 1 */
279 | outline-offset: -2px;
280 | /* 2 */
281 | }
282 |
283 | /*
284 | Remove the inner padding in Chrome and Safari on macOS.
285 | */
286 |
287 | ::-webkit-search-decoration {
288 | -webkit-appearance: none;
289 | }
290 |
291 | /*
292 | 1. Correct the inability to style clickable types in iOS and Safari.
293 | 2. Change font properties to `inherit` in Safari.
294 | */
295 |
296 | ::-webkit-file-upload-button {
297 | -webkit-appearance: button;
298 | /* 1 */
299 | font: inherit;
300 | /* 2 */
301 | }
302 |
303 | /*
304 | Add the correct display in Chrome and Safari.
305 | */
306 |
307 | summary {
308 | display: list-item;
309 | }
310 |
311 | /*
312 | Removes the default spacing and border for appropriate elements.
313 | */
314 |
315 | blockquote,
316 | dl,
317 | dd,
318 | h1,
319 | h2,
320 | h3,
321 | h4,
322 | h5,
323 | h6,
324 | hr,
325 | figure,
326 | p,
327 | pre {
328 | margin: 0;
329 | }
330 |
331 | fieldset {
332 | margin: 0;
333 | padding: 0;
334 | }
335 |
336 | legend {
337 | padding: 0;
338 | }
339 |
340 | ol,
341 | ul,
342 | menu {
343 | list-style: none;
344 | margin: 0;
345 | padding: 0;
346 | }
347 |
348 | /*
349 | Reset default styling for dialogs.
350 | */
351 |
352 | dialog {
353 | padding: 0;
354 | }
355 |
356 | /*
357 | Prevent resizing textareas horizontally by default.
358 | */
359 |
360 | textarea {
361 | resize: vertical;
362 | }
363 |
364 | /*
365 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
366 | 2. Set the default placeholder color to the user's configured gray 400 color.
367 | */
368 |
369 | input::-moz-placeholder, textarea::-moz-placeholder {
370 | opacity: 1;
371 | /* 1 */
372 | color: #9ca3af;
373 | /* 2 */
374 | }
375 |
376 | input::placeholder,
377 | textarea::placeholder {
378 | opacity: 1;
379 | /* 1 */
380 | color: #9ca3af;
381 | /* 2 */
382 | }
383 |
384 | /*
385 | Set the default cursor for buttons.
386 | */
387 |
388 | button,
389 | [role="button"] {
390 | cursor: pointer;
391 | }
392 |
393 | /*
394 | Make sure disabled buttons don't get the pointer cursor.
395 | */
396 |
397 | :disabled {
398 | cursor: default;
399 | }
400 |
401 | /*
402 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
403 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
404 | This can trigger a poorly considered lint error in some tools but is included by design.
405 | */
406 |
407 | img,
408 | svg,
409 | video,
410 | canvas,
411 | audio,
412 | iframe,
413 | embed,
414 | object {
415 | display: block;
416 | /* 1 */
417 | vertical-align: middle;
418 | /* 2 */
419 | }
420 |
421 | /*
422 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
423 | */
424 |
425 | img,
426 | video {
427 | max-width: 100%;
428 | height: auto;
429 | }
430 |
431 | /* Make elements with the HTML hidden attribute stay hidden by default */
432 |
433 | [hidden] {
434 | display: none;
435 | }
436 |
437 | *, ::before, ::after {
438 | --tw-border-spacing-x: 0;
439 | --tw-border-spacing-y: 0;
440 | --tw-translate-x: 0;
441 | --tw-translate-y: 0;
442 | --tw-rotate: 0;
443 | --tw-skew-x: 0;
444 | --tw-skew-y: 0;
445 | --tw-scale-x: 1;
446 | --tw-scale-y: 1;
447 | --tw-pan-x: ;
448 | --tw-pan-y: ;
449 | --tw-pinch-zoom: ;
450 | --tw-scroll-snap-strictness: proximity;
451 | --tw-gradient-from-position: ;
452 | --tw-gradient-via-position: ;
453 | --tw-gradient-to-position: ;
454 | --tw-ordinal: ;
455 | --tw-slashed-zero: ;
456 | --tw-numeric-figure: ;
457 | --tw-numeric-spacing: ;
458 | --tw-numeric-fraction: ;
459 | --tw-ring-inset: ;
460 | --tw-ring-offset-width: 0px;
461 | --tw-ring-offset-color: #fff;
462 | --tw-ring-color: rgb(59 130 246 / 0.5);
463 | --tw-ring-offset-shadow: 0 0 #0000;
464 | --tw-ring-shadow: 0 0 #0000;
465 | --tw-shadow: 0 0 #0000;
466 | --tw-shadow-colored: 0 0 #0000;
467 | --tw-blur: ;
468 | --tw-brightness: ;
469 | --tw-contrast: ;
470 | --tw-grayscale: ;
471 | --tw-hue-rotate: ;
472 | --tw-invert: ;
473 | --tw-saturate: ;
474 | --tw-sepia: ;
475 | --tw-drop-shadow: ;
476 | --tw-backdrop-blur: ;
477 | --tw-backdrop-brightness: ;
478 | --tw-backdrop-contrast: ;
479 | --tw-backdrop-grayscale: ;
480 | --tw-backdrop-hue-rotate: ;
481 | --tw-backdrop-invert: ;
482 | --tw-backdrop-opacity: ;
483 | --tw-backdrop-saturate: ;
484 | --tw-backdrop-sepia: ;
485 | }
486 |
487 | ::backdrop {
488 | --tw-border-spacing-x: 0;
489 | --tw-border-spacing-y: 0;
490 | --tw-translate-x: 0;
491 | --tw-translate-y: 0;
492 | --tw-rotate: 0;
493 | --tw-skew-x: 0;
494 | --tw-skew-y: 0;
495 | --tw-scale-x: 1;
496 | --tw-scale-y: 1;
497 | --tw-pan-x: ;
498 | --tw-pan-y: ;
499 | --tw-pinch-zoom: ;
500 | --tw-scroll-snap-strictness: proximity;
501 | --tw-gradient-from-position: ;
502 | --tw-gradient-via-position: ;
503 | --tw-gradient-to-position: ;
504 | --tw-ordinal: ;
505 | --tw-slashed-zero: ;
506 | --tw-numeric-figure: ;
507 | --tw-numeric-spacing: ;
508 | --tw-numeric-fraction: ;
509 | --tw-ring-inset: ;
510 | --tw-ring-offset-width: 0px;
511 | --tw-ring-offset-color: #fff;
512 | --tw-ring-color: rgb(59 130 246 / 0.5);
513 | --tw-ring-offset-shadow: 0 0 #0000;
514 | --tw-ring-shadow: 0 0 #0000;
515 | --tw-shadow: 0 0 #0000;
516 | --tw-shadow-colored: 0 0 #0000;
517 | --tw-blur: ;
518 | --tw-brightness: ;
519 | --tw-contrast: ;
520 | --tw-grayscale: ;
521 | --tw-hue-rotate: ;
522 | --tw-invert: ;
523 | --tw-saturate: ;
524 | --tw-sepia: ;
525 | --tw-drop-shadow: ;
526 | --tw-backdrop-blur: ;
527 | --tw-backdrop-brightness: ;
528 | --tw-backdrop-contrast: ;
529 | --tw-backdrop-grayscale: ;
530 | --tw-backdrop-hue-rotate: ;
531 | --tw-backdrop-invert: ;
532 | --tw-backdrop-opacity: ;
533 | --tw-backdrop-saturate: ;
534 | --tw-backdrop-sepia: ;
535 | }
536 |
537 | .static {
538 | position: static;
539 | }
540 |
541 | .absolute {
542 | position: absolute;
543 | }
544 |
545 | .relative {
546 | position: relative;
547 | }
548 |
549 | .top-0 {
550 | top: 0px;
551 | }
552 |
553 | .z-30 {
554 | z-index: 30;
555 | }
556 |
557 | .z-40 {
558 | z-index: 40;
559 | }
560 |
561 | .z-50 {
562 | z-index: 50;
563 | }
564 |
565 | .flex {
566 | display: flex;
567 | }
568 |
569 | .hidden {
570 | display: none;
571 | }
572 |
573 | .h-\[100vh\] {
574 | height: 100vh;
575 | }
576 |
577 | .h-full {
578 | height: 100%;
579 | }
580 |
581 | .w-3\/4 {
582 | width: 75%;
583 | }
584 |
585 | .w-fit {
586 | width: -moz-fit-content;
587 | width: fit-content;
588 | }
589 |
590 | .w-full {
591 | width: 100%;
592 | }
593 |
594 | .cursor-pointer {
595 | cursor: pointer;
596 | }
597 |
598 | .select-none {
599 | -webkit-user-select: none;
600 | -moz-user-select: none;
601 | user-select: none;
602 | }
603 |
604 | .flex-row {
605 | flex-direction: row;
606 | }
607 |
608 | .flex-col {
609 | flex-direction: column;
610 | }
611 |
612 | .flex-wrap {
613 | flex-wrap: wrap;
614 | }
615 |
616 | .items-start {
617 | align-items: flex-start;
618 | }
619 |
620 | .items-center {
621 | align-items: center;
622 | }
623 |
624 | .justify-between {
625 | justify-content: space-between;
626 | }
627 |
628 | .gap-1 {
629 | gap: 0.25rem;
630 | }
631 |
632 | .gap-12 {
633 | gap: 3rem;
634 | }
635 |
636 | .gap-2 {
637 | gap: 0.5rem;
638 | }
639 |
640 | .gap-4 {
641 | gap: 1rem;
642 | }
643 |
644 | .overflow-hidden {
645 | overflow: hidden;
646 | }
647 |
648 | .rounded {
649 | border-radius: 0.25rem;
650 | }
651 |
652 | .border {
653 | border-width: 1px;
654 | }
655 |
656 | .border-b {
657 | border-bottom-width: 1px;
658 | }
659 |
660 | .border-r {
661 | border-right-width: 1px;
662 | }
663 |
664 | .border-gray-200 {
665 | --tw-border-opacity: 1;
666 | border-color: rgb(229 231 235 / var(--tw-border-opacity));
667 | }
668 |
669 | .bg-black {
670 | --tw-bg-opacity: 1;
671 | background-color: rgb(0 0 0 / var(--tw-bg-opacity));
672 | }
673 |
674 | .bg-blue-700 {
675 | --tw-bg-opacity: 1;
676 | background-color: rgb(29 78 216 / var(--tw-bg-opacity));
677 | }
678 |
679 | .bg-gray-200 {
680 | --tw-bg-opacity: 1;
681 | background-color: rgb(229 231 235 / var(--tw-bg-opacity));
682 | }
683 |
684 | .bg-white {
685 | --tw-bg-opacity: 1;
686 | background-color: rgb(255 255 255 / var(--tw-bg-opacity));
687 | }
688 |
689 | .p-1 {
690 | padding: 0.25rem;
691 | }
692 |
693 | .p-2 {
694 | padding: 0.5rem;
695 | }
696 |
697 | .p-4 {
698 | padding: 1rem;
699 | }
700 |
701 | .px-4 {
702 | padding-left: 1rem;
703 | padding-right: 1rem;
704 | }
705 |
706 | .py-1 {
707 | padding-top: 0.25rem;
708 | padding-bottom: 0.25rem;
709 | }
710 |
711 | .pr-4 {
712 | padding-right: 1rem;
713 | }
714 |
715 | .text-sm {
716 | font-size: 0.875rem;
717 | line-height: 1.25rem;
718 | }
719 |
720 | .text-xl {
721 | font-size: 1.25rem;
722 | line-height: 1.75rem;
723 | }
724 |
725 | .font-bold {
726 | font-weight: 700;
727 | }
728 |
729 | .text-red-500 {
730 | --tw-text-opacity: 1;
731 | color: rgb(239 68 68 / var(--tw-text-opacity));
732 | }
733 |
734 | .text-white {
735 | --tw-text-opacity: 1;
736 | color: rgb(255 255 255 / var(--tw-text-opacity));
737 | }
738 |
739 | .opacity-50 {
740 | opacity: 0.5;
741 | }
742 |
743 | .outline-none {
744 | outline: 2px solid transparent;
745 | outline-offset: 2px;
746 | }
747 |
748 | .focus\:border-gray-400:focus {
749 | --tw-border-opacity: 1;
750 | border-color: rgb(156 163 175 / var(--tw-border-opacity));
751 | }
--------------------------------------------------------------------------------