initial commit
0
.gitignore → client/.gitignore
vendored
BIN
client/bun.lockb
Executable file
15
client/next.config.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
const internalHost = process.env.TAURI_DEV_HOST || "localhost";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
assetPrefix: isProd ? undefined : `http://${internalHost}:3000/`,
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
output: "export",
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
1440
package-lock.json → client/package-lock.json
generated
34
client/package.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "cspj",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/cache": "^11.13.1",
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@mui/icons-material": "^6.1.6",
|
||||
"@mui/lab": "^6.0.0-beta.14",
|
||||
"@mui/material": "^6.1.6",
|
||||
"@tauri-apps/api": "^2.1.0",
|
||||
"next": "^15.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.1.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.0.3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
BIN
client/public/fonts/ComicSans/comic.ttf
Executable file
BIN
client/public/fonts/ComicSans/comicbd.ttf
Executable file
BIN
client/public/fonts/ComicSans/comici.ttf
Executable file
BIN
client/public/fonts/ComicSans/comicz.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-Black.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-BlackItalic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-Bold.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-BoldItalic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-Italic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-Light.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-LightItalic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-Medium.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-MediumItalic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-Regular.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-Thin.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSans-ThinItalic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansCondensed-Bold.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansCondensed-BoldItalic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansCondensed-Italic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansCondensed-Light.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansCondensed-LightItalic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansCondensed-Medium.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansCondensed-MediumItalic.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansCondensed-Regular.ttf
Executable file
BIN
client/public/fonts/GoogleSans/GoogleSansNum-3R.ttf
Executable file
BIN
client/public/fonts/GoogleSans/MiLanProVF.ttf
Executable file
BIN
client/public/fonts/GoogleSans/RGoogleSansNum-3L.ttf
Executable file
BIN
client/public/images/cry.webp
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
client/public/images/logo.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
client/public/images/logo.xcf
Normal file
4
client/src-tauri/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/gen/schemas
|
4804
client/src-tauri/Cargo.lock
generated
Normal file
25
client/src-tauri/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.1.0", features = [] }
|
||||
tauri-plugin-log = "2.0.0-rc"
|
3
client/src-tauri/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
11
client/src-tauri/capabilities/default.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
BIN
client/src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
client/src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
client/src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
client/src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
client/src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
client/src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
client/src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
client/src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
client/src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
client/src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
client/src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
client/src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
client/src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
client/src-tauri/icons/icon.icns
Normal file
BIN
client/src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
client/src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
16
client/src-tauri/src/lib.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
6
client/src-tauri/src/main.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
}
|
44
client/src-tauri/tauri.conf.json
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"app": {
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"decorations": false,
|
||||
"height": 600,
|
||||
"maximized": true,
|
||||
"resizable": true,
|
||||
"title": "CSPJ",
|
||||
"width": 800
|
||||
}
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"beforeBuildCommand": "bun run build",
|
||||
"beforeDevCommand": "bun run dev",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"frontendDist": "../out"
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"longDescription": "CSPJ Application Attack Simulator",
|
||||
"shortDescription": "CSPJ",
|
||||
"targets": [
|
||||
"appimage",
|
||||
"msi"
|
||||
]
|
||||
},
|
||||
"identifier": "com.cspj.application",
|
||||
"mainBinaryName": "stort",
|
||||
"productName": "cspj",
|
||||
"version": "0.1.0"
|
||||
}
|
107
client/src/components/Generic/FloatingDialog.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { defaultSettings } from "@/lib/settings";
|
||||
import { Close, UnfoldLess, UnfoldMore } from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Modal,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { FC, ReactNode } from "react";
|
||||
|
||||
interface FloatingDialog {
|
||||
actionButtons?: ReactNode;
|
||||
body: ReactNode;
|
||||
bottomBar?: ReactNode;
|
||||
close: () => void;
|
||||
maximisedState: boolean;
|
||||
openButton: ReactNode;
|
||||
openState: boolean;
|
||||
setMaximisedState: (state: boolean) => void;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const FloatingDialog: FC<FloatingDialog> = ({
|
||||
actionButtons,
|
||||
body,
|
||||
bottomBar,
|
||||
close,
|
||||
maximisedState,
|
||||
openButton,
|
||||
openState,
|
||||
setMaximisedState,
|
||||
title,
|
||||
}) => {
|
||||
// contexts
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
{openButton}
|
||||
|
||||
<Modal onClose={close} open={openState}>
|
||||
<Box
|
||||
sx={{
|
||||
backdropFilter: `blur(${defaultSettings.style.blur_radius}px)`,
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderRadius: maximisedState
|
||||
? "0px"
|
||||
: defaultSettings.style.radius + "px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: maximisedState
|
||||
? "100%"
|
||||
: defaultSettings.style.window_height + "%",
|
||||
left: "50%",
|
||||
maxHeight: maximisedState ? "100vh" : "96vh",
|
||||
maxWidth: maximisedState ? "100vw" : "96vw",
|
||||
p: 2,
|
||||
position: "absolute" as const,
|
||||
top: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
transition: "all ease-in-out",
|
||||
transitionDuration:
|
||||
defaultSettings.style.transition_duration + "ms",
|
||||
width: maximisedState
|
||||
? "100vw"
|
||||
: defaultSettings.style.window_width + "px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
px: 1,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">{title}</Typography>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
{actionButtons}
|
||||
|
||||
<Tooltip title={maximisedState ? "Minimise" : "Maximise"}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setMaximisedState(!maximisedState);
|
||||
}}
|
||||
sx={{
|
||||
mr: 1,
|
||||
}}
|
||||
>
|
||||
{maximisedState ? <UnfoldLess /> : <UnfoldMore />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Close">
|
||||
<IconButton onClick={close}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
{body}
|
||||
{bottomBar}
|
||||
</Box>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
28
client/src/components/Generic/Layout.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Box, useTheme } from "@mui/material";
|
||||
import { HeaderBar } from "../HeaderBar/HeaderBar";
|
||||
|
||||
export const Layout = () => {
|
||||
// contexts
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: theme.palette.background.default,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
<HeaderBar />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
p: 1,
|
||||
}}
|
||||
></Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
25
client/src/components/Generic/LoadingScreen.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { Box, Typography, LinearProgress } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
|
||||
interface LoadingScreenProps {
|
||||
loadingText?: string;
|
||||
}
|
||||
|
||||
export const LoadingScreen: FC<LoadingScreenProps> = ({ loadingText }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
{loadingText}
|
||||
</Typography>
|
||||
<LinearProgress color="primary" sx={{ width: "80%" }} />
|
||||
</Box>
|
||||
);
|
||||
};
|
56
client/src/components/HeaderBar/HeaderBar.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { Box, Stack } from "@mui/material";
|
||||
import { WindowButtons } from "./WindowButtons";
|
||||
|
||||
export const HeaderBar = () => {
|
||||
return (
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
// backdropFilter: "blur(10px)",
|
||||
// backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
// TODO: remove when done
|
||||
// borderBottom: "1px solid red",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
height: "48px",
|
||||
justifyContent: "space-between",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
hello this is the left side
|
||||
</Box>
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<WindowButtons />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
104
client/src/components/HeaderBar/WindowButtons.tsx
Normal file
|
@ -0,0 +1,104 @@
|
|||
import {
|
||||
Close,
|
||||
Fullscreen,
|
||||
FullscreenExit,
|
||||
Minimize,
|
||||
WebAssetOutlined,
|
||||
} from "@mui/icons-material";
|
||||
import { Box, IconButton, Stack, useTheme } from "@mui/material";
|
||||
import {
|
||||
getCurrentWebviewWindow,
|
||||
WebviewWindow,
|
||||
} from "@tauri-apps/api/webviewWindow";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const WindowButtons = () => {
|
||||
// contexts
|
||||
const userTheme = useTheme();
|
||||
|
||||
// states
|
||||
const [appWindow, setAppWindow] = useState<WebviewWindow>();
|
||||
|
||||
// explanation:
|
||||
// this is needed due to the server-sided nature of next js,
|
||||
// this means that the window object might not be available on first load
|
||||
// we will use dynamic imports to load the window object only when needed
|
||||
// in hindsight, using next js for this project was a mistake
|
||||
// using create-react-app or vite would have been a better choice just to generate a static site
|
||||
const initializeAppWindow = async () => {
|
||||
setAppWindow(getCurrentWebviewWindow());
|
||||
};
|
||||
|
||||
const toggleFullscreen = async () => {
|
||||
appWindow?.setFullscreen(!(await appWindow?.isFullscreen()));
|
||||
};
|
||||
|
||||
const minimize = () => {
|
||||
appWindow?.minimize();
|
||||
};
|
||||
|
||||
const toggleMaximize = () => {
|
||||
appWindow?.toggleMaximize();
|
||||
};
|
||||
|
||||
const close = async () => {
|
||||
appWindow?.close();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initializeAppWindow();
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" spacing={1}>
|
||||
<IconButton
|
||||
onClick={toggleFullscreen}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: userTheme.palette.grey[800],
|
||||
}}
|
||||
>
|
||||
{appWindow?.isFullscreen() ? (
|
||||
<FullscreenExit fontSize="inherit" />
|
||||
) : (
|
||||
<Fullscreen fontSize="inherit" />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={minimize}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: userTheme.palette.grey[800],
|
||||
}}
|
||||
>
|
||||
<Minimize fontSize="inherit" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={toggleMaximize}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: userTheme.palette.grey[800],
|
||||
}}
|
||||
>
|
||||
<WebAssetOutlined fontSize="inherit" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={close}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: userTheme.palette.grey[800],
|
||||
}}
|
||||
>
|
||||
<Close fontSize="inherit" />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
113
client/src/contexts/ThemeContext.tsx
Normal file
|
@ -0,0 +1,113 @@
|
|||
// TODO: add settings functionality, refer to whensapp project
|
||||
|
||||
import { createTheme, ThemeProvider } from "@mui/material/styles";
|
||||
import { FC, ReactNode } from "react";
|
||||
|
||||
interface UserThemeProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const UserThemeProvider: FC<UserThemeProviderProps> = ({ children }) => {
|
||||
// palette and accent settings
|
||||
const userPalette = {
|
||||
primary: {
|
||||
// light: '#a1c3f9',
|
||||
main: "#8ab4f8",
|
||||
// dark: '#4285f4',
|
||||
},
|
||||
secondary: {
|
||||
// light: '#a1c3f9',
|
||||
main: "#8ab4f8",
|
||||
// dark: '#4285f4',
|
||||
},
|
||||
error: {
|
||||
light: "#e57373",
|
||||
main: "#f44336",
|
||||
dark: "#d32f2f",
|
||||
},
|
||||
warning: {
|
||||
light: "#ffb74d",
|
||||
main: "#ffa726",
|
||||
dark: "#f57c00",
|
||||
},
|
||||
info: {
|
||||
light: "#a1c3f9",
|
||||
main: "#8ab4f8",
|
||||
dark: "#4285f4",
|
||||
},
|
||||
success: {
|
||||
light: "#81c784",
|
||||
main: "#66bb6a",
|
||||
dark: "#388e3c",
|
||||
},
|
||||
grey: {
|
||||
50: "#fafafa",
|
||||
100: "#f5f5f5",
|
||||
200: "#eeeeee",
|
||||
300: "#e0e0e0",
|
||||
400: "#bdbdbd",
|
||||
500: "#9e9e9e",
|
||||
600: "#757575",
|
||||
700: "#616161",
|
||||
800: "#424242",
|
||||
900: "#212121",
|
||||
A100: "#f5f5f5",
|
||||
A200: "#eeeeee",
|
||||
A400: "#bdbdbd",
|
||||
A700: "#616161",
|
||||
},
|
||||
background: {
|
||||
paper: "#303134",
|
||||
default: "#202124",
|
||||
},
|
||||
};
|
||||
|
||||
// font family settings
|
||||
// TODO: figure out how to bundle fonts in tauri
|
||||
const fontFamily = "GoogleSans";
|
||||
|
||||
// font scaling settings
|
||||
const fontSize = 14;
|
||||
|
||||
const userTheme = createTheme({
|
||||
typography: {
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
},
|
||||
palette: {
|
||||
mode: "dark",
|
||||
...userPalette,
|
||||
},
|
||||
transitions: {
|
||||
duration: {
|
||||
shortest: 200,
|
||||
shorter: 200,
|
||||
short: 200,
|
||||
// most basic recommended timing
|
||||
standard: 200,
|
||||
// this is to be used in complex animations
|
||||
complex: 200,
|
||||
// recommended when something is entering screen
|
||||
enteringScreen: 200,
|
||||
// recommended when something is leaving screen
|
||||
leavingScreen: 200,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: { textTransform: "none" },
|
||||
},
|
||||
},
|
||||
MuiTab: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return <ThemeProvider theme={userTheme}>{children}</ThemeProvider>;
|
||||
};
|
17
client/src/lib/createEmotionCache.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import createCache from "@emotion/cache";
|
||||
|
||||
const isBrowser = typeof document !== "undefined";
|
||||
|
||||
// On the client side, Create a meta tag at the top of the <head> and set it as insertionPoint.
|
||||
// This assures that MUI styles are loaded first.
|
||||
// It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
|
||||
export default function createEmotionCache() {
|
||||
let insertionPoint;
|
||||
|
||||
if (isBrowser) {
|
||||
const emotionInsertionPoint = document.querySelector<HTMLMetaElement>('meta[name="emotion-insertion-point"]');
|
||||
insertionPoint = emotionInsertionPoint ?? undefined;
|
||||
}
|
||||
|
||||
return createCache({ key: "mui-style", insertionPoint });
|
||||
}
|
64
client/src/lib/logcatService.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
type LogLevel = "DEBUG" | "INFO" | "WARN" | "ERROR" | "CRITICAL";
|
||||
|
||||
export interface LogEntry {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export class LogcatServiceClass {
|
||||
private logs: LogEntry[] = [];
|
||||
private lastLogTimestamp: number | null = null;
|
||||
private listeners: (() => void)[] = [];
|
||||
|
||||
constructor() {
|
||||
this.log("LogcatService initialised", "INFO");
|
||||
}
|
||||
|
||||
addListener(callback: () => void) {
|
||||
this.listeners.push(callback);
|
||||
}
|
||||
|
||||
removeListener(callback: () => void) {
|
||||
this.listeners = this.listeners.filter((listener) => listener !== callback);
|
||||
}
|
||||
|
||||
private notifyListeners() {
|
||||
this.listeners.forEach((listener) => listener());
|
||||
}
|
||||
|
||||
log(message: string, level: LogLevel = "INFO"): void {
|
||||
const currentTimestamp = new Date().getTime();
|
||||
const timeSinceLastLog = this.lastLogTimestamp ? currentTimestamp - this.lastLogTimestamp : 0;
|
||||
|
||||
const logEntry: LogEntry = { message, level, timestamp: timeSinceLastLog };
|
||||
this.logs.push(logEntry);
|
||||
|
||||
switch (level) {
|
||||
case "DEBUG":
|
||||
console.debug(`> [${level}] ${message} (+${timeSinceLastLog}ms)`);
|
||||
break;
|
||||
case "INFO":
|
||||
console.info(`> [${level}] ${message} (+${timeSinceLastLog}ms)`);
|
||||
break;
|
||||
case "WARN":
|
||||
console.warn(`> [${level}] ${message} (+${timeSinceLastLog}ms)`);
|
||||
break;
|
||||
case "ERROR":
|
||||
console.error(`> [${level}] ${message} (+${timeSinceLastLog}ms)`);
|
||||
break;
|
||||
case "CRITICAL":
|
||||
console.error(`> [${level}] ${message} (+${timeSinceLastLog}ms)`);
|
||||
break;
|
||||
}
|
||||
|
||||
this.lastLogTimestamp = currentTimestamp;
|
||||
this.notifyListeners();
|
||||
}
|
||||
|
||||
getLogs(): LogEntry[] {
|
||||
return this.logs;
|
||||
}
|
||||
}
|
||||
|
||||
export const logcat = new LogcatServiceClass();
|
30
client/src/lib/settings.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
export const defaultSettings = {
|
||||
background: {
|
||||
background_image_path: "" as string,
|
||||
},
|
||||
colors: {
|
||||
accent_color: "#8ab4f8" as string,
|
||||
background_color: "#202124" as string,
|
||||
background_color_popup: "#303134" as string,
|
||||
footer_color: "#000000" as string,
|
||||
},
|
||||
style: {
|
||||
blur_radius: 10 as number,
|
||||
dark_mode: true as boolean,
|
||||
font_family: "monospace" as string,
|
||||
font_scaling: 100,
|
||||
opacity: 0.8 as number,
|
||||
radius: 8 as number,
|
||||
transition_duration: 200 as number,
|
||||
window_height: 80 as number,
|
||||
window_width: 500 as number,
|
||||
},
|
||||
window: {
|
||||
fullscreen_button: false as boolean,
|
||||
maximize_button: false as boolean,
|
||||
minimize_button: false as boolean,
|
||||
start_fullscreen: false as boolean, // TODO: this should be true on prod
|
||||
},
|
||||
};
|
||||
|
||||
export type SettingsType = typeof defaultSettings;
|
31
client/src/pages/_app.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { CacheProvider, EmotionCache } from "@emotion/react";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { UserThemeProvider } from "../contexts/ThemeContext";
|
||||
import createEmotionCache from "../lib/createEmotionCache";
|
||||
import "../styles/global.css";
|
||||
|
||||
// Client-side cache, shared for the whole session of the user in the browser.
|
||||
const clientSideEmotionCache = createEmotionCache();
|
||||
|
||||
export interface MyAppProps extends AppProps {
|
||||
emotionCache?: EmotionCache;
|
||||
}
|
||||
|
||||
export default function MyApp(props: MyAppProps) {
|
||||
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
|
||||
return (
|
||||
<CacheProvider value={emotionCache}>
|
||||
<Head>
|
||||
<title>Stort</title>
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||
</Head>
|
||||
<UserThemeProvider>
|
||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||
<CssBaseline />
|
||||
<Component {...pageProps} />
|
||||
</UserThemeProvider>
|
||||
</CacheProvider>
|
||||
);
|
||||
}
|
89
client/src/pages/_document.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
import createEmotionServer from "@emotion/server/create-instance";
|
||||
import { AppType } from "next/app";
|
||||
import Document, {
|
||||
DocumentContext,
|
||||
DocumentProps,
|
||||
Head,
|
||||
Html,
|
||||
Main,
|
||||
NextScript,
|
||||
} from "next/document";
|
||||
import createEmotionCache from "../lib/createEmotionCache";
|
||||
import { MyAppProps } from "./_app";
|
||||
|
||||
interface MyDocumentProps extends DocumentProps {
|
||||
emotionStyleTags: JSX.Element[];
|
||||
}
|
||||
|
||||
export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>{emotionStyleTags}</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
|
||||
// `getInitialProps` belongs to `_document` (instead of `_app`),
|
||||
// it's compatible with static-site generation (SSG).
|
||||
MyDocument.getInitialProps = async (ctx: DocumentContext) => {
|
||||
// Resolution order
|
||||
//
|
||||
// On the server:
|
||||
// 1. app.getInitialProps
|
||||
// 2. page.getInitialProps
|
||||
// 3. document.getInitialProps
|
||||
// 4. app.render
|
||||
// 5. page.render
|
||||
// 6. document.render
|
||||
//
|
||||
// On the server with error:
|
||||
// 1. document.getInitialProps
|
||||
// 2. app.render
|
||||
// 3. page.render
|
||||
// 4. document.render
|
||||
//
|
||||
// On the client
|
||||
// 1. app.getInitialProps
|
||||
// 2. page.getInitialProps
|
||||
// 3. app.render
|
||||
// 4. page.render
|
||||
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
|
||||
// You can consider sharing the same Emotion cache between all the SSR requests to speed up performance.
|
||||
// However, be aware that it can have global side effects.
|
||||
const cache = createEmotionCache();
|
||||
const { extractCriticalToChunks } = createEmotionServer(cache);
|
||||
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
enhanceApp: (
|
||||
App: React.ComponentType<React.ComponentProps<AppType> & MyAppProps>,
|
||||
) =>
|
||||
function EnhanceApp(props) {
|
||||
return <App emotionCache={cache} {...props} />;
|
||||
},
|
||||
});
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
// This is important. It prevents Emotion to render invalid HTML.
|
||||
// See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
|
||||
const emotionStyles = extractCriticalToChunks(initialProps.html);
|
||||
const emotionStyleTags = emotionStyles.styles.map((style) => (
|
||||
<style
|
||||
data-emotion={`${style.key} ${style.ids.join(" ")}`}
|
||||
key={style.key}
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: style.css }}
|
||||
/>
|
||||
));
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
emotionStyleTags,
|
||||
};
|
||||
};
|
5
client/src/pages/index.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { Layout } from "../components/Generic/Layout";
|
||||
|
||||
export default function Home() {
|
||||
return <Layout />;
|
||||
}
|
5
client/src/pages/testing.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { Box } from "@mui/material";
|
||||
|
||||
export default function Testing() {
|
||||
return <Box>asdf</Box>;
|
||||
}
|
129
client/src/styles/global.css
Normal file
|
@ -0,0 +1,129 @@
|
|||
@font-face {
|
||||
font-family: 'JetBrainsMono';
|
||||
src: url('../../public/fonts/JetBrainsMono/JetBrainsMono-VariableFont_wght.ttf') format('truetype-variations');
|
||||
font-style: normal italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-Medium.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-Bold.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-Italic.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-MediumItalic.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-BoldItalic.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-Thin.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-ThinItalic.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-Light.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-LightItalic.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-Black.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-BlackItalic.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GoogleSans';
|
||||
src: url('../../public/fonts/GoogleSans/GoogleSans-ThinItalic.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'ComicSans';
|
||||
src: url('../../public/fonts/ComicSans/comic.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'ComicSans';
|
||||
src: url('../../public/fonts/ComicSans/comicbd.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'ComicSans';
|
||||
src: url('../../public/fonts/ComicSans/comici.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'ComicSans';
|
||||
src: url('../../public/fonts/ComicSans/comicz.ttf') format('truetype');
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
24
package.json
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"name": "cspj",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "19.0.0-rc-66855b96-20241106",
|
||||
"react-dom": "19.0.0-rc-66855b96-20241106",
|
||||
"next": "15.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.0.3"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 25 KiB |
|
@ -1 +0,0 @@
|
|||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
Before Width: | Height: | Size: 391 B |
|
@ -1 +0,0 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
Before Width: | Height: | Size: 1 KiB |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1 +0,0 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
Before Width: | Height: | Size: 128 B |
|
@ -1 +0,0 @@
|
|||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
Before Width: | Height: | Size: 385 B |
|
@ -1,6 +0,0 @@
|
|||
import "@/styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
type Data = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>,
|
||||
) {
|
||||
res.status(200).json({ name: "John Doe" });
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import localFont from "next/font/local";
|
||||
import styles from "@/styles/Home.module.css";
|
||||
|
||||
const geistSans = localFont({
|
||||
src: "./fonts/GeistVF.woff",
|
||||
variable: "--font-geist-sans",
|
||||
weight: "100 900",
|
||||
});
|
||||
const geistMono = localFont({
|
||||
src: "./fonts/GeistMonoVF.woff",
|
||||
variable: "--font-geist-mono",
|
||||
weight: "100 900",
|
||||
});
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Create Next App</title>
|
||||
<meta name="description" content="Generated by create next app" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<div
|
||||
className={`${styles.page} ${geistSans.variable} ${geistMono.variable}`}
|
||||
>
|
||||
<main className={styles.main}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol>
|
||||
<li>
|
||||
Get started by editing <code>src/pages/index.tsx</code>.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className={styles.ctas}>
|
||||
<a
|
||||
className={styles.primary}
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.secondary}
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className={styles.footer}>
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
.page {
|
||||
--gray-rgb: 0, 0, 0;
|
||||
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
|
||||
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
|
||||
|
||||
--button-primary-hover: #383838;
|
||||
--button-secondary-hover: #f2f2f2;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: 20px 1fr 20px;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
min-height: 100svh;
|
||||
padding: 80px;
|
||||
gap: 64px;
|
||||
font-family: var(--font-geist-sans);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.page {
|
||||
--gray-rgb: 255, 255, 255;
|
||||
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
|
||||
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
|
||||
|
||||
--button-primary-hover: #ccc;
|
||||
--button-secondary-hover: #1a1a1a;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
grid-row-start: 2;
|
||||
}
|
||||
|
||||
.main ol {
|
||||
font-family: var(--font-geist-mono);
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.01em;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.main li:not(:last-of-type) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.main code {
|
||||
font-family: inherit;
|
||||
background: var(--gray-alpha-100);
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ctas {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.ctas a {
|
||||
appearance: none;
|
||||
border-radius: 128px;
|
||||
height: 48px;
|
||||
padding: 0 20px;
|
||||
border: none;
|
||||
border: 1px solid transparent;
|
||||
transition:
|
||||
background 0.2s,
|
||||
color 0.2s,
|
||||
border-color 0.2s;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a.primary {
|
||||
background: var(--foreground);
|
||||
color: var(--background);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
a.secondary {
|
||||
border-color: var(--gray-alpha-200);
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
grid-row-start: 3;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.footer img {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Enable hover only on non-touch devices */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
a.primary:hover {
|
||||
background: var(--button-primary-hover);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
a.secondary:hover {
|
||||
background: var(--button-secondary-hover);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.page {
|
||||
padding: 32px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.main {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main ol {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ctas {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ctas a {
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
a.secondary {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.logo {
|
||||
filter: invert();
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|