diff --git a/.oxfmtrc.json b/.oxfmtrc.json index fce110d..ce081e8 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -1,4 +1,4 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", - "ignorePatterns": ["src/routeTree.gen.ts"] + "ignorePatterns": ["**/routeTree.gen.ts"] } diff --git a/.oxlintrc.json b/.oxlintrc.json index 9a1a6ba..10c812a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,6 +1,6 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", - "ignorePatterns": ["src/routeTree.gen.ts"], + "ignorePatterns": ["**/routeTree.gen.ts"], "plugins": ["react", "react-perf", "import", "jsx-a11y", "promise"], "rules": { "eqeqeq": ["error", "smart"], diff --git a/package.json b/package.json index 4a7a909..dbd4da9 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@tanstack/react-router": "^1.166.3", "@tanstack/react-router-devtools": "^1.166.3", "arktype": "^2.2.0", + "clsx": "^2.1.1", "i18next": "^25.8.17", "i18next-http-backend": "^3.0.2", "motion": "^12.35.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec30225..cb38b78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: arktype: specifier: ^2.2.0 version: 2.2.0 + clsx: + specifier: ^2.1.1 + version: 2.1.1 i18next: specifier: ^25.8.17 version: 25.8.17(typescript@5.9.3) diff --git a/public/fonts/BalsamicSans/BalsamiqSans-Bold.ttf b/public/fonts/BalsamicSans/BalsamiqSans-Bold.ttf new file mode 100644 index 0000000..ab0ecd4 Binary files /dev/null and b/public/fonts/BalsamicSans/BalsamiqSans-Bold.ttf differ diff --git a/public/fonts/BalsamicSans/BalsamiqSans-BoldItalic.ttf b/public/fonts/BalsamicSans/BalsamiqSans-BoldItalic.ttf new file mode 100644 index 0000000..3665fe2 Binary files /dev/null and b/public/fonts/BalsamicSans/BalsamiqSans-BoldItalic.ttf differ diff --git a/public/fonts/BalsamicSans/BalsamiqSans-Italic.ttf b/public/fonts/BalsamicSans/BalsamiqSans-Italic.ttf new file mode 100644 index 0000000..400c52f Binary files /dev/null and b/public/fonts/BalsamicSans/BalsamiqSans-Italic.ttf differ diff --git a/public/fonts/BalsamicSans/BalsamiqSans-Regular.ttf b/public/fonts/BalsamicSans/BalsamiqSans-Regular.ttf new file mode 100644 index 0000000..0310611 Binary files /dev/null and b/public/fonts/BalsamicSans/BalsamiqSans-Regular.ttf differ diff --git a/public/locales/en.d.ts b/public/locales/en.d.ts index 36d140b..e667571 100644 --- a/public/locales/en.d.ts +++ b/public/locales/en.d.ts @@ -1 +1 @@ -export declare const resources: { hello: "Hello World!" }; +export declare const resources: { hello: "Hello World!"; "actionModal.close": "Close" }; diff --git a/public/locales/en.json b/public/locales/en.json index 5de8883..c57b965 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -1,3 +1,4 @@ { - "hello": "Hello World!" + "hello": "Hello World!", + "actionModal.close": "Close" } diff --git a/public/locales/ru.d.ts b/public/locales/ru.d.ts index d039f6d..94b80d7 100644 --- a/public/locales/ru.d.ts +++ b/public/locales/ru.d.ts @@ -1 +1 @@ -export declare const resources: { hello: "Привет мир" }; +export declare const resources: { hello: "Привет мир"; "actionModal.close": "Закрыть" }; diff --git a/public/locales/ru.json b/public/locales/ru.json index 31b3150..16ae368 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -1,3 +1,4 @@ { - "hello": "Привет мир" + "hello": "Привет мир", + "actionModal.close": "Закрыть" } diff --git a/src/components/atoms/Button/Button.module.css b/src/components/atoms/Button/Button.module.css new file mode 100644 index 0000000..07bab05 --- /dev/null +++ b/src/components/atoms/Button/Button.module.css @@ -0,0 +1,85 @@ +@layer base { + .baseButton { + cursor: pointer; + border-radius: 9999px; + padding: 10px; + + font-weight: 700; + font-size: 18px; + line-height: 18px; + + width: fit-content; + display: flex; + justify-content: center; + align-items: center; + gap: 4px; + + border-width: 0.7px; + border-style: solid; + + text-shadow: 0px 1px 1px #00000059; + -webkit-text-stroke-width: 0.7px; + + user-select: none; + + &:disabled { + pointer-events: none; + } + } + + .blueButton { + background: linear-gradient(180deg, #6cc3f1 0%, #3c6bbd 100%); + border-color: #012142; + box-shadow: + 0px 3px 1px 1px #bfe9ff inset, + 0px 2px 1px 1px #8ccef0 inset, + 0px -3px 1px 1px #8fc9e966 inset, + 0px -2px 1px 1px #29497f inset, + 0px 1px 2px 0px #00000073; + + color: #d9edff; + -webkit-text-stroke-color: #003a73; + } + + .redButton { + background: linear-gradient(180deg, #f16c6c 0%, #a73030 100%); + border-color: #420101; + box-shadow: + 0px 3px 1px 1px #ffbfbf inset, + 0px 2px 1px 1px #f08c8c inset, + 0px -3px 1px 1px #e98f8f66 inset, + 0px -2px 1px 1px #7f2929 inset, + 0px 1px 2px 0px #00000073; + + color: #ffd9d9; + -webkit-text-stroke-color: #730000; + } + + .greenButton { + background: linear-gradient(180deg, #5bdf5b 0%, #15812e 100%); + border-color: #00450a; + box-shadow: + 0px 3px 1px 1px #9bff9f inset, + 0px 2px 1px 1px #8cf08e inset, + 0px -3px 1px 1px #8fe99b66 inset, + 0px -2px 1px 1px #1d672d inset, + 0px 1px 2px 0px #00000073; + + color: #cdffd2; + -webkit-text-stroke-color: #007313; + } + + .yellowButton { + background: linear-gradient(180deg, #ffbd42 0%, #e59a0f 100%); + border-color: #453100; + box-shadow: + 0px 3px 1px 1px #ffefb2 inset, + 0px 2px 1px 1px #ffe57b inset, + 0px -3px 1px 1px #ffe9ab66 inset, + 0px -2px 1px 1px #a88010 inset, + 0px 1px 2px 0px #00000073; + + color: #fffdcd; + -webkit-text-stroke-color: #735a00; + } +} diff --git a/src/components/atoms/Button/Button.tsx b/src/components/atoms/Button/Button.tsx new file mode 100644 index 0000000..fa5cfa7 --- /dev/null +++ b/src/components/atoms/Button/Button.tsx @@ -0,0 +1,29 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +import classes from "./Button.module.css"; + +type Props = Omit, "className"> & { + className?: ClassValue; + variant?: "blue" | "red" | "green" | "yellow"; +}; + +const VARIANTS_MAP = { + blue: classes.blueButton, + green: classes.greenButton, + red: classes.redButton, + yellow: classes.yellowButton, +} satisfies Record, string>; + +export default function Button({ className, variant = "blue", ...props }: Props) { + return ( + + ); +} diff --git a/src/components/atoms/Button/index.ts b/src/components/atoms/Button/index.ts new file mode 100644 index 0000000..c4719be --- /dev/null +++ b/src/components/atoms/Button/index.ts @@ -0,0 +1 @@ +export { default } from "./Button"; diff --git a/src/components/atoms/Counter/Counter.tsx b/src/components/atoms/Counter/Counter.tsx new file mode 100644 index 0000000..fcebdb4 --- /dev/null +++ b/src/components/atoms/Counter/Counter.tsx @@ -0,0 +1,24 @@ +import { motion, useMotionValue, useTransform, animate } from "motion/react"; +import { useEffect } from "react"; +import clsx, { type ClassValue } from "clsx"; + +type Props = { + className?: ClassValue; + value: number; +}; + +export default function Counter({ className, value }: Props) { + const motionValue = useMotionValue(0); + const display = useTransform(motionValue, (v) => Math.round(v).toLocaleString("fr-FR")); + + useEffect( + () => + animate(motionValue, value, { + duration: 0.3, + ease: "easeOut", + }).stop, + [motionValue, value], + ); + + return {display}; +} diff --git a/src/components/atoms/Counter/index.ts b/src/components/atoms/Counter/index.ts new file mode 100644 index 0000000..e09713b --- /dev/null +++ b/src/components/atoms/Counter/index.ts @@ -0,0 +1 @@ +export { default } from "./Counter"; diff --git a/src/components/atoms/Progress/Progress.module.css b/src/components/atoms/Progress/Progress.module.css new file mode 100644 index 0000000..7cb63d0 --- /dev/null +++ b/src/components/atoms/Progress/Progress.module.css @@ -0,0 +1,56 @@ +@layer base { + .container { + position: relative; + width: 100%; + height: 24px; + background: transparent; + border-radius: 9999px; + overflow: hidden; + } + + .progressBar { + position: absolute; + top: 0; + left: 0; + height: 100%; + border-radius: 9999px; + } + + .yellowProgress { + background: #e08b0c; + box-shadow: + 0px 3px 4px 0px #ffffff73 inset, + 0px -3px 4px 0px #00000040 inset; + } + + .greenProgress { + background: linear-gradient(180deg, #5bdf5b 0%, #15812e 100%); + border: 0.7px solid #00450a; + box-shadow: + 0px 3px 1px 1px #9bff9f inset, + 0px 2px 1px 1px #8cf08e inset, + 0px -3px 1px 1px #8fe99b66 inset, + 0px -2px 1px 1px #1d672d inset, + 0px 1px 2px 0px #00000073; + } + + .text { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 18px; + line-height: 18px; + font-weight: 700; + white-space: nowrap; + } + + .yellowText { + color: #ffdc9d; + } + + .greenText { + color: #ffffffcc; + -webkit-text-stroke: 1px #00000080; + } +} diff --git a/src/components/atoms/Progress/Progress.tsx b/src/components/atoms/Progress/Progress.tsx new file mode 100644 index 0000000..d9b25df --- /dev/null +++ b/src/components/atoms/Progress/Progress.tsx @@ -0,0 +1,40 @@ +import { motion } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +import Counter from "@components/atoms/Counter"; +import classes from "./Progress.module.css"; + +type Props = { + className?: ClassValue; + value: number; + max: number; + variant: "green" | "yellow"; +}; + +const VARIANTS_MAP = { + green: classes.greenProgress, + yellow: classes.yellowProgress, +} satisfies Record; + +const TEXT_VARIANTS_MAP = { + green: classes.greenText, + yellow: classes.yellowText, +} satisfies Record; + +export default function Progress({ className, value, max, variant }: Props) { + const percentage = max > 0 ? (value / max) * 100 : 0; + + return ( +
+ + + / {} + +
+ ); +} diff --git a/src/components/atoms/Progress/index.ts b/src/components/atoms/Progress/index.ts new file mode 100644 index 0000000..c9ad22f --- /dev/null +++ b/src/components/atoms/Progress/index.ts @@ -0,0 +1 @@ +export { default } from "./Progress"; diff --git a/src/components/atoms/TabSelector/TabSelector.module.css b/src/components/atoms/TabSelector/TabSelector.module.css new file mode 100644 index 0000000..cb17284 --- /dev/null +++ b/src/components/atoms/TabSelector/TabSelector.module.css @@ -0,0 +1,45 @@ +@layer base { + .container { + padding: 4px; + display: flex; + border-radius: 9999px; + } + + .tabsContainer { + position: relative; + flex: 1; + display: flex; + overflow: hidden; + border-radius: 9999px; + } + + .tabs { + display: flex; + width: 100%; + flex: 1; + gap: 0; + } + + .tab { + flex: 1; + background: transparent; + border: none; + cursor: pointer; + padding: 8px 12px; + color: #ffffff; + -webkit-text-stroke: 0.7px #331b01; + font-size: 18px; + line-height: 18px; + font-weight: 700; + position: relative; + z-index: 1; + } + + .thumb { + position: absolute; + top: 0; + height: 100%; + border-radius: 9999px; + z-index: 0; + } +} diff --git a/src/components/atoms/TabSelector/TabSelector.tsx b/src/components/atoms/TabSelector/TabSelector.tsx new file mode 100644 index 0000000..2feac01 --- /dev/null +++ b/src/components/atoms/TabSelector/TabSelector.tsx @@ -0,0 +1,54 @@ +import clsx, { type ClassValue } from "clsx"; +import ContentSurface from "@components/surface/ContentSurface"; +import DarkSurface from "@components/surface/DarkSurface"; +import { motion, type HTMLMotionProps } from "motion/react"; + +import classes from "./TabSelector.module.css"; + +type Tab = { + key: string; + title: string; +}; + +type Props = Omit, "className" | "onChange"> & { + tabs: Tab[]; + value?: string | null; + onChange?: (key: string) => void; + className?: ClassValue; +}; + +export default function TabSelector({ tabs, value, onChange, className, ...props }: Props) { + const selectedIndex = value != null ? tabs.findIndex((tab) => tab.key === value) : -1; + + return ( + +
+
+ {tabs.map((tab) => ( + onChange?.(tab.key)} + className={classes.tab} + > + {tab.title} + + ))} +
+ {selectedIndex >= 0 && ( + + )} +
+
+ ); +} diff --git a/src/components/atoms/TabSelector/index.ts b/src/components/atoms/TabSelector/index.ts new file mode 100644 index 0000000..47905d2 --- /dev/null +++ b/src/components/atoms/TabSelector/index.ts @@ -0,0 +1 @@ +export { default } from "./TabSelector"; diff --git a/src/components/form/NumberInput/NumberInput.module.css b/src/components/form/NumberInput/NumberInput.module.css new file mode 100644 index 0000000..ae97c8c --- /dev/null +++ b/src/components/form/NumberInput/NumberInput.module.css @@ -0,0 +1,82 @@ +@layer base { + .container { + width: 100%; + min-height: 35px; + height: 35px; + background: #0000004d; + border-top: 1px solid #472715; + box-shadow: + 0px 4px 2px 0px #00000040 inset, + 0px -1.5px 0px 0px #8a582d inset; + border-radius: 9999px; + outline: none; + display: flex; + align-items: center; + padding: 0 0 0 14px; + box-sizing: border-box; + overflow: hidden; + + &.error { + outline: 2px solid #af4242; + } + } + + .inputWrapper { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + flex: 1; + height: 100%; + + .input { + background: transparent; + border: none; + outline: none; + color: #fbe6be; + font-size: 18px; + line-height: 18px; + font-weight: 700; + text-align: center; + padding: 0; + min-width: 20px; + field-sizing: content; + align-self: stretch; + + &::placeholder { + color: #fbe6be66; + } + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + } + + .prefix { + color: #fbe6be; + font-size: 18px; + line-height: 18px; + font-weight: 700; + } + } + + .iconButton { + margin: 0px 3px; + padding: 1px; + cursor: pointer; + border-radius: 9999px; + background: radial-gradient(circle, #00000000 0%, #00000000 50%, #fbe6be 50%, #fbe6be 100%); + box-shadow: + 0px 2px 0px 0px #ffffffbf inset, + -1px 0px 0px 0px #00000059 inset, + 1px 0px 0px 0px #00000059 inset, + 0px -1px 0px 0px #00000059 inset; + + .icon { + width: 28px; + color: #fbe6be; + } + } +} diff --git a/src/components/form/NumberInput/NumberInput.tsx b/src/components/form/NumberInput/NumberInput.tsx new file mode 100644 index 0000000..7ae112f --- /dev/null +++ b/src/components/form/NumberInput/NumberInput.tsx @@ -0,0 +1,75 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; +import { type ReactNode, useRef, useState, type ChangeEvent, useId } from "react"; +import KeyboardIcon from "@components/icons/KeyboardIcon"; + +import classes from "./NumberInput.module.css"; + +type Props = Omit, "className" | "type" | "onChange"> & { + className?: ClassValue; + error?: boolean; + prefix?: ReactNode; + value?: number; + onChange?: (value: number) => void; +}; + +const NUMERIC_REGEX = /[^0-9.]/g; + +function filterNumericInput(input: string): string { + const filtered = input.replace(NUMERIC_REGEX, ""); + const parts = filtered.split("."); + return parts.length > 2 ? `${parts[0]}.${parts.slice(1).join("")}` : filtered; +} + +export default function NumberInput({ + className, + error, + prefix, + value, + onChange, + ...props +}: Props) { + const stableId = useId(); + const inputRef = useRef(null); + const id = props.id ?? stableId; + + const [strValue, setStrValue] = useState(() => value?.toString() ?? ""); + const prevValueRef = useRef(value); + if (prevValueRef.current !== value) { + prevValueRef.current = value; + setStrValue(value?.toString() ?? ""); + } + + const handleChange = (e: ChangeEvent) => { + const normalized = filterNumericInput(e.target.value); + setStrValue(normalized); + const num = parseFloat(normalized); + onChange?.(isNaN(num) ? 0 : num); + }; + + return ( +
+ + inputRef.current?.focus()} + > + + +
+ ); +} diff --git a/src/components/form/NumberInput/index.ts b/src/components/form/NumberInput/index.ts new file mode 100644 index 0000000..aefce1f --- /dev/null +++ b/src/components/form/NumberInput/index.ts @@ -0,0 +1 @@ +export { default } from "./NumberInput"; diff --git a/src/components/form/RangeInput/RangeInput.module.css b/src/components/form/RangeInput/RangeInput.module.css new file mode 100644 index 0000000..d66ca24 --- /dev/null +++ b/src/components/form/RangeInput/RangeInput.module.css @@ -0,0 +1,82 @@ +@layer base { + .rangeInput { + -webkit-appearance: none; + appearance: none; + background: transparent; + cursor: pointer; + + --thumb-size: 30px; + --track-height: 10px; + width: 100%; + padding: calc(var(--thumb-size) / 2) 0; + height: 10px; + + &::-webkit-slider-runnable-track { + border-radius: 9px; + height: var(--track-height); + background: #0000004d; + border-top: 1px solid #472715; + box-shadow: + 0px 4px 2px 0px #00000040 inset, + 0px -1.5px 0px 0px #8a582d inset; + } + + &::-moz-range-track { + border-radius: 9px; + height: var(--track-height); + background: #0000004d; + border-top: 1px solid #472715; + box-shadow: + 0px 4px 2px 0px #00000040 inset, + 0px -1.5px 0px 0px #8a582d inset; + } + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: var(--thumb-size); + height: var(--thumb-size); + margin-top: calc((var(--track-height) / 2) - var(--thumb-size) / 2); + background: #895428; + border-radius: 50%; + box-shadow: + -2px 0px 1px 0px #00000040 inset, + 2px 0px 1px 0px #00000040 inset, + 0px 2px 1px 0px #be7f4a inset, + 0px -2px 1px 0px #3a1c09 inset, + 0px 1px 0px 0px #00000033; + transition: transform 0.1s ease; + } + + &:active::-webkit-slider-thumb { + transform: scale(1.1); + } + + ::-moz-range-thumb { + width: var(--thumb-size); + height: var(--thumb-size); + margin-top: calc((var(--track-height) / 2) - var(--thumb-size) / 2); + background: #895428; + border-radius: 50%; + box-shadow: + -2px 0px 1px 0px #00000040 inset, + 2px 0px 1px 0px #00000040 inset, + 0px 2px 1px 0px #be7f4a inset, + 0px -2px 1px 0px #3a1c09 inset, + 0px 1px 0px 0px #00000033; + transition: transform 0.1s ease; + } + + &:active::-moz-range-thumb { + transform: scale(1.1); + } + } + + .dragging::-webkit-slider-thumb { + transform: scale(1.1); + } + + .dragging::-moz-range-thumb { + transform: scale(1.1); + } +} diff --git a/src/components/form/RangeInput/RangeInput.tsx b/src/components/form/RangeInput/RangeInput.tsx new file mode 100644 index 0000000..e10375a --- /dev/null +++ b/src/components/form/RangeInput/RangeInput.tsx @@ -0,0 +1,26 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import { useState } from "react"; +import clsx, { type ClassValue } from "clsx"; + +import classes from "./RangeInput.module.css"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function RangeInput({ className, ...props }: Props) { + const [isDragging, setIsDragging] = useState(false); + + return ( + setIsDragging(true)} + onMouseUp={() => setIsDragging(false)} + onMouseLeave={() => setIsDragging(false)} + onTouchStart={() => setIsDragging(true)} + onTouchEnd={() => setIsDragging(false)} + /> + ); +} diff --git a/src/components/form/RangeInput/index.ts b/src/components/form/RangeInput/index.ts new file mode 100644 index 0000000..862f813 --- /dev/null +++ b/src/components/form/RangeInput/index.ts @@ -0,0 +1 @@ +export { default } from "./RangeInput"; diff --git a/src/components/form/SwitchInput/SwitchInput.module.css b/src/components/form/SwitchInput/SwitchInput.module.css new file mode 100644 index 0000000..f239852 --- /dev/null +++ b/src/components/form/SwitchInput/SwitchInput.module.css @@ -0,0 +1,51 @@ +@layer base { + .container { + padding: 2px; + display: flex; + border-radius: 9999px; + width: 80px; + height: 30px; + } + + .optionsContainer { + position: relative; + display: flex; + overflow: hidden; + border-radius: 9999px; + } + + .options { + display: flex; + width: 100%; + flex: 1; + gap: 0; + z-index: 1; + } + + .option { + flex: 1; + background: transparent; + border: none; + cursor: pointer; + padding: 6px 4px; + color: #fbe6be; + font-size: 18px; + line-height: 18px; + font-weight: 700; + position: relative; + z-index: 1; + transition: color 0.2s ease; + } + + .selected { + color: #4b2c13; + } + + .thumb { + position: absolute; + top: 0; + height: 100%; + border-radius: 9999px; + z-index: 0; + } +} diff --git a/src/components/form/SwitchInput/SwitchInput.tsx b/src/components/form/SwitchInput/SwitchInput.tsx new file mode 100644 index 0000000..c69bd71 --- /dev/null +++ b/src/components/form/SwitchInput/SwitchInput.tsx @@ -0,0 +1,57 @@ +import clsx, { type ClassValue } from "clsx"; +import ContentSurface from "@components/surface/ContentSurface"; +import LightSurface from "@components/surface/LightSurface"; +import { motion, type HTMLMotionProps } from "motion/react"; + +import classes from "./SwitchInput.module.css"; + +type Props = Omit, "className" | "onChange"> & { + value?: boolean | null; + onChange?: (value: boolean) => void; + className?: ClassValue; +}; + +export default function SwitchInput({ value, onChange, className, ...props }: Props) { + const selectedIndex = value != null ? (value ? 0 : 1) : -1; + + return ( + +
+
+ onChange?.(true)} + className={clsx(classes.option, value === true && classes.selected)} + > + on + + onChange?.(false)} + className={clsx(classes.option, value === false && classes.selected)} + > + off + +
+ {selectedIndex >= 0 && ( + + )} +
+
+ ); +} diff --git a/src/components/form/SwitchInput/index.ts b/src/components/form/SwitchInput/index.ts new file mode 100644 index 0000000..1e178df --- /dev/null +++ b/src/components/form/SwitchInput/index.ts @@ -0,0 +1 @@ +export { default } from "./SwitchInput"; diff --git a/src/components/form/TextAreaInput/TextAreaInput.module.css b/src/components/form/TextAreaInput/TextAreaInput.module.css new file mode 100644 index 0000000..22c3f65 --- /dev/null +++ b/src/components/form/TextAreaInput/TextAreaInput.module.css @@ -0,0 +1,30 @@ +@layer base { + .input { + width: 100%; + min-height: 54px; + height: 54px; + background: #0000004d; + border-top: 1px solid #472715; + box-shadow: + 0px 4px 2px 0px #00000040 inset, + 0px -1.5px 0px 0px #8a582d inset; + color: #fbe6be; + font-size: 18px; + line-height: 18px; + font-weight: 700; + border-radius: 14px; + border: none; + outline: none; + padding: 12px 16px; + resize: none; + box-sizing: border-box; + + &::placeholder { + color: #fbe6be66; + } + } + + .error { + border: 2px solid #af4242; + } +} diff --git a/src/components/form/TextAreaInput/TextAreaInput.tsx b/src/components/form/TextAreaInput/TextAreaInput.tsx new file mode 100644 index 0000000..d61bbe8 --- /dev/null +++ b/src/components/form/TextAreaInput/TextAreaInput.tsx @@ -0,0 +1,18 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +import classes from "./TextAreaInput.module.css"; + +type Props = Omit, "className"> & { + className?: ClassValue; + error?: boolean; +}; + +export default function TextAreaInput({ className, error, ...props }: Props) { + return ( + + ); +} diff --git a/src/components/form/TextAreaInput/index.ts b/src/components/form/TextAreaInput/index.ts new file mode 100644 index 0000000..60843dd --- /dev/null +++ b/src/components/form/TextAreaInput/index.ts @@ -0,0 +1 @@ +export { default } from "./TextAreaInput"; diff --git a/src/components/form/TextInput/TextInput.module.css b/src/components/form/TextInput/TextInput.module.css new file mode 100644 index 0000000..0f90cf3 --- /dev/null +++ b/src/components/form/TextInput/TextInput.module.css @@ -0,0 +1,29 @@ +@layer base { + .input { + width: 100%; + height: 32px; + background: #0000004d; + border-top: 1px solid #472715; + box-shadow: + 0px 4px 2px 0px #00000040 inset, + 0px -1.5px 0px 0px #8a582d inset; + color: #fbe6be; + font-size: 18px; + line-height: 18px; + font-weight: 700; + border-radius: 14px; + border: none; + outline: none; + padding: 7px 16px; + text-align: center; + box-sizing: border-box; + + &::placeholder { + color: #fbe6be66; + } + } + + .error { + border: 2px solid #af4242; + } +} diff --git a/src/components/form/TextInput/TextInput.tsx b/src/components/form/TextInput/TextInput.tsx new file mode 100644 index 0000000..7a3ae14 --- /dev/null +++ b/src/components/form/TextInput/TextInput.tsx @@ -0,0 +1,19 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +import classes from "./TextInput.module.css"; + +type Props = Omit, "className"> & { + className?: ClassValue; + error?: boolean; +}; + +export default function TextInput({ className, error, ...props }: Props) { + return ( + + ); +} diff --git a/src/components/form/TextInput/index.ts b/src/components/form/TextInput/index.ts new file mode 100644 index 0000000..bb6fff3 --- /dev/null +++ b/src/components/form/TextInput/index.ts @@ -0,0 +1 @@ +export { default } from "./TextInput"; diff --git a/src/components/icons/KeyboardIcon.tsx b/src/components/icons/KeyboardIcon.tsx new file mode 100644 index 0000000..999a825 --- /dev/null +++ b/src/components/icons/KeyboardIcon.tsx @@ -0,0 +1,52 @@ +import { motion, type SVGMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function KeyboardIcon(props: Props) { + return ( + + + + + + + + + + + ); +} diff --git a/src/components/modal/ActionModal/ActionModal.module.css b/src/components/modal/ActionModal/ActionModal.module.css new file mode 100644 index 0000000..3326de5 --- /dev/null +++ b/src/components/modal/ActionModal/ActionModal.module.css @@ -0,0 +1,38 @@ +@layer base { + .overlay { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + } + + .modal { + display: flex; + flex-direction: column; + gap: 6px; + width: 100%; + max-width: 320px; + padding: 13px; + + .content { + border-radius: 22px; + + .description { + padding: 12px; + border-radius: 18px; + text-align: center; + } + } + } + + .buttons { + display: flex; + gap: 6px; + } + + .button { + flex: 1; + } +} diff --git a/src/components/modal/ActionModal/ActionModal.tsx b/src/components/modal/ActionModal/ActionModal.tsx new file mode 100644 index 0000000..0db9ae1 --- /dev/null +++ b/src/components/modal/ActionModal/ActionModal.tsx @@ -0,0 +1,62 @@ +import { AnimatePresence, motion } from "motion/react"; +import { useTranslation } from "react-i18next"; + +import SectionSurface from "../../surface/SectionSurface/SectionSurface"; +import ContentSurface from "../../surface/ContentSurface/ContentSurface"; +import LightSurface from "../../surface/LightSurface/LightSurface"; +import Button from "../../atoms/Button/Button"; +import classes from "./ActionModal.module.css"; + +type WithConfirm = { + onConfirm: () => void; + confirmText: string; +}; + +type WithoutConfirm = { + onConfirm?: never; + confirmText?: never; +}; + +type Props = (WithConfirm | WithoutConfirm) & { + open: boolean; + description: string; + onClose: () => void; +}; + +export default function ActionModal({ open, description, onClose, onConfirm, confirmText }: Props) { + const { t } = useTranslation(); + + return ( + + {open && ( + + + + {description} + +
+ + {onConfirm != null && ( + + )} +
+
+
+ )} +
+ ); +} diff --git a/src/components/modal/ActionModal/index.ts b/src/components/modal/ActionModal/index.ts new file mode 100644 index 0000000..027f5f4 --- /dev/null +++ b/src/components/modal/ActionModal/index.ts @@ -0,0 +1 @@ +export { default } from "./ActionModal"; diff --git a/src/components/surface/BlueSectionSurface/BlueSectionSurface.module.css b/src/components/surface/BlueSectionSurface/BlueSectionSurface.module.css new file mode 100644 index 0000000..d6a2a69 --- /dev/null +++ b/src/components/surface/BlueSectionSurface/BlueSectionSurface.module.css @@ -0,0 +1,37 @@ +@reference "@/index.css"; + +@layer base { + .blueSectionSurface { + background: linear-gradient(180deg, #278789 0%, #206f66 100%); + border: 1px solid #123f3f; + box-shadow: + -3px 0px 1px 0px #00000059 inset, + 3px 0px 1px 0px #00000059 inset, + 0px 4px 1px 0px #ffffff26 inset, + 0px -3px 1px 0px #00000059 inset; + padding: 16px; + border-radius: 40px; + } + + .blueSurface { + background: #0c2836; + box-shadow: + 0px -1.5px 0px 0px #32a39b inset, + 1.5px 0px 0px 0px #32a39b inset, + -1.5px 0px 0px 0px #32a39b inset, + 0px 1.5px 0px 0px #114647 inset; + color: #befbe8; + padding: 8px; + border-radius: 23px; + } + + .blueSurfaceContent { + background: #0000004d; + border-top: 1px solid #0a2929; + box-shadow: + 0px 4px 2px 0px #00000040 inset, + 0px -1.5px 0px 0px #207475 inset; + padding: 4px; + border-radius: 9999px; + } +} diff --git a/src/components/surface/BlueSectionSurface/BlueSectionSurface.tsx b/src/components/surface/BlueSectionSurface/BlueSectionSurface.tsx new file mode 100644 index 0000000..f966f49 --- /dev/null +++ b/src/components/surface/BlueSectionSurface/BlueSectionSurface.tsx @@ -0,0 +1,28 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +import classes from "./BlueSectionSurface.module.css"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function BlueSectionSurface(props: Props) { + return ( + + ); +} + +export function BlueSurface(props: Props) { + return ; +} + +export function BlueSurfaceContent(props: Props) { + return ; +} diff --git a/src/components/surface/BlueSectionSurface/index.ts b/src/components/surface/BlueSectionSurface/index.ts new file mode 100644 index 0000000..26591bb --- /dev/null +++ b/src/components/surface/BlueSectionSurface/index.ts @@ -0,0 +1 @@ +export { default, BlueSurface, BlueSurfaceContent } from "./BlueSectionSurface"; diff --git a/src/components/surface/ContentSurface/ContentSurface.module.css b/src/components/surface/ContentSurface/ContentSurface.module.css new file mode 100644 index 0000000..04c874c --- /dev/null +++ b/src/components/surface/ContentSurface/ContentSurface.module.css @@ -0,0 +1,14 @@ +@layer base { + .contentSurface { + background: #432710; + box-shadow: + 0px -1.5px 0px 0px #a17346 inset, + 1.5px 0px 0px 0px #a17346 inset, + -1.5px 0px 0px 0px #a17346 inset, + 0px 1.5px 0px 0px #6b4521 inset; + padding: 3px; + display: flex; + flex-direction: column; + gap: 1px; + } +} diff --git a/src/components/surface/ContentSurface/ContentSurface.tsx b/src/components/surface/ContentSurface/ContentSurface.tsx new file mode 100644 index 0000000..f71bf4a --- /dev/null +++ b/src/components/surface/ContentSurface/ContentSurface.tsx @@ -0,0 +1,11 @@ +import classes from "./ContentSurface.module.css"; +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function ContentSurface(props: Props) { + return ; +} diff --git a/src/components/surface/ContentSurface/index.ts b/src/components/surface/ContentSurface/index.ts new file mode 100644 index 0000000..a7ac4d1 --- /dev/null +++ b/src/components/surface/ContentSurface/index.ts @@ -0,0 +1 @@ +export { default } from "./ContentSurface"; diff --git a/src/components/surface/DarkSurface/DarkSurface.module.css b/src/components/surface/DarkSurface/DarkSurface.module.css new file mode 100644 index 0000000..615af7c --- /dev/null +++ b/src/components/surface/DarkSurface/DarkSurface.module.css @@ -0,0 +1,11 @@ +@layer base { + .darkSurface { + background: #5a391c; + box-shadow: + 0px 2px 0px 0px #8a582d inset, + -1px 0px 0px 0px #00000059 inset, + 1px 0px 0px 0px #00000059 inset, + 0px -1px 0px 0px #00000059 inset; + color: #fbe6be; + } +} diff --git a/src/components/surface/DarkSurface/DarkSurface.tsx b/src/components/surface/DarkSurface/DarkSurface.tsx new file mode 100644 index 0000000..1c85d9e --- /dev/null +++ b/src/components/surface/DarkSurface/DarkSurface.tsx @@ -0,0 +1,11 @@ +import classes from "./DarkSurface.module.css"; +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function DarkSurface(props: Props) { + return ; +} diff --git a/src/components/surface/DarkSurface/index.ts b/src/components/surface/DarkSurface/index.ts new file mode 100644 index 0000000..b1edd86 --- /dev/null +++ b/src/components/surface/DarkSurface/index.ts @@ -0,0 +1 @@ +export { default } from "./DarkSurface"; diff --git a/src/components/surface/DataSurface/DataSurface.module.css b/src/components/surface/DataSurface/DataSurface.module.css new file mode 100644 index 0000000..8735042 --- /dev/null +++ b/src/components/surface/DataSurface/DataSurface.module.css @@ -0,0 +1,9 @@ +@layer base { + .dataSurface { + background: #3f2814; + box-shadow: 0px 4px 2px 0px #00000040 inset; + padding: 6px 10px; + border-radius: 99999px; + color: #fbe6be; + } +} diff --git a/src/components/surface/DataSurface/DataSurface.tsx b/src/components/surface/DataSurface/DataSurface.tsx new file mode 100644 index 0000000..4ec4587 --- /dev/null +++ b/src/components/surface/DataSurface/DataSurface.tsx @@ -0,0 +1,11 @@ +import classes from "./DataSurface.module.css"; +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function DataSurface(props: Props) { + return ; +} diff --git a/src/components/surface/DataSurface/index.ts b/src/components/surface/DataSurface/index.ts new file mode 100644 index 0000000..f30e35e --- /dev/null +++ b/src/components/surface/DataSurface/index.ts @@ -0,0 +1 @@ +export { default } from "./DataSurface"; diff --git a/src/components/surface/GlassSurface/GlassSurface.module.css b/src/components/surface/GlassSurface/GlassSurface.module.css new file mode 100644 index 0000000..0d10b55 --- /dev/null +++ b/src/components/surface/GlassSurface/GlassSurface.module.css @@ -0,0 +1,14 @@ +@layer base { + .glassSurface { + background: #ffffff1a; + backdrop-filter: blur(4px); + box-shadow: + 0px 2px 1px 0px #ffffff40 inset, + 0px 4px 4px 0px #00000073; + } + + .glassSurfaceContent { + background: #06060666; + box-shadow: 0px 1px 4px 1px #00000040 inset; + } +} diff --git a/src/components/surface/GlassSurface/GlassSurface.tsx b/src/components/surface/GlassSurface/GlassSurface.tsx new file mode 100644 index 0000000..4f415fb --- /dev/null +++ b/src/components/surface/GlassSurface/GlassSurface.tsx @@ -0,0 +1,16 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +import classes from "./GlassSurface.module.css"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function GlassSurface(props: Props) { + return ; +} + +export function GlassSurfaceContent(props: Props) { + return ; +} diff --git a/src/components/surface/GlassSurface/index.ts b/src/components/surface/GlassSurface/index.ts new file mode 100644 index 0000000..f9e41bc --- /dev/null +++ b/src/components/surface/GlassSurface/index.ts @@ -0,0 +1 @@ +export { default, GlassSurfaceContent } from "./GlassSurface"; diff --git a/src/components/surface/GreenSurface/GreenSurface.module.css b/src/components/surface/GreenSurface/GreenSurface.module.css new file mode 100644 index 0000000..1c3a45f --- /dev/null +++ b/src/components/surface/GreenSurface/GreenSurface.module.css @@ -0,0 +1,24 @@ +@reference "@/index.css"; + +@layer base { + .greenSurface { + background: #295a1c; + box-shadow: + 0px 2px 0px 0px #318a2d inset, + -1px 0px 0px 0px #00000059 inset, + 1px 0px 0px 0px #00000059 inset, + 0px -1px 0px 0px #00000059 inset; + border-radius: 14px; + padding: 4px 8px; + color: #befbbe; + } + + .greenSurfaceContent { + background: #2f1e0099; + box-shadow: + 0px 2px 3px 1px #00000040 inset, + 0px -1px 0px 0px #4fa146 inset; + padding: 4px; + border-radius: 9999px; + } +} diff --git a/src/components/surface/GreenSurface/GreenSurface.tsx b/src/components/surface/GreenSurface/GreenSurface.tsx new file mode 100644 index 0000000..5c2e1cd --- /dev/null +++ b/src/components/surface/GreenSurface/GreenSurface.tsx @@ -0,0 +1,16 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +import classes from "./GreenSurface.module.css"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function GreenSurface(props: Props) { + return ; +} + +export function GreenSurfaceContent(props: Props) { + return ; +} diff --git a/src/components/surface/GreenSurface/index.ts b/src/components/surface/GreenSurface/index.ts new file mode 100644 index 0000000..a5a82b7 --- /dev/null +++ b/src/components/surface/GreenSurface/index.ts @@ -0,0 +1 @@ +export { default, GreenSurfaceContent } from "./GreenSurface"; diff --git a/src/components/surface/LightSurface/LightSurface.module.css b/src/components/surface/LightSurface/LightSurface.module.css new file mode 100644 index 0000000..a043e9f --- /dev/null +++ b/src/components/surface/LightSurface/LightSurface.module.css @@ -0,0 +1,11 @@ +@layer base { + .lightSurface { + background: #fbe6be; + box-shadow: + 0px 2px 0px 0px #ffffffbf inset, + -1px 0px 0px 0px #00000059 inset, + 1px 0px 0px 0px #00000059 inset, + 0px -1px 0px 0px #00000059 inset; + color: #4b2c13; + } +} diff --git a/src/components/surface/LightSurface/LightSurface.tsx b/src/components/surface/LightSurface/LightSurface.tsx new file mode 100644 index 0000000..4db70d1 --- /dev/null +++ b/src/components/surface/LightSurface/LightSurface.tsx @@ -0,0 +1,11 @@ +import classes from "./LightSurface.module.css"; +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function LightSurface(props: Props) { + return ; +} diff --git a/src/components/surface/LightSurface/index.ts b/src/components/surface/LightSurface/index.ts new file mode 100644 index 0000000..3d89183 --- /dev/null +++ b/src/components/surface/LightSurface/index.ts @@ -0,0 +1 @@ +export { default } from "./LightSurface"; diff --git a/src/components/surface/RedSurface/RedSurface.module.css b/src/components/surface/RedSurface/RedSurface.module.css new file mode 100644 index 0000000..046d2b4 --- /dev/null +++ b/src/components/surface/RedSurface/RedSurface.module.css @@ -0,0 +1,22 @@ +@layer base { + .redSurface { + background: #5a2b1c; + box-shadow: + 0px 2px 0px 0px #8a392d inset, + -1px 0px 0px 0px #00000059 inset, + 1px 0px 0px 0px #00000059 inset, + 0px -1px 0px 0px #00000059 inset; + border-radius: 14px; + padding: 4px 8px; + color: #fbccbe; + } + + .redSurfaceContent { + background: #2f1e0099; + box-shadow: + 0px 2px 3px 1px #00000040 inset, + 0px -1px 0px 0px #a15746 inset; + padding: 4px; + border-radius: 9999px; + } +} diff --git a/src/components/surface/RedSurface/RedSurface.tsx b/src/components/surface/RedSurface/RedSurface.tsx new file mode 100644 index 0000000..597c689 --- /dev/null +++ b/src/components/surface/RedSurface/RedSurface.tsx @@ -0,0 +1,16 @@ +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +import classes from "./RedSurface.module.css"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function RedSurface(props: Props) { + return ; +} + +export function RedSurfaceContent(props: Props) { + return ; +} diff --git a/src/components/surface/RedSurface/index.ts b/src/components/surface/RedSurface/index.ts new file mode 100644 index 0000000..d98c307 --- /dev/null +++ b/src/components/surface/RedSurface/index.ts @@ -0,0 +1 @@ +export { default, RedSurfaceContent } from "./RedSurface"; diff --git a/src/components/surface/SectionSurface/SectionSurface.module.css b/src/components/surface/SectionSurface/SectionSurface.module.css new file mode 100644 index 0000000..23bee81 --- /dev/null +++ b/src/components/surface/SectionSurface/SectionSurface.module.css @@ -0,0 +1,13 @@ +@layer base { + .sectionSurface { + background: linear-gradient(180deg, #935a2b 0%, #6f4420 100%); + box-shadow: + -3px 0px 1px 0px #00000059 inset, + 3px 0px 1px 0px #00000059 inset, + 0px 4px 1px 0px #ffffff26 inset, + 0px -3px 1px 0px #00000059 inset; + border: 1px solid #511e00; + border-radius: 40px; + padding: 10px; + } +} diff --git a/src/components/surface/SectionSurface/SectionSurface.tsx b/src/components/surface/SectionSurface/SectionSurface.tsx new file mode 100644 index 0000000..369b0f4 --- /dev/null +++ b/src/components/surface/SectionSurface/SectionSurface.tsx @@ -0,0 +1,19 @@ +import classes from "./SectionSurface.module.css"; +import { motion, type HTMLMotionProps } from "motion/react"; +import clsx, { type ClassValue } from "clsx"; + +type Props = Omit, "className"> & { + className?: ClassValue; +}; + +export default function SectionSurface(props: Props) { + return ( + + ); +} diff --git a/src/components/surface/SectionSurface/index.ts b/src/components/surface/SectionSurface/index.ts new file mode 100644 index 0000000..34010a5 --- /dev/null +++ b/src/components/surface/SectionSurface/index.ts @@ -0,0 +1 @@ +export { default } from "./SectionSurface"; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index 575c53f..0000000 --- a/src/index.css +++ /dev/null @@ -1,4 +0,0 @@ -@import "tailwindcss"; - -@theme { -} diff --git a/src/main.tsx b/src/main.tsx index 422fb85..9b4a37d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client"; import { RouterProvider, createRouter } from "@tanstack/react-router"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import "./index.css"; +import "./styles/index.css"; import { routeTree } from "./routeTree.gen"; import "./i18next"; diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 59499d9..34ec39d 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -9,68 +9,180 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as AboutRouteImport } from './routes/about' -import { Route as IndexRouteImport } from './routes/index' +import { Route as TasksIndexRouteImport } from './routes/tasks/index' +import { Route as ShopIndexRouteImport } from './routes/shop/index' +import { Route as RouletteIndexRouteImport } from './routes/roulette/index' +import { Route as GameIndexRouteImport } from './routes/game/index' +import { Route as EarningsIndexRouteImport } from './routes/earnings/index' +import { Route as CashdeskIndexRouteImport } from './routes/cashdesk/index' +import { Route as ApiaryIndexRouteImport } from './routes/apiary/index' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', +const TasksIndexRoute = TasksIndexRouteImport.update({ + id: '/tasks/', + path: '/tasks/', getParentRoute: () => rootRouteImport, } as any) -const IndexRoute = IndexRouteImport.update({ - id: '/', - path: '/', +const ShopIndexRoute = ShopIndexRouteImport.update({ + id: '/shop/', + path: '/shop/', + getParentRoute: () => rootRouteImport, +} as any) +const RouletteIndexRoute = RouletteIndexRouteImport.update({ + id: '/roulette/', + path: '/roulette/', + getParentRoute: () => rootRouteImport, +} as any) +const GameIndexRoute = GameIndexRouteImport.update({ + id: '/game/', + path: '/game/', + getParentRoute: () => rootRouteImport, +} as any) +const EarningsIndexRoute = EarningsIndexRouteImport.update({ + id: '/earnings/', + path: '/earnings/', + getParentRoute: () => rootRouteImport, +} as any) +const CashdeskIndexRoute = CashdeskIndexRouteImport.update({ + id: '/cashdesk/', + path: '/cashdesk/', + getParentRoute: () => rootRouteImport, +} as any) +const ApiaryIndexRoute = ApiaryIndexRouteImport.update({ + id: '/apiary/', + path: '/apiary/', getParentRoute: () => rootRouteImport, } as any) export interface FileRoutesByFullPath { - '/': typeof IndexRoute - '/about': typeof AboutRoute + '/apiary/': typeof ApiaryIndexRoute + '/cashdesk/': typeof CashdeskIndexRoute + '/earnings/': typeof EarningsIndexRoute + '/game/': typeof GameIndexRoute + '/roulette/': typeof RouletteIndexRoute + '/shop/': typeof ShopIndexRoute + '/tasks/': typeof TasksIndexRoute } export interface FileRoutesByTo { - '/': typeof IndexRoute - '/about': typeof AboutRoute + '/apiary': typeof ApiaryIndexRoute + '/cashdesk': typeof CashdeskIndexRoute + '/earnings': typeof EarningsIndexRoute + '/game': typeof GameIndexRoute + '/roulette': typeof RouletteIndexRoute + '/shop': typeof ShopIndexRoute + '/tasks': typeof TasksIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport - '/': typeof IndexRoute - '/about': typeof AboutRoute + '/apiary/': typeof ApiaryIndexRoute + '/cashdesk/': typeof CashdeskIndexRoute + '/earnings/': typeof EarningsIndexRoute + '/game/': typeof GameIndexRoute + '/roulette/': typeof RouletteIndexRoute + '/shop/': typeof ShopIndexRoute + '/tasks/': typeof TasksIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' | '/about' + fullPaths: + | '/apiary/' + | '/cashdesk/' + | '/earnings/' + | '/game/' + | '/roulette/' + | '/shop/' + | '/tasks/' fileRoutesByTo: FileRoutesByTo - to: '/' | '/about' - id: '__root__' | '/' | '/about' + to: + | '/apiary' + | '/cashdesk' + | '/earnings' + | '/game' + | '/roulette' + | '/shop' + | '/tasks' + id: + | '__root__' + | '/apiary/' + | '/cashdesk/' + | '/earnings/' + | '/game/' + | '/roulette/' + | '/shop/' + | '/tasks/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { - IndexRoute: typeof IndexRoute - AboutRoute: typeof AboutRoute + ApiaryIndexRoute: typeof ApiaryIndexRoute + CashdeskIndexRoute: typeof CashdeskIndexRoute + EarningsIndexRoute: typeof EarningsIndexRoute + GameIndexRoute: typeof GameIndexRoute + RouletteIndexRoute: typeof RouletteIndexRoute + ShopIndexRoute: typeof ShopIndexRoute + TasksIndexRoute: typeof TasksIndexRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport + '/tasks/': { + id: '/tasks/' + path: '/tasks' + fullPath: '/tasks/' + preLoaderRoute: typeof TasksIndexRouteImport parentRoute: typeof rootRouteImport } - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/shop/': { + id: '/shop/' + path: '/shop' + fullPath: '/shop/' + preLoaderRoute: typeof ShopIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/roulette/': { + id: '/roulette/' + path: '/roulette' + fullPath: '/roulette/' + preLoaderRoute: typeof RouletteIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/game/': { + id: '/game/' + path: '/game' + fullPath: '/game/' + preLoaderRoute: typeof GameIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/earnings/': { + id: '/earnings/' + path: '/earnings' + fullPath: '/earnings/' + preLoaderRoute: typeof EarningsIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/cashdesk/': { + id: '/cashdesk/' + path: '/cashdesk' + fullPath: '/cashdesk/' + preLoaderRoute: typeof CashdeskIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/apiary/': { + id: '/apiary/' + path: '/apiary' + fullPath: '/apiary/' + preLoaderRoute: typeof ApiaryIndexRouteImport parentRoute: typeof rootRouteImport } } } const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - AboutRoute: AboutRoute, + ApiaryIndexRoute: ApiaryIndexRoute, + CashdeskIndexRoute: CashdeskIndexRoute, + EarningsIndexRoute: EarningsIndexRoute, + GameIndexRoute: GameIndexRoute, + RouletteIndexRoute: RouletteIndexRoute, + ShopIndexRoute: ShopIndexRoute, + TasksIndexRoute: TasksIndexRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 0521b63..e42a063 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,20 +1,18 @@ -import { createRootRoute, Link, Outlet } from "@tanstack/react-router"; +import { createRootRoute, Outlet, useNavigate } from "@tanstack/react-router"; import { TanStackDevtools } from "@tanstack/react-devtools"; import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools"; +import { useEffect } from "react"; + +import { Route as GameRoute } from "./game"; export const Route = createRootRoute({ + notFoundComponent: () => { + const navigate = useNavigate(); + useEffect(() => void navigate({ to: GameRoute.to }), [navigate]); + }, component: () => ( <> -
- - Home - {" "} - - About - -
-
Hello from About!
, -}); diff --git a/src/routes/apiary/-/ApiaryRoute.tsx b/src/routes/apiary/-/ApiaryRoute.tsx new file mode 100644 index 0000000..d2a62c7 --- /dev/null +++ b/src/routes/apiary/-/ApiaryRoute.tsx @@ -0,0 +1,5 @@ +import SectionSurface from "@components/surface/SectionSurface"; + +export default function ApiaryRoute() { + return Hello "/apiary"!; +} diff --git a/src/routes/apiary/index.tsx b/src/routes/apiary/index.tsx new file mode 100644 index 0000000..54605de --- /dev/null +++ b/src/routes/apiary/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from "@tanstack/react-router"; +import component from "./-/ApiaryRoute"; +export const Route = createFileRoute("/apiary/")({ component }); diff --git a/src/routes/cashdesk/-/CashdeskRoute.tsx b/src/routes/cashdesk/-/CashdeskRoute.tsx new file mode 100644 index 0000000..95444da --- /dev/null +++ b/src/routes/cashdesk/-/CashdeskRoute.tsx @@ -0,0 +1,5 @@ +import SectionSurface from "@components/surface/SectionSurface"; + +export default function CashdeskRoute() { + return Hello "/cashdesk"!; +} diff --git a/src/routes/cashdesk/index.tsx b/src/routes/cashdesk/index.tsx new file mode 100644 index 0000000..93fb8f4 --- /dev/null +++ b/src/routes/cashdesk/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from "@tanstack/react-router"; +import component from "./-/CashdeskRoute"; +export const Route = createFileRoute("/cashdesk/")({ component }); diff --git a/src/routes/earnings/-/EarningsRoute.tsx b/src/routes/earnings/-/EarningsRoute.tsx new file mode 100644 index 0000000..e29b674 --- /dev/null +++ b/src/routes/earnings/-/EarningsRoute.tsx @@ -0,0 +1,5 @@ +import SectionSurface from "@components/surface/SectionSurface"; + +export default function EarningsRoute() { + return Hello "/earnings"!; +} diff --git a/src/routes/earnings/index.tsx b/src/routes/earnings/index.tsx new file mode 100644 index 0000000..4c77a65 --- /dev/null +++ b/src/routes/earnings/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from "@tanstack/react-router"; +import component from "./-/EarningsRoute"; +export const Route = createFileRoute("/earnings/")({ component }); diff --git a/src/routes/game/-/GameRoute.tsx b/src/routes/game/-/GameRoute.tsx new file mode 100644 index 0000000..14a5f73 --- /dev/null +++ b/src/routes/game/-/GameRoute.tsx @@ -0,0 +1,94 @@ +import { useState } from "react"; + +import SectionSurface from "@components/surface/SectionSurface"; +import { Link } from "@tanstack/react-router"; +import DarkSurface from "@components/surface/DarkSurface"; +import ContentSurface from "@components/surface/ContentSurface"; +import LightSurface from "@components/surface/LightSurface"; +import DataSurface from "@components/surface/DataSurface"; +import GlassSurface, { GlassSurfaceContent } from "@components/surface/GlassSurface"; +import Button from "@components/atoms/Button"; +import TabSelector from "@components/atoms/TabSelector"; +import Progress from "@components/atoms/Progress"; +import RangeInput from "@components/form/RangeInput"; +import SwitchInput from "@components/form/SwitchInput"; +import TextInput from "@components/form/TextInput"; +import NumberInput from "@components/form/NumberInput"; +import TextAreaInput from "@components/form/TextAreaInput"; +import ActionModal from "@components/modal/ActionModal"; + +const TABS = [ + { key: "tab1", title: "Tab 1" }, + { key: "tab2", title: "Tab 2" }, + { key: "tab3", title: "Tab 3" }, +]; + +export default function GameRoute() { + const [activeTab, setActiveTab] = useState(TABS[0].key); + const [progressValue, setProgressValue] = useState(0); + const [switchValue, setSwitchValue] = useState(false); + const [modalOpen, setModalOpen] = useState(false); + + return ( + + + + + + Hello "/game"! + 100$ + + + Roulette + 100$ + + + + + + + + + + + + + + + setProgressValue(Number(e.target.value))} + min={0} + max={10000} + /> + + + + + + +
+ {[1, 2, 3, 4, 5].map((i) => ( + + + 10 + + + ))} +
+ setModalOpen(false)} + onConfirm={() => setModalOpen(false)} + confirmText="Confirm" + /> +
+ ); +} diff --git a/src/routes/game/index.tsx b/src/routes/game/index.tsx new file mode 100644 index 0000000..b95b38f --- /dev/null +++ b/src/routes/game/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from "@tanstack/react-router"; +import component from "./-/GameRoute"; +export const Route = createFileRoute("/game/")({ component }); diff --git a/src/routes/index.tsx b/src/routes/index.tsx deleted file mode 100644 index 18a6785..0000000 --- a/src/routes/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { useTranslation } from "react-i18next"; -import { useEffect, useState } from "react"; -import { languages } from "../i18next"; - -export const Route = createFileRoute("/")({ - component: () => { - const { t, i18n } = useTranslation(); - - const [langIdx, setLangIdx] = useState(0); - useEffect(() => { - i18n.changeLanguage(languages[langIdx].key); - }, [langIdx, i18n]); - - return ( -
-

- {t("hello")} - -

-
- ); - }, -}); diff --git a/src/routes/roulette/-/RouletteRoute.tsx b/src/routes/roulette/-/RouletteRoute.tsx new file mode 100644 index 0000000..14ad64d --- /dev/null +++ b/src/routes/roulette/-/RouletteRoute.tsx @@ -0,0 +1,11 @@ +import SectionSurface from "@components/surface/SectionSurface"; +import { Link } from "@tanstack/react-router"; + +export default function RouletteRoute() { + return ( + + Hello "/roulette"! + Game + + ); +} diff --git a/src/routes/roulette/index.tsx b/src/routes/roulette/index.tsx new file mode 100644 index 0000000..f416996 --- /dev/null +++ b/src/routes/roulette/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from "@tanstack/react-router"; +import component from "./-/RouletteRoute"; +export const Route = createFileRoute("/roulette/")({ component }); diff --git a/src/routes/shop/-/ShopRoute.tsx b/src/routes/shop/-/ShopRoute.tsx new file mode 100644 index 0000000..3d93354 --- /dev/null +++ b/src/routes/shop/-/ShopRoute.tsx @@ -0,0 +1,5 @@ +import SectionSurface from "@components/surface/SectionSurface"; + +export default function ShopRoute() { + return Shop; +} diff --git a/src/routes/shop/index.tsx b/src/routes/shop/index.tsx new file mode 100644 index 0000000..3a35aee --- /dev/null +++ b/src/routes/shop/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from "@tanstack/react-router"; +import component from "./-/ShopRoute"; +export const Route = createFileRoute("/shop/")({ component }); diff --git a/src/routes/tasks/-/TasksRoute.tsx b/src/routes/tasks/-/TasksRoute.tsx new file mode 100644 index 0000000..a69b134 --- /dev/null +++ b/src/routes/tasks/-/TasksRoute.tsx @@ -0,0 +1,5 @@ +import SectionSurface from "@components/surface/SectionSurface"; + +export default function TasksRoute() { + return Hello "/tasks"!; +} diff --git a/src/routes/tasks/index.tsx b/src/routes/tasks/index.tsx new file mode 100644 index 0000000..c50e0e5 --- /dev/null +++ b/src/routes/tasks/index.tsx @@ -0,0 +1,3 @@ +import { createFileRoute } from "@tanstack/react-router"; +import component from "./-/TasksRoute"; +export const Route = createFileRoute("/tasks/")({ component }); diff --git a/src/styles/fonts/BalsamiqSans.css b/src/styles/fonts/BalsamiqSans.css new file mode 100644 index 0000000..b016752 --- /dev/null +++ b/src/styles/fonts/BalsamiqSans.css @@ -0,0 +1,31 @@ +@font-face { + font-family: "BalsamiqSans"; + src: url("/fonts/BalsamicSans/BalsamiqSans-Regular.ttf") format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "BalsamiqSans"; + src: url("/fonts/BalsamicSans/BalsamiqSans-Italic.ttf") format("truetype"); + font-weight: 400; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "BalsamiqSans"; + src: url("/fonts/BalsamicSans/BalsamiqSans-Bold.ttf") format("truetype"); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "BalsamiqSans"; + src: url("/fonts/BalsamicSans/BalsamiqSans-BoldItalic.ttf") format("truetype"); + font-weight: 700; + font-style: italic; + font-display: swap; +} diff --git a/src/styles/index.css b/src/styles/index.css new file mode 100644 index 0000000..a1e22fb --- /dev/null +++ b/src/styles/index.css @@ -0,0 +1,22 @@ +@import "tailwindcss"; + +@import "./fonts/BalsamiqSans.css"; + +@theme { +} + +html, +body { + @apply w-dvw h-dvh bg-top-left bg-green-300; + font-family: "BalsamiqSans", sans-serif; +} + +#root { + @apply w-full h-full max-w-150 m-auto overflow-hidden relative; +} + +button { + &:focus-visible { + outline: none; + } +} diff --git a/tsconfig.app.json b/tsconfig.app.json index d9c2316..3f365de 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -7,7 +7,6 @@ "module": "ESNext", "types": ["vite/client"], "skipLibCheck": true, - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, @@ -15,12 +14,15 @@ "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", - /* Linting */ "strict": true, "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + "paths": { + "@/*": ["src/*"], + "@components/*": ["src/components/*"] + } }, "include": ["src"] } diff --git a/vite.config.ts b/vite.config.ts index 8f74407..06ba4c3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,6 +9,7 @@ import i18nextConfig, { LOCALES_PATH, LOCAL_LOCALES_PATH, } from "./i18next.config"; +import path from "node:path"; export default defineConfig({ plugins: [ @@ -46,4 +47,10 @@ export default defineConfig({ __LOCALES_PATH__: JSON.stringify(LOCALES_PATH), __DEFAULT_LANG__: JSON.stringify(DEFAULT_LANGUAGE), }, + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + "@components": path.resolve(__dirname, "./src/components"), + }, + }, });