initial commit

This commit is contained in:
Vomitblood 2024-11-10 19:27:13 +08:00
parent 17c57285bd
commit c084b5fa48
94 changed files with 7140 additions and 462 deletions

View file

BIN
client/bun.lockb Executable file

Binary file not shown.

15
client/next.config.ts Normal file
View 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;

File diff suppressed because it is too large Load diff

34
client/package.json Normal file
View 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"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

4
client/src-tauri/.gitignore vendored Normal file
View 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

File diff suppressed because it is too large Load diff

View 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"

View file

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View file

@ -0,0 +1,11 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View 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");
}

View 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();
}

View 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"
}

View 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>
</>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>;
};

View 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 });
}

View 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();

View 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
View 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>
);
}

View 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,
};
};

View file

@ -0,0 +1,5 @@
import { Layout } from "../components/Generic/Layout";
export default function Home() {
return <Layout />;
}

View file

@ -0,0 +1,5 @@
import { Box } from "@mui/material";
export default function Testing() {
return <Box>asdf</Box>;
}

View 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;
}

View file

@ -1,8 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
reactStrictMode: true,
};
export default nextConfig;

View file

@ -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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,6 +0,0 @@
import "@/styles/globals.css";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}

View file

@ -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>
);
}

View file

@ -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" });
}

Binary file not shown.

Binary file not shown.

View file

@ -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>
</>
);
}

View file

@ -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();
}
}

View file

@ -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;
}
}