├── .gitattributes
├── .gitignore
├── Dockerfile
├── LICENSE
├── LOCALIZATION_PLAN.md
├── README.md
├── TECHNICAL_DESIGN.md
├── components.json
├── convert_themes.py
├── electron
├── .gitignore
├── icon.ico
├── main.js
└── package.json
├── eslint.config.js
├── font_install.bat
├── index.html
├── jsconfig.json
├── landingpage
├── ai_writer_dark.png
├── ai_writer_light.png
├── chat_dark.png
├── chat_light.png
├── concept_write_dark.png
├── concept_write_light.png
├── de
│ └── index.html
├── icon.PNG
├── index.html
├── overview_plan_dark.png
├── overview_plan_light.png
├── phone.PNG
├── rabbit.svg
├── rocket.svg
├── ru
│ └── index.html
├── script.js
├── style.css
├── themes2.png
├── themes_dark.png
├── themes_light.png
├── vi
│ └── index.html
└── zh
│ └── index.html
├── mac-build
├── README.md
├── build
│ ├── main.js
│ └── static
│ │ ├── assets
│ │ ├── index-B6url4st.css
│ │ ├── index-tXyTnOTH.js
│ │ ├── inter-cyrillic-400-normal-BLGc9T1a.woff2
│ │ ├── inter-cyrillic-400-normal-ZzOtrSSW.woff
│ │ ├── inter-cyrillic-700-normal-Bc8_fv8J.woff
│ │ ├── inter-cyrillic-700-normal-bGtGjVdZ.woff2
│ │ ├── inter-cyrillic-ext-400-normal-BPnxn4xp.woff
│ │ ├── inter-cyrillic-ext-400-normal-Dc4VJyIJ.woff2
│ │ ├── inter-cyrillic-ext-700-normal-Ced3hgUT.woff
│ │ ├── inter-cyrillic-ext-700-normal-ClVoMEGq.woff2
│ │ ├── inter-greek-400-normal-BZzXV7-1.woff
│ │ ├── inter-greek-400-normal-DxZsaF_h.woff2
│ │ ├── inter-greek-700-normal-BRYTaFLL.woff
│ │ ├── inter-greek-700-normal-Cxpycf-U.woff2
│ │ ├── inter-greek-ext-400-normal-Bput3-QP.woff2
│ │ ├── inter-greek-ext-400-normal-DCpCPQOf.woff
│ │ ├── inter-greek-ext-700-normal-DXvzx4Na.woff
│ │ ├── inter-greek-ext-700-normal-SzCdnevJ.woff2
│ │ ├── inter-latin-400-normal-BOOGhInR.woff2
│ │ ├── inter-latin-400-normal-gitzw0hO.woff
│ │ ├── inter-latin-700-normal-B8MtJ_2k.woff
│ │ ├── inter-latin-700-normal-Sckx8rpT.woff2
│ │ ├── inter-latin-ext-400-normal-C1t-h-pH.woff
│ │ ├── inter-latin-ext-400-normal-hnt3BR84.woff2
│ │ ├── inter-latin-ext-700-normal-6V9MnIL5.woff
│ │ ├── inter-latin-ext-700-normal-CzikT_rs.woff2
│ │ ├── inter-vietnamese-400-normal-BUNmGMP1.woff
│ │ ├── inter-vietnamese-400-normal-DMkecbls.woff2
│ │ ├── inter-vietnamese-700-normal-CGpBpxLq.woff2
│ │ ├── inter-vietnamese-700-normal-dAnkLlTo.woff
│ │ ├── lato-latin-400-normal-B11PyLys.woff
│ │ ├── lato-latin-400-normal-BEhtfm5r.woff2
│ │ ├── lato-latin-700-normal-BUGMgin4.woff2
│ │ ├── lato-latin-700-normal-DAdL7O4w.woff
│ │ ├── lato-latin-ext-400-normal-C8eBZ-j2.woff2
│ │ ├── lato-latin-ext-700-normal-C5WWfNwx.woff2
│ │ ├── montserrat-cyrillic-400-normal-BNZrkLa0.woff2
│ │ ├── montserrat-cyrillic-400-normal-DpKvHuFb.woff
│ │ ├── montserrat-cyrillic-700-normal-CF3jbqy3.woff
│ │ ├── montserrat-cyrillic-700-normal-DO8I1zZZ.woff2
│ │ ├── montserrat-cyrillic-ext-400-normal-BWKiD3Kv.woff
│ │ ├── montserrat-cyrillic-ext-400-normal-C8cdMEH1.woff2
│ │ ├── montserrat-cyrillic-ext-700-normal-B5jSujIz.woff
│ │ ├── montserrat-cyrillic-ext-700-normal-D3s9QaN5.woff2
│ │ ├── montserrat-latin-400-normal-CIMKXMQI.woff2
│ │ ├── montserrat-latin-400-normal-Sf4eHQyG.woff
│ │ ├── montserrat-latin-700-normal-BYRy5CoC.woff2
│ │ ├── montserrat-latin-700-normal-DHOpyQza.woff
│ │ ├── montserrat-latin-ext-400-normal-Cd0_BKvP.woff2
│ │ ├── montserrat-latin-ext-400-normal-Cy-iUbkq.woff
│ │ ├── montserrat-latin-ext-700-normal-DTfQ8iG5.woff2
│ │ ├── montserrat-latin-ext-700-normal-DdSde0HZ.woff
│ │ ├── montserrat-vietnamese-400-normal-B33MQ3yC.woff
│ │ ├── montserrat-vietnamese-400-normal-CgEMIOQI.woff2
│ │ ├── montserrat-vietnamese-700-normal-D8RPk-a5.woff2
│ │ ├── montserrat-vietnamese-700-normal-DUoPxeMJ.woff
│ │ ├── open-sans-cyrillic-400-normal-CzqaL1iZ.woff2
│ │ ├── open-sans-cyrillic-400-normal-kTLgxuft.woff
│ │ ├── open-sans-cyrillic-700-normal-BUZkPNet.woff2
│ │ ├── open-sans-cyrillic-700-normal-COG0hEe-.woff
│ │ ├── open-sans-cyrillic-ext-400-normal-UJz4QWDf.woff
│ │ ├── open-sans-cyrillic-ext-400-normal-pR6qsPVh.woff2
│ │ ├── open-sans-cyrillic-ext-700-normal-DlRmNm63.woff2
│ │ ├── open-sans-cyrillic-ext-700-normal-cxRSGggR.woff
│ │ ├── open-sans-greek-400-normal-B-s2YDfe.woff
│ │ ├── open-sans-greek-400-normal-BhbJw-ID.woff2
│ │ ├── open-sans-greek-700-normal-3ZuBy_Ld.woff
│ │ ├── open-sans-greek-700-normal-DZLtG1eN.woff2
│ │ ├── open-sans-hebrew-400-normal-CyH9M0u4.woff
│ │ ├── open-sans-hebrew-400-normal-DcuuLq9a.woff2
│ │ ├── open-sans-hebrew-700-normal-ByxHPEwX.woff2
│ │ ├── open-sans-hebrew-700-normal-CdblwzFU.woff
│ │ ├── open-sans-latin-400-normal-Cjao0ETp.woff2
│ │ ├── open-sans-latin-400-normal-Dxjvn725.woff
│ │ ├── open-sans-latin-700-normal-B572f0fn.woff
│ │ ├── open-sans-latin-700-normal-C2okHfb_.woff2
│ │ ├── open-sans-latin-ext-400-normal-BXLcHD4a.woff2
│ │ ├── open-sans-latin-ext-400-normal-DkLPCGEk.woff
│ │ ├── open-sans-latin-ext-700-normal-C-A0l_kt.woff2
│ │ ├── open-sans-latin-ext-700-normal-h5QwRtwi.woff
│ │ ├── open-sans-math-400-normal-Bw1rHEuQ.woff
│ │ ├── open-sans-math-400-normal-CoiAIOH6.woff2
│ │ ├── open-sans-math-700-normal-C_zQ2Upn.woff2
│ │ ├── open-sans-math-700-normal-D9Ix8Mnc.woff
│ │ ├── open-sans-symbols-400-normal-BczhyrVJ.woff2
│ │ ├── open-sans-symbols-400-normal-D7CElg7Y.woff
│ │ ├── open-sans-symbols-700-normal-9UGYaiOr.woff2
│ │ ├── open-sans-symbols-700-normal-BkRMExv1.woff
│ │ ├── open-sans-vietnamese-400-normal-CWzy0rt5.woff2
│ │ ├── open-sans-vietnamese-400-normal-Coa9Lb0B.woff
│ │ ├── open-sans-vietnamese-700-normal-KVBVKU1x.woff2
│ │ ├── open-sans-vietnamese-700-normal-js5t13xB.woff
│ │ ├── poppins-devanagari-400-normal-CJDn6rn8.woff2
│ │ ├── poppins-devanagari-400-normal-CqVvlrh5.woff
│ │ ├── poppins-devanagari-700-normal-O-jipLrW.woff2
│ │ ├── poppins-devanagari-700-normal-fHs-vx92.woff
│ │ ├── poppins-latin-400-normal-BOb3E3N0.woff
│ │ ├── poppins-latin-400-normal-cpxAROuN.woff2
│ │ ├── poppins-latin-700-normal-BVuQR_eA.woff
│ │ ├── poppins-latin-700-normal-Qrb0O0WB.woff2
│ │ ├── poppins-latin-ext-400-normal-DaBSavcJ.woff
│ │ ├── poppins-latin-ext-400-normal-by3JarPu.woff2
│ │ ├── poppins-latin-ext-700-normal-DctTR6Tg.woff
│ │ ├── poppins-latin-ext-700-normal-cby-RkWa.woff2
│ │ ├── roboto-cyrillic-400-normal-DAIM1_dR.woff2
│ │ ├── roboto-cyrillic-400-normal-Dry59Hjn.woff
│ │ ├── roboto-cyrillic-700-normal-CB1Rmiii.woff
│ │ ├── roboto-cyrillic-700-normal-CzEIZVQR.woff2
│ │ ├── roboto-cyrillic-ext-400-normal-BxX1-eA_.woff
│ │ ├── roboto-cyrillic-ext-400-normal-DzMWdK87.woff2
│ │ ├── roboto-cyrillic-ext-700-normal-CIu0AXX2.woff
│ │ ├── roboto-cyrillic-ext-700-normal-D_fA0fHY.woff2
│ │ ├── roboto-greek-400-normal-CAI06USH.woff
│ │ ├── roboto-greek-400-normal-jFM2czAU.woff2
│ │ ├── roboto-greek-700-normal-DCrt6r9F.woff
│ │ ├── roboto-greek-700-normal-DpKAje7q.woff2
│ │ ├── roboto-latin-400-normal-551zQQ7R.woff
│ │ ├── roboto-latin-400-normal-CNwBRw8h.woff2
│ │ ├── roboto-latin-700-normal-CTLkNcF_.woff
│ │ ├── roboto-latin-700-normal-DZr4b_KL.woff2
│ │ ├── roboto-latin-ext-400-normal-ZYmyxeOy.woff2
│ │ ├── roboto-latin-ext-400-normal-uRIBRJt5.woff
│ │ ├── roboto-latin-ext-700-normal-BNPgmEQS.woff2
│ │ ├── roboto-latin-ext-700-normal-Cnx4FGpK.woff
│ │ ├── roboto-math-400-normal-B3wgz80t.woff2
│ │ ├── roboto-math-400-normal-DHrwdhE6.woff
│ │ ├── roboto-math-700-normal-CpTCM92H.woff
│ │ ├── roboto-math-700-normal-xbpggnJp.woff2
│ │ ├── roboto-symbols-400-normal-bG5rsNFs.woff
│ │ ├── roboto-symbols-400-normal-fF1SLJBj.woff2
│ │ ├── roboto-symbols-700-normal-B2QKVW64.woff
│ │ ├── roboto-symbols-700-normal-DKkQdRpM.woff2
│ │ ├── roboto-vietnamese-400-normal-CDDxGrUb.woff2
│ │ ├── roboto-vietnamese-400-normal-DgufTq8s.woff
│ │ ├── roboto-vietnamese-700-normal-BFWtvCOj.woff2
│ │ ├── roboto-vietnamese-700-normal-ChAl_rRV.woff
│ │ ├── source-sans-3-cyrillic-400-normal-7kQcAFpS.woff
│ │ ├── source-sans-3-cyrillic-400-normal-DjhbW-4D.woff2
│ │ ├── source-sans-3-cyrillic-700-normal-CIuudVLB.woff2
│ │ ├── source-sans-3-cyrillic-700-normal-DYE6gq6k.woff
│ │ ├── source-sans-3-cyrillic-ext-400-normal-BwJdxs7r.woff2
│ │ ├── source-sans-3-cyrillic-ext-400-normal-Cd63HKDh.woff
│ │ ├── source-sans-3-cyrillic-ext-700-normal-B6RPsSdD.woff
│ │ ├── source-sans-3-cyrillic-ext-700-normal-BhUSqjb8.woff2
│ │ ├── source-sans-3-greek-400-normal-DJm9Ud5h.woff
│ │ ├── source-sans-3-greek-400-normal-DZReXJla.woff2
│ │ ├── source-sans-3-greek-700-normal-BWdBblwd.woff
│ │ ├── source-sans-3-greek-700-normal-C8Tp2qRF.woff2
│ │ ├── source-sans-3-greek-ext-400-normal-BI1kUwmR.woff
│ │ ├── source-sans-3-greek-ext-400-normal-CpIwTHMM.woff2
│ │ ├── source-sans-3-greek-ext-700-normal-C6ITAlBU.woff
│ │ ├── source-sans-3-greek-ext-700-normal-Dd6uIcuy.woff2
│ │ ├── source-sans-3-latin-400-normal-AFMiCETP.woff
│ │ ├── source-sans-3-latin-400-normal-DQi5PRDE.woff2
│ │ ├── source-sans-3-latin-700-normal-BBbVrHF_.woff
│ │ ├── source-sans-3-latin-700-normal-CkzU-mV9.woff2
│ │ ├── source-sans-3-latin-ext-400-normal-BGPHfnm_.woff
│ │ ├── source-sans-3-latin-ext-400-normal-UNsKFIBq.woff2
│ │ ├── source-sans-3-latin-ext-700-normal-6Xi0A2be.woff2
│ │ ├── source-sans-3-latin-ext-700-normal-ChH_v7nA.woff
│ │ ├── source-sans-3-vietnamese-400-normal-CwtaIDr2.woff2
│ │ ├── source-sans-3-vietnamese-400-normal-dYp9C364.woff
│ │ ├── source-sans-3-vietnamese-700-normal-BHxM10oo.woff
│ │ └── source-sans-3-vietnamese-700-normal-Bgamivog.woff2
│ │ ├── index.html
│ │ └── rabbit.svg
├── package-lock.json
├── package.json
├── resources
│ └── icon.icns
└── scripts
│ ├── create-mac-icon.sh
│ └── prepare-build.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── discord.svg
├── github.svg
└── rabbit.svg
├── src
├── App.css
├── App.jsx
├── CODE REVIEW.md
├── RootApp.jsx
├── assets
│ └── react.svg
├── components
│ ├── ai
│ │ ├── AIChatModal.jsx
│ │ ├── AINovelWriterModal.jsx
│ │ ├── AISuggestionModal.jsx
│ │ └── FullscreenTextareaEditModal.jsx
│ ├── concept
│ │ ├── ConceptCacheList.jsx
│ │ ├── ConceptFormModal.jsx
│ │ ├── CreateConceptModal.jsx
│ │ ├── ManageTemplatesModal.jsx
│ │ └── TemplateFormModal.jsx
│ ├── novel
│ │ ├── AddNewNovelCard.jsx
│ │ ├── CreateNovelFormModal.jsx
│ │ ├── ExportModal.jsx
│ │ ├── NovelCard.jsx
│ │ ├── NovelGridView.jsx
│ │ └── NovelOverviewTab.jsx
│ ├── plan
│ │ ├── ActFormModal.jsx
│ │ ├── ChapterFormModal.jsx
│ │ ├── ImportOutlineModal.jsx
│ │ ├── PlanView.jsx
│ │ └── SceneFormModal.jsx
│ ├── settings
│ │ ├── EndpointProfileFormModal.jsx
│ │ ├── FontSettingsControl.jsx
│ │ ├── LanguageSwitcher.jsx
│ │ ├── SettingsView.jsx
│ │ └── ThemeEditor.jsx
│ ├── ui
│ │ ├── ConfirmModal.jsx
│ │ ├── accordion.jsx
│ │ ├── alert-dialog.jsx
│ │ ├── alert.jsx
│ │ ├── aspect-ratio.jsx
│ │ ├── avatar.jsx
│ │ ├── badge.jsx
│ │ ├── breadcrumb.jsx
│ │ ├── button.jsx
│ │ ├── calendar.jsx
│ │ ├── card.jsx
│ │ ├── carousel.jsx
│ │ ├── chart.jsx
│ │ ├── checkbox.jsx
│ │ ├── collapsible.jsx
│ │ ├── command.jsx
│ │ ├── context-menu.jsx
│ │ ├── dialog.jsx
│ │ ├── drawer.jsx
│ │ ├── dropdown-menu.jsx
│ │ ├── form.jsx
│ │ ├── hover-card.jsx
│ │ ├── input-otp.jsx
│ │ ├── input.jsx
│ │ ├── label.jsx
│ │ ├── menubar.jsx
│ │ ├── navigation-menu.jsx
│ │ ├── pagination.jsx
│ │ ├── popover.jsx
│ │ ├── progress.jsx
│ │ ├── radio-group.jsx
│ │ ├── resizable.jsx
│ │ ├── scroll-area.jsx
│ │ ├── select.jsx
│ │ ├── separator.jsx
│ │ ├── sheet.jsx
│ │ ├── skeleton.jsx
│ │ ├── slider.jsx
│ │ ├── sonner.jsx
│ │ ├── switch.jsx
│ │ ├── table.jsx
│ │ ├── tabs.jsx
│ │ ├── textarea.jsx
│ │ ├── toast.jsx
│ │ ├── toaster.jsx
│ │ ├── toggle-group.jsx
│ │ ├── toggle.jsx
│ │ └── tooltip.jsx
│ └── write
│ │ ├── NovelOutlinePopover.jsx
│ │ └── WriteView.jsx
├── context
│ ├── DataContext.jsx
│ └── SettingsContext.jsx
├── data
│ ├── conceptTemplates.js
│ ├── models.js
│ └── themePresets.js
├── hooks
│ └── use-toast.js
├── i18n.js
├── index.css
├── lib
│ ├── aiContextUtils.js
│ ├── indexedDb.js
│ └── utils.js
├── locales
│ ├── cn
│ │ └── translation.json
│ ├── de
│ │ └── translation.json
│ ├── en
│ │ └── translation.json
│ ├── es
│ │ └── translation.json
│ ├── ru
│ │ └── translation.json
│ └── vi
│ │ └── translation.json
└── main.jsx
├── tailwind.config.js
└── vite.config.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | stuff
11 | node_modules
12 | dist
13 | dist-ssr
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Stage 1: Build the application
2 | FROM node:20-alpine AS builder
3 | WORKDIR /app
4 |
5 | # Copy package.json and package-lock.json (or yarn.lock)
6 | COPY package*.json ./
7 |
8 | # Install dependencies
9 | RUN npm install
10 |
11 | # Copy the rest of the application code
12 | COPY . .
13 |
14 | # Build the application
15 | RUN npm run build
16 |
17 | # Stage 2: Serve the application
18 | FROM node:20-alpine
19 | WORKDIR /app
20 |
21 | # Copy built assets from the builder stage
22 | COPY --from=builder /app/dist ./dist
23 | COPY package*.json ./
24 |
25 | # Install only production dependencies (if any are needed by the server, though vite preview is self-contained)
26 | # For vite preview, it's often not strictly necessary to run npm install again if vite is a devDependency,
27 | # but to be safe and ensure vite is available, we can install it.
28 | RUN npm install --omit=dev vite # Install vite itself
29 |
30 | # Expose the port the app runs on. Vite preview defaults to 4173.
31 | # If you want to use a different port, you can change it here and in the CMD.
32 | EXPOSE 4173
33 |
34 | # Command to run the application
35 | # The '--host' flag makes Vite listen on all available network interfaces (0.0.0.0)
36 | # This is crucial for Docker port mapping to work.
37 | CMD ["npm", "run", "preview", "--", "--host", "--port", "4173"]
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Nguyen Phan
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.
22 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": false,
5 | "tsx": false,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/index.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/electron/.gitignore:
--------------------------------------------------------------------------------
1 | static
2 | dist
3 | node_modules
--------------------------------------------------------------------------------
/electron/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/electron/icon.ico
--------------------------------------------------------------------------------
/electron/main.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow } = require('electron')
2 | const path = require('path')
3 |
4 | function createWindow () {
5 | const win = new BrowserWindow({
6 | width: 1440,
7 | height: 810,
8 | autoHideMenuBar: true,
9 | icon: path.join(__dirname, 'icon.ico'),
10 | webPreferences: {
11 | nodeIntegration: false,
12 | contextIsolation: true
13 | }
14 | })
15 |
16 | win.loadFile(path.join(__dirname, 'static', 'index.html'))
17 | }
18 |
19 | app.whenReady().then(() => {
20 | createWindow()
21 |
22 | app.on('activate', () => {
23 | if (BrowserWindow.getAllWindows().length === 0) {
24 | createWindow()
25 | }
26 | })
27 | })
28 |
29 | app.on('window-all-closed', () => {
30 | if (process.platform !== 'darwin') {
31 | app.quit()
32 | }
33 | })
--------------------------------------------------------------------------------
/electron/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "plotbunni",
3 | "version": "1.1.0",
4 | "description": "Plot Bunni",
5 | "main": "main.js",
6 | "scripts": {
7 | "start": "electron .",
8 | "build:installer": "electron-builder --win nsis",
9 | "build:portable": "electron-builder --win portable"
10 | },
11 | "build": {
12 | "appId": "com.fyrean.plotbunni",
13 | "productName": "Plot Bunni",
14 | "files": [
15 | "main.js",
16 | "static/**/*",
17 | "icon.ico"
18 | ],
19 | "win": {
20 | "icon": "icon.ico"
21 | },
22 | "compression": "maximum"
23 | },
24 | "author": "",
25 | "license": "ISC",
26 | "devDependencies": {
27 | "electron": "^28.3.3",
28 | "electron-builder": "^24.13.3"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import react from 'eslint-plugin-react'
4 | import reactHooks from 'eslint-plugin-react-hooks'
5 | import reactRefresh from 'eslint-plugin-react-refresh'
6 |
7 | export default [
8 | {
9 | files: ['**/*.{js,jsx}'],
10 | ignores: ['dist'],
11 | languageOptions: {
12 | ecmaVersion: 2020,
13 | globals: globals.browser,
14 | parserOptions: {
15 | ecmaVersion: 'latest',
16 | ecmaFeatures: { jsx: true },
17 | sourceType: 'module',
18 | },
19 | },
20 | settings: { react: { version: '18.3' } },
21 | plugins: {
22 | react,
23 | 'react-hooks': reactHooks,
24 | 'react-refresh': reactRefresh,
25 | },
26 | rules: {
27 | ...js.configs.recommended.rules,
28 | ...react.configs.recommended.rules,
29 | ...react.configs['jsx-runtime'].rules,
30 | ...reactHooks.configs.recommended.rules,
31 | 'react/jsx-no-target-blank': 'off',
32 | 'react-refresh/only-export-components': [
33 | 'warn',
34 | { allowConstantExport: true },
35 | ],
36 | },
37 | },
38 | ]
39 |
--------------------------------------------------------------------------------
/font_install.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | REM This batch file installs several Fontsource font packages using npm.
3 | REM Make sure you have Node.js and npm installed on your system.
4 | REM Run this file in the root directory of your React project.
5 |
6 | echo Installing Fontsource npm packages...
7 |
8 | REM List of Fontsource packages to install
9 | set "FONTS_TO_INSTALL=@fontsource/inter @fontsource/roboto @fontsource/open-sans @fontsource/lato @fontsource/montserrat @fontsource/source-sans-3 @fontsource/poppins"
10 |
11 | REM Execute the npm install command
12 | npm install %FONTS_TO_INSTALL%
13 |
14 | REM Check if the installation was successful
15 | if %errorlevel% equ 0 (
16 | echo Fontsource packages installed successfully!
17 | ) else (
18 | echo An error occurred during installation. Please check the output above.
19 | )
20 |
21 | echo Installation process finished.
22 | pause
23 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Plot Bunni
8 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["src/*"]
6 | }
7 | },
8 | "include": ["src/**/*"]
9 | }
--------------------------------------------------------------------------------
/landingpage/ai_writer_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/ai_writer_dark.png
--------------------------------------------------------------------------------
/landingpage/ai_writer_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/ai_writer_light.png
--------------------------------------------------------------------------------
/landingpage/chat_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/chat_dark.png
--------------------------------------------------------------------------------
/landingpage/chat_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/chat_light.png
--------------------------------------------------------------------------------
/landingpage/concept_write_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/concept_write_dark.png
--------------------------------------------------------------------------------
/landingpage/concept_write_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/concept_write_light.png
--------------------------------------------------------------------------------
/landingpage/icon.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/icon.PNG
--------------------------------------------------------------------------------
/landingpage/overview_plan_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/overview_plan_dark.png
--------------------------------------------------------------------------------
/landingpage/overview_plan_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/overview_plan_light.png
--------------------------------------------------------------------------------
/landingpage/phone.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/phone.PNG
--------------------------------------------------------------------------------
/landingpage/rabbit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/landingpage/rocket.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/landingpage/script.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', () => {
2 | const themeSwitcher = document.getElementById('theme-switcher');
3 | const themeIconLight = document.getElementById('theme-icon-light');
4 | const themeIconDark = document.getElementById('theme-icon-dark');
5 | const lightImages = document.querySelectorAll('.light-image');
6 | const darkImages = document.querySelectorAll('.dark-image');
7 | const htmlElement = document.documentElement;
8 |
9 | // Function to apply theme
10 | const applyTheme = (theme) => {
11 | if (theme === 'dark') {
12 | htmlElement.classList.add('dark');
13 | themeIconLight.classList.add('hidden');
14 | themeIconDark.classList.remove('hidden');
15 | lightImages.forEach(img => img.classList.add('hidden'));
16 | darkImages.forEach(img => img.classList.remove('hidden'));
17 | localStorage.setItem('theme', 'dark');
18 | } else {
19 | htmlElement.classList.remove('dark');
20 | themeIconLight.classList.remove('hidden');
21 | themeIconDark.classList.add('hidden');
22 | lightImages.forEach(img => img.classList.remove('hidden'));
23 | darkImages.forEach(img => img.classList.add('hidden'));
24 | localStorage.setItem('theme', 'light');
25 | }
26 | };
27 |
28 | // Check for saved theme preference or system preference
29 | const preferredTheme = localStorage.getItem('theme') || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
30 | applyTheme(preferredTheme);
31 |
32 | // Theme switcher event listener
33 | themeSwitcher.addEventListener('click', () => {
34 | const currentTheme = htmlElement.classList.contains('dark') ? 'dark' : 'light';
35 | applyTheme(currentTheme === 'dark' ? 'light' : 'dark');
36 | });
37 |
38 | // Update current year in footer
39 | const currentYearSpan = document.getElementById('current-year');
40 | if (currentYearSpan) {
41 | currentYearSpan.textContent = new Date().getFullYear();
42 | }
43 | });
44 |
--------------------------------------------------------------------------------
/landingpage/style.css:
--------------------------------------------------------------------------------
1 | /* Import from current_theme.css */
2 | :root {
3 | --background: 260 23.08% 97.45%;
4 | --foreground: 243.16 13.67% 27.25%;
5 | --card: 0 0% 100%;
6 | --card-foreground: 243.16 13.67% 27.25%;
7 | --popover: 0 0% 100%;
8 | --popover-foreground: 243.16 13.67% 27.25%;
9 | --primary: 260.40 22.94% 57.25%;
10 | --primary-foreground: 260 23.08% 97.45%;
11 | --secondary: 258.95 33.33% 88.82%;
12 | --secondary-foreground: 243.16 13.67% 27.25%;
13 | --muted: 258.00 15.15% 87.06%;
14 | --muted-foreground: 247.50 10.34% 45.49%;
15 | --accent: 342.46 56.52% 77.45%;
16 | --accent-foreground: 343.45 23.97% 23.73%;
17 | --destructive: 0 62.19% 60.59%;
18 | --destructive-foreground: 260 23.08% 97.45%;
19 | --border: 258.75 17.39% 81.96%;
20 | --input: 260.00 23.08% 92.35%;
21 | --ring: 260.40 22.94% 57.25%;
22 | --chart-1: 260.40 22.94% 57.25%;
23 | --chart-2: 342.46 56.52% 77.45%;
24 | --chart-3: 158.77 31.40% 59.41%;
25 | --chart-4: 35.76 76.74% 74.71%;
26 | --chart-5: 215.82 54.47% 75.88%;
27 | --sidebar: 260.00 23.08% 94.90%;
28 | --sidebar-foreground: 243.16 13.67% 27.25%;
29 | --sidebar-primary: 260.40 22.94% 57.25%;
30 | --sidebar-primary-foreground: 260 23.08% 97.45%;
31 | --sidebar-accent: 342.46 56.52% 77.45%;
32 | --sidebar-accent-foreground: 343.45 23.97% 23.73%;
33 | --sidebar-border: 261.43 18.42% 85.10%;
34 | --sidebar-ring: 260.40 22.94% 57.25%;
35 | --radius: 0.5rem;
36 | --shadow-2xs: 1px 2px 5px 1px hsl(0 0% 0% / 0.03);
37 | --shadow-xs: 1px 2px 5px 1px hsl(0 0% 0% / 0.03);
38 | --shadow-sm: 1px 2px 5px 1px hsl(0 0% 0% / 0.06), 1px 1px 2px 0px hsl(0 0% 0% / 0.06);
39 | --shadow: 1px 2px 5px 1px hsl(0 0% 0% / 0.06), 1px 1px 2px 0px hsl(0 0% 0% / 0.06);
40 | --shadow-md: 1px 2px 5px 1px hsl(0 0% 0% / 0.06), 1px 2px 4px 0px hsl(0 0% 0% / 0.06);
41 | --shadow-lg: 1px 2px 5px 1px hsl(0 0% 0% / 0.06), 1px 4px 6px 0px hsl(0 0% 0% / 0.06);
42 | --shadow-xl: 1px 2px 5px 1px hsl(0 0% 0% / 0.06), 1px 8px 10px 0px hsl(0 0% 0% / 0.06);
43 | --shadow-2xl: 1px 2px 5px 1px hsl(0 0% 0% / 0.15);
44 | }
45 |
46 | .dark {
47 | --background: 250.91 18.64% 11.57%;
48 | --foreground: 250.00 36.00% 90.20%;
49 | --card: 251.25 20% 15.69%;
50 | --card-foreground: 250.00 36.00% 90.20%;
51 | --popover: 251.25 20% 15.69%;
52 | --popover-foreground: 250.00 36.00% 90.20%;
53 | --primary: 263.08 32.50% 68.63%;
54 | --primary-foreground: 250.91 18.64% 11.57%;
55 | --secondary: 254.48 14.87% 38.24%;
56 | --secondary-foreground: 250.00 36.00% 90.20%;
57 | --muted: 254.12 20.99% 15.88%;
58 | --muted-foreground: 258.95 10.38% 64.12%;
59 | --accent: 271.76 15.60% 21.37%;
60 | --accent-foreground: 345.52 69.05% 83.53%;
61 | --destructive: 0 68.67% 67.45%;
62 | --destructive-foreground: 250.91 18.64% 11.57%;
63 | --border: 252 18.52% 21.18%;
64 | --input: 249.47 19.59% 19.02%;
65 | --ring: 263.08 32.50% 68.63%;
66 | --chart-1: 263.08 32.50% 68.63%;
67 | --chart-2: 345.52 69.05% 83.53%;
68 | --chart-3: 158.77 31.40% 59.41%;
69 | --chart-4: 35.76 76.74% 74.71%;
70 | --chart-5: 215.82 54.47% 75.88%;
71 | --sidebar: 252 20.00% 9.80%;
72 | --sidebar-foreground: 250.00 36.00% 90.20%;
73 | --sidebar-primary: 263.08 32.50% 68.63%;
74 | --sidebar-primary-foreground: 250.91 18.64% 11.57%;
75 | --sidebar-accent: 271.76 15.60% 21.37%;
76 | --sidebar-accent-foreground: 345.52 69.05% 83.53%;
77 | --sidebar-border: 249.47 19.59% 19.02%;
78 | --sidebar-ring: 263.08 32.50% 68.63%;
79 | --radius: 0.5rem;
80 | --shadow-2xs: 1px 2px 5px 1px hsl(0 0% 100% / 0.03);
81 | --shadow-xs: 1px 2px 5px 1px hsl(0 0% 100% / 0.03);
82 | --shadow-sm: 1px 2px 5px 1px hsl(0 0% 100% / 0.04), 1px 1px 2px 0px hsl(0 0% 100% / 0.04);
83 | --shadow: 1px 2px 5px 1px hsl(0 0% 100% / 0.05), 1px 1px 2px 0px hsl(0 0% 100% / 0.05);
84 | --shadow-md: 1px 2px 5px 1px hsl(0 0% 100% / 0.05), 1px 2px 4px 0px hsl(0 0% 100% / 0.05);
85 | --shadow-lg: 1px 2px 5px 1px hsl(0 0% 100% / 0.05), 1px 4px 6px 0px hsl(0 0% 100% / 0.05);
86 | --shadow-xl: 1px 2px 5px 1px hsl(0 0% 100% / 0.05), 1px 8px 10px 0px hsl(0 0% 100% / 0.05);
87 | --shadow-2xl: 1px 2px 5px 1px hsl(0 0% 100% / 0.1);
88 | }
89 |
90 | body {
91 | transition: background-color 0.3s ease, color 0.3s ease;
92 | }
93 |
--------------------------------------------------------------------------------
/landingpage/themes2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/themes2.png
--------------------------------------------------------------------------------
/landingpage/themes_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/themes_dark.png
--------------------------------------------------------------------------------
/landingpage/themes_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/landingpage/themes_light.png
--------------------------------------------------------------------------------
/mac-build/README.md:
--------------------------------------------------------------------------------
1 | # Plot Bunni - Mac Build Configuration
2 |
3 | This folder contains the necessary configuration to build Plot Bunni for macOS. It's designed to be portable so you can easily copy it to new versions of the app when it's updated.
4 |
5 | ## Setup
6 |
7 | 1. Copy this entire `mac-build` folder to the root of the Plot Bunni project
8 | 2. Navigate to the mac-build directory:
9 | ```bash
10 | cd mac-build
11 | ```
12 | 3. Install dependencies:
13 | ```bash
14 | npm install
15 | ```
16 | 4. To build only for Mac:
17 | ```bash
18 | npm run build
19 | ```
20 |
21 | The built applications will be available in the `dist` folder as a dmg file. Double click the dmg file to install Plot Bunni.
22 |
23 | ## Folder Structure
24 |
25 | - `resources/` - Contains generated Mac-specific resources (icons)
26 | - `scripts/` - Contains build preparation scripts
27 | - `prepare-build.js` - Prepares the build environment
28 | - `create-mac-icon.sh` - Converts the original icon to Mac format
29 |
30 | ## Updating the App
31 |
32 | When the original Plot Bunni app is updated:
33 |
34 | 1. Download/update the main app
35 | 2. Copy this entire `mac-build` folder to the root of the updated app
36 | 3. Follow the setup and build instructions above
37 |
38 | The configuration is designed to reference the main app's files while maintaining its own Mac-specific build settings.
39 |
--------------------------------------------------------------------------------
/mac-build/build/main.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow } = require('electron')
2 | const path = require('path')
3 |
4 | function createWindow () {
5 | const win = new BrowserWindow({
6 | width: 1440,
7 | height: 810,
8 | autoHideMenuBar: true,
9 | icon: path.join(__dirname, 'icon.ico'),
10 | webPreferences: {
11 | nodeIntegration: false,
12 | contextIsolation: true
13 | }
14 | })
15 |
16 | win.loadFile(path.join(__dirname, 'static', 'index.html'))
17 | }
18 |
19 | app.whenReady().then(() => {
20 | createWindow()
21 |
22 | app.on('activate', () => {
23 | if (BrowserWindow.getAllWindows().length === 0) {
24 | createWindow()
25 | }
26 | })
27 | })
28 |
29 | app.on('window-all-closed', () => {
30 | if (process.platform !== 'darwin') {
31 | app.quit()
32 | }
33 | })
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-cyrillic-400-normal-BLGc9T1a.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-cyrillic-400-normal-BLGc9T1a.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-cyrillic-400-normal-ZzOtrSSW.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-cyrillic-400-normal-ZzOtrSSW.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-cyrillic-700-normal-Bc8_fv8J.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-cyrillic-700-normal-Bc8_fv8J.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-cyrillic-700-normal-bGtGjVdZ.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-cyrillic-700-normal-bGtGjVdZ.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-cyrillic-ext-400-normal-BPnxn4xp.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-cyrillic-ext-400-normal-BPnxn4xp.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-cyrillic-ext-400-normal-Dc4VJyIJ.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-cyrillic-ext-400-normal-Dc4VJyIJ.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-cyrillic-ext-700-normal-Ced3hgUT.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-cyrillic-ext-700-normal-Ced3hgUT.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-cyrillic-ext-700-normal-ClVoMEGq.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-cyrillic-ext-700-normal-ClVoMEGq.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-greek-400-normal-BZzXV7-1.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-greek-400-normal-BZzXV7-1.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-greek-400-normal-DxZsaF_h.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-greek-400-normal-DxZsaF_h.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-greek-700-normal-BRYTaFLL.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-greek-700-normal-BRYTaFLL.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-greek-700-normal-Cxpycf-U.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-greek-700-normal-Cxpycf-U.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-greek-ext-400-normal-Bput3-QP.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-greek-ext-400-normal-Bput3-QP.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-greek-ext-400-normal-DCpCPQOf.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-greek-ext-400-normal-DCpCPQOf.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-greek-ext-700-normal-DXvzx4Na.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-greek-ext-700-normal-DXvzx4Na.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-greek-ext-700-normal-SzCdnevJ.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-greek-ext-700-normal-SzCdnevJ.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-latin-400-normal-BOOGhInR.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-latin-400-normal-BOOGhInR.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-latin-400-normal-gitzw0hO.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-latin-400-normal-gitzw0hO.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-latin-700-normal-B8MtJ_2k.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-latin-700-normal-B8MtJ_2k.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-latin-700-normal-Sckx8rpT.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-latin-700-normal-Sckx8rpT.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-latin-ext-400-normal-C1t-h-pH.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-latin-ext-400-normal-C1t-h-pH.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-latin-ext-400-normal-hnt3BR84.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-latin-ext-400-normal-hnt3BR84.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-latin-ext-700-normal-6V9MnIL5.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-latin-ext-700-normal-6V9MnIL5.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-latin-ext-700-normal-CzikT_rs.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-latin-ext-700-normal-CzikT_rs.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-vietnamese-400-normal-BUNmGMP1.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-vietnamese-400-normal-BUNmGMP1.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-vietnamese-400-normal-DMkecbls.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-vietnamese-400-normal-DMkecbls.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-vietnamese-700-normal-CGpBpxLq.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-vietnamese-700-normal-CGpBpxLq.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/inter-vietnamese-700-normal-dAnkLlTo.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/inter-vietnamese-700-normal-dAnkLlTo.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/lato-latin-400-normal-B11PyLys.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/lato-latin-400-normal-B11PyLys.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/lato-latin-400-normal-BEhtfm5r.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/lato-latin-400-normal-BEhtfm5r.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/lato-latin-700-normal-BUGMgin4.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/lato-latin-700-normal-BUGMgin4.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/lato-latin-700-normal-DAdL7O4w.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/lato-latin-700-normal-DAdL7O4w.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/lato-latin-ext-400-normal-C8eBZ-j2.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/lato-latin-ext-400-normal-C8eBZ-j2.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/lato-latin-ext-700-normal-C5WWfNwx.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/lato-latin-ext-700-normal-C5WWfNwx.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-cyrillic-400-normal-BNZrkLa0.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-cyrillic-400-normal-BNZrkLa0.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-cyrillic-400-normal-DpKvHuFb.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-cyrillic-400-normal-DpKvHuFb.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-cyrillic-700-normal-CF3jbqy3.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-cyrillic-700-normal-CF3jbqy3.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-cyrillic-700-normal-DO8I1zZZ.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-cyrillic-700-normal-DO8I1zZZ.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-cyrillic-ext-400-normal-BWKiD3Kv.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-cyrillic-ext-400-normal-BWKiD3Kv.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-cyrillic-ext-400-normal-C8cdMEH1.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-cyrillic-ext-400-normal-C8cdMEH1.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-cyrillic-ext-700-normal-B5jSujIz.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-cyrillic-ext-700-normal-B5jSujIz.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-cyrillic-ext-700-normal-D3s9QaN5.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-cyrillic-ext-700-normal-D3s9QaN5.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-latin-400-normal-CIMKXMQI.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-latin-400-normal-CIMKXMQI.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-latin-400-normal-Sf4eHQyG.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-latin-400-normal-Sf4eHQyG.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-latin-700-normal-BYRy5CoC.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-latin-700-normal-BYRy5CoC.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-latin-700-normal-DHOpyQza.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-latin-700-normal-DHOpyQza.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-latin-ext-400-normal-Cd0_BKvP.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-latin-ext-400-normal-Cd0_BKvP.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-latin-ext-400-normal-Cy-iUbkq.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-latin-ext-400-normal-Cy-iUbkq.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-latin-ext-700-normal-DTfQ8iG5.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-latin-ext-700-normal-DTfQ8iG5.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-latin-ext-700-normal-DdSde0HZ.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-latin-ext-700-normal-DdSde0HZ.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-vietnamese-400-normal-B33MQ3yC.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-vietnamese-400-normal-B33MQ3yC.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-vietnamese-400-normal-CgEMIOQI.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-vietnamese-400-normal-CgEMIOQI.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-vietnamese-700-normal-D8RPk-a5.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-vietnamese-700-normal-D8RPk-a5.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/montserrat-vietnamese-700-normal-DUoPxeMJ.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/montserrat-vietnamese-700-normal-DUoPxeMJ.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-cyrillic-400-normal-CzqaL1iZ.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-cyrillic-400-normal-CzqaL1iZ.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-cyrillic-400-normal-kTLgxuft.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-cyrillic-400-normal-kTLgxuft.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-cyrillic-700-normal-BUZkPNet.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-cyrillic-700-normal-BUZkPNet.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-cyrillic-700-normal-COG0hEe-.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-cyrillic-700-normal-COG0hEe-.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-cyrillic-ext-400-normal-UJz4QWDf.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-cyrillic-ext-400-normal-UJz4QWDf.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-cyrillic-ext-400-normal-pR6qsPVh.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-cyrillic-ext-400-normal-pR6qsPVh.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-cyrillic-ext-700-normal-DlRmNm63.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-cyrillic-ext-700-normal-DlRmNm63.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-cyrillic-ext-700-normal-cxRSGggR.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-cyrillic-ext-700-normal-cxRSGggR.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-greek-400-normal-B-s2YDfe.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-greek-400-normal-B-s2YDfe.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-greek-400-normal-BhbJw-ID.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-greek-400-normal-BhbJw-ID.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-greek-700-normal-3ZuBy_Ld.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-greek-700-normal-3ZuBy_Ld.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-greek-700-normal-DZLtG1eN.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-greek-700-normal-DZLtG1eN.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-hebrew-400-normal-CyH9M0u4.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-hebrew-400-normal-CyH9M0u4.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-hebrew-400-normal-DcuuLq9a.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-hebrew-400-normal-DcuuLq9a.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-hebrew-700-normal-ByxHPEwX.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-hebrew-700-normal-ByxHPEwX.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-hebrew-700-normal-CdblwzFU.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-hebrew-700-normal-CdblwzFU.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-latin-400-normal-Cjao0ETp.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-latin-400-normal-Cjao0ETp.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-latin-400-normal-Dxjvn725.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-latin-400-normal-Dxjvn725.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-latin-700-normal-B572f0fn.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-latin-700-normal-B572f0fn.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-latin-700-normal-C2okHfb_.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-latin-700-normal-C2okHfb_.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-latin-ext-400-normal-BXLcHD4a.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-latin-ext-400-normal-BXLcHD4a.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-latin-ext-400-normal-DkLPCGEk.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-latin-ext-400-normal-DkLPCGEk.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-latin-ext-700-normal-C-A0l_kt.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-latin-ext-700-normal-C-A0l_kt.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-latin-ext-700-normal-h5QwRtwi.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-latin-ext-700-normal-h5QwRtwi.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-math-400-normal-Bw1rHEuQ.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-math-400-normal-Bw1rHEuQ.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-math-400-normal-CoiAIOH6.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-math-400-normal-CoiAIOH6.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-math-700-normal-C_zQ2Upn.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-math-700-normal-C_zQ2Upn.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-math-700-normal-D9Ix8Mnc.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-math-700-normal-D9Ix8Mnc.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-symbols-400-normal-BczhyrVJ.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-symbols-400-normal-BczhyrVJ.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-symbols-400-normal-D7CElg7Y.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-symbols-400-normal-D7CElg7Y.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-symbols-700-normal-9UGYaiOr.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-symbols-700-normal-9UGYaiOr.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-symbols-700-normal-BkRMExv1.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-symbols-700-normal-BkRMExv1.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-vietnamese-400-normal-CWzy0rt5.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-vietnamese-400-normal-CWzy0rt5.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-vietnamese-400-normal-Coa9Lb0B.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-vietnamese-400-normal-Coa9Lb0B.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-vietnamese-700-normal-KVBVKU1x.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-vietnamese-700-normal-KVBVKU1x.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/open-sans-vietnamese-700-normal-js5t13xB.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/open-sans-vietnamese-700-normal-js5t13xB.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-devanagari-400-normal-CqVvlrh5.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-devanagari-400-normal-CqVvlrh5.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-devanagari-700-normal-O-jipLrW.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-devanagari-700-normal-O-jipLrW.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-devanagari-700-normal-fHs-vx92.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-devanagari-700-normal-fHs-vx92.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-latin-400-normal-BOb3E3N0.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-latin-400-normal-BOb3E3N0.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-latin-400-normal-cpxAROuN.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-latin-400-normal-cpxAROuN.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-latin-700-normal-BVuQR_eA.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-latin-700-normal-BVuQR_eA.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-latin-700-normal-Qrb0O0WB.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-latin-700-normal-Qrb0O0WB.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-latin-ext-400-normal-by3JarPu.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-latin-ext-400-normal-by3JarPu.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-cyrillic-400-normal-DAIM1_dR.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-cyrillic-400-normal-DAIM1_dR.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-cyrillic-400-normal-Dry59Hjn.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-cyrillic-400-normal-Dry59Hjn.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-cyrillic-700-normal-CB1Rmiii.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-cyrillic-700-normal-CB1Rmiii.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-cyrillic-700-normal-CzEIZVQR.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-cyrillic-700-normal-CzEIZVQR.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-cyrillic-ext-400-normal-BxX1-eA_.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-cyrillic-ext-400-normal-BxX1-eA_.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-cyrillic-ext-400-normal-DzMWdK87.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-cyrillic-ext-400-normal-DzMWdK87.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-cyrillic-ext-700-normal-CIu0AXX2.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-cyrillic-ext-700-normal-CIu0AXX2.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-cyrillic-ext-700-normal-D_fA0fHY.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-cyrillic-ext-700-normal-D_fA0fHY.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-greek-400-normal-CAI06USH.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-greek-400-normal-CAI06USH.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-greek-400-normal-jFM2czAU.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-greek-400-normal-jFM2czAU.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-greek-700-normal-DCrt6r9F.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-greek-700-normal-DCrt6r9F.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-greek-700-normal-DpKAje7q.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-greek-700-normal-DpKAje7q.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-latin-400-normal-551zQQ7R.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-latin-400-normal-551zQQ7R.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-latin-400-normal-CNwBRw8h.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-latin-400-normal-CNwBRw8h.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-latin-700-normal-CTLkNcF_.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-latin-700-normal-CTLkNcF_.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-latin-700-normal-DZr4b_KL.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-latin-700-normal-DZr4b_KL.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-latin-ext-400-normal-ZYmyxeOy.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-latin-ext-400-normal-ZYmyxeOy.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-latin-ext-400-normal-uRIBRJt5.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-latin-ext-400-normal-uRIBRJt5.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-latin-ext-700-normal-BNPgmEQS.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-latin-ext-700-normal-BNPgmEQS.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-latin-ext-700-normal-Cnx4FGpK.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-latin-ext-700-normal-Cnx4FGpK.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-math-400-normal-B3wgz80t.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-math-400-normal-B3wgz80t.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-math-400-normal-DHrwdhE6.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-math-400-normal-DHrwdhE6.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-math-700-normal-CpTCM92H.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-math-700-normal-CpTCM92H.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-math-700-normal-xbpggnJp.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-math-700-normal-xbpggnJp.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-symbols-400-normal-bG5rsNFs.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-symbols-400-normal-bG5rsNFs.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-symbols-400-normal-fF1SLJBj.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-symbols-400-normal-fF1SLJBj.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-symbols-700-normal-B2QKVW64.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-symbols-700-normal-B2QKVW64.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-symbols-700-normal-DKkQdRpM.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-symbols-700-normal-DKkQdRpM.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-vietnamese-400-normal-CDDxGrUb.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-vietnamese-400-normal-CDDxGrUb.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-vietnamese-400-normal-DgufTq8s.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-vietnamese-400-normal-DgufTq8s.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-vietnamese-700-normal-BFWtvCOj.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-vietnamese-700-normal-BFWtvCOj.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/roboto-vietnamese-700-normal-ChAl_rRV.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/roboto-vietnamese-700-normal-ChAl_rRV.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-cyrillic-400-normal-7kQcAFpS.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-cyrillic-400-normal-7kQcAFpS.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-cyrillic-400-normal-DjhbW-4D.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-cyrillic-400-normal-DjhbW-4D.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-cyrillic-700-normal-CIuudVLB.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-cyrillic-700-normal-CIuudVLB.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-cyrillic-700-normal-DYE6gq6k.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-cyrillic-700-normal-DYE6gq6k.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-cyrillic-ext-400-normal-BwJdxs7r.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-cyrillic-ext-400-normal-BwJdxs7r.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-cyrillic-ext-400-normal-Cd63HKDh.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-cyrillic-ext-400-normal-Cd63HKDh.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-cyrillic-ext-700-normal-B6RPsSdD.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-cyrillic-ext-700-normal-B6RPsSdD.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-cyrillic-ext-700-normal-BhUSqjb8.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-cyrillic-ext-700-normal-BhUSqjb8.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-greek-400-normal-DJm9Ud5h.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-greek-400-normal-DJm9Ud5h.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-greek-400-normal-DZReXJla.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-greek-400-normal-DZReXJla.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-greek-700-normal-BWdBblwd.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-greek-700-normal-BWdBblwd.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-greek-700-normal-C8Tp2qRF.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-greek-700-normal-C8Tp2qRF.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-greek-ext-400-normal-BI1kUwmR.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-greek-ext-400-normal-BI1kUwmR.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-greek-ext-400-normal-CpIwTHMM.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-greek-ext-400-normal-CpIwTHMM.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-greek-ext-700-normal-C6ITAlBU.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-greek-ext-700-normal-C6ITAlBU.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-greek-ext-700-normal-Dd6uIcuy.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-greek-ext-700-normal-Dd6uIcuy.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-latin-400-normal-AFMiCETP.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-latin-400-normal-AFMiCETP.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-latin-400-normal-DQi5PRDE.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-latin-400-normal-DQi5PRDE.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-latin-700-normal-BBbVrHF_.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-latin-700-normal-BBbVrHF_.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-latin-700-normal-CkzU-mV9.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-latin-700-normal-CkzU-mV9.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-latin-ext-400-normal-BGPHfnm_.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-latin-ext-400-normal-BGPHfnm_.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-latin-ext-400-normal-UNsKFIBq.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-latin-ext-400-normal-UNsKFIBq.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-latin-ext-700-normal-6Xi0A2be.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-latin-ext-700-normal-6Xi0A2be.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-latin-ext-700-normal-ChH_v7nA.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-latin-ext-700-normal-ChH_v7nA.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-vietnamese-400-normal-CwtaIDr2.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-vietnamese-400-normal-CwtaIDr2.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-vietnamese-400-normal-dYp9C364.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-vietnamese-400-normal-dYp9C364.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-vietnamese-700-normal-BHxM10oo.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-vietnamese-700-normal-BHxM10oo.woff
--------------------------------------------------------------------------------
/mac-build/build/static/assets/source-sans-3-vietnamese-700-normal-Bgamivog.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/build/static/assets/source-sans-3-vietnamese-700-normal-Bgamivog.woff2
--------------------------------------------------------------------------------
/mac-build/build/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Plot Bunni
8 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/mac-build/build/static/rabbit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mac-build/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "plotbunni-mac",
3 | "version": "1.0.0",
4 | "description": "Plot Bunni - Mac Build Configuration",
5 | "main": "build/main.js",
6 | "scripts": {
7 | "start": "electron .",
8 | "build": "node ./scripts/prepare-build.js && electron-builder --mac",
9 | "build-both": "node ./scripts/prepare-build.js && electron-builder --win portable --mac"
10 | },
11 | "build": {
12 | "appId": "com.fyrean.plotbunni",
13 | "productName": "Plot Bunni",
14 | "files": [
15 | "build/**/*",
16 | "resources/icon.icns"
17 | ],
18 | "mac": {
19 | "icon": "resources/icon.icns",
20 | "target": ["dmg", "zip"],
21 | "category": "public.app-category.developer-tools"
22 | },
23 | "compression": "maximum"
24 | },
25 | "devDependencies": {
26 | "electron": "^28.3.3",
27 | "electron-builder": "^24.13.3",
28 | "fs-extra": "^11.2.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/mac-build/resources/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/mac-build/resources/icon.icns
--------------------------------------------------------------------------------
/mac-build/scripts/create-mac-icon.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Get script directory
4 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5 | RESOURCES_DIR="$SCRIPT_DIR/../resources"
6 | ORIGINAL_ICON="$SCRIPT_DIR/../../electron/icon.ico"
7 |
8 | # Create iconset directory
9 | mkdir -p "$RESOURCES_DIR/icon.iconset"
10 |
11 | # Convert icon.ico to PNG images of different sizes
12 | sips -s format png "$ORIGINAL_ICON" --out "$RESOURCES_DIR/icon.png"
13 | for size in 16 32 64 128 256 512; do
14 | sips -z $size $size "$RESOURCES_DIR/icon.png" --out "$RESOURCES_DIR/icon.iconset/icon_${size}x${size}.png"
15 | if [ $size -le 32 ]; then
16 | sips -z $size $size "$RESOURCES_DIR/icon.png" --out "$RESOURCES_DIR/icon.iconset/icon_${size}x${size}@2x.png"
17 | fi
18 | done
19 |
20 | # Create icns file
21 | cd "$RESOURCES_DIR"
22 | iconutil -c icns icon.iconset
23 |
24 | # Clean up
25 | rm -rf icon.iconset icon.png
26 |
--------------------------------------------------------------------------------
/mac-build/scripts/prepare-build.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs-extra');
2 | const path = require('path');
3 | const { execSync } = require('child_process');
4 |
5 | // Paths
6 | const rootDir = path.join(__dirname, '..');
7 | const resourcesDir = path.join(rootDir, 'resources');
8 | const buildDir = path.join(rootDir, 'build');
9 | const staticDir = path.join(buildDir, 'static');
10 | const iconScript = path.join(__dirname, 'create-mac-icon.sh');
11 |
12 | // Source paths
13 | const projectRoot = path.join(__dirname, '../..');
14 | const electronDir = path.join(projectRoot, 'electron');
15 | const distDir = path.join(projectRoot, 'dist');
16 |
17 | // Clean and create necessary directories
18 | fs.removeSync(buildDir);
19 | fs.removeSync(distDir);
20 | fs.ensureDirSync(resourcesDir);
21 | fs.ensureDirSync(buildDir);
22 | fs.ensureDirSync(staticDir);
23 |
24 | // Install dependencies in the main project
25 | console.log('📦 Installing dependencies...');
26 | try {
27 | execSync('npm install', {
28 | stdio: 'inherit',
29 | cwd: projectRoot
30 | });
31 | console.log('✅ Dependencies installed');
32 | } catch (error) {
33 | console.error('❌ Error installing dependencies:', error);
34 | process.exit(1);
35 | }
36 |
37 | // Build the Vite app
38 | console.log('🏗️ Building Vite app...');
39 | try {
40 | execSync('npm run build', {
41 | stdio: 'inherit',
42 | cwd: projectRoot
43 | });
44 | console.log('✅ Vite build successful');
45 | } catch (error) {
46 | console.error('❌ Error building Vite app:', error);
47 | process.exit(1);
48 | }
49 |
50 | // Copy the built files
51 | console.log('📦 Copying built files...');
52 | if (fs.existsSync(distDir)) {
53 | fs.copySync(distDir, staticDir);
54 | // Verify the copy
55 | if (!fs.existsSync(path.join(staticDir, 'index.html'))) {
56 | console.error('❌ index.html not found in static directory after copy');
57 | process.exit(1);
58 | }
59 | console.log('✅ Built files copied successfully');
60 | } else {
61 | console.error('❌ Dist directory not found. Make sure the Vite build was successful.');
62 | process.exit(1);
63 | }
64 |
65 | // Copy and modify main.js
66 | console.log('📦 Copying and updating main.js...');
67 | let mainJsContent = fs.readFileSync(path.join(electronDir, 'main.js'), 'utf8');
68 |
69 | // Update the path to the index.html file
70 | mainJsContent = mainJsContent.replace(
71 | "win.loadFile(path.join(__dirname, 'static', 'index.html'))",
72 | "win.loadFile(path.join(__dirname, 'static', 'index.html'))"
73 | );
74 |
75 | fs.writeFileSync(path.join(buildDir, 'main.js'), mainJsContent);
76 |
77 | // Make icon conversion script executable and run it
78 | try {
79 | fs.chmodSync(iconScript, '755');
80 | execSync(iconScript, { stdio: 'inherit' });
81 | console.log('✅ Mac icon created successfully');
82 | } catch (error) {
83 | console.error('❌ Error creating Mac icon:', error);
84 | process.exit(1);
85 | }
86 |
87 | console.log('✅ Build preparation complete');
88 |
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "claudetemplate",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@fontsource/inter": "^5.2.5",
14 | "@fontsource/lato": "^5.2.5",
15 | "@fontsource/montserrat": "^5.2.5",
16 | "@fontsource/open-sans": "^5.2.5",
17 | "@fontsource/poppins": "^5.2.6",
18 | "@fontsource/roboto": "^5.2.5",
19 | "@fontsource/source-sans-3": "^5.2.7",
20 | "@hookform/resolvers": "^3.9.0",
21 | "@radix-ui/react-accordion": "^1.2.0",
22 | "@radix-ui/react-alert-dialog": "^1.1.1",
23 | "@radix-ui/react-aspect-ratio": "^1.1.0",
24 | "@radix-ui/react-avatar": "^1.1.0",
25 | "@radix-ui/react-checkbox": "^1.1.1",
26 | "@radix-ui/react-collapsible": "^1.1.0",
27 | "@radix-ui/react-context-menu": "^2.2.1",
28 | "@radix-ui/react-dialog": "^1.1.1",
29 | "@radix-ui/react-dropdown-menu": "^2.1.1",
30 | "@radix-ui/react-hover-card": "^1.1.1",
31 | "@radix-ui/react-label": "^2.1.0",
32 | "@radix-ui/react-menubar": "^1.1.1",
33 | "@radix-ui/react-navigation-menu": "^1.2.0",
34 | "@radix-ui/react-popover": "^1.1.1",
35 | "@radix-ui/react-progress": "^1.1.0",
36 | "@radix-ui/react-radio-group": "^1.2.0",
37 | "@radix-ui/react-scroll-area": "^1.1.0",
38 | "@radix-ui/react-select": "^2.1.1",
39 | "@radix-ui/react-separator": "^1.1.0",
40 | "@radix-ui/react-slider": "^1.2.0",
41 | "@radix-ui/react-slot": "^1.1.0",
42 | "@radix-ui/react-switch": "^1.1.0",
43 | "@radix-ui/react-tabs": "^1.1.0",
44 | "@radix-ui/react-toast": "^1.2.1",
45 | "@radix-ui/react-toggle": "^1.1.0",
46 | "@radix-ui/react-toggle-group": "^1.1.0",
47 | "@radix-ui/react-tooltip": "^1.1.2",
48 | "class-variance-authority": "^0.7.0",
49 | "clsx": "^2.1.1",
50 | "cmdk": "^1.0.0",
51 | "date-fns": "^3.6.0",
52 | "embla-carousel-react": "^8.3.0",
53 | "i18n": "^0.15.1",
54 | "i18next": "^25.1.3",
55 | "i18next-browser-languagedetector": "^8.1.0",
56 | "input-otp": "^1.2.4",
57 | "jszip": "^3.10.1",
58 | "lucide-react": "^0.427.0",
59 | "next-themes": "^0.3.0",
60 | "pdfmake": "^0.2.20",
61 | "react": "^18.3.1",
62 | "react-colorful": "^5.6.1",
63 | "react-day-picker": "^8.10.1",
64 | "react-dom": "^18.3.1",
65 | "react-hook-form": "^7.53.0",
66 | "react-i18next": "^15.5.1",
67 | "react-joyride": "^2.9.3",
68 | "react-markdown": "^8.0.7",
69 | "react-resizable-panels": "^2.1.3",
70 | "react-router-dom": "^7.6.0",
71 | "recharts": "^2.12.7",
72 | "sonner": "^1.5.0",
73 | "tailwind-merge": "^2.4.0",
74 | "tailwindcss-animate": "^1.0.7",
75 | "uuid": "^11.1.0",
76 | "vaul": "^1.0.0",
77 | "zod": "^3.23.8"
78 | },
79 | "devDependencies": {
80 | "@eslint/js": "^9.8.0",
81 | "@tailwindcss/typography": "^0.5.16",
82 | "@types/react": "^18.3.3",
83 | "@types/react-dom": "^18.3.0",
84 | "@vitejs/plugin-react": "^4.3.1",
85 | "autoprefixer": "^10.4.20",
86 | "eslint": "^9.8.0",
87 | "eslint-plugin-react": "^7.35.0",
88 | "eslint-plugin-react-hooks": "^5.1.0-rc.0",
89 | "eslint-plugin-react-refresh": "^0.4.9",
90 | "globals": "^15.9.0",
91 | "postcss": "^8.4.41",
92 | "tailwindcss": "^3.4.9",
93 | "vite": "^5.4.0"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/discord.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/rabbit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangoLion/plotbunni/a5eda1cc6aa24191236456cea0969f911c7b0373/src/App.css
--------------------------------------------------------------------------------
/src/RootApp.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useTranslation } from 'react-i18next';
3 | import {
4 | createHashRouter,
5 | RouterProvider,
6 | useParams,
7 | } from 'react-router-dom';
8 | import App from './App';
9 | import NovelGridView from './components/novel/NovelGridView';
10 | import { DataProvider } from './context/DataContext';
11 | import { SettingsProvider } from './context/SettingsContext';
12 |
13 | // Novel Editor View Layout
14 | // Extracts novelId from params and provides DataContext for that novel
15 | const NovelEditorLayout = () => {
16 | const { novelId } = useParams();
17 | const { t } = useTranslation();
18 |
19 | if (!novelId) {
20 | // This case should ideally be handled by routing or a redirect
21 | // For now, show a message or redirect to home.
22 | return (
23 |
27 | );
28 | }
29 |
30 | return (
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | const router = createHashRouter([
38 | {
39 | path: '/',
40 | element: ,
41 | },
42 | {
43 | path: '/novel/:novelId',
44 | element: , // Use a layout component to grab params
45 | },
46 | ]);
47 |
48 | function RootApp() {
49 | return (
50 |
51 |
52 |
53 | );
54 | }
55 |
56 | export default RootApp;
57 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ai/FullscreenTextareaEditModal.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useTranslation } from 'react-i18next';
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogHeader,
7 | DialogTitle,
8 | DialogFooter,
9 | } from '@/components/ui/dialog';
10 | import { Button } from '@/components/ui/button';
11 | import { Textarea } from '@/components/ui/textarea';
12 | import { Label } from '@/components/ui/label';
13 |
14 | export const FullscreenTextareaEditModal = ({
15 | isOpen,
16 | onClose,
17 | initialValue,
18 | onSave,
19 | title,
20 | textareaId,
21 | }) => {
22 | const { t } = useTranslation();
23 | const [currentValue, setCurrentValue] = useState(initialValue);
24 |
25 | useEffect(() => {
26 | if (isOpen) {
27 | setCurrentValue(initialValue);
28 | }
29 | }, [isOpen, initialValue]);
30 |
31 | const handleSave = () => {
32 | onSave(currentValue);
33 | onClose();
34 | };
35 |
36 | const handleCancel = () => {
37 | onClose();
38 | };
39 |
40 | if (!isOpen) {
41 | return null;
42 | }
43 |
44 | return (
45 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/src/components/novel/AddNewNovelCard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useTranslation } from 'react-i18next';
3 | import { Card } from "@/components/ui/card";
4 | import { PlusCircle } from 'lucide-react';
5 |
6 | /**
7 | * AddNewNovelCard Component
8 | * A card that acts as a button to trigger the creation of a new novel.
9 | *
10 | * @param {object} props
11 | * @param {function} props.onClick - Callback function when the card is clicked.
12 | */
13 | const AddNewNovelCard = ({ onClick }) => {
14 | const { t } = useTranslation();
15 | return (
16 |
21 |
22 |
23 | {t('add_new_novel_card_text')}
24 |
25 |
26 | );
27 | };
28 |
29 | export default AddNewNovelCard;
30 |
--------------------------------------------------------------------------------
/src/components/novel/NovelCard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useTranslation } from 'react-i18next';
3 | import { Card, CardTitle } from "@/components/ui/card";
4 | import { Button } from "@/components/ui/button";
5 | import { Trash2, Edit3 } from 'lucide-react';
6 |
7 | /**
8 | * NovelCard Component
9 | * Displays a novel's cover image (or gradient), name, and action icons.
10 | * Clicking the card opens the novel.
11 | *
12 | * @param {object} props
13 | * @param {object} props.novel - The novel object { id, name, coverImage? }.
14 | * @param {function} props.onOpenNovel - Callback function when card is clicked, passed novelId.
15 | * @param {function} props.onDeleteNovel - Callback function for delete icon, passed novelId.
16 | * @param {function} props.onEditNovel - Callback function for edit icon, passed novelId.
17 | */
18 | const NovelCard = ({ novel, onOpenNovel, onDeleteNovel, onEditNovel }) => {
19 | const { t } = useTranslation();
20 | const { id, name, coverImage } = novel;
21 |
22 | const handleCardClick = () => {
23 | onOpenNovel(id);
24 | };
25 |
26 | const handleEditClick = (e) => {
27 | e.stopPropagation(); // Prevent card click event
28 | onEditNovel(id);
29 | };
30 |
31 | const handleDeleteClick = (e) => {
32 | e.stopPropagation(); // Prevent card click event
33 | onDeleteNovel(id);
34 | };
35 |
36 | return (
37 |
38 |
42 | {coverImage ? (
43 |

48 | ) : (
49 |
50 | {/* Optional: Could add a subtle text or icon here for "No Cover" if desired */}
51 |
52 | )}
53 | {/* Icon buttons positioned over the image */}
54 |
55 |
58 |
61 |
62 | {/* Novel Name Overlay */}
63 |
64 |
65 |
69 | {name || t('novel_card_untitled_novel')}
70 |
71 |
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | export default NovelCard;
79 |
--------------------------------------------------------------------------------
/src/components/plan/ActFormModal.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useTranslation } from 'react-i18next';
3 | import { Button } from "@/components/ui/button";
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogDescription,
8 | DialogFooter,
9 | DialogHeader,
10 | DialogTitle,
11 | DialogClose,
12 | } from "@/components/ui/dialog";
13 | import { Input } from "@/components/ui/input";
14 | import { Label } from "@/components/ui/label";
15 | import { useData } from '@/context/DataContext';
16 | import ConfirmModal from '@/components/ui/ConfirmModal'; // Import ConfirmModal
17 | import { Trash2 } from 'lucide-react'; // Import Trash2 icon
18 |
19 | const ActFormModal = ({ open, onOpenChange, actToEdit }) => {
20 | const { t } = useTranslation();
21 | const { addAct, updateAct, deleteAct } = useData(); // Add deleteAct
22 | const [name, setName] = useState('');
23 | const [isConfirmDeleteOpen, setIsConfirmDeleteOpen] = useState(false); // State for confirm modal
24 | const isEditing = Boolean(actToEdit);
25 |
26 | useEffect(() => {
27 | if (open) {
28 | if (isEditing && actToEdit) {
29 | setName(actToEdit.name || '');
30 | } else {
31 | setName('');
32 | }
33 | }
34 | }, [actToEdit, isEditing, open]);
35 |
36 | const resetForm = () => setName('');
37 |
38 | const handleSubmit = () => {
39 | if (isEditing && actToEdit) {
40 | updateAct(actToEdit.id, { name });
41 | } else {
42 | addAct({ name });
43 | }
44 | resetForm();
45 | onOpenChange(false);
46 | };
47 |
48 | const handleDeleteAct = () => {
49 | if (actToEdit) {
50 | deleteAct(actToEdit.id);
51 | resetForm();
52 | onOpenChange(false); // Close main modal
53 | setIsConfirmDeleteOpen(false); // Close confirm modal
54 | }
55 | };
56 |
57 | useEffect(() => {
58 | if (!open) {
59 | resetForm();
60 | setIsConfirmDeleteOpen(false); // Ensure confirm modal is closed when main modal closes
61 | }
62 | }, [open]);
63 |
64 | return (
65 | <>
66 |
96 |
97 | {actToEdit && (
98 |
105 | )}
106 | >
107 | );
108 | };
109 |
110 | export default ActFormModal;
111 |
--------------------------------------------------------------------------------
/src/components/plan/ChapterFormModal.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useTranslation } from 'react-i18next';
3 | import { Button } from "@/components/ui/button";
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogDescription,
8 | DialogFooter,
9 | DialogHeader,
10 | DialogTitle,
11 | DialogClose,
12 | } from "@/components/ui/dialog";
13 | import { Input } from "@/components/ui/input";
14 | import { Label } from "@/components/ui/label";
15 | import { useData } from '@/context/DataContext';
16 | import ConfirmModal from '@/components/ui/ConfirmModal'; // Import ConfirmModal
17 | import { Trash2 } from 'lucide-react'; // Import Trash2 icon
18 |
19 | const ChapterFormModal = ({ open, onOpenChange, chapterToEdit, actId }) => {
20 | const { t } = useTranslation();
21 | const { addChapterToAct, updateChapter, deleteChapter } = useData(); // Add deleteChapter
22 | const [name, setName] = useState('');
23 | const [isConfirmDeleteOpen, setIsConfirmDeleteOpen] = useState(false); // State for confirm modal
24 | const isEditing = Boolean(chapterToEdit);
25 |
26 | useEffect(() => {
27 | if (open) {
28 | if (isEditing && chapterToEdit) {
29 | setName(chapterToEdit.name || '');
30 | } else {
31 | setName('');
32 | }
33 | }
34 | }, [chapterToEdit, isEditing, open]);
35 |
36 | const resetForm = () => setName('');
37 |
38 | const handleSubmit = () => {
39 | if (!isEditing && !actId) {
40 | console.error(t('chapter_form_modal_error_act_id_required'));
41 | return;
42 | }
43 |
44 | if (isEditing && chapterToEdit) {
45 | updateChapter(chapterToEdit.id, { name });
46 | } else if (actId) {
47 | addChapterToAct(actId, { name });
48 | }
49 | resetForm();
50 | onOpenChange(false);
51 | };
52 |
53 | const handleDeleteChapter = () => {
54 | if (chapterToEdit && actId) { // Ensure actId is available for deletion context
55 | deleteChapter(chapterToEdit.id, actId);
56 | resetForm();
57 | onOpenChange(false); // Close main modal
58 | setIsConfirmDeleteOpen(false); // Close confirm modal
59 | } else if (chapterToEdit) {
60 | // Fallback if actId is somehow not passed during edit (should not happen with current PlanView structure)
61 | console.warn("Attempting to delete chapter without parent actId. This might lead to orphaned data if not handled by deleteChapter globally.");
62 | deleteChapter(chapterToEdit.id, null); // Or handle as an error
63 | resetForm();
64 | onOpenChange(false);
65 | setIsConfirmDeleteOpen(false);
66 | }
67 | };
68 |
69 | useEffect(() => {
70 | if (!open) {
71 | resetForm();
72 | setIsConfirmDeleteOpen(false);
73 | }
74 | }, [open]);
75 |
76 | return (
77 | <>
78 |
110 |
111 | {chapterToEdit && (
112 |
119 | )}
120 | >
121 | );
122 | };
123 |
124 | export default ChapterFormModal;
125 |
--------------------------------------------------------------------------------
/src/components/settings/FontSettingsControl.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useTranslation } from 'react-i18next';
3 | import { Label } from "@/components/ui/label";
4 | import { Button } from "@/components/ui/button";
5 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
6 | import { AArrowUp, AArrowDown } from 'lucide-react';
7 | import { useSettings } from '@/context/SettingsContext';
8 |
9 | const FontSettingsControl = () => {
10 | const { t } = useTranslation();
11 | const {
12 | fontFamily,
13 | fontSize,
14 | setFontFamily,
15 | setFontSize,
16 | AVAILABLE_FONTS,
17 | } = useSettings();
18 |
19 | return (
20 |
21 |
22 |
23 |
35 |
36 |
37 |
38 |
39 |
48 |
{fontSize}px
49 |
58 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default FontSettingsControl;
65 |
--------------------------------------------------------------------------------
/src/components/settings/LanguageSwitcher.jsx:
--------------------------------------------------------------------------------
1 | // Example: LanguageSwitcher component
2 | import { useTranslation } from 'react-i18next';
3 |
4 | function LanguageSwitcher() {
5 | const { t, i18n } = useTranslation(); // Destructure t
6 |
7 | const changeLanguage = (lng) => {
8 | i18n.changeLanguage(lng);
9 | };
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | {/* Add buttons for other languages */}
19 |
20 | );
21 | }
22 |
23 | export default LanguageSwitcher;
24 |
--------------------------------------------------------------------------------
/src/components/ui/ConfirmModal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | AlertDialog,
4 | AlertDialogAction,
5 | AlertDialogCancel,
6 | AlertDialogContent,
7 | AlertDialogDescription,
8 | AlertDialogFooter,
9 | AlertDialogHeader,
10 | AlertDialogTitle,
11 | } from '@/components/ui/alert-dialog'; // Assuming this is the correct path
12 | import { useTranslation } from 'react-i18next';
13 |
14 | const ConfirmModal = ({ open, onOpenChange, title, description, onConfirm, confirmText, cancelText }) => {
15 | const { t } = useTranslation();
16 | if (!open) return null;
17 |
18 | const finalConfirmText = confirmText || t('confirm');
19 | const finalCancelText = cancelText || t('cancel');
20 |
21 | return (
22 |
23 |
24 |
25 | {title}
26 | {description && {description}}
27 |
28 |
29 | onOpenChange(false)}>{finalCancelText}
30 | {
31 | onConfirm();
32 | onOpenChange(false);
33 | }}>
34 | {finalConfirmText}
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default ConfirmModal;
43 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
3 | import { ChevronDown } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Accordion = AccordionPrimitive.Root
8 |
9 | const AccordionItem = React.forwardRef(({ className, ...props }, ref) => (
10 |
11 | ))
12 | AccordionItem.displayName = "AccordionItem"
13 |
14 | const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
15 |
16 | svg]:rotate-180",
20 | className
21 | )}
22 | {...props}>
23 | {children}
24 |
25 |
26 |
27 | ))
28 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
29 |
30 | const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => (
31 |
35 | {children}
36 |
37 | ))
38 |
39 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
40 |
41 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
42 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3 |
4 | import { cn } from "@/lib/utils"
5 | import { buttonVariants } from "@/components/ui/button"
6 |
7 | const AlertDialog = AlertDialogPrimitive.Root
8 |
9 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10 |
11 | const AlertDialogPortal = AlertDialogPrimitive.Portal
12 |
13 | const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
14 |
21 | ))
22 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
23 |
24 | const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (
25 |
26 |
27 |
34 |
35 | ))
36 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
37 |
38 | const AlertDialogHeader = ({
39 | className,
40 | ...props
41 | }) => (
42 |
45 | )
46 | AlertDialogHeader.displayName = "AlertDialogHeader"
47 |
48 | const AlertDialogFooter = ({
49 | className,
50 | ...props
51 | }) => (
52 |
55 | )
56 | AlertDialogFooter.displayName = "AlertDialogFooter"
57 |
58 | const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (
59 |
60 | ))
61 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
62 |
63 | const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (
64 |
68 | ))
69 | AlertDialogDescription.displayName =
70 | AlertDialogPrimitive.Description.displayName
71 |
72 | const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (
73 |
74 | ))
75 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
76 |
77 | const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (
78 |
82 | ))
83 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
84 |
85 | export {
86 | AlertDialog,
87 | AlertDialogPortal,
88 | AlertDialogOverlay,
89 | AlertDialogTrigger,
90 | AlertDialogContent,
91 | AlertDialogHeader,
92 | AlertDialogFooter,
93 | AlertDialogTitle,
94 | AlertDialogDescription,
95 | AlertDialogAction,
96 | AlertDialogCancel,
97 | }
98 |
--------------------------------------------------------------------------------
/src/components/ui/alert.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva } from "class-variance-authority";
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef(({ className, variant, ...props }, ref) => (
23 |
28 | ))
29 | Alert.displayName = "Alert"
30 |
31 | const AlertTitle = React.forwardRef(({ className, ...props }, ref) => (
32 |
36 | ))
37 | AlertTitle.displayName = "AlertTitle"
38 |
39 | const AlertDescription = React.forwardRef(({ className, ...props }, ref) => (
40 |
44 | ))
45 | AlertDescription.displayName = "AlertDescription"
46 |
47 | export { Alert, AlertTitle, AlertDescription }
48 |
--------------------------------------------------------------------------------
/src/components/ui/aspect-ratio.jsx:
--------------------------------------------------------------------------------
1 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2 |
3 | const AspectRatio = AspectRatioPrimitive.Root
4 |
5 | export { AspectRatio }
6 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Avatar = React.forwardRef(({ className, ...props }, ref) => (
7 |
11 | ))
12 | Avatar.displayName = AvatarPrimitive.Root.displayName
13 |
14 | const AvatarImage = React.forwardRef(({ className, ...props }, ref) => (
15 |
19 | ))
20 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
21 |
22 | const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => (
23 |
30 | ))
31 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
32 |
33 | export { Avatar, AvatarImage, AvatarFallback }
34 |
--------------------------------------------------------------------------------
/src/components/ui/badge.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva } from "class-variance-authority";
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | function Badge({
27 | className,
28 | variant,
29 | ...props
30 | }) {
31 | return ();
32 | }
33 |
34 | export { Badge, badgeVariants }
35 |
--------------------------------------------------------------------------------
/src/components/ui/breadcrumb.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { ChevronRight, MoreHorizontal } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Breadcrumb = React.forwardRef(
8 | ({ ...props }, ref) =>
9 | )
10 | Breadcrumb.displayName = "Breadcrumb"
11 |
12 | const BreadcrumbList = React.forwardRef(({ className, ...props }, ref) => (
13 |
20 | ))
21 | BreadcrumbList.displayName = "BreadcrumbList"
22 |
23 | const BreadcrumbItem = React.forwardRef(({ className, ...props }, ref) => (
24 |
28 | ))
29 | BreadcrumbItem.displayName = "BreadcrumbItem"
30 |
31 | const BreadcrumbLink = React.forwardRef(({ asChild, className, ...props }, ref) => {
32 | const Comp = asChild ? Slot : "a"
33 |
34 | return (
35 | ()
39 | );
40 | })
41 | BreadcrumbLink.displayName = "BreadcrumbLink"
42 |
43 | const BreadcrumbPage = React.forwardRef(({ className, ...props }, ref) => (
44 |
51 | ))
52 | BreadcrumbPage.displayName = "BreadcrumbPage"
53 |
54 | const BreadcrumbSeparator = ({
55 | children,
56 | className,
57 | ...props
58 | }) => (
59 | svg]:size-3.5", className)}
63 | {...props}>
64 | {children ?? }
65 |
66 | )
67 | BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
68 |
69 | const BreadcrumbEllipsis = ({
70 | className,
71 | ...props
72 | }) => (
73 |
78 |
79 | More
80 |
81 | )
82 | BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
83 |
84 | export {
85 | Breadcrumb,
86 | BreadcrumbList,
87 | BreadcrumbItem,
88 | BreadcrumbLink,
89 | BreadcrumbPage,
90 | BreadcrumbSeparator,
91 | BreadcrumbEllipsis,
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/ui/button.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
37 | const Comp = asChild ? Slot : "button"
38 | return (
39 | ()
43 | );
44 | })
45 | Button.displayName = "Button"
46 |
47 | export { Button, buttonVariants }
48 |
--------------------------------------------------------------------------------
/src/components/ui/calendar.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { ChevronLeft, ChevronRight } from "lucide-react"
3 | import { DayPicker } from "react-day-picker"
4 |
5 | import { cn } from "@/lib/utils"
6 | import { buttonVariants } from "@/components/ui/button"
7 |
8 | function Calendar({
9 | className,
10 | classNames,
11 | showOutsideDays = true,
12 | ...props
13 | }) {
14 | return (
15 | ( ,
54 | IconRight: ({ ...props }) => ,
55 | }}
56 | {...props} />)
57 | );
58 | }
59 | Calendar.displayName = "Calendar"
60 |
61 | export { Calendar }
62 |
--------------------------------------------------------------------------------
/src/components/ui/card.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef(({ className, ...props }, ref) => (
6 |
10 | ))
11 | Card.displayName = "Card"
12 |
13 | const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
14 |
18 | ))
19 | CardHeader.displayName = "CardHeader"
20 |
21 | const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
22 |
26 | ))
27 | CardTitle.displayName = "CardTitle"
28 |
29 | const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
30 |
34 | ))
35 | CardDescription.displayName = "CardDescription"
36 |
37 | const CardContent = React.forwardRef(({ className, ...props }, ref) => (
38 |
39 | ))
40 | CardContent.displayName = "CardContent"
41 |
42 | const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
43 |
47 | ))
48 | CardFooter.displayName = "CardFooter"
49 |
50 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
51 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3 | import { Check } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Checkbox = React.forwardRef(({ className, ...props }, ref) => (
8 |
15 |
16 |
17 |
18 |
19 | ))
20 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
21 |
22 | export { Checkbox }
23 |
--------------------------------------------------------------------------------
/src/components/ui/collapsible.jsx:
--------------------------------------------------------------------------------
1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
2 |
3 | const Collapsible = CollapsiblePrimitive.Root
4 |
5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
6 |
7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8 |
9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
10 |
--------------------------------------------------------------------------------
/src/components/ui/command.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Command as CommandPrimitive } from "cmdk"
3 | import { Search } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 | import { Dialog, DialogContent } from "@/components/ui/dialog"
7 |
8 | const Command = React.forwardRef(({ className, ...props }, ref) => (
9 |
16 | ))
17 | Command.displayName = CommandPrimitive.displayName
18 |
19 | const CommandDialog = ({
20 | children,
21 | ...props
22 | }) => {
23 | return (
24 | ()
32 | );
33 | }
34 |
35 | const CommandInput = React.forwardRef(({ className, ...props }, ref) => (
36 |
37 |
38 |
45 |
46 | ))
47 |
48 | CommandInput.displayName = CommandPrimitive.Input.displayName
49 |
50 | const CommandList = React.forwardRef(({ className, ...props }, ref) => (
51 |
55 | ))
56 |
57 | CommandList.displayName = CommandPrimitive.List.displayName
58 |
59 | const CommandEmpty = React.forwardRef((props, ref) => (
60 |
61 | ))
62 |
63 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
64 |
65 | const CommandGroup = React.forwardRef(({ className, ...props }, ref) => (
66 |
73 | ))
74 |
75 | CommandGroup.displayName = CommandPrimitive.Group.displayName
76 |
77 | const CommandSeparator = React.forwardRef(({ className, ...props }, ref) => (
78 |
79 | ))
80 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
81 |
82 | const CommandItem = React.forwardRef(({ className, ...props }, ref) => (
83 |
90 | ))
91 |
92 | CommandItem.displayName = CommandPrimitive.Item.displayName
93 |
94 | const CommandShortcut = ({
95 | className,
96 | ...props
97 | }) => {
98 | return (
99 | ()
102 | );
103 | }
104 | CommandShortcut.displayName = "CommandShortcut"
105 |
106 | export {
107 | Command,
108 | CommandDialog,
109 | CommandInput,
110 | CommandList,
111 | CommandEmpty,
112 | CommandGroup,
113 | CommandItem,
114 | CommandShortcut,
115 | CommandSeparator,
116 | }
117 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as DialogPrimitive from "@radix-ui/react-dialog"
3 | import { X } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Dialog = DialogPrimitive.Root
8 |
9 | const DialogTrigger = DialogPrimitive.Trigger
10 |
11 | const DialogPortal = DialogPrimitive.Portal
12 |
13 | const DialogClose = DialogPrimitive.Close
14 |
15 | const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
16 |
23 | ))
24 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
25 |
26 | const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (
27 |
28 |
29 |
36 | {children}
37 |
39 |
40 | Close
41 |
42 |
43 |
44 | ))
45 | DialogContent.displayName = DialogPrimitive.Content.displayName
46 |
47 | const DialogHeader = ({
48 | className,
49 | ...props
50 | }) => (
51 |
54 | )
55 | DialogHeader.displayName = "DialogHeader"
56 |
57 | const DialogFooter = ({
58 | className,
59 | ...props
60 | }) => (
61 |
64 | )
65 | DialogFooter.displayName = "DialogFooter"
66 |
67 | const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
68 |
72 | ))
73 | DialogTitle.displayName = DialogPrimitive.Title.displayName
74 |
75 | const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
76 |
80 | ))
81 | DialogDescription.displayName = DialogPrimitive.Description.displayName
82 |
83 | export {
84 | Dialog,
85 | DialogPortal,
86 | DialogOverlay,
87 | DialogClose,
88 | DialogTrigger,
89 | DialogContent,
90 | DialogHeader,
91 | DialogFooter,
92 | DialogTitle,
93 | DialogDescription,
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/ui/drawer.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Drawer as DrawerPrimitive } from "vaul"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Drawer = ({
7 | shouldScaleBackground = true,
8 | ...props
9 | }) => (
10 |
11 | )
12 | Drawer.displayName = "Drawer"
13 |
14 | const DrawerTrigger = DrawerPrimitive.Trigger
15 |
16 | const DrawerPortal = DrawerPrimitive.Portal
17 |
18 | const DrawerClose = DrawerPrimitive.Close
19 |
20 | const DrawerOverlay = React.forwardRef(({ className, ...props }, ref) => (
21 |
25 | ))
26 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
27 |
28 | const DrawerContent = React.forwardRef(({ className, children, ...props }, ref) => (
29 |
30 |
31 |
38 |
39 | {children}
40 |
41 |
42 | ))
43 | DrawerContent.displayName = "DrawerContent"
44 |
45 | const DrawerHeader = ({
46 | className,
47 | ...props
48 | }) => (
49 |
52 | )
53 | DrawerHeader.displayName = "DrawerHeader"
54 |
55 | const DrawerFooter = ({
56 | className,
57 | ...props
58 | }) => (
59 |
60 | )
61 | DrawerFooter.displayName = "DrawerFooter"
62 |
63 | const DrawerTitle = React.forwardRef(({ className, ...props }, ref) => (
64 |
68 | ))
69 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName
70 |
71 | const DrawerDescription = React.forwardRef(({ className, ...props }, ref) => (
72 |
76 | ))
77 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName
78 |
79 | export {
80 | Drawer,
81 | DrawerPortal,
82 | DrawerOverlay,
83 | DrawerTrigger,
84 | DrawerClose,
85 | DrawerContent,
86 | DrawerHeader,
87 | DrawerFooter,
88 | DrawerTitle,
89 | DrawerDescription,
90 | }
91 |
--------------------------------------------------------------------------------
/src/components/ui/form.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { Controller, FormProvider, useFormContext } from "react-hook-form";
4 |
5 | import { cn } from "@/lib/utils"
6 | import { Label } from "@/components/ui/label"
7 |
8 | const Form = FormProvider
9 |
10 | const FormFieldContext = React.createContext({})
11 |
12 | const FormField = (
13 | {
14 | ...props
15 | }
16 | ) => {
17 | return (
18 | (
19 |
20 | )
21 | );
22 | }
23 |
24 | const useFormField = () => {
25 | const fieldContext = React.useContext(FormFieldContext)
26 | const itemContext = React.useContext(FormItemContext)
27 | const { getFieldState, formState } = useFormContext()
28 |
29 | const fieldState = getFieldState(fieldContext.name, formState)
30 |
31 | if (!fieldContext) {
32 | throw new Error("useFormField should be used within ")
33 | }
34 |
35 | const { id } = itemContext
36 |
37 | return {
38 | id,
39 | name: fieldContext.name,
40 | formItemId: `${id}-form-item`,
41 | formDescriptionId: `${id}-form-item-description`,
42 | formMessageId: `${id}-form-item-message`,
43 | ...fieldState,
44 | }
45 | }
46 |
47 | const FormItemContext = React.createContext({})
48 |
49 | const FormItem = React.forwardRef(({ className, ...props }, ref) => {
50 | const id = React.useId()
51 |
52 | return (
53 | (
54 |
55 | )
56 | );
57 | })
58 | FormItem.displayName = "FormItem"
59 |
60 | const FormLabel = React.forwardRef(({ className, ...props }, ref) => {
61 | const { error, formItemId } = useFormField()
62 |
63 | return (
64 | ()
69 | );
70 | })
71 | FormLabel.displayName = "FormLabel"
72 |
73 | const FormControl = React.forwardRef(({ ...props }, ref) => {
74 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
75 |
76 | return (
77 | ()
87 | );
88 | })
89 | FormControl.displayName = "FormControl"
90 |
91 | const FormDescription = React.forwardRef(({ className, ...props }, ref) => {
92 | const { formDescriptionId } = useFormField()
93 |
94 | return (
95 | ()
100 | );
101 | })
102 | FormDescription.displayName = "FormDescription"
103 |
104 | const FormMessage = React.forwardRef(({ className, children, ...props }, ref) => {
105 | const { error, formMessageId } = useFormField()
106 | const body = error ? String(error?.message) : children
107 |
108 | if (!body) {
109 | return null
110 | }
111 |
112 | return (
113 | (
118 | {body}
119 |
)
120 | );
121 | })
122 | FormMessage.displayName = "FormMessage"
123 |
124 | export {
125 | useFormField,
126 | Form,
127 | FormItem,
128 | FormLabel,
129 | FormControl,
130 | FormDescription,
131 | FormMessage,
132 | FormField,
133 | }
134 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const HoverCard = HoverCardPrimitive.Root
7 |
8 | const HoverCardTrigger = HoverCardPrimitive.Trigger
9 |
10 | const HoverCardContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
11 |
20 | ))
21 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
22 |
23 | export { HoverCard, HoverCardTrigger, HoverCardContent }
24 |
--------------------------------------------------------------------------------
/src/components/ui/input-otp.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { OTPInput, OTPInputContext } from "input-otp"
3 | import { Dot } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const InputOTP = React.forwardRef(({ className, containerClassName, ...props }, ref) => (
8 |
13 | ))
14 | InputOTP.displayName = "InputOTP"
15 |
16 | const InputOTPGroup = React.forwardRef(({ className, ...props }, ref) => (
17 |
18 | ))
19 | InputOTPGroup.displayName = "InputOTPGroup"
20 |
21 | const InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
22 | const inputOTPContext = React.useContext(OTPInputContext)
23 | const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
24 |
25 | return (
26 | (
34 | {char}
35 | {hasFakeCaret && (
36 |
40 | )}
41 |
)
42 | );
43 | })
44 | InputOTPSlot.displayName = "InputOTPSlot"
45 |
46 | const InputOTPSeparator = React.forwardRef(({ ...props }, ref) => (
47 |
48 |
49 |
50 | ))
51 | InputOTPSeparator.displayName = "InputOTPSeparator"
52 |
53 | export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
54 |
--------------------------------------------------------------------------------
/src/components/ui/input.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Input = React.forwardRef(({ className, type, ...props }, ref) => {
6 | return (
7 | ()
15 | );
16 | })
17 | Input.displayName = "Input"
18 |
19 | export { Input }
20 |
--------------------------------------------------------------------------------
/src/components/ui/label.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { cva } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const labelVariants = cva(
8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9 | )
10 |
11 | const Label = React.forwardRef(({ className, ...props }, ref) => (
12 |
13 | ))
14 | Label.displayName = LabelPrimitive.Root.displayName
15 |
16 | export { Label }
17 |
--------------------------------------------------------------------------------
/src/components/ui/navigation-menu.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3 | import { cva } from "class-variance-authority"
4 | import { ChevronDown } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const NavigationMenu = React.forwardRef(({ className, children, ...props }, ref) => (
9 |
16 | {children}
17 |
18 |
19 | ))
20 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
21 |
22 | const NavigationMenuList = React.forwardRef(({ className, ...props }, ref) => (
23 |
30 | ))
31 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
32 |
33 | const NavigationMenuItem = NavigationMenuPrimitive.Item
34 |
35 | const navigationMenuTriggerStyle = cva(
36 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
37 | )
38 |
39 | const NavigationMenuTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
40 |
44 | {children}{" "}
45 |
48 |
49 | ))
50 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
51 |
52 | const NavigationMenuContent = React.forwardRef(({ className, ...props }, ref) => (
53 |
60 | ))
61 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
62 |
63 | const NavigationMenuLink = NavigationMenuPrimitive.Link
64 |
65 | const NavigationMenuViewport = React.forwardRef(({ className, ...props }, ref) => (
66 |
67 |
74 |
75 | ))
76 | NavigationMenuViewport.displayName =
77 | NavigationMenuPrimitive.Viewport.displayName
78 |
79 | const NavigationMenuIndicator = React.forwardRef(({ className, ...props }, ref) => (
80 |
87 |
89 |
90 | ))
91 | NavigationMenuIndicator.displayName =
92 | NavigationMenuPrimitive.Indicator.displayName
93 |
94 | export {
95 | navigationMenuTriggerStyle,
96 | NavigationMenu,
97 | NavigationMenuList,
98 | NavigationMenuItem,
99 | NavigationMenuContent,
100 | NavigationMenuTrigger,
101 | NavigationMenuLink,
102 | NavigationMenuIndicator,
103 | NavigationMenuViewport,
104 | }
105 |
--------------------------------------------------------------------------------
/src/components/ui/pagination.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
3 |
4 | import { cn } from "@/lib/utils"
5 | import { buttonVariants } from "@/components/ui/button";
6 |
7 | const Pagination = ({
8 | className,
9 | ...props
10 | }) => (
11 |
16 | )
17 | Pagination.displayName = "Pagination"
18 |
19 | const PaginationContent = React.forwardRef(({ className, ...props }, ref) => (
20 |
24 | ))
25 | PaginationContent.displayName = "PaginationContent"
26 |
27 | const PaginationItem = React.forwardRef(({ className, ...props }, ref) => (
28 |
29 | ))
30 | PaginationItem.displayName = "PaginationItem"
31 |
32 | const PaginationLink = ({
33 | className,
34 | isActive,
35 | size = "icon",
36 | ...props
37 | }) => (
38 |
45 | )
46 | PaginationLink.displayName = "PaginationLink"
47 |
48 | const PaginationPrevious = ({
49 | className,
50 | ...props
51 | }) => (
52 |
57 |
58 | Previous
59 |
60 | )
61 | PaginationPrevious.displayName = "PaginationPrevious"
62 |
63 | const PaginationNext = ({
64 | className,
65 | ...props
66 | }) => (
67 |
72 | Next
73 |
74 |
75 | )
76 | PaginationNext.displayName = "PaginationNext"
77 |
78 | const PaginationEllipsis = ({
79 | className,
80 | ...props
81 | }) => (
82 |
86 |
87 | More pages
88 |
89 | )
90 | PaginationEllipsis.displayName = "PaginationEllipsis"
91 |
92 | export {
93 | Pagination,
94 | PaginationContent,
95 | PaginationEllipsis,
96 | PaginationItem,
97 | PaginationLink,
98 | PaginationNext,
99 | PaginationPrevious,
100 | }
101 |
--------------------------------------------------------------------------------
/src/components/ui/popover.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as PopoverPrimitive from "@radix-ui/react-popover"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Popover = PopoverPrimitive.Root
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger
9 |
10 | const PopoverContent = React.forwardRef(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
11 |
12 |
21 |
22 | ))
23 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
24 |
25 | export { Popover, PopoverTrigger, PopoverContent }
26 |
--------------------------------------------------------------------------------
/src/components/ui/progress.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ProgressPrimitive from "@radix-ui/react-progress"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Progress = React.forwardRef(({ className, value, ...props }, ref) => (
7 |
11 |
14 |
15 | ))
16 | Progress.displayName = ProgressPrimitive.Root.displayName
17 |
18 | export { Progress }
19 |
--------------------------------------------------------------------------------
/src/components/ui/radio-group.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
3 | import { Circle } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const RadioGroup = React.forwardRef(({ className, ...props }, ref) => {
8 | return ();
9 | })
10 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
11 |
12 | const RadioGroupItem = React.forwardRef(({ className, ...props }, ref) => {
13 | return (
14 | (
21 |
22 |
23 |
24 | )
25 | );
26 | })
27 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
28 |
29 | export { RadioGroup, RadioGroupItem }
30 |
--------------------------------------------------------------------------------
/src/components/ui/resizable.jsx:
--------------------------------------------------------------------------------
1 | import React from "react"; // Import React
2 | import { GripVertical } from "lucide-react"
3 | import * as ResizablePrimitive from "react-resizable-panels"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const ResizablePanelGroup = React.forwardRef(({ className, ...props }, ref) => (
8 |
15 | ))
16 | ResizablePanelGroup.displayName = "ResizablePanelGroup" // Optional: for better debugging
17 |
18 | const ResizablePanel = React.forwardRef((props, ref) => (
19 |
20 | ));
21 | ResizablePanel.displayName = "ResizablePanel";
22 |
23 | const ResizableHandle = ({
24 | withHandle,
25 | className,
26 | ...props
27 | }) => (
28 | div]:rotate-90",
31 | className
32 | )}
33 | {...props}>
34 | {withHandle && (
35 |
37 |
38 |
39 | )}
40 |
41 | )
42 |
43 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
44 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => (
7 |
11 |
12 | {children}
13 |
14 |
15 |
16 |
17 | ))
18 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
19 |
20 | const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => (
21 |
33 |
34 |
35 | ))
36 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
37 |
38 | export { ScrollArea, ScrollBar }
39 |
--------------------------------------------------------------------------------
/src/components/ui/select.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SelectPrimitive from "@radix-ui/react-select"
3 | import { Check, ChevronDown, ChevronUp } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Select = SelectPrimitive.Root
8 |
9 | const SelectGroup = SelectPrimitive.Group
10 |
11 | const SelectValue = SelectPrimitive.Value
12 |
13 | const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
14 | span]:line-clamp-1",
18 | className
19 | )}
20 | {...props}>
21 | {children}
22 |
23 |
24 |
25 |
26 | ))
27 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
28 |
29 | const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => (
30 |
34 |
35 |
36 | ))
37 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
38 |
39 | const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => (
40 |
44 |
45 |
46 | ))
47 | SelectScrollDownButton.displayName =
48 | SelectPrimitive.ScrollDownButton.displayName
49 |
50 | const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => (
51 |
52 |
62 |
63 |
66 | {children}
67 |
68 |
69 |
70 |
71 | ))
72 | SelectContent.displayName = SelectPrimitive.Content.displayName
73 |
74 | const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (
75 |
79 | ))
80 | SelectLabel.displayName = SelectPrimitive.Label.displayName
81 |
82 | const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (
83 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | {children}
97 |
98 | ))
99 | SelectItem.displayName = SelectPrimitive.Item.displayName
100 |
101 | const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (
102 |
106 | ))
107 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
108 |
109 | export {
110 | Select,
111 | SelectGroup,
112 | SelectValue,
113 | SelectTrigger,
114 | SelectContent,
115 | SelectLabel,
116 | SelectItem,
117 | SelectSeparator,
118 | SelectScrollUpButton,
119 | SelectScrollDownButton,
120 | }
121 |
--------------------------------------------------------------------------------
/src/components/ui/separator.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Separator = React.forwardRef((
7 | { className, orientation = "horizontal", decorative = true, ...props },
8 | ref
9 | ) => (
10 |
20 | ))
21 | Separator.displayName = SeparatorPrimitive.Root.displayName
22 |
23 | export { Separator }
24 |
--------------------------------------------------------------------------------
/src/components/ui/sheet.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SheetPrimitive from "@radix-ui/react-dialog"
3 | import { cva } from "class-variance-authority";
4 | import { X } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Sheet = SheetPrimitive.Root
9 |
10 | const SheetTrigger = SheetPrimitive.Trigger
11 |
12 | const SheetClose = SheetPrimitive.Close
13 |
14 | const SheetPortal = SheetPrimitive.Portal
15 |
16 | const SheetOverlay = React.forwardRef(({ className, ...props }, ref) => (
17 |
24 | ))
25 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
26 |
27 | const sheetVariants = cva(
28 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
29 | {
30 | variants: {
31 | side: {
32 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
33 | bottom:
34 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
35 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
36 | right:
37 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
38 | },
39 | },
40 | defaultVariants: {
41 | side: "right",
42 | },
43 | }
44 | )
45 |
46 | const SheetContent = React.forwardRef(({ side = "right", className, children, ...props }, ref) => (
47 |
48 |
49 |
50 | {children}
51 |
53 |
54 | Close
55 |
56 |
57 |
58 | ))
59 | SheetContent.displayName = SheetPrimitive.Content.displayName
60 |
61 | const SheetHeader = ({
62 | className,
63 | ...props
64 | }) => (
65 |
68 | )
69 | SheetHeader.displayName = "SheetHeader"
70 |
71 | const SheetFooter = ({
72 | className,
73 | ...props
74 | }) => (
75 |
78 | )
79 | SheetFooter.displayName = "SheetFooter"
80 |
81 | const SheetTitle = React.forwardRef(({ className, ...props }, ref) => (
82 |
86 | ))
87 | SheetTitle.displayName = SheetPrimitive.Title.displayName
88 |
89 | const SheetDescription = React.forwardRef(({ className, ...props }, ref) => (
90 |
94 | ))
95 | SheetDescription.displayName = SheetPrimitive.Description.displayName
96 |
97 | export {
98 | Sheet,
99 | SheetPortal,
100 | SheetOverlay,
101 | SheetTrigger,
102 | SheetClose,
103 | SheetContent,
104 | SheetHeader,
105 | SheetFooter,
106 | SheetTitle,
107 | SheetDescription,
108 | }
109 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton.jsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }) {
7 | return ();
8 | }
9 |
10 | export { Skeleton }
11 |
--------------------------------------------------------------------------------
/src/components/ui/slider.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SliderPrimitive from "@radix-ui/react-slider"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Slider = React.forwardRef(({ className, ...props }, ref) => (
7 |
11 |
13 |
14 |
15 |
17 |
18 | ))
19 | Slider.displayName = SliderPrimitive.Root.displayName
20 |
21 | export { Slider }
22 |
--------------------------------------------------------------------------------
/src/components/ui/sonner.jsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from "next-themes"
2 | import { Toaster as Sonner } from "sonner"
3 |
4 | const Toaster = ({
5 | ...props
6 | }) => {
7 | const { theme = "system" } = useTheme()
8 |
9 | return (
10 | ()
25 | );
26 | }
27 |
28 | export { Toaster }
29 |
--------------------------------------------------------------------------------
/src/components/ui/switch.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SwitchPrimitives from "@radix-ui/react-switch"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Switch = React.forwardRef(({ className, ...props }, ref) => (
7 |
14 |
18 |
19 | ))
20 | Switch.displayName = SwitchPrimitives.Root.displayName
21 |
22 | export { Switch }
23 |
--------------------------------------------------------------------------------
/src/components/ui/table.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Table = React.forwardRef(({ className, ...props }, ref) => (
6 |
12 | ))
13 | Table.displayName = "Table"
14 |
15 | const TableHeader = React.forwardRef(({ className, ...props }, ref) => (
16 |
17 | ))
18 | TableHeader.displayName = "TableHeader"
19 |
20 | const TableBody = React.forwardRef(({ className, ...props }, ref) => (
21 |
25 | ))
26 | TableBody.displayName = "TableBody"
27 |
28 | const TableFooter = React.forwardRef(({ className, ...props }, ref) => (
29 | tr]:last:border-b-0", className)}
32 | {...props} />
33 | ))
34 | TableFooter.displayName = "TableFooter"
35 |
36 | const TableRow = React.forwardRef(({ className, ...props }, ref) => (
37 |
44 | ))
45 | TableRow.displayName = "TableRow"
46 |
47 | const TableHead = React.forwardRef(({ className, ...props }, ref) => (
48 | |
55 | ))
56 | TableHead.displayName = "TableHead"
57 |
58 | const TableCell = React.forwardRef(({ className, ...props }, ref) => (
59 | |
63 | ))
64 | TableCell.displayName = "TableCell"
65 |
66 | const TableCaption = React.forwardRef(({ className, ...props }, ref) => (
67 |
71 | ))
72 | TableCaption.displayName = "TableCaption"
73 |
74 | export {
75 | Table,
76 | TableHeader,
77 | TableBody,
78 | TableFooter,
79 | TableHead,
80 | TableRow,
81 | TableCell,
82 | TableCaption,
83 | }
84 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TabsPrimitive from "@radix-ui/react-tabs"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Tabs = TabsPrimitive.Root
7 |
8 | const TabsList = React.forwardRef(({ className, ...props }, ref) => (
9 |
16 | ))
17 | TabsList.displayName = TabsPrimitive.List.displayName
18 |
19 | const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
20 |
27 | ))
28 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
29 |
30 | const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
31 |
38 | ))
39 | TabsContent.displayName = TabsPrimitive.Content.displayName
40 |
41 | export { Tabs, TabsList, TabsTrigger, TabsContent }
42 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Textarea = React.forwardRef(({ className, ...props }, ref) => {
6 | return (
7 | ()
14 | );
15 | })
16 | Textarea.displayName = "Textarea"
17 |
18 | export { Textarea }
19 |
--------------------------------------------------------------------------------
/src/components/ui/toast.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToastPrimitives from "@radix-ui/react-toast"
3 | import { cva } from "class-variance-authority";
4 | import { X } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ToastProvider = ToastPrimitives.Provider
9 |
10 | const ToastViewport = React.forwardRef(({ className, ...props }, ref) => (
11 |
18 | ))
19 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
20 |
21 | const toastVariants = cva(
22 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
23 | {
24 | variants: {
25 | variant: {
26 | default: "border bg-background text-foreground",
27 | destructive:
28 | "destructive group border-destructive bg-destructive text-destructive-foreground",
29 | },
30 | },
31 | defaultVariants: {
32 | variant: "default",
33 | },
34 | }
35 | )
36 |
37 | const Toast = React.forwardRef(({ className, variant, ...props }, ref) => {
38 | return (
39 | ()
43 | );
44 | })
45 | Toast.displayName = ToastPrimitives.Root.displayName
46 |
47 | const ToastAction = React.forwardRef(({ className, ...props }, ref) => (
48 |
55 | ))
56 | ToastAction.displayName = ToastPrimitives.Action.displayName
57 |
58 | const ToastClose = React.forwardRef(({ className, ...props }, ref) => (
59 |
67 |
68 |
69 | ))
70 | ToastClose.displayName = ToastPrimitives.Close.displayName
71 |
72 | const ToastTitle = React.forwardRef(({ className, ...props }, ref) => (
73 |
74 | ))
75 | ToastTitle.displayName = ToastPrimitives.Title.displayName
76 |
77 | const ToastDescription = React.forwardRef(({ className, ...props }, ref) => (
78 |
79 | ))
80 | ToastDescription.displayName = ToastPrimitives.Description.displayName
81 |
82 | export { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction };
83 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.jsx:
--------------------------------------------------------------------------------
1 | import { useToast } from "@/components/hooks/use-toast"
2 | import {
3 | Toast,
4 | ToastClose,
5 | ToastDescription,
6 | ToastProvider,
7 | ToastTitle,
8 | ToastViewport,
9 | } from "@/components/ui/toast"
10 |
11 | export function Toaster() {
12 | const { toasts } = useToast()
13 |
14 | return (
15 | (
16 | {toasts.map(function ({ id, title, description, action, ...props }) {
17 | return (
18 | (
19 |
20 | {title && {title}}
21 | {description && (
22 | {description}
23 | )}
24 |
25 | {action}
26 |
27 | )
28 | );
29 | })}
30 |
31 | )
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/ui/toggle-group.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
3 |
4 | import { cn } from "@/lib/utils"
5 | import { toggleVariants } from "@/components/ui/toggle"
6 |
7 | const ToggleGroupContext = React.createContext({
8 | size: "default",
9 | variant: "default",
10 | })
11 |
12 | const ToggleGroup = React.forwardRef(({ className, variant, size, children, ...props }, ref) => (
13 |
17 |
18 | {children}
19 |
20 |
21 | ))
22 |
23 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
24 |
25 | const ToggleGroupItem = React.forwardRef(({ className, children, variant, size, ...props }, ref) => {
26 | const context = React.useContext(ToggleGroupContext)
27 |
28 | return (
29 | (
36 | {children}
37 | )
38 | );
39 | })
40 |
41 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
42 |
43 | export { ToggleGroup, ToggleGroupItem }
44 |
--------------------------------------------------------------------------------
/src/components/ui/toggle.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TogglePrimitive from "@radix-ui/react-toggle"
3 | import { cva } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const toggleVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-transparent",
13 | outline:
14 | "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
15 | },
16 | size: {
17 | default: "h-10 px-3",
18 | sm: "h-9 px-2.5",
19 | lg: "h-11 px-5",
20 | },
21 | },
22 | defaultVariants: {
23 | variant: "default",
24 | size: "default",
25 | },
26 | }
27 | )
28 |
29 | const Toggle = React.forwardRef(({ className, variant, size, ...props }, ref) => (
30 |
34 | ))
35 |
36 | Toggle.displayName = TogglePrimitive.Root.displayName
37 |
38 | export { Toggle, toggleVariants }
39 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider
7 |
8 | const Tooltip = TooltipPrimitive.Root
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger
11 |
12 | const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props }, ref) => (
13 |
21 | ))
22 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
23 |
24 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
25 |
--------------------------------------------------------------------------------
/src/data/conceptTemplates.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file src/data/conceptTemplates.js
3 | * @description Defines the default concept templates for new novels.
4 | * Each object in DEFAULT_CONCEPT_TEMPLATES will be processed by `createConceptTemplate`
5 | * in `models.js` to add `id`, `isDefault: true`, `creation_date`, and `last_modified_date`.
6 | */
7 |
8 | /**
9 | * @typedef {import('./models').ConceptTemplateData} ConceptTemplateData
10 | */
11 |
12 | /**
13 | * @type {Array<{name: string, templateData: ConceptTemplateData}>}
14 | */
15 | export const DEFAULT_CONCEPT_TEMPLATES = [
16 | {
17 | name: "Character",
18 | templateData: {
19 | tags: ["character"],
20 | description: "A character in the story. Consider their motivations, backstory, appearance, and role.",
21 | notes: "- Strengths:\n- Weaknesses:\n- Goals:\n- Conflicts:",
22 | priority: 10,
23 | }
24 | },
25 | {
26 | name: "Location",
27 | templateData: {
28 | tags: ["location"],
29 | description: "A place where events in the story occur. Think about its atmosphere, significance, and key features.",
30 | notes: "- Sensory details (sight, sound, smell):\n- History:\n- Importance to plot:",
31 | priority: 20,
32 | }
33 | },
34 | {
35 | name: "Item",
36 | templateData: {
37 | tags: ["item", "object"],
38 | description: "A significant object in the story. What is its origin, purpose, or power?",
39 | notes: "- Appearance:\n- Abilities/Properties:\n- Who possesses it?",
40 | priority: 30,
41 | }
42 | }
43 |
44 | ];
45 |
--------------------------------------------------------------------------------
/src/hooks/use-toast.js:
--------------------------------------------------------------------------------
1 | // Inspired by react-hot-toast library
2 | import * as React from "react"
3 |
4 | const TOAST_LIMIT = 1
5 | const TOAST_REMOVE_DELAY = 1000000
6 |
7 | const actionTypes = {
8 | ADD_TOAST: "ADD_TOAST",
9 | UPDATE_TOAST: "UPDATE_TOAST",
10 | DISMISS_TOAST: "DISMISS_TOAST",
11 | REMOVE_TOAST: "REMOVE_TOAST"
12 | }
13 |
14 | let count = 0
15 |
16 | function genId() {
17 | count = (count + 1) % Number.MAX_SAFE_INTEGER
18 | return count.toString();
19 | }
20 |
21 | const toastTimeouts = new Map()
22 |
23 | const addToRemoveQueue = (toastId) => {
24 | if (toastTimeouts.has(toastId)) {
25 | return
26 | }
27 |
28 | const timeout = setTimeout(() => {
29 | toastTimeouts.delete(toastId)
30 | dispatch({
31 | type: "REMOVE_TOAST",
32 | toastId: toastId,
33 | })
34 | }, TOAST_REMOVE_DELAY)
35 |
36 | toastTimeouts.set(toastId, timeout)
37 | }
38 |
39 | export const reducer = (state, action) => {
40 | switch (action.type) {
41 | case "ADD_TOAST":
42 | return {
43 | ...state,
44 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
45 | };
46 |
47 | case "UPDATE_TOAST":
48 | return {
49 | ...state,
50 | toasts: state.toasts.map((t) =>
51 | t.id === action.toast.id ? { ...t, ...action.toast } : t),
52 | };
53 |
54 | case "DISMISS_TOAST": {
55 | const { toastId } = action
56 |
57 | // ! Side effects ! - This could be extracted into a dismissToast() action,
58 | // but I'll keep it here for simplicity
59 | if (toastId) {
60 | addToRemoveQueue(toastId)
61 | } else {
62 | state.toasts.forEach((toast) => {
63 | addToRemoveQueue(toast.id)
64 | })
65 | }
66 |
67 | return {
68 | ...state,
69 | toasts: state.toasts.map((t) =>
70 | t.id === toastId || toastId === undefined
71 | ? {
72 | ...t,
73 | open: false,
74 | }
75 | : t),
76 | };
77 | }
78 | case "REMOVE_TOAST":
79 | if (action.toastId === undefined) {
80 | return {
81 | ...state,
82 | toasts: [],
83 | }
84 | }
85 | return {
86 | ...state,
87 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
88 | };
89 | }
90 | }
91 |
92 | const listeners = []
93 |
94 | let memoryState = { toasts: [] }
95 |
96 | function dispatch(action) {
97 | memoryState = reducer(memoryState, action)
98 | listeners.forEach((listener) => {
99 | listener(memoryState)
100 | })
101 | }
102 |
103 | function toast({
104 | ...props
105 | }) {
106 | const id = genId()
107 |
108 | const update = (props) =>
109 | dispatch({
110 | type: "UPDATE_TOAST",
111 | toast: { ...props, id },
112 | })
113 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
114 |
115 | dispatch({
116 | type: "ADD_TOAST",
117 | toast: {
118 | ...props,
119 | id,
120 | open: true,
121 | onOpenChange: (open) => {
122 | if (!open) dismiss()
123 | },
124 | },
125 | })
126 |
127 | return {
128 | id: id,
129 | dismiss,
130 | update,
131 | }
132 | }
133 |
134 | function useToast() {
135 | const [state, setState] = React.useState(memoryState)
136 |
137 | React.useEffect(() => {
138 | listeners.push(setState)
139 | return () => {
140 | const index = listeners.indexOf(setState)
141 | if (index > -1) {
142 | listeners.splice(index, 1)
143 | }
144 | };
145 | }, [state])
146 |
147 | return {
148 | ...state,
149 | toast,
150 | dismiss: (toastId) => dispatch({ type: "DISMISS_TOAST", toastId }),
151 | };
152 | }
153 |
154 | export { useToast, toast }
155 |
--------------------------------------------------------------------------------
/src/i18n.js:
--------------------------------------------------------------------------------
1 | // src/i18n.js
2 | import i18n from 'i18next';
3 | import { initReactI18next } from 'react-i18next';
4 | import LanguageDetector from 'i18next-browser-languagedetector';
5 |
6 | // Import translation files (example for English and Spanish)
7 | // These files will be created in the next steps.
8 | // For now, we'll assume they exist to avoid import errors during setup.
9 | // We will create them with placeholder content or the content from the plan.
10 | import en from './locales/en/translation.json';
11 | import es from './locales/es/translation.json';
12 | import de from './locales/de/translation.json';
13 | import cn from './locales/cn/translation.json';
14 | import ru from './locales/ru/translation.json';
15 | import vi from './locales/vi/translation.json';
16 |
17 | const resources = {
18 | en: {
19 | translation: en,
20 | },
21 | es: {
22 | translation: es,
23 | },
24 | de: {
25 | translation: de,
26 | },
27 | cn: {
28 | translation: cn,
29 | },
30 | ru: {
31 | translation: ru,
32 | },
33 | vi: {
34 | translation: vi,
35 | }
36 | };
37 |
38 | i18n
39 | .use(LanguageDetector) // Detect user language
40 | .use(initReactI18next) // Pass i18n to react-i18next
41 | .init({
42 | resources,
43 | fallbackLng: 'en', // Fallback language if user language is not available
44 | debug: true, // Set to false in production
45 |
46 | interpolation: {
47 | escapeValue: false, // React already escapes by default
48 | },
49 |
50 | // Options for LanguageDetector
51 | detection: {
52 | order: ['navigator', 'localStorage', 'cookie'],
53 | caches: ['localStorage'],
54 | },
55 | });
56 |
57 | export default i18n;
58 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /* Font Imports */
2 | @import "@fontsource/inter/400.css";
3 | @import "@fontsource/inter/700.css";
4 | @import "@fontsource/roboto/400.css";
5 | @import "@fontsource/roboto/700.css";
6 | @import "@fontsource/open-sans/400.css";
7 | @import "@fontsource/open-sans/700.css";
8 | @import "@fontsource/lato/400.css";
9 | @import "@fontsource/lato/700.css";
10 | @import "@fontsource/poppins/400.css";
11 | @import "@fontsource/poppins/700.css";
12 | @import "@fontsource/montserrat/400.css";
13 | @import "@fontsource/montserrat/700.css";
14 | @import "@fontsource/source-sans-3/400.css";
15 | @import "@fontsource/source-sans-3/700.css";
16 |
17 | @tailwind base;
18 | @tailwind components;
19 | @tailwind utilities;
20 |
21 | @layer base {
22 | :root {
23 | --background: 35 30% 90%;
24 | --foreground: 35 25% 25%;
25 | --card: 35 30% 92%;
26 | --card-foreground: 35 25% 25%;
27 | --popover: 35 30% 92%;
28 | --popover-foreground: 35 25% 25%;
29 | --primary: 15 15% 50%;
30 | --primary-foreground: 35 30% 95%;
31 | --secondary: 35 20% 85%;
32 | --secondary-foreground: 35 25% 25%;
33 | --muted: 35 20% 85%;
34 | --muted-foreground: 30 20% 40%;
35 | --accent: 35 40% 75%;
36 | --accent-foreground: 35 25% 25%;
37 | --destructive: 0 70% 50%;
38 | --destructive-foreground: 35 30% 95%;
39 | --border: 35 30% 80%;
40 | --input: 35 30% 80%;
41 | --ring: 35 40% 40%;
42 | --radius: 0.5rem;
43 | /* Font variables */
44 | --font-sans: "Inter", 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";
45 | --font-size-base: 16px;
46 | --chart-1: 35 70% 55%;
47 | --chart-2: 25 65% 45%;
48 | --chart-3: 15 60% 35%;
49 | --chart-4: 45 75% 50%;
50 | --chart-5: 55 80% 60%;
51 | }
52 |
53 | .dark {
54 | --background: 220 10% 12%;
55 | --foreground: 220 10% 90%;
56 | --card: 220 10% 14%;
57 | --card-foreground: 220 10% 90%;
58 | --popover: 220 10% 14%;
59 | --popover-foreground: 220 10% 90%;
60 | --primary: 210 100% 60%;
61 | --primary-foreground: 220 10% 12%;
62 | --secondary: 220 10% 20%;
63 | --secondary-foreground: 220 10% 90%;
64 | --muted: 220 10% 20%;
65 | --muted-foreground: 220 10% 70%;
66 | --accent: 210 100% 40%;
67 | --accent-foreground: 220 10% 90%;
68 | --destructive: 0 70% 50%;
69 | --destructive-foreground: 220 10% 90%;
70 | --border: 220 10% 25%;
71 | --input: 220 10% 25%;
72 | --ring: 210 100% 60%;
73 | /* Font variables are inherited from :root */
74 | --chart-1: 210 100% 60%;
75 | --chart-2: 195 90% 50%;
76 | --chart-3: 180 80% 45%;
77 | --chart-4: 225 85% 65%;
78 | --chart-5: 240 90% 70%;
79 | }
80 | }
81 |
82 | @layer base {
83 | html {
84 | font-family: var(--font-sans);
85 | font-size: var(--font-size-base); /* Set base font size on HTML for REM calculation */
86 | }
87 |
88 | html, body, #root {
89 | @apply h-full w-full; /* Ensure full height and width */
90 | }
91 |
92 | * {
93 | @apply border-border;
94 | }
95 |
96 | body {
97 | @apply bg-background text-foreground;
98 | /* font-family is inherited from html */
99 | /* font-size is inherited from html and scales with rem units */
100 | /* Ensure no centering or flex properties that might constrain width */
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/lib/utils.js:
--------------------------------------------------------------------------------
1 | import { clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
8 | /**
9 | * Removes leading whitespace (spaces or tabs) from each line of a given string.
10 | * This is useful for preventing Markdown renderers from interpreting indentation
11 | * as code blocks, especially in contexts like novel writing where indentation
12 | * might be used for formatting paragraphs but not for code.
13 | *
14 | * @param {string} text The input string.
15 | * @returns {string} The string with leading whitespace removed from each line.
16 | */
17 | export function removeIndentation(text) {
18 | if (!text) {
19 | return "";
20 | }
21 | // Split the text into lines, remove leading whitespace from each line,
22 | // and then join them back together.
23 | return text
24 | .split('\n')
25 | .map(line => line.replace(/^[ \t]+/, ''))
26 | .join('\n');
27 | }
28 |
29 |
30 | export const tokenCount = (text) => {
31 | if (!text) return 0;
32 | const tokenLength = 4; // Average characters per token
33 | return Math.ceil(text.length / tokenLength);
34 | };
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { StrictMode, Suspense } from 'react' // Add Suspense
2 | import { createRoot } from 'react-dom/client'
3 | import './i18n'; // Import the i18n configuration
4 | import RootApp from './RootApp.jsx'; // Import the new RootApp
5 | import './index.css'
6 | // DataProvider will be used within RootApp or its children where needed,
7 | // specifically around the NovelEditor component (App.jsx).
8 |
9 | createRoot(document.getElementById('root')).render(
10 |
11 | Loading...}> {/* Add Suspense */}
12 |
13 |
14 | ,
15 | );
16 |
17 | // Add 'loaded' class to body to hide spinner and show app
18 | document.body.classList.add('loaded');
19 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{js,jsx}',
6 | './components/**/*.{js,jsx}',
7 | './app/**/*.{js,jsx}',
8 | './src/**/*.{js,jsx}',
9 | ],
10 | prefix: "",
11 | theme: {
12 | container: {
13 | center: 'true',
14 | padding: '2rem',
15 | screens: {
16 | '2xl': '1400px'
17 | }
18 | },
19 | extend: {
20 | colors: {
21 | border: 'hsl(var(--border))',
22 | input: 'hsl(var(--input))',
23 | ring: 'hsl(var(--ring))',
24 | background: 'hsl(var(--background))',
25 | foreground: 'hsl(var(--foreground))',
26 | primary: {
27 | DEFAULT: 'hsl(var(--primary))',
28 | foreground: 'hsl(var(--primary-foreground))'
29 | },
30 | secondary: {
31 | DEFAULT: 'hsl(var(--secondary))',
32 | foreground: 'hsl(var(--secondary-foreground))'
33 | },
34 | destructive: {
35 | DEFAULT: 'hsl(var(--destructive))',
36 | foreground: 'hsl(var(--destructive-foreground))'
37 | },
38 | muted: {
39 | DEFAULT: 'hsl(var(--muted))',
40 | foreground: 'hsl(var(--muted-foreground))'
41 | },
42 | accent: {
43 | DEFAULT: 'hsl(var(--accent))',
44 | foreground: 'hsl(var(--accent-foreground))'
45 | },
46 | popover: {
47 | DEFAULT: 'hsl(var(--popover))',
48 | foreground: 'hsl(var(--popover-foreground))'
49 | },
50 | card: {
51 | DEFAULT: 'hsl(var(--card))',
52 | foreground: 'hsl(var(--card-foreground))'
53 | }
54 | },
55 | borderRadius: {
56 | lg: 'var(--radius)',
57 | md: 'calc(var(--radius) - 2px)',
58 | sm: 'calc(var(--radius) - 4px)'
59 | },
60 | fontFamily: {
61 | sans: ['var(--font-sans)'],
62 | },
63 | keyframes: {
64 | 'accordion-down': {
65 | from: {
66 | height: '0'
67 | },
68 | to: {
69 | height: 'var(--radix-accordion-content-height)'
70 | }
71 | },
72 | 'accordion-up': {
73 | from: {
74 | height: 'var(--radix-accordion-content-height)'
75 | },
76 | to: {
77 | height: '0'
78 | }
79 | }
80 | },
81 | animation: {
82 | 'accordion-down': 'accordion-down 0.2s ease-out',
83 | 'accordion-up': 'accordion-up 0.2s ease-out'
84 | }
85 | }
86 | },
87 | plugins: [
88 | require("tailwindcss-animate"),
89 | require('@tailwindcss/typography'),
90 | ],
91 | }
92 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import path from 'path'
4 |
5 | export default defineConfig({
6 | plugins: [react()],
7 | base: './',
8 | resolve: {
9 | alias: {
10 | '@': path.resolve(__dirname, './src'),
11 | },
12 | },
13 | })
--------------------------------------------------------------------------------