+1
.gitignore
+1
.gitignore
···
1
+
node_modules
+11
.zed/settings.json
+11
.zed/settings.json
···
1
+
// Folder-specific settings
2
+
//
3
+
// For a full list of overridable settings, and general information on folder-specific settings,
4
+
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
5
+
{
6
+
"languages": {
7
+
"TypeScript": {
8
+
"tab_size": 4
9
+
}
10
+
}
11
+
}
+1
html/README.md
+1
html/README.md
···
1
+
# A Basic HTML Page
+42
html/css/dino.css
+42
html/css/dino.css
···
1
+
body {
2
+
margin: 0;
3
+
padding: 0;
4
+
display: flex;
5
+
justify-content: center;
6
+
align-items: center;
7
+
min-height: 100vh;
8
+
background-color: #f7f7f7;
9
+
font-family: Arial, sans-serif;
10
+
}
11
+
12
+
.game-container {
13
+
position: relative;
14
+
}
15
+
16
+
#gameCanvas {
17
+
border: 2px solid #535353;
18
+
background-color: #fff;
19
+
display: block;
20
+
}
21
+
22
+
#score {
23
+
position: absolute;
24
+
top: 10px;
25
+
right: 10px;
26
+
font-size: 20px;
27
+
font-weight: bold;
28
+
color: #535353;
29
+
}
30
+
31
+
#gameOver {
32
+
position: absolute;
33
+
width: 100%;
34
+
text-align: center;
35
+
top: 50%;
36
+
left: 50%;
37
+
transform: translate(-50%, -50%);
38
+
font-size: 24px;
39
+
font-weight: bold;
40
+
color: #535353;
41
+
display: none;
42
+
}
+85
html/css/form.css
+85
html/css/form.css
···
1
+
.container {
2
+
display: flex;
3
+
flex-direction: column;
4
+
align-items: center;
5
+
justify-content: center;
6
+
}
7
+
8
+
form {
9
+
display: flex;
10
+
flex-direction: column;
11
+
align-items: center;
12
+
justify-content: center;
13
+
gap: 0.5rem;
14
+
width: 80%;
15
+
}
16
+
17
+
form > * {
18
+
width: 100%;
19
+
}
20
+
21
+
form :nth-child(even) {
22
+
margin-bottom: 1rem;
23
+
}
24
+
25
+
form > button {
26
+
width: 100%;
27
+
padding: 0.25rem;
28
+
margin: 0.5rem;
29
+
}
30
+
31
+
@media (min-width: 769px) {
32
+
form {
33
+
gap: 1rem;
34
+
width: 100%;
35
+
max-width: 48rem;
36
+
display: grid;
37
+
column-gap: 1rem;
38
+
grid-template-columns: repeat(2, minmax(0, 1fr));
39
+
}
40
+
41
+
form > button {
42
+
width: 100%;
43
+
grid-column: span 2;
44
+
padding: 0.25rem;
45
+
margin: 0.5rem;
46
+
}
47
+
}
48
+
49
+
nav {
50
+
display: flex;
51
+
padding: 0.5rem 0rem;
52
+
background-color: #8aacdf;
53
+
position: sticky;
54
+
top: 0;
55
+
left: 0;
56
+
right: 0;
57
+
margin-left: -0.5rem;
58
+
margin-top: -0.5rem;
59
+
justify-content: space-around;
60
+
align-items: center;
61
+
width: 100vw;
62
+
margin-bottom: 4rem;
63
+
}
64
+
65
+
.navitem {
66
+
padding: 0.5rem 4rem;
67
+
border: 1px solid #000;
68
+
border-radius: 0.5rem;
69
+
}
70
+
71
+
#footer {
72
+
width: 100vw;
73
+
height: 6rem;
74
+
position: absolute;
75
+
bottom: 0;
76
+
left: 0;
77
+
justify-content: space-around;
78
+
align-items: center;
79
+
background-color: #000;
80
+
color: #fff;
81
+
text-align: center;
82
+
display: flex;
83
+
vertical-align: center;
84
+
font-size: 2rem;
85
+
}
+70
html/css/styles.css
+70
html/css/styles.css
···
1
+
h1 {
2
+
font-size: 48px;
3
+
margin: 0;
4
+
color: #8aadf4;
5
+
}
6
+
7
+
h2 {
8
+
font-size: 36px;
9
+
margin: 0;
10
+
}
11
+
12
+
.heading {
13
+
color: #a5adcb;
14
+
}
15
+
16
+
* {
17
+
font-family:
18
+
system-ui,
19
+
-apple-system,
20
+
BlinkMacSystemFont,
21
+
"Segoe UI",
22
+
Roboto,
23
+
Oxygen,
24
+
Ubuntu,
25
+
Cantarell,
26
+
"Open Sans",
27
+
"Helvetica Neue",
28
+
sans-serif;
29
+
color: #cad3f5;
30
+
}
31
+
32
+
body {
33
+
background-color: #24273a;
34
+
}
35
+
36
+
main {
37
+
margin: none;
38
+
display: flex;
39
+
flex-direction: column;
40
+
gap: 5rem;
41
+
align-items: center;
42
+
}
43
+
44
+
.hr {
45
+
width: 80rem;
46
+
border-top: 2px solid #6e738d;
47
+
}
48
+
49
+
.container {
50
+
display: flex;
51
+
width: 160rem;
52
+
flex-direction: column;
53
+
gap: 0.1rem;
54
+
align-items: center;
55
+
}
56
+
57
+
.card {
58
+
width: 15rem;
59
+
height: 20rem;
60
+
background-color: #363950;
61
+
border-radius: 0.5rem;
62
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
63
+
transition: transform 0.15s ease-in-out;
64
+
cursor: pointer;
65
+
}
66
+
67
+
.card:hover {
68
+
transform: scale(1.05);
69
+
transition: transform 0.15s ease-in-out;
70
+
}
+19
html/dino.html
+19
html/dino.html
···
1
+
<!doctype html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8" />
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+
<title>Dino Game</title>
7
+
<link rel="stylesheet" href="css/dino.css" />
8
+
</head>
9
+
<body>
10
+
<div class="game-container">
11
+
<canvas id="gameCanvas" width="800" height="600"></canvas>
12
+
<div id="score">Score: 0</div>
13
+
<div id="gameOver">
14
+
Game Over! Press <kbd>Space</kbd> to Restart
15
+
</div>
16
+
</div>
17
+
<script src="js/dino.js"></script>
18
+
</body>
19
+
</html>
+28
html/form.html
+28
html/form.html
···
1
+
<!doctype html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8" />
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+
<title>Form</title>
7
+
<link rel="stylesheet" href="css/form.css" />
8
+
<script src="js/form.js"></script>
9
+
</head>
10
+
<body>
11
+
<nav>
12
+
<a class="navitem" href="/">Home</a>
13
+
<a class="navitem" href="/dino.html">Dino</a>
14
+
</nav>
15
+
<div class="container">
16
+
<form>
17
+
<label for="name">Name:</label>
18
+
<input type="text" id="name" name="name" required />
19
+
<label for="email">Email:</label>
20
+
<input type="email" id="email" name="email" required />
21
+
<label for="message">Message:</label>
22
+
<textarea id="message" name="message" required></textarea>
23
+
<button type="submit">Submit</button>
24
+
</form>
25
+
</div>
26
+
<div id="footer">Totally good footer text</div>
27
+
</body>
28
+
</html>
+40
html/index.html
+40
html/index.html
···
1
+
<html>
2
+
<head>
3
+
<title>HTML Page</title>
4
+
<link rel="stylesheet" href="css/styles.css" />
5
+
<script src="js/script.js"></script>
6
+
</head>
7
+
<body style="margin: 0; padding: 8px">
8
+
<main>
9
+
<h1>Samuel Shuert</h1>
10
+
<div class="container">
11
+
<h2 class="heading">Projects</h2>
12
+
<div class="hr"></div>
13
+
<div
14
+
style="
15
+
display: flex;
16
+
flex-direction: row;
17
+
justify-content: center;
18
+
padding-top: 2rem;
19
+
gap: 1rem;
20
+
"
21
+
>
22
+
<div class="card" onclick="openProject('dino')">Dino</div>
23
+
<div class="card" onclick="openProject('temp')"></div>
24
+
<div class="card" onclick="openProject('temp')"></div>
25
+
</div>
26
+
</div>
27
+
<div class="container">
28
+
<div class="hr" style="width: 20rem; margin-bottom: 2rem"></div>
29
+
<h2 class="heading">Education</h2>
30
+
<div class="hr"></div>
31
+
</div>
32
+
<div class="container">
33
+
<div class="hr" style="width: 20rem; margin-bottom: 2rem"></div>
34
+
<h2 class="heading">Skills</h2>
35
+
<div class="hr"></div>
36
+
</div>
37
+
<div class="hr" style="width: 20rem"></div>
38
+
</main>
39
+
</body>
40
+
</html>
+246
html/js/dino.js
+246
html/js/dino.js
···
1
+
const canvas = document.getElementById("gameCanvas");
2
+
const ctx = canvas.getContext("2d");
3
+
const scoreElement = document.getElementById("score");
4
+
const gameOverElement = document.getElementById("gameOver");
5
+
6
+
let score = 0;
7
+
let gameSpeed = 3;
8
+
let isGameOver = false;
9
+
let frameCount = 0;
10
+
let nextObstacleFrame = 10;
11
+
12
+
const dino = {
13
+
x: 50,
14
+
y: 150,
15
+
width: 40,
16
+
height: 50,
17
+
dy: 0,
18
+
gravity: 0.3,
19
+
jumpPower: -8,
20
+
isJumping: false,
21
+
isDucking: false,
22
+
23
+
draw() {
24
+
ctx.fillStyle = "#8aacdf";
25
+
ctx.fillRect(this.x, this.y, this.width, this.height);
26
+
},
27
+
28
+
update() {
29
+
if (this.isDucking) {
30
+
this.y = 170;
31
+
this.height = 30;
32
+
} else {
33
+
this.height = 50;
34
+
}
35
+
36
+
if (this.isJumping) {
37
+
this.dy += this.gravity;
38
+
this.y += this.dy;
39
+
40
+
if (this.y >= 150) {
41
+
this.y = 150;
42
+
this.isJumping = false;
43
+
this.dy = 0;
44
+
}
45
+
}
46
+
},
47
+
48
+
duck() {
49
+
if (!this.isJumping && !isGameOver) {
50
+
this.isDucking = true;
51
+
} else if (this.isJumping && !isGameOver) {
52
+
this.dy += this.gravity * 10;
53
+
this.y += this.dy;
54
+
}
55
+
},
56
+
57
+
jump() {
58
+
if (!this.isJumping && !isGameOver) {
59
+
this.isJumping = true;
60
+
this.dy = this.jumpPower;
61
+
}
62
+
},
63
+
};
64
+
65
+
const obstacles = [];
66
+
67
+
class Obstacle {
68
+
constructor() {
69
+
this.x = canvas.width;
70
+
const type = Math.random();
71
+
if (type < 0.5) {
72
+
this.width = 20;
73
+
this.height = 40;
74
+
this.y = 160;
75
+
} else if (type < 0.8) {
76
+
this.width = 20;
77
+
this.height = 25;
78
+
this.y = 175;
79
+
} else {
80
+
this.width = 40;
81
+
this.height = 32;
82
+
this.y = 168;
83
+
}
84
+
}
85
+
86
+
draw() {
87
+
ctx.fillStyle = "#40a02b";
88
+
ctx.fillRect(this.x, this.y, this.width, this.height);
89
+
}
90
+
91
+
update() {
92
+
this.x -= gameSpeed;
93
+
}
94
+
}
95
+
96
+
class BirdObstacle {
97
+
constructor() {
98
+
this.x = canvas.width;
99
+
this.width = 30;
100
+
this.height = 20;
101
+
const heightType = Math.floor(Math.random() * 3);
102
+
if (heightType === 0) {
103
+
this.y = 170;
104
+
} else if (heightType === 1) {
105
+
this.y = 150;
106
+
} else {
107
+
this.y = 130;
108
+
}
109
+
}
110
+
111
+
draw() {
112
+
ctx.fillStyle = "#dd7878";
113
+
ctx.fillRect(this.x, this.y, this.width, this.height);
114
+
}
115
+
116
+
update() {
117
+
this.x -= gameSpeed;
118
+
}
119
+
}
120
+
121
+
function checkCollision(dino, obstacle) {
122
+
return (
123
+
dino.x < obstacle.x + obstacle.width &&
124
+
dino.x + dino.width > obstacle.x &&
125
+
dino.y < obstacle.y + obstacle.height &&
126
+
dino.y + dino.height > obstacle.y
127
+
);
128
+
}
129
+
130
+
function spawnObstacle() {
131
+
if (frameCount >= nextObstacleFrame) {
132
+
const type = Math.random();
133
+
if (type < 0.9) {
134
+
obstacles.push(new Obstacle());
135
+
} else {
136
+
obstacles.push(new BirdObstacle());
137
+
}
138
+
139
+
const baseMin = 80;
140
+
const baseMax = 140;
141
+
142
+
const speedFactor = 3 / gameSpeed;
143
+
144
+
const minInterval = Math.floor(baseMin * speedFactor);
145
+
const maxInterval = Math.floor(baseMax * speedFactor);
146
+
nextObstacleFrame =
147
+
frameCount +
148
+
Math.floor(Math.random() * (maxInterval - minInterval) + minInterval);
149
+
}
150
+
}
151
+
152
+
function updateScore() {
153
+
if (!isGameOver) {
154
+
score++;
155
+
scoreElement.textContent = `Score: ${Math.floor(score / 10)}`;
156
+
157
+
// Increase difficulty
158
+
if (score % 500 === 0) {
159
+
gameSpeed += 0.5;
160
+
}
161
+
}
162
+
}
163
+
164
+
function gameLoop() {
165
+
if (isGameOver) return;
166
+
167
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
168
+
169
+
ctx.strokeStyle = "#535353";
170
+
ctx.beginPath();
171
+
ctx.moveTo(0, 200);
172
+
ctx.lineTo(canvas.width, 200);
173
+
ctx.stroke();
174
+
175
+
dino.update();
176
+
dino.draw();
177
+
178
+
spawnObstacle();
179
+
180
+
for (let i = obstacles.length - 1; i >= 0; i--) {
181
+
obstacles[i].update();
182
+
obstacles[i].draw();
183
+
184
+
if (checkCollision(dino, obstacles[i])) {
185
+
isGameOver = true;
186
+
gameOverElement.style.display = "block";
187
+
}
188
+
189
+
if (obstacles[i].x + obstacles[i].width < 0) {
190
+
obstacles.splice(i, 1);
191
+
}
192
+
}
193
+
194
+
updateScore();
195
+
frameCount++;
196
+
197
+
requestAnimationFrame(gameLoop);
198
+
}
199
+
200
+
function resetGame() {
201
+
score = 0;
202
+
gameSpeed = 3;
203
+
isGameOver = false;
204
+
frameCount = 0;
205
+
obstacles.length = 0;
206
+
dino.y = 150;
207
+
dino.dy = 0;
208
+
dino.isJumping = false;
209
+
gameOverElement.style.display = "none";
210
+
nextObstacleFrame = 10;
211
+
gameLoop();
212
+
}
213
+
214
+
document.addEventListener("keydown", (e) => {
215
+
e.preventDefault();
216
+
switch (e.code) {
217
+
case "ArrowUp":
218
+
case "Space": {
219
+
if (isGameOver) {
220
+
resetGame();
221
+
} else {
222
+
dino.jump();
223
+
}
224
+
break;
225
+
}
226
+
case "ArrowDown": {
227
+
dino.duck();
228
+
break;
229
+
}
230
+
default:
231
+
}
232
+
});
233
+
234
+
document.addEventListener("keyup", (e) => {
235
+
switch (e.code) {
236
+
case "ArrowDown": {
237
+
e.preventDefault();
238
+
dino.isDucking = false;
239
+
if (!dino.isJumping) {
240
+
dino.y = 150;
241
+
}
242
+
}
243
+
}
244
+
});
245
+
246
+
gameLoop();
+11
html/js/script.js
+11
html/js/script.js
+70
-33
nilla.nix
+70
-33
nilla.nix
···
8
8
nilla = import pins.nilla;
9
9
in
10
10
nilla.create (
11
-
{ config, lib }:
11
+
{ config }:
12
12
{
13
13
config = {
14
14
inputs = {
15
15
nixpkgs = {
16
16
src = pins.nixpkgs;
17
-
};
17
+
};
18
18
};
19
19
packages.cmu-graphics = {
20
20
systems = [ "x86_64-linux" ];
21
21
package =
22
-
{
23
-
pkgs,
24
-
python313,
25
-
fetchPypi
26
-
}:
22
+
{
23
+
pkgs,
24
+
python313,
25
+
fetchPypi,
26
+
}:
27
27
let
28
28
pname = "cmu_graphics";
29
29
version = "1.1.43";
···
47
47
python313.pkgs.pycairo
48
48
python313.pkgs.pygame
49
49
];
50
-
50
+
51
51
build-system = [
52
52
python313.pkgs.setuptools
53
53
python313.pkgs.wheel
54
54
pkgs.pre-commit
55
55
];
56
-
56
+
57
57
};
58
58
};
59
-
shells.default = config.shells.python;
59
+
shells.default = config.shells.html;
60
60
shells.python = {
61
61
# Declare what systems the shell can be used on.
62
62
systems = [ "x86_64-linux" ];
63
-
63
+
64
64
# Define our shell environment.
65
65
shell =
66
66
{
···
68
68
mkShell,
69
69
...
70
70
}:
71
+
let
72
+
python3 = pkgs.python313.override {
73
+
packageOverrides = pyfinal: pyprev: {
74
+
cmu_graphics = config.packages.cmu-graphics.result.x86_64-linux;
75
+
};
76
+
};
77
+
in
71
78
mkShell {
72
79
shellHook = ''
73
-
export ZED_PREDICT_EDITS_URL=http://localhost:9000/predict_edits
80
+
[ "$(hostname)" = "shorthair" ] && export ZED_PREDICT_EDITS_URL=http://localhost:9000/predict_edits
74
81
'';
75
82
packages = [
76
-
pkgs.python314
83
+
(python3.withPackages (ppkgs: [
84
+
ppkgs.pandas
85
+
ppkgs.pandas-stubs
86
+
ppkgs.matplotlib
87
+
ppkgs.seaborn
88
+
ppkgs.numpy
89
+
ppkgs.requests
90
+
ppkgs.geopy
91
+
ppkgs.cmu_graphics
92
+
ppkgs.pycairo
93
+
ppkgs.pygame
94
+
ppkgs.pillow
95
+
ppkgs.numpy
96
+
]))
77
97
pkgs.black
78
98
];
79
99
};
80
100
};
81
-
shells.cmu-graphics = {
101
+
shells.ts = {
102
+
systems = [ "x86_64-linux" ];
103
+
104
+
shell =
105
+
{
106
+
pkgs,
107
+
mkShell,
108
+
}:
109
+
mkShell {
110
+
packages = [
111
+
pkgs.bun
112
+
pkgs.eslint_d
113
+
pkgs.eslint
114
+
pkgs.typescript
115
+
pkgs.typescript-language-server
116
+
pkgs.package-version-server
117
+
pkgs.nixd
118
+
pkgs.nil
119
+
];
120
+
};
121
+
};
122
+
shells.html = {
82
123
systems = [ "x86_64-linux" ];
124
+
83
125
shell =
84
126
{
85
127
pkgs,
86
128
mkShell,
87
-
...
88
129
}:
89
-
let
90
-
python3 = pkgs.python313.override {
91
-
packageOverrides = pyfinal: pyprev: {
92
-
cmu_graphics = config.packages.cmu-graphics.result.x86_64-linux;
93
-
};
94
-
};
95
-
in
96
-
mkShell {
97
-
packages = [
98
-
(python3.withPackages (ppkgs: [
99
-
ppkgs.cmu_graphics
100
-
ppkgs.pycairo
101
-
ppkgs.pygame
102
-
ppkgs.pillow
103
-
ppkgs.numpy
104
-
]))
105
-
];
106
-
};
130
+
mkShell {
131
+
shellHook = ''
132
+
serve() {
133
+
live-server /home/coded/Programming/CMU/html --port 5000
134
+
}
135
+
export -f serve
136
+
'';
137
+
packages = [
138
+
pkgs.emmet-language-server
139
+
pkgs.nixd
140
+
pkgs.nil
141
+
pkgs.nodePackages.live-server
142
+
];
143
+
};
107
144
};
108
145
};
109
146
}
+3
-3
python/oct10/Tables/main.py
+3
-3
python/oct10/Tables/main.py
···
148
148
# so it will raise a custom exception.
149
149
error = None
150
150
try:
151
-
col = table.getCol(3)
151
+
_col = table.getCol(3)
152
152
except Exception as e:
153
153
error = str(e)
154
154
assert error == "Col 3 is out of range"
···
186
186
# so it will raise a custom exception.
187
187
error = None
188
188
try:
189
-
row = table.getRow(10)
189
+
_row = table.getRow(10)
190
190
except Exception as e:
191
191
error = str(e)
192
192
assert error == "Row 10 is out of range"
···
199
199
# there is no such header, so it will raise a custom exception.
200
200
error = None
201
201
try:
202
-
i = table.getHeaderIndex("Missing")
202
+
_i = table.getHeaderIndex("Missing")
203
203
except Exception as e:
204
204
error = str(e)
205
205
# assert(error == 'No such header: Missing')
+6
-4
python/oct14/level1/maxDigit.py
+6
-4
python/oct14/level1/maxDigit.py
···
1
1
def maxDigit(n: int) -> int:
2
2
n = abs(n)
3
-
d = n % 10
4
-
r = n // 10
5
-
next = maxDigit(r) if r > 0 else 0
6
-
return d if d > next else next
3
+
if n == 0:
4
+
return 0
5
+
rest = n // 10
6
+
ones = n % 10
7
+
next = maxDigit(rest)
8
+
return max(ones, next)
7
9
8
10
def testMaxDigit():
9
11
print('Testing maxDigit()...', end='')
python/oct15/cats/Figure_1.png
python/oct15/cats/Figure_1.png
This is a binary file and will not be displayed.
+6
python/oct15/cats/README.txt
+6
python/oct15/cats/README.txt
+1001
python/oct15/cats/cats_dataset.csv
+1001
python/oct15/cats/cats_dataset.csv
···
1
+
Breed,Age (Years),Weight (kg),Color,Gender
2
+
Russian Blue,19,7,Tortoiseshell,Female
3
+
Norwegian Forest,19,9,Tortoiseshell,Female
4
+
Chartreux,3,3,Brown,Female
5
+
Persian,13,6,Sable,Female
6
+
Ragdoll,10,8,Tabby,Male
7
+
Ocicat,9,8,Blue,Female
8
+
Ragdoll,6,5,Calico,Female
9
+
Abyssinian,12,3,Tabby,Male
10
+
Oriental,2,7,White,Male
11
+
Egyptian Mau,12,3,White,Male
12
+
Chartreux,16,4,White,Male
13
+
American Shorthair,13,5,Black,Male
14
+
Bengal,16,6,White,Male
15
+
Cornish Rex,13,6,Pointed,Male
16
+
British Shorthair,19,3,Red,Female
17
+
Burmese,14,5,Pointed,Male
18
+
Burmese,13,3,Pointed,Male
19
+
Russian Blue,10,7,Black,Male
20
+
Singapura,7,3,Blue,Female
21
+
Maine Coon,5,6,Tricolor,Male
22
+
British Shorthair,17,6,Cream,Female
23
+
Abyssinian,9,3,Tabby,Male
24
+
Cornish Rex,6,9,Pointed,Male
25
+
Turkish Angora,16,6,Sable,Female
26
+
Burmese,6,7,Calico,Female
27
+
Himalayan,6,6,Tortoiseshell,Male
28
+
Sphynx,18,4,Cream,Male
29
+
Manx,1,4,Blue,Male
30
+
Siberian,17,2,Calico,Male
31
+
Birman,17,7,Pointed,Female
32
+
Oriental,1,2,Tortoiseshell,Female
33
+
Balinese,11,9,White,Male
34
+
Egyptian Mau,13,3,Pointed,Male
35
+
Devon Rex,1,3,Orange,Male
36
+
Egyptian Mau,8,3,Cream,Male
37
+
Sphynx,6,3,Tortoiseshell,Female
38
+
Norwegian Forest,5,9,Sable,Female
39
+
Norwegian Forest,5,6,Tabby,Male
40
+
Persian,6,2,Sable,Male
41
+
American Shorthair,13,8,Black,Male
42
+
Ragdoll,18,3,Cream,Male
43
+
Burmese,13,4,Blue,Female
44
+
Himalayan,7,9,Tabby,Female
45
+
Egyptian Mau,8,6,White,Male
46
+
Exotic Shorthair,17,2,White,Male
47
+
Egyptian Mau,1,7,Gray,Female
48
+
Egyptian Mau,16,6,White,Male
49
+
Turkish Angora,11,5,Bicolor,Male
50
+
Abyssinian,4,9,Blue,Female
51
+
Persian,4,3,Bicolor,Male
52
+
Maine Coon,17,4,Black,Male
53
+
Oriental,17,7,Red,Female
54
+
Manx,10,9,Tricolor,Female
55
+
Persian,6,2,Blue,Male
56
+
Scottish Fold,17,6,Tricolor,Male
57
+
Balinese,18,2,Orange,Male
58
+
Cornish Rex,10,5,Tabby,Male
59
+
Ragdoll,13,4,Brown,Male
60
+
Savannah,16,7,Gray,Male
61
+
American Shorthair,7,8,Bicolor,Female
62
+
Cornish Rex,15,6,Blue,Male
63
+
Munchkin,2,7,Blue,Male
64
+
Siamese,5,9,Brown,Female
65
+
Persian,17,5,White,Male
66
+
Persian,5,4,Bicolor,Male
67
+
Bengal,8,3,Red,Female
68
+
Chartreux,15,2,Calico,Male
69
+
Sphynx,8,8,Calico,Male
70
+
British Shorthair,3,5,Tricolor,Male
71
+
Birman,17,7,Pointed,Female
72
+
Balinese,16,7,Tricolor,Female
73
+
Himalayan,16,9,Orange,Male
74
+
Norwegian Forest,19,2,Brown,Female
75
+
Bengal,12,2,Tricolor,Male
76
+
Oriental,15,5,Tabby,Female
77
+
Scottish Fold,8,5,Tricolor,Male
78
+
Singapura,3,6,Calico,Male
79
+
Burmese,18,4,Bicolor,Female
80
+
Maine Coon,4,8,Black,Female
81
+
Maine Coon,8,7,Tricolor,Female
82
+
Abyssinian,14,3,Pointed,Female
83
+
Oriental,17,4,Gray,Female
84
+
Manx,6,7,Black,Male
85
+
Ragdoll,5,8,Cream,Female
86
+
British Shorthair,14,7,Red,Female
87
+
Cornish Rex,6,8,Red,Female
88
+
Savannah,12,3,Sable,Female
89
+
Turkish Angora,9,3,Orange,Male
90
+
American Shorthair,1,8,Tricolor,Male
91
+
Ocicat,18,6,Sable,Female
92
+
Persian,6,8,Calico,Female
93
+
Turkish Angora,5,4,Brown,Male
94
+
Ragdoll,2,5,Tricolor,Male
95
+
Chartreux,6,2,Tabby,Male
96
+
Russian Blue,17,7,Cream,Male
97
+
Manx,7,6,Sable,Male
98
+
Scottish Fold,9,6,Sable,Male
99
+
Balinese,5,6,White,Female
100
+
Savannah,11,9,Red,Female
101
+
Bengal,11,5,Tortoiseshell,Female
102
+
British Shorthair,4,7,Red,Male
103
+
Bengal,6,8,White,Female
104
+
Norwegian Forest,10,2,Cream,Female
105
+
Balinese,9,3,Black,Female
106
+
Ragdoll,18,7,Red,Male
107
+
Russian Blue,9,5,Tortoiseshell,Male
108
+
Bengal,11,4,Calico,Male
109
+
Balinese,2,4,Orange,Female
110
+
Birman,15,5,Cream,Male
111
+
Turkish Angora,6,6,Brown,Female
112
+
Norwegian Forest,17,5,Black,Female
113
+
Balinese,4,7,Tortoiseshell,Female
114
+
Ragdoll,8,9,Calico,Male
115
+
Singapura,2,8,Blue,Male
116
+
Cornish Rex,9,2,Tricolor,Female
117
+
Norwegian Forest,1,7,Cream,Female
118
+
Siberian,6,3,Gray,Female
119
+
Chartreux,16,8,Calico,Female
120
+
Chartreux,7,9,Bicolor,Male
121
+
Devon Rex,16,3,Pointed,Male
122
+
Himalayan,6,9,Sable,Female
123
+
Sphynx,10,3,Tabby,Male
124
+
Oriental,18,8,Tortoiseshell,Female
125
+
Ragdoll,17,7,Black,Male
126
+
Persian,17,3,Bicolor,Female
127
+
Sphynx,12,6,Red,Female
128
+
Persian,15,7,Pointed,Female
129
+
Himalayan,6,8,Brown,Female
130
+
American Shorthair,16,7,Bicolor,Male
131
+
Bengal,14,4,Calico,Male
132
+
Siamese,12,9,Red,Female
133
+
Devon Rex,12,4,Blue,Male
134
+
Exotic Shorthair,2,5,Black,Female
135
+
Ragdoll,8,3,Cream,Male
136
+
Siamese,17,4,Tortoiseshell,Female
137
+
Scottish Fold,8,2,Brown,Male
138
+
Siberian,16,8,Bicolor,Female
139
+
Devon Rex,1,5,Blue,Female
140
+
Abyssinian,5,9,Tortoiseshell,Female
141
+
Chartreux,19,6,Calico,Female
142
+
Oriental,3,4,Orange,Female
143
+
Persian,9,3,Black,Male
144
+
Scottish Fold,11,8,Gray,Male
145
+
American Shorthair,6,8,Bicolor,Female
146
+
Savannah,7,8,Orange,Female
147
+
Singapura,7,6,Pointed,Male
148
+
Cornish Rex,5,2,White,Female
149
+
Ocicat,2,2,Calico,Female
150
+
Siamese,5,2,Brown,Female
151
+
Manx,6,6,Calico,Female
152
+
Siamese,6,7,Tabby,Female
153
+
Ragdoll,7,4,Calico,Male
154
+
Ocicat,4,4,Red,Female
155
+
Ragdoll,18,7,Sable,Male
156
+
American Shorthair,16,7,Tabby,Female
157
+
Turkish Angora,19,3,Gray,Male
158
+
Ragdoll,10,7,Blue,Female
159
+
Balinese,5,6,Gray,Male
160
+
Himalayan,18,4,Red,Male
161
+
American Shorthair,3,7,Pointed,Male
162
+
Ocicat,4,5,Red,Female
163
+
Turkish Angora,14,3,Brown,Male
164
+
Oriental,12,4,Red,Male
165
+
Singapura,14,5,Tricolor,Male
166
+
Maine Coon,5,3,Calico,Male
167
+
Bengal,18,2,Orange,Male
168
+
Scottish Fold,7,2,Gray,Male
169
+
Tonkinese,16,5,Bicolor,Female
170
+
Savannah,3,2,Pointed,Male
171
+
Birman,11,5,Black,Male
172
+
Savannah,18,3,Brown,Female
173
+
Siamese,6,4,Bicolor,Male
174
+
Egyptian Mau,5,9,Sable,Female
175
+
Devon Rex,5,3,Cream,Male
176
+
Persian,16,5,Orange,Female
177
+
Tonkinese,3,8,Brown,Male
178
+
Persian,12,6,Red,Male
179
+
British Shorthair,3,6,Bicolor,Female
180
+
Egyptian Mau,2,9,Gray,Male
181
+
Turkish Angora,15,8,Red,Male
182
+
Scottish Fold,17,7,Gray,Female
183
+
Egyptian Mau,12,7,White,Male
184
+
Burmese,2,7,Tortoiseshell,Male
185
+
Munchkin,2,5,Orange,Male
186
+
Birman,14,2,Cream,Male
187
+
Siberian,7,5,Gray,Male
188
+
Burmese,14,9,Pointed,Female
189
+
Exotic Shorthair,7,9,Brown,Female
190
+
Siamese,17,7,Tricolor,Female
191
+
Ragdoll,4,4,Pointed,Female
192
+
Siamese,15,9,Pointed,Female
193
+
Savannah,3,3,White,Female
194
+
Siberian,6,5,Tricolor,Female
195
+
Devon Rex,8,9,Pointed,Male
196
+
Birman,16,7,Bicolor,Female
197
+
Birman,15,8,Pointed,Female
198
+
Egyptian Mau,4,4,Tricolor,Male
199
+
Birman,8,6,Gray,Female
200
+
Singapura,8,5,Orange,Female
201
+
Siamese,7,2,Orange,Male
202
+
Burmese,18,7,Blue,Male
203
+
Balinese,12,5,Tabby,Female
204
+
Ragdoll,4,7,Tabby,Female
205
+
Russian Blue,14,6,Gray,Male
206
+
Exotic Shorthair,5,7,Red,Male
207
+
Turkish Angora,7,7,Sable,Male
208
+
Persian,12,4,Orange,Male
209
+
Bengal,15,5,Tricolor,Male
210
+
Ragdoll,19,9,Tortoiseshell,Male
211
+
Siberian,13,5,Sable,Male
212
+
Savannah,4,7,Black,Male
213
+
Munchkin,6,8,Bicolor,Male
214
+
Tonkinese,8,2,Tortoiseshell,Female
215
+
Siberian,15,5,Pointed,Female
216
+
Manx,12,3,Black,Male
217
+
Siberian,10,8,Black,Female
218
+
Savannah,12,8,Black,Male
219
+
Burmese,17,5,Orange,Male
220
+
Devon Rex,8,4,Tricolor,Male
221
+
Burmese,2,8,Tortoiseshell,Female
222
+
Exotic Shorthair,19,3,Cream,Male
223
+
Chartreux,1,2,Cream,Male
224
+
Turkish Angora,10,4,Tabby,Female
225
+
Manx,9,2,Pointed,Female
226
+
Devon Rex,19,8,Brown,Female
227
+
Munchkin,12,8,Bicolor,Female
228
+
Manx,18,8,Black,Male
229
+
Tonkinese,16,5,Red,Female
230
+
Chartreux,4,5,White,Male
231
+
Exotic Shorthair,11,5,Tricolor,Male
232
+
Egyptian Mau,15,6,Tabby,Female
233
+
Maine Coon,16,6,Tabby,Female
234
+
Birman,2,9,Bicolor,Male
235
+
Persian,13,6,Sable,Female
236
+
Bengal,12,3,Black,Female
237
+
Ocicat,8,6,Red,Male
238
+
Ocicat,12,7,Sable,Female
239
+
Manx,14,9,Blue,Female
240
+
British Shorthair,3,7,Tortoiseshell,Male
241
+
Chartreux,2,5,Red,Male
242
+
Siberian,17,9,Bicolor,Female
243
+
Abyssinian,2,5,Tabby,Female
244
+
Burmese,17,2,Black,Male
245
+
Burmese,2,4,Tricolor,Female
246
+
Norwegian Forest,2,4,Tricolor,Male
247
+
Oriental,13,3,Tabby,Female
248
+
American Shorthair,7,4,Orange,Female
249
+
Birman,9,9,Sable,Male
250
+
Tonkinese,9,8,Calico,Male
251
+
Norwegian Forest,14,3,Cream,Female
252
+
Savannah,11,9,Brown,Female
253
+
British Shorthair,19,2,Sable,Female
254
+
Norwegian Forest,1,5,Tricolor,Female
255
+
Exotic Shorthair,5,5,Tricolor,Male
256
+
Manx,1,3,Tricolor,Male
257
+
Maine Coon,6,6,Cream,Female
258
+
Tonkinese,16,9,Orange,Female
259
+
Russian Blue,16,2,White,Male
260
+
Burmese,13,9,Pointed,Male
261
+
Exotic Shorthair,14,9,Sable,Female
262
+
Burmese,11,7,Sable,Female
263
+
Ragdoll,10,5,Black,Male
264
+
American Shorthair,17,5,Brown,Male
265
+
Norwegian Forest,7,9,Cream,Male
266
+
Ragdoll,4,8,Orange,Male
267
+
Scottish Fold,17,2,Sable,Male
268
+
Russian Blue,1,7,Brown,Female
269
+
British Shorthair,2,7,Red,Female
270
+
Ragdoll,19,3,Gray,Female
271
+
Tonkinese,1,9,Cream,Female
272
+
Exotic Shorthair,12,6,Bicolor,Female
273
+
Sphynx,19,8,Blue,Male
274
+
Cornish Rex,19,8,Orange,Male
275
+
Savannah,16,3,Tabby,Male
276
+
Persian,16,2,Calico,Male
277
+
Savannah,8,4,Black,Female
278
+
Birman,13,3,Bicolor,Female
279
+
Balinese,14,4,Bicolor,Male
280
+
Scottish Fold,19,8,Tricolor,Male
281
+
Tonkinese,15,2,Black,Female
282
+
Savannah,15,7,Brown,Female
283
+
Sphynx,8,4,Blue,Male
284
+
Exotic Shorthair,13,5,Tortoiseshell,Female
285
+
Munchkin,12,9,Sable,Female
286
+
Norwegian Forest,13,4,Sable,Female
287
+
Birman,16,6,White,Female
288
+
Siamese,12,6,Pointed,Female
289
+
Ocicat,14,8,Cream,Male
290
+
Egyptian Mau,15,2,Tabby,Male
291
+
Manx,19,6,Cream,Female
292
+
Cornish Rex,5,9,Brown,Female
293
+
Chartreux,2,7,Gray,Male
294
+
Egyptian Mau,18,7,Tricolor,Female
295
+
Turkish Angora,18,8,Gray,Male
296
+
Balinese,17,4,Tortoiseshell,Male
297
+
Chartreux,10,8,Gray,Male
298
+
Exotic Shorthair,17,9,Calico,Male
299
+
Ragdoll,3,5,Pointed,Female
300
+
Ragdoll,16,2,Cream,Male
301
+
Abyssinian,18,9,Tortoiseshell,Male
302
+
Oriental,7,3,Tricolor,Female
303
+
Oriental,1,5,Tabby,Male
304
+
Oriental,7,8,Cream,Male
305
+
Cornish Rex,13,3,White,Female
306
+
Ragdoll,16,2,Tortoiseshell,Male
307
+
Turkish Angora,11,3,Calico,Female
308
+
Singapura,3,5,Bicolor,Female
309
+
American Shorthair,9,9,Sable,Female
310
+
Russian Blue,7,6,Red,Female
311
+
Ragdoll,3,8,Red,Male
312
+
Oriental,1,2,White,Male
313
+
Scottish Fold,11,6,Tabby,Male
314
+
Ocicat,13,6,Tortoiseshell,Male
315
+
Siberian,4,6,Black,Male
316
+
Abyssinian,10,5,Tabby,Female
317
+
Abyssinian,8,8,Orange,Female
318
+
Exotic Shorthair,17,7,Blue,Female
319
+
Chartreux,11,8,Black,Female
320
+
Maine Coon,5,4,Tricolor,Male
321
+
Russian Blue,18,6,Gray,Female
322
+
Siamese,4,4,Gray,Female
323
+
Siamese,6,2,Orange,Male
324
+
Burmese,7,3,Bicolor,Male
325
+
Maine Coon,12,6,White,Male
326
+
Sphynx,3,7,Sable,Male
327
+
Birman,8,4,Bicolor,Female
328
+
Bengal,10,2,Black,Male
329
+
Persian,1,5,Orange,Female
330
+
Savannah,17,7,Blue,Male
331
+
Siamese,18,8,Calico,Female
332
+
Sphynx,9,7,Brown,Female
333
+
Chartreux,16,2,Sable,Male
334
+
Devon Rex,17,5,White,Male
335
+
Birman,18,3,Brown,Female
336
+
Exotic Shorthair,11,3,White,Female
337
+
Maine Coon,18,7,Black,Male
338
+
Himalayan,4,4,White,Female
339
+
Devon Rex,8,9,Blue,Female
340
+
Ragdoll,10,5,Orange,Female
341
+
Burmese,4,5,Pointed,Female
342
+
Ocicat,8,7,Orange,Male
343
+
Birman,17,3,Tricolor,Male
344
+
Balinese,3,7,Cream,Female
345
+
Bengal,5,7,Tabby,Female
346
+
Devon Rex,5,2,Gray,Male
347
+
Manx,7,2,Blue,Male
348
+
Ragdoll,1,4,Bicolor,Female
349
+
Abyssinian,18,3,Black,Female
350
+
Munchkin,6,8,Brown,Female
351
+
Ragdoll,15,9,Pointed,Male
352
+
American Shorthair,9,3,Tortoiseshell,Male
353
+
Exotic Shorthair,3,6,Pointed,Female
354
+
Himalayan,18,7,Pointed,Male
355
+
Maine Coon,16,9,Black,Female
356
+
American Shorthair,17,4,Red,Male
357
+
Himalayan,9,3,Red,Female
358
+
Exotic Shorthair,13,6,Tortoiseshell,Female
359
+
Balinese,8,5,White,Female
360
+
Exotic Shorthair,13,9,Orange,Male
361
+
Siamese,1,8,Tabby,Female
362
+
Sphynx,12,7,Calico,Female
363
+
Sphynx,12,6,Tricolor,Female
364
+
Russian Blue,9,3,Black,Male
365
+
Russian Blue,1,2,Gray,Male
366
+
Chartreux,14,4,Bicolor,Female
367
+
Exotic Shorthair,14,5,Pointed,Male
368
+
Siamese,6,8,Orange,Male
369
+
Norwegian Forest,16,9,Bicolor,Female
370
+
Ocicat,8,7,Blue,Female
371
+
Chartreux,19,9,White,Female
372
+
Abyssinian,5,2,Tortoiseshell,Male
373
+
Abyssinian,15,4,Gray,Female
374
+
Savannah,13,5,Gray,Male
375
+
Tonkinese,6,5,Brown,Female
376
+
Himalayan,17,7,Orange,Female
377
+
British Shorthair,5,7,Orange,Male
378
+
Abyssinian,10,3,Bicolor,Male
379
+
American Shorthair,5,6,Pointed,Female
380
+
Munchkin,17,7,Orange,Female
381
+
Savannah,19,9,Tabby,Female
382
+
Egyptian Mau,6,2,Cream,Female
383
+
Himalayan,15,7,Calico,Male
384
+
Oriental,4,9,Gray,Female
385
+
Exotic Shorthair,17,6,Blue,Female
386
+
Turkish Angora,1,9,Cream,Male
387
+
Chartreux,18,3,Gray,Male
388
+
Oriental,10,3,Gray,Female
389
+
Manx,19,2,Red,Male
390
+
American Shorthair,2,8,Black,Male
391
+
Persian,17,5,Pointed,Male
392
+
Ocicat,18,9,Pointed,Female
393
+
Oriental,13,4,Tabby,Female
394
+
Munchkin,7,8,Red,Female
395
+
Savannah,5,5,Bicolor,Male
396
+
Himalayan,4,6,Sable,Female
397
+
Oriental,7,8,Orange,Male
398
+
Singapura,5,7,Black,Female
399
+
Persian,19,2,Tricolor,Female
400
+
Tonkinese,7,8,Sable,Female
401
+
Devon Rex,13,3,Bicolor,Male
402
+
Scottish Fold,4,7,Sable,Male
403
+
Bengal,15,5,Tricolor,Female
404
+
Burmese,18,2,Cream,Female
405
+
Balinese,4,4,Pointed,Female
406
+
Ragdoll,4,7,Bicolor,Male
407
+
Egyptian Mau,2,9,Gray,Male
408
+
Birman,5,8,Tricolor,Female
409
+
Burmese,8,3,Blue,Female
410
+
Ocicat,11,5,Red,Female
411
+
Exotic Shorthair,11,3,Black,Female
412
+
Ocicat,18,9,Gray,Female
413
+
Turkish Angora,19,9,Brown,Male
414
+
Cornish Rex,17,6,Black,Female
415
+
Persian,1,4,Red,Male
416
+
Egyptian Mau,5,9,Black,Male
417
+
Birman,3,7,Red,Female
418
+
Abyssinian,14,4,Pointed,Female
419
+
Cornish Rex,2,4,Brown,Female
420
+
Siberian,3,8,Black,Female
421
+
Singapura,3,6,Brown,Female
422
+
Himalayan,13,5,Blue,Male
423
+
Burmese,8,9,Sable,Male
424
+
Balinese,14,9,Black,Female
425
+
Cornish Rex,18,9,Pointed,Male
426
+
Persian,13,7,Sable,Male
427
+
Savannah,18,5,Calico,Female
428
+
Munchkin,1,8,Gray,Male
429
+
Bengal,2,5,Tortoiseshell,Male
430
+
Manx,5,5,Pointed,Male
431
+
Persian,11,9,Bicolor,Male
432
+
Scottish Fold,11,5,Gray,Female
433
+
Turkish Angora,1,2,Blue,Female
434
+
Tonkinese,3,8,Blue,Female
435
+
Munchkin,2,6,Blue,Female
436
+
Balinese,14,4,Orange,Female
437
+
American Shorthair,16,5,Bicolor,Male
438
+
Bengal,18,5,Brown,Female
439
+
Tonkinese,5,4,Cream,Male
440
+
Ragdoll,8,4,Orange,Male
441
+
Singapura,7,2,Bicolor,Female
442
+
Egyptian Mau,10,6,Pointed,Female
443
+
Abyssinian,16,3,Brown,Female
444
+
Birman,19,7,Pointed,Female
445
+
Exotic Shorthair,11,2,Black,Female
446
+
Siberian,19,3,Tabby,Male
447
+
Sphynx,3,2,Tricolor,Male
448
+
Persian,18,6,White,Male
449
+
Birman,4,6,Bicolor,Female
450
+
Norwegian Forest,17,3,Cream,Male
451
+
Norwegian Forest,13,8,Tortoiseshell,Female
452
+
Chartreux,4,5,Tabby,Male
453
+
Savannah,14,7,Tortoiseshell,Female
454
+
Bengal,3,5,Cream,Male
455
+
Abyssinian,4,5,Calico,Female
456
+
Munchkin,18,8,Orange,Female
457
+
Siberian,13,3,Sable,Male
458
+
Singapura,7,5,Tabby,Male
459
+
Scottish Fold,11,4,Black,Female
460
+
Balinese,11,4,Tortoiseshell,Male
461
+
Turkish Angora,6,6,Tortoiseshell,Male
462
+
Himalayan,11,3,Sable,Male
463
+
Ocicat,9,5,Blue,Male
464
+
Egyptian Mau,16,4,White,Female
465
+
Ragdoll,5,5,White,Male
466
+
Oriental,6,7,Gray,Female
467
+
American Shorthair,4,2,Black,Male
468
+
Chartreux,8,5,Gray,Female
469
+
Siamese,16,6,White,Male
470
+
Egyptian Mau,16,8,Gray,Male
471
+
Chartreux,11,2,Tortoiseshell,Female
472
+
Singapura,14,9,Tricolor,Male
473
+
Savannah,8,3,White,Male
474
+
Cornish Rex,6,5,Orange,Female
475
+
Munchkin,8,5,Red,Female
476
+
Munchkin,19,2,Bicolor,Female
477
+
American Shorthair,8,6,Blue,Female
478
+
Siberian,1,9,Orange,Female
479
+
Cornish Rex,13,6,Cream,Female
480
+
Maine Coon,8,6,Blue,Male
481
+
British Shorthair,9,7,Black,Male
482
+
Scottish Fold,16,3,Orange,Male
483
+
Chartreux,5,8,Tortoiseshell,Male
484
+
Devon Rex,11,9,Tabby,Male
485
+
Ocicat,17,2,Gray,Male
486
+
Devon Rex,9,9,Red,Male
487
+
Singapura,17,8,Black,Female
488
+
Munchkin,15,4,Black,Male
489
+
Ragdoll,1,3,Calico,Female
490
+
Singapura,16,6,Gray,Male
491
+
Himalayan,13,6,Blue,Female
492
+
Scottish Fold,3,5,Cream,Male
493
+
Oriental,14,7,Sable,Male
494
+
Russian Blue,3,7,Red,Male
495
+
Siberian,18,3,Gray,Female
496
+
Manx,14,2,Tortoiseshell,Male
497
+
British Shorthair,17,8,Brown,Male
498
+
British Shorthair,6,8,Calico,Female
499
+
Abyssinian,8,6,White,Male
500
+
Birman,2,4,Gray,Male
501
+
Siberian,18,5,Pointed,Male
502
+
Burmese,3,2,Calico,Male
503
+
Abyssinian,2,2,Tricolor,Male
504
+
Devon Rex,5,7,Tortoiseshell,Female
505
+
Burmese,9,2,Gray,Male
506
+
Tonkinese,19,9,Sable,Male
507
+
Burmese,2,7,Tricolor,Male
508
+
Norwegian Forest,4,7,Cream,Male
509
+
Manx,17,8,White,Female
510
+
Birman,4,7,Brown,Female
511
+
Ragdoll,6,9,Bicolor,Female
512
+
British Shorthair,2,5,Tricolor,Male
513
+
Himalayan,4,4,Tricolor,Female
514
+
Abyssinian,8,6,Blue,Female
515
+
Balinese,2,5,Pointed,Male
516
+
Manx,2,5,Gray,Male
517
+
Siamese,8,4,Brown,Female
518
+
Munchkin,5,8,Tortoiseshell,Male
519
+
Devon Rex,14,8,Pointed,Female
520
+
Burmese,13,2,Cream,Female
521
+
American Shorthair,19,6,Gray,Female
522
+
Siberian,1,3,Cream,Female
523
+
Cornish Rex,12,9,Sable,Male
524
+
Burmese,3,2,Tricolor,Male
525
+
Bengal,14,7,White,Male
526
+
Bengal,5,2,Tabby,Male
527
+
Russian Blue,17,4,Bicolor,Female
528
+
Himalayan,17,3,White,Female
529
+
Cornish Rex,11,6,Bicolor,Female
530
+
Tonkinese,12,6,Cream,Female
531
+
Manx,4,9,Tabby,Female
532
+
Balinese,9,6,Bicolor,Female
533
+
Maine Coon,15,9,Calico,Male
534
+
Sphynx,1,4,Tabby,Female
535
+
Manx,1,7,Tortoiseshell,Female
536
+
Cornish Rex,3,3,Gray,Male
537
+
Ocicat,12,3,Brown,Male
538
+
Exotic Shorthair,6,2,Brown,Female
539
+
Maine Coon,3,7,Orange,Male
540
+
Egyptian Mau,8,5,Red,Female
541
+
Chartreux,13,3,Calico,Male
542
+
Cornish Rex,12,6,Calico,Male
543
+
Egyptian Mau,2,6,Cream,Male
544
+
Tonkinese,17,5,Cream,Male
545
+
Tonkinese,19,5,Pointed,Male
546
+
Abyssinian,19,2,Gray,Male
547
+
Oriental,15,2,Bicolor,Male
548
+
Himalayan,16,8,Red,Male
549
+
Oriental,16,4,Blue,Male
550
+
Tonkinese,6,7,Black,Female
551
+
Persian,2,8,Calico,Female
552
+
Egyptian Mau,11,6,Sable,Female
553
+
Burmese,3,4,Cream,Male
554
+
Bengal,9,6,Tricolor,Female
555
+
Himalayan,16,4,Cream,Male
556
+
Balinese,8,9,Gray,Male
557
+
Chartreux,13,2,Pointed,Male
558
+
Devon Rex,4,3,Calico,Male
559
+
Ragdoll,2,7,Tabby,Female
560
+
Scottish Fold,17,8,Sable,Male
561
+
Maine Coon,18,5,Tabby,Male
562
+
Balinese,17,6,Red,Female
563
+
Bengal,6,7,Orange,Male
564
+
Bengal,11,2,Red,Female
565
+
Devon Rex,18,8,White,Male
566
+
Devon Rex,14,2,Gray,Male
567
+
Norwegian Forest,1,2,Tortoiseshell,Male
568
+
Scottish Fold,2,4,Tortoiseshell,Male
569
+
Siberian,7,7,Cream,Male
570
+
Birman,10,9,Bicolor,Female
571
+
Ocicat,6,9,Tricolor,Male
572
+
Savannah,13,5,Calico,Female
573
+
Balinese,11,8,Tabby,Female
574
+
British Shorthair,17,2,Cream,Female
575
+
Oriental,18,4,Calico,Female
576
+
Munchkin,5,8,Red,Female
577
+
Ocicat,15,2,Gray,Female
578
+
Norwegian Forest,14,9,Cream,Male
579
+
Ragdoll,18,4,Tabby,Female
580
+
Egyptian Mau,7,9,Sable,Male
581
+
Russian Blue,11,7,Blue,Male
582
+
American Shorthair,6,3,Bicolor,Male
583
+
Himalayan,5,9,Cream,Female
584
+
Scottish Fold,5,4,Tricolor,Male
585
+
Siamese,19,6,Gray,Male
586
+
Maine Coon,19,9,Brown,Female
587
+
Savannah,11,5,Gray,Female
588
+
Ragdoll,12,4,Pointed,Female
589
+
Russian Blue,15,5,Tabby,Male
590
+
Bengal,17,9,White,Female
591
+
Singapura,2,4,Tricolor,Female
592
+
Maine Coon,9,5,Blue,Female
593
+
Himalayan,8,9,Tabby,Male
594
+
Persian,6,9,Gray,Male
595
+
Abyssinian,16,7,Red,Female
596
+
Burmese,10,3,Red,Female
597
+
Singapura,1,6,Cream,Male
598
+
Bengal,15,6,Tricolor,Male
599
+
Ragdoll,3,6,Pointed,Female
600
+
Sphynx,5,8,Sable,Male
601
+
British Shorthair,1,8,Pointed,Male
602
+
Ragdoll,6,4,Pointed,Female
603
+
Siamese,18,9,Brown,Female
604
+
Manx,5,8,White,Male
605
+
Birman,1,6,Bicolor,Male
606
+
Munchkin,18,2,Red,Female
607
+
Maine Coon,18,2,Cream,Female
608
+
Munchkin,9,8,Sable,Male
609
+
Bengal,10,8,Pointed,Female
610
+
Singapura,5,6,Black,Female
611
+
Persian,10,7,Gray,Male
612
+
Burmese,13,6,Red,Female
613
+
Siberian,17,4,Pointed,Female
614
+
Egyptian Mau,4,8,White,Female
615
+
Oriental,1,6,Black,Male
616
+
Chartreux,15,5,Pointed,Female
617
+
Sphynx,1,7,Calico,Female
618
+
Maine Coon,12,3,White,Female
619
+
Abyssinian,2,4,Red,Female
620
+
Exotic Shorthair,19,4,Tortoiseshell,Female
621
+
Birman,15,8,Red,Female
622
+
Tonkinese,13,2,Pointed,Female
623
+
Balinese,9,4,Blue,Female
624
+
Abyssinian,6,4,Black,Male
625
+
Ragdoll,3,8,Cream,Female
626
+
Chartreux,18,3,Cream,Female
627
+
Ocicat,16,7,Blue,Female
628
+
Russian Blue,4,9,Tricolor,Female
629
+
Ragdoll,17,5,Cream,Female
630
+
British Shorthair,19,8,Tortoiseshell,Female
631
+
Balinese,6,3,Gray,Male
632
+
Abyssinian,19,7,Tortoiseshell,Male
633
+
Egyptian Mau,8,7,Gray,Male
634
+
Persian,18,7,Sable,Female
635
+
Sphynx,5,5,Calico,Male
636
+
Ocicat,14,2,Gray,Female
637
+
Savannah,16,7,Sable,Female
638
+
Oriental,15,3,Tricolor,Male
639
+
Egyptian Mau,19,3,Tricolor,Female
640
+
Abyssinian,1,3,Tabby,Male
641
+
Chartreux,5,6,Tricolor,Male
642
+
Sphynx,7,6,Gray,Male
643
+
Munchkin,13,8,Tricolor,Male
644
+
Birman,4,9,Tricolor,Male
645
+
Savannah,19,7,Gray,Male
646
+
Turkish Angora,14,3,Blue,Male
647
+
Persian,18,4,Bicolor,Female
648
+
Birman,2,2,Bicolor,Female
649
+
Egyptian Mau,19,4,Orange,Male
650
+
British Shorthair,13,2,Tortoiseshell,Male
651
+
Manx,16,9,Sable,Female
652
+
Scottish Fold,8,8,Sable,Male
653
+
Egyptian Mau,3,8,Brown,Male
654
+
Siamese,8,7,Pointed,Female
655
+
Ocicat,14,8,Cream,Male
656
+
Cornish Rex,17,2,Sable,Female
657
+
Persian,2,6,White,Female
658
+
Egyptian Mau,17,8,Calico,Female
659
+
Singapura,14,2,Tabby,Female
660
+
Turkish Angora,6,8,Calico,Male
661
+
Persian,19,9,Cream,Male
662
+
Egyptian Mau,17,3,Calico,Male
663
+
British Shorthair,13,9,Gray,Female
664
+
Maine Coon,5,9,Sable,Female
665
+
Russian Blue,17,3,Pointed,Female
666
+
Ocicat,15,7,Black,Female
667
+
Manx,9,7,Blue,Male
668
+
American Shorthair,15,8,Gray,Male
669
+
Siberian,18,2,White,Male
670
+
Oriental,11,8,Tricolor,Male
671
+
Bengal,15,6,Red,Female
672
+
Manx,7,4,Pointed,Female
673
+
Exotic Shorthair,14,6,Calico,Male
674
+
Ocicat,5,6,Pointed,Female
675
+
Himalayan,16,9,Gray,Male
676
+
Ragdoll,15,4,Red,Female
677
+
Siamese,11,3,Tricolor,Female
678
+
Balinese,12,8,Blue,Female
679
+
Siberian,15,7,Red,Female
680
+
Manx,12,8,Tabby,Female
681
+
Norwegian Forest,3,5,Blue,Male
682
+
Cornish Rex,1,6,Tabby,Female
683
+
Bengal,15,3,Tortoiseshell,Female
684
+
Oriental,7,2,Blue,Male
685
+
Bengal,19,6,Red,Male
686
+
Himalayan,15,6,Calico,Male
687
+
American Shorthair,14,9,Tortoiseshell,Female
688
+
Siberian,13,8,Pointed,Female
689
+
Siberian,6,2,Cream,Male
690
+
Cornish Rex,9,9,Blue,Male
691
+
Siamese,9,5,Gray,Female
692
+
Ragdoll,16,5,Red,Female
693
+
Oriental,11,4,Brown,Female
694
+
Singapura,11,7,Tricolor,Female
695
+
Cornish Rex,18,2,Red,Female
696
+
Singapura,1,9,Calico,Male
697
+
Tonkinese,8,3,Brown,Male
698
+
Tonkinese,7,3,Tortoiseshell,Male
699
+
Russian Blue,3,5,Gray,Female
700
+
Norwegian Forest,2,4,White,Female
701
+
Siamese,17,7,Calico,Female
702
+
Singapura,14,4,Tricolor,Female
703
+
Egyptian Mau,18,8,White,Female
704
+
British Shorthair,8,3,Black,Male
705
+
Turkish Angora,2,7,Pointed,Male
706
+
Devon Rex,3,7,Bicolor,Female
707
+
Exotic Shorthair,1,8,Bicolor,Female
708
+
Munchkin,19,4,Black,Male
709
+
Norwegian Forest,7,7,Cream,Male
710
+
Singapura,7,5,White,Female
711
+
Birman,4,5,Bicolor,Male
712
+
Persian,5,4,Cream,Female
713
+
Scottish Fold,19,5,Black,Female
714
+
Savannah,7,7,Gray,Male
715
+
Siberian,15,8,Calico,Female
716
+
Abyssinian,16,5,Black,Female
717
+
British Shorthair,18,4,Calico,Female
718
+
Siberian,2,5,Tabby,Male
719
+
Chartreux,10,9,Orange,Male
720
+
Norwegian Forest,18,2,Tortoiseshell,Female
721
+
Oriental,12,5,Blue,Male
722
+
British Shorthair,1,6,Tricolor,Female
723
+
Chartreux,4,3,Tabby,Female
724
+
Savannah,5,2,Black,Male
725
+
American Shorthair,16,4,Tricolor,Female
726
+
Abyssinian,6,3,White,Male
727
+
Munchkin,5,6,Red,Female
728
+
Maine Coon,10,3,Brown,Female
729
+
Oriental,17,5,Blue,Female
730
+
Siamese,17,3,Gray,Female
731
+
American Shorthair,6,9,Pointed,Male
732
+
Munchkin,1,4,Tortoiseshell,Female
733
+
Turkish Angora,4,5,Orange,Male
734
+
Sphynx,9,9,Orange,Male
735
+
Ocicat,10,5,Tricolor,Male
736
+
Tonkinese,11,6,Tricolor,Male
737
+
Bengal,7,8,Cream,Female
738
+
Exotic Shorthair,13,7,Black,Female
739
+
American Shorthair,12,9,Black,Male
740
+
Munchkin,3,3,Tortoiseshell,Male
741
+
Turkish Angora,18,7,Pointed,Male
742
+
Exotic Shorthair,8,9,Calico,Male
743
+
American Shorthair,8,6,Tricolor,Male
744
+
Maine Coon,7,8,Blue,Female
745
+
Russian Blue,6,5,Sable,Male
746
+
Persian,10,5,Blue,Male
747
+
Oriental,13,8,Bicolor,Female
748
+
Devon Rex,9,6,Orange,Female
749
+
Siamese,2,8,Calico,Male
750
+
Birman,15,5,Tabby,Male
751
+
Egyptian Mau,14,8,Sable,Male
752
+
Exotic Shorthair,16,7,Black,Female
753
+
Scottish Fold,5,7,Red,Female
754
+
Devon Rex,3,3,Cream,Male
755
+
Singapura,8,2,Blue,Female
756
+
Ocicat,14,7,Gray,Male
757
+
Burmese,9,8,Tortoiseshell,Female
758
+
Burmese,6,6,Bicolor,Female
759
+
Maine Coon,8,4,Tabby,Male
760
+
Persian,16,2,Sable,Male
761
+
Sphynx,16,4,Black,Female
762
+
Burmese,10,5,Pointed,Male
763
+
Siamese,10,2,Orange,Female
764
+
Norwegian Forest,12,5,Brown,Female
765
+
Ragdoll,4,9,White,Male
766
+
Munchkin,8,5,Sable,Female
767
+
Turkish Angora,19,9,Bicolor,Female
768
+
Sphynx,9,9,Red,Female
769
+
Savannah,2,9,White,Male
770
+
Egyptian Mau,4,8,Sable,Female
771
+
Burmese,19,7,Black,Male
772
+
British Shorthair,18,6,Brown,Male
773
+
Balinese,15,8,Red,Female
774
+
British Shorthair,11,8,Calico,Male
775
+
Munchkin,11,4,Bicolor,Male
776
+
Egyptian Mau,12,8,Calico,Male
777
+
British Shorthair,14,7,Tortoiseshell,Female
778
+
Siamese,15,3,Black,Male
779
+
Munchkin,6,6,Pointed,Male
780
+
Munchkin,11,6,Blue,Female
781
+
British Shorthair,8,4,Pointed,Male
782
+
Sphynx,18,6,Cream,Male
783
+
Ocicat,6,9,Orange,Male
784
+
Sphynx,7,4,Red,Female
785
+
Siberian,12,6,Orange,Male
786
+
Oriental,12,2,Black,Male
787
+
Bengal,18,9,Blue,Male
788
+
Tonkinese,19,4,Bicolor,Female
789
+
Siberian,6,3,Orange,Female
790
+
Chartreux,18,8,Blue,Female
791
+
Ocicat,9,7,Orange,Male
792
+
Himalayan,18,6,Black,Female
793
+
Ragdoll,11,7,Brown,Male
794
+
Siberian,8,8,Calico,Male
795
+
Egyptian Mau,12,2,Tricolor,Female
796
+
Singapura,15,7,Pointed,Male
797
+
Birman,1,3,Red,Male
798
+
Manx,2,8,Calico,Male
799
+
Chartreux,2,6,Pointed,Male
800
+
Siberian,5,6,Bicolor,Female
801
+
Sphynx,13,7,Blue,Male
802
+
Tonkinese,9,7,Orange,Male
803
+
Devon Rex,19,7,Orange,Male
804
+
Manx,13,5,Tabby,Male
805
+
Turkish Angora,4,6,Calico,Male
806
+
Cornish Rex,11,3,Orange,Male
807
+
Sphynx,8,3,Tricolor,Female
808
+
Maine Coon,15,2,Sable,Male
809
+
Sphynx,9,5,Cream,Male
810
+
British Shorthair,15,2,Tortoiseshell,Male
811
+
Russian Blue,4,9,Gray,Male
812
+
Singapura,4,8,Orange,Female
813
+
Scottish Fold,18,2,Sable,Female
814
+
Ocicat,13,6,Bicolor,Female
815
+
Tonkinese,14,8,Sable,Male
816
+
Turkish Angora,8,2,Tricolor,Female
817
+
Birman,16,4,Brown,Male
818
+
Abyssinian,15,5,Sable,Male
819
+
Siberian,8,6,Brown,Female
820
+
Sphynx,11,4,Tortoiseshell,Male
821
+
Norwegian Forest,14,3,Red,Male
822
+
Ocicat,19,5,Tortoiseshell,Male
823
+
Scottish Fold,2,9,Sable,Female
824
+
Ragdoll,12,3,Tabby,Male
825
+
Scottish Fold,19,7,Cream,Male
826
+
American Shorthair,15,8,Brown,Male
827
+
Manx,6,3,Tricolor,Female
828
+
Ocicat,9,6,White,Male
829
+
Manx,19,4,Orange,Female
830
+
Ragdoll,3,5,Orange,Female
831
+
Exotic Shorthair,12,6,White,Male
832
+
Turkish Angora,13,2,Calico,Female
833
+
American Shorthair,12,5,Tabby,Female
834
+
Sphynx,9,6,Tricolor,Female
835
+
Singapura,5,9,Sable,Male
836
+
Bengal,18,5,Brown,Female
837
+
Himalayan,13,9,Cream,Male
838
+
Sphynx,18,5,Calico,Female
839
+
Burmese,11,8,Brown,Female
840
+
Ragdoll,17,6,Tabby,Male
841
+
Sphynx,11,3,Orange,Female
842
+
Birman,12,8,Tricolor,Female
843
+
Bengal,3,3,Tabby,Male
844
+
Singapura,11,8,Blue,Female
845
+
Himalayan,13,6,White,Female
846
+
Cornish Rex,18,5,Bicolor,Female
847
+
Burmese,8,3,Brown,Male
848
+
Maine Coon,17,4,Sable,Female
849
+
American Shorthair,11,2,Blue,Male
850
+
Maine Coon,3,2,Brown,Male
851
+
Turkish Angora,16,4,Black,Female
852
+
Himalayan,12,9,Pointed,Female
853
+
Abyssinian,15,8,Sable,Female
854
+
Chartreux,10,4,Tricolor,Female
855
+
Savannah,4,4,Black,Female
856
+
Turkish Angora,17,5,Blue,Male
857
+
Devon Rex,1,3,Tabby,Female
858
+
Balinese,14,4,Blue,Female
859
+
Maine Coon,7,6,Tortoiseshell,Male
860
+
Himalayan,5,7,Brown,Female
861
+
Exotic Shorthair,4,2,Gray,Male
862
+
Russian Blue,18,2,Sable,Female
863
+
Tonkinese,17,4,Brown,Male
864
+
Turkish Angora,8,3,Black,Male
865
+
Tonkinese,12,4,Tabby,Male
866
+
Persian,3,6,Blue,Female
867
+
Siamese,9,4,White,Female
868
+
Egyptian Mau,9,5,Bicolor,Female
869
+
Cornish Rex,7,9,Orange,Male
870
+
Munchkin,2,8,Orange,Male
871
+
Manx,5,8,Brown,Male
872
+
Turkish Angora,12,8,Cream,Male
873
+
American Shorthair,6,8,Pointed,Male
874
+
Savannah,8,4,Bicolor,Male
875
+
Balinese,11,5,Brown,Female
876
+
Oriental,10,3,Tortoiseshell,Male
877
+
Turkish Angora,19,8,Gray,Male
878
+
Persian,6,7,Brown,Female
879
+
Russian Blue,14,8,Gray,Female
880
+
Tonkinese,16,3,Calico,Female
881
+
Oriental,13,9,Bicolor,Female
882
+
American Shorthair,12,6,Brown,Female
883
+
Bengal,1,3,Orange,Female
884
+
Abyssinian,15,5,Red,Male
885
+
Ragdoll,8,3,Bicolor,Female
886
+
Devon Rex,1,5,Tortoiseshell,Female
887
+
Russian Blue,2,6,Gray,Male
888
+
Sphynx,16,5,Pointed,Male
889
+
Balinese,14,4,Tortoiseshell,Female
890
+
Siberian,12,8,Gray,Male
891
+
Devon Rex,16,3,Bicolor,Male
892
+
Siberian,5,8,Gray,Female
893
+
American Shorthair,11,8,Blue,Male
894
+
Egyptian Mau,8,2,Brown,Male
895
+
Himalayan,11,8,Cream,Female
896
+
Abyssinian,12,4,Calico,Female
897
+
Singapura,1,7,Red,Female
898
+
Himalayan,18,8,Tricolor,Female
899
+
Turkish Angora,13,8,Gray,Male
900
+
Exotic Shorthair,12,5,Gray,Male
901
+
Ragdoll,16,9,Red,Female
902
+
Scottish Fold,3,7,Tricolor,Male
903
+
Balinese,3,7,Brown,Male
904
+
Scottish Fold,11,7,Brown,Male
905
+
Maine Coon,5,4,Pointed,Female
906
+
Cornish Rex,5,4,Black,Female
907
+
Balinese,13,4,Red,Male
908
+
Devon Rex,18,8,Bicolor,Male
909
+
Scottish Fold,17,8,Tortoiseshell,Male
910
+
American Shorthair,16,4,Tricolor,Male
911
+
Munchkin,7,5,Black,Female
912
+
Singapura,7,3,Tabby,Male
913
+
Cornish Rex,17,3,Cream,Male
914
+
Balinese,10,4,Orange,Female
915
+
Devon Rex,14,2,Calico,Male
916
+
Siamese,10,9,Blue,Male
917
+
British Shorthair,3,3,Orange,Male
918
+
Oriental,10,5,Tricolor,Male
919
+
Siamese,15,8,White,Female
920
+
Balinese,5,5,Blue,Male
921
+
Russian Blue,1,3,Sable,Female
922
+
Oriental,11,5,Cream,Male
923
+
Manx,14,5,Calico,Female
924
+
Munchkin,12,6,Bicolor,Male
925
+
Chartreux,3,5,Red,Female
926
+
Bengal,6,3,Tortoiseshell,Male
927
+
Singapura,8,3,Orange,Male
928
+
Bengal,18,7,Brown,Male
929
+
Oriental,4,7,Sable,Male
930
+
Scottish Fold,16,8,Tricolor,Male
931
+
Russian Blue,3,2,Brown,Female
932
+
Birman,17,4,Tabby,Female
933
+
Maine Coon,19,3,Tortoiseshell,Female
934
+
Sphynx,19,5,Black,Female
935
+
Abyssinian,11,7,White,Male
936
+
Persian,19,7,Sable,Male
937
+
Singapura,4,2,Orange,Female
938
+
Maine Coon,18,6,Tabby,Male
939
+
Singapura,8,9,Orange,Female
940
+
Ragdoll,11,8,Gray,Male
941
+
Manx,15,7,Black,Male
942
+
American Shorthair,6,2,Cream,Female
943
+
Burmese,12,5,Tricolor,Male
944
+
American Shorthair,11,3,Bicolor,Male
945
+
Balinese,11,8,Calico,Male
946
+
Munchkin,10,8,Calico,Female
947
+
Birman,4,2,Sable,Male
948
+
American Shorthair,12,8,Tortoiseshell,Female
949
+
Manx,1,5,Red,Female
950
+
Burmese,7,6,Brown,Female
951
+
Abyssinian,2,7,Cream,Female
952
+
Scottish Fold,13,2,White,Female
953
+
Ocicat,10,9,Cream,Male
954
+
Munchkin,12,5,Brown,Male
955
+
Burmese,4,4,White,Male
956
+
American Shorthair,10,3,Tortoiseshell,Male
957
+
Ragdoll,9,5,Tortoiseshell,Male
958
+
Russian Blue,9,4,Brown,Female
959
+
Egyptian Mau,10,3,Pointed,Male
960
+
Siberian,14,7,Bicolor,Male
961
+
Tonkinese,16,9,Pointed,Female
962
+
Persian,16,3,Tabby,Male
963
+
Scottish Fold,5,3,Blue,Female
964
+
Ragdoll,2,4,Tricolor,Female
965
+
British Shorthair,6,2,Red,Male
966
+
Ocicat,11,7,Sable,Male
967
+
Ragdoll,1,4,Tortoiseshell,Male
968
+
Manx,7,8,Orange,Female
969
+
British Shorthair,4,4,Blue,Female
970
+
Egyptian Mau,17,5,Sable,Female
971
+
Siamese,4,5,White,Male
972
+
American Shorthair,7,6,Cream,Female
973
+
American Shorthair,10,9,Red,Female
974
+
British Shorthair,6,4,Cream,Male
975
+
Balinese,12,4,Orange,Male
976
+
Savannah,18,6,Tortoiseshell,Female
977
+
Tonkinese,12,4,Tortoiseshell,Male
978
+
Persian,4,9,Calico,Female
979
+
Bengal,15,4,Brown,Female
980
+
Siberian,15,6,Tabby,Female
981
+
Oriental,17,6,Blue,Male
982
+
Siamese,4,6,Pointed,Male
983
+
Maine Coon,7,3,White,Female
984
+
Russian Blue,12,5,Calico,Female
985
+
Norwegian Forest,7,6,Bicolor,Female
986
+
Tonkinese,3,3,Red,Female
987
+
Maine Coon,1,9,Sable,Male
988
+
Cornish Rex,2,7,Tabby,Male
989
+
Russian Blue,5,5,White,Male
990
+
Sphynx,14,9,White,Male
991
+
British Shorthair,9,9,White,Male
992
+
Maine Coon,8,6,Tabby,Female
993
+
Scottish Fold,18,9,Red,Male
994
+
British Shorthair,9,7,Tabby,Female
995
+
Exotic Shorthair,6,4,Calico,Female
996
+
British Shorthair,2,4,Tabby,Female
997
+
British Shorthair,19,5,Gray,Female
998
+
British Shorthair,11,2,Bicolor,Female
999
+
Savannah,12,5,Bicolor,Female
1000
+
American Shorthair,8,3,Tortoiseshell,Female
1001
+
Chartreux,11,4,Sable,Female
+55
python/oct15/cats/main.py
+55
python/oct15/cats/main.py
···
1
+
import numpy
2
+
import pandas as pd
3
+
import matplotlib.pyplot as plt
4
+
import seaborn as sns
5
+
6
+
7
+
def loadCatsDatabase():
8
+
df = pd.read_csv('./cats/cats_dataset.csv')
9
+
return df
10
+
11
+
12
+
def avg_age(df: pd.DataFrame, breed: str|None = None) -> float:
13
+
new_df = df
14
+
if breed:
15
+
new_df = df[df["Breed"] == breed]
16
+
return numpy.round(new_df['Age (Years)'].astype(int).sum()/len(new_df['Age (Years)']), 2)
17
+
18
+
def high_age_breed(df: pd.DataFrame) -> tuple[str, float]:
19
+
vals: list[tuple[str, float]] = []
20
+
for breed in df["Breed"].unique():
21
+
vals.append((breed, avg_age(df, str(breed))))
22
+
highest = vals[0]
23
+
for (b, v) in vals[1:]:
24
+
if v > highest[1]:
25
+
highest = (b, v)
26
+
return highest
27
+
28
+
def weight_avg(df: pd.DataFrame) -> float:
29
+
return numpy.round(df["Weight (kg)"].astype(float).sum()/len(df["Weight (kg)"]), 2)
30
+
31
+
def gender_heavier(df: pd.DataFrame) -> str:
32
+
male_avg = weight_avg(df[df["Gender"] == "Male"])
33
+
female_avg = weight_avg(df[df["Gender"] == "Female"])
34
+
return "male" if male_avg > female_avg else "female"
35
+
36
+
def correlation(df: pd.DataFrame) -> bool:
37
+
corr = df["Age (Years)"].corr(df["Weight (kg)"])
38
+
return abs(corr) > 0.9
39
+
40
+
def cat_age_histogram(df: pd.DataFrame) -> None:
41
+
ages = df["Age (Years)"].astype(int)
42
+
_ = sns.histplot(data=ages)
43
+
plt.show()
44
+
45
+
print("Running tests...", end="")
46
+
try:
47
+
df = loadCatsDatabase()
48
+
assert(avg_age(df) == 10.21) # Q1
49
+
assert(high_age_breed(df) == ("Himalayan", 11.67)) # Q2
50
+
assert(gender_heavier(df) == "female") # Q3
51
+
assert(not correlation(df)) # Q4
52
+
cat_age_histogram(df)
53
+
print("Passed!")
54
+
except:
55
+
print("Failed :(")
python/oct15/fast-food-nutrition/Figure_1.png
python/oct15/fast-food-nutrition/Figure_1.png
This is a binary file and will not be displayed.
python/oct15/fast-food-nutrition/Figure_2.png
python/oct15/fast-food-nutrition/Figure_2.png
This is a binary file and will not be displayed.
+6
python/oct15/fast-food-nutrition/README.txt
+6
python/oct15/fast-food-nutrition/README.txt
+516
python/oct15/fast-food-nutrition/fastfood.csv
+516
python/oct15/fast-food-nutrition/fastfood.csv
···
1
+
restaurant,item,calories,cal_fat,total_fat,sat_fat,trans_fat,cholesterol,sodium,total_carb,fiber,sugar,protein,vit_a,vit_c,calcium,salad
2
+
Mcdonalds,Artisan Grilled Chicken Sandwich,380,60,7,2,0,95,1110,44,3,11,37,4,20,20,Other
3
+
Mcdonalds,Single Bacon Smokehouse Burger,840,410,45,17,1.5,130,1580,62,2,18,46,6,20,20,Other
4
+
Mcdonalds,Double Bacon Smokehouse Burger,1130,600,67,27,3,220,1920,63,3,18,70,10,20,50,Other
5
+
Mcdonalds,Grilled Bacon Smokehouse Chicken Sandwich,750,280,31,10,0.5,155,1940,62,2,18,55,6,25,20,Other
6
+
Mcdonalds,Crispy Bacon Smokehouse Chicken Sandwich,920,410,45,12,0.5,120,1980,81,4,18,46,6,20,20,Other
7
+
Mcdonalds,Big Mac,540,250,28,10,1,80,950,46,3,9,25,10,2,15,Other
8
+
Mcdonalds,Cheeseburger,300,100,12,5,0.5,40,680,33,2,7,15,10,2,10,Other
9
+
Mcdonalds,Classic Chicken Sandwich,510,210,24,4,0,65,1040,49,3,6,25,0,4,2,Other
10
+
Mcdonalds,Double Cheeseburger,430,190,21,11,1,85,1040,35,2,7,25,20,4,15,Other
11
+
Mcdonalds,Double Quarter Pounderยฎ with Cheese,770,400,45,21,2.5,175,1290,42,3,10,51,20,6,20,Other
12
+
Mcdonalds,Filet-O-Fishยฎ,380,170,18,4,0,40,640,38,2,5,15,2,0,15,Other
13
+
Mcdonalds,Garlic White Cheddar Burger,620,300,34,13,1.5,95,790,48,3,11,32,10,10,35,Other
14
+
Mcdonalds,Grilled Garlic White Cheddar Chicken Sandwich,530,180,20,7,0,125,1150,48,3,11,42,10,20,35,Other
15
+
Mcdonalds,Crispy Garlic White Cheddar Chicken Sandwich,700,300,34,9,0,85,1190,67,5,11,33,10,15,35,Other
16
+
Mcdonalds,Hamburger,250,70,8,3,0,30,480,31,2,6,13,2,2,4,Other
17
+
Mcdonalds,Lobster Roll,290,50,5,1.5,0,65,630,35,2,3,24,4,6,15,Other
18
+
Mcdonalds,Maple Bacon Dijon 1/4 lb Burger,640,330,36,14,1.5,110,1260,40,3,10,37,6,15,15,Other
19
+
Mcdonalds,Grilled Maple Bacon Dijon Chicken Sandwich,580,190,21,8,0,135,1890,50,3,14,48,4,30,30,Other
20
+
Mcdonalds,Crispy Maple Bacon Dijon Chicken Sandwich,740,310,35,9,0.5,95,1780,69,5,14,39,4,20,290,Other
21
+
Mcdonalds,McChicken,350,130,15,3.5,0,40,600,40,2,5,15,2,2,4,Other
22
+
Mcdonalds,McDouble,380,160,18,8,1,70,840,34,2,7,23,10,2,10,Other
23
+
Mcdonalds,McRib,480,200,22,7,0,80,870,45,2,12,25,2,2,6,Other
24
+
Mcdonalds,Pico Guacamole 1/4 lb Burger,580,300,33,12,1.5,95,920,41,4,7,29,8,15,15,Other
25
+
Mcdonalds,Grilled Pico Guacamole Chicken Sandwich,520,160,18,6,0,115,1540,50,4,12,40,8,25,30,Other
26
+
Mcdonalds,Crispy Pico Guacamole Chicken Sandwich,680,280,32,7,0,80,1430,69,6,12,31,8,15,30,Other
27
+
Mcdonalds,Premium Buttermilk Crispy Chicken Deluxe Sandwich,570,200,23,5,0,60,1050,64,4,11,28,4,10,20,Other
28
+
Mcdonalds,Premium Crispy Chicken Deluxe Sandwich,530,200,22,4,0,45,1000,59,3,13,25,6,10,20,Other
29
+
Mcdonalds,Quarter Pounderยฎ with Cheese,530,240,27,13,1.5,100,1090,41,3,10,31,20,6,15,Other
30
+
Mcdonalds,Signature Sriracha Burger,670,320,35,12,1.5,95,1010,56,4,13,32,20,15,30,Other
31
+
Mcdonalds,Grilled Signature Sriracha Chicken Sandwich,560,180,20,5,0,115,1550,56,4,14,41,20,25,30,Other
32
+
Mcdonalds,Crispy Signature Sriracha Chicken Sandwich,730,300,33,7,0,80,1430,75,5,13,32,20,20,30,Other
33
+
Mcdonalds,Sweet BBQ Bacon 1/4 lb Burger,690,340,37,14,1.5,110,1310,51,3,14,38,6,15,15,Other
34
+
Mcdonalds,Grilled Sweet BBQ Bacon Chicken Sandwich,630,200,22,7,0,135,1930,61,4,18,48,4,30,25,Other
35
+
Mcdonalds,Crispy Sweet BBQ Bacon Chicken Sandwich,800,320,36,9,0.5,95,1820,80,5,18,39,4,20,30,Other
36
+
Mcdonalds,3 piece Buttermilk Crispy Chicken Tenders,370,190,21,3.5,0,70,910,16,0,0,28,0,0,2,Other
37
+
Mcdonalds,4 piece Buttermilk Crispy Chicken Tenders,480,250,28,4.5,0,95,1290,21,0,1,38,0,0,2,Other
38
+
Mcdonalds,6 piece Buttermilk Crispy Chicken Tenders,760,390,44,8,0.5,145,1890,32,1,1,58,0,0,2,Other
39
+
Mcdonalds,10 piece Buttermilk Crispy Chicken Tenders,1210,630,70,12,1,240,3230,52,1,4,94,0,0,4,Other
40
+
Mcdonalds,12 piece Buttermilk Crispy Chicken Tenders,1510,790,88,15,1,295,3770,64,1,2,115,0,2,6,Other
41
+
Mcdonalds,20 piece Buttermilk Crispy Chicken Tenders,2430,1270,141,24,2,475,6080,103,2,3,186,0,2,8,Other
42
+
Mcdonalds,4 Piece Chicken McNuggets,180,100,11,2,0,30,340,11,1,0,10,0,2,0,Other
43
+
Mcdonalds,6 Piece Chicken McNuggets,270,140,16,2.5,0,45,510,16,1,0,15,0,2,0,Other
44
+
Mcdonalds,10 Piece Chicken McNuggets,440,240,27,4.5,0,75,840,26,2,0,24,0,4,2,Other
45
+
Mcdonalds,20 Piece Chicken McNuggets,890,480,53,9,0,145,1680,53,4,0,49,0,8,4,Other
46
+
Mcdonalds,40 piece Chicken McNuggets,1770,960,107,18,0.5,295,3370,105,7,1,98,0,15,6,Other
47
+
Mcdonalds,4 piece Sweet N' Spicy Honey BBQ Glazed Tenders,640,240,27,4,0,105,1780,63,2,35,39,4,15,4,Other
48
+
Mcdonalds,6 piece Sweet N' Spicy Honey BBQ Glazed Tenders,960,360,40,6,0,160,2670,94,3,52,58,4,25,8,Other
49
+
Mcdonalds,10 piece Sweet N' Spicy Honey BBQ Glazed Tenders,1600,600,66,10,0,265,4450,156,5,87,97,8,40,10,Other
50
+
Mcdonalds,Premium Asian Salad w/o Chicken,140,70,7,0.5,0,0,20,13,5,7,7,180,45,10,Other
51
+
Mcdonalds,Premium Asian Salad w/ Grilled Chicken,270,80,9,1,0,80,740,18,5,10,31,180,70,10,Other
52
+
Mcdonalds,Premium Asian Salad w/ Crispy Chicken,490,250,28,8,0,95,1120,28,4,4,33,180,60,15,Other
53
+
Mcdonalds,Premium Bacon Ranch Salad w/o Chicken,190,110,12,5,0,40,660,9,3,3,14,180,50,15,Other
54
+
Mcdonalds,Premium Bacon Ranch Salad w/ Grilled Chicken,320,120,14,6,0,45,1230,9,3,4,42,180,60,15,Other
55
+
Mcdonalds,Premium Bacon Ranch Salad w/ Crispy Chicken,490,250,28,8,0,95,1120,28,4,4,33,180,60,15,Other
56
+
Mcdonalds,Premium Southwest Salad w/o Chicken,220,90,10,3.5,0,15,500,26,6,9,8,180,40,20,Other
57
+
Mcdonalds,Premium Southwest Salad w/ Grilled Chicken,350,100,12,4.5,0,110,1070,27,6,9,37,180,50,20,Other
58
+
Mcdonalds,Premium Southwest Salad w/ Crispy Chicken,520,230,25,6,0,75,960,46,8,9,28,180,40,20,Other
59
+
Chick Fil-A,Chargrilled Chicken Club Sandwich,430,144,16,8,0,85,1120,37,3,7,37,30,40,25,Other
60
+
Chick Fil-A,Chargrilled Chicken Sandwich,310,54,6,2,0,55,820,36,3,7,29,25,40,10,Other
61
+
Chick Fil-A,Chick-n-Slider,270,99,11,2.5,0,45,800,26,1,4,16,NA,0,2,Other
62
+
Chick Fil-A,1 Piece Chick-n-Strips,120,54,6,3,0,25,320,6,0,1,11,0,0,2,Other
63
+
Chick Fil-A,2 Piece Chick-n-Strips,230,108,12,3,0,55,630,13,1,1,22,0,2,4,Other
64
+
Chick Fil-A,3 Piece Chick-n-Strips,350,153,17,3,0,70,940,22,1,3,28,2,2,6,Other
65
+
Chick Fil-A,4 piece Chick-n-Strips,470,207,23,3,0,90,1250,29,1,4,37,2,4,8,Other
66
+
Chick Fil-A,Chicken Deluxe,500,207,23,7,0,75,1590,42,3,6,31,30,10,20,Other
67
+
Chick Fil-A,4 piece Chicken Nuggets,130,54,6,1.5,0,40,490,5,1,0,14,0,2,2,Other
68
+
Chick Fil-A,6 piece Chicken Nuggets,190,81,9,1.5,0,55,730,7,1,0,21,0,4,2,Other
69
+
Chick Fil-A,8 piece Chicken Nuggets,260,110,12,3,0,70,990,9,1,1,28,0,2,4,Other
70
+
Chick Fil-A,12 piece Chicken Nuggets,390,162,18,1.5,0,115,1460,14,2,1,41,0,8,4,Other
71
+
Chick Fil-A,30 piece Chicken Nuggets,970,414,46,2.5,0,285,3660,35,4,1,103,NA,20,10,Other
72
+
Chick Fil-A,Chicken Salad Sandwich,490,170,19,3,0,80,1130,55,5,12,28,35,8,15,Other
73
+
Chick Fil-A,Chicken Sandwich,440,171,19,4,0,60,1350,40,2,5,28,2,4,15,Other
74
+
Chick Fil-A,4 Piece Grilled Chicken Nuggets,70,18,2,1,0,35,220,1,0,0,13,0,6,0,Other
75
+
Chick Fil-A,6 Piece Grilled Chicken Nuggets,110,27,3,1,0,50,330,2,0,0,19,0,8,0,Other
76
+
Chick Fil-A,8 piece Grilled Chicken Nuggets,140,36,4,1,0,70,440,2,0,0,25,0,10,2,Other
77
+
Chick Fil-A,12 Piece Grilled Chicken Nuggets,210,45,5,1,0,100,670,3,0,1,38,0,20,2,Other
78
+
Chick Fil-A,Spicy Grilled Chicken Sub Sandwich,430,108,12,4.5,0,85,1310,47,5,9,33,NA,25,25,Other
79
+
Chick Fil-A,Regular Grilled Chicken Sub Sandwich,450,117,13,6,0,75,1000,48,4,10,34,NA,50,25,Other
80
+
Chick Fil-A,Smokehouse BBQ Bacon Sandwich,500,162,18,0,0,95,1200,46,2,10,33,45,40,20,Other
81
+
Chick Fil-A,Spicy Chicken Sandwich,450,171,19,4,0,60,1620,41,1,5,29,4,2,15,Other
82
+
Chick Fil-A,Spicy Deluxe,540,225,25,8,0,80,1760,43,2,6,34,30,10,30,Other
83
+
Chick Fil-A,Chargrilled Chicken Cool Wrap,350,126,14,5,0,60,960,29,15,3,37,60,35,35,Other
84
+
Chick Fil-A,Chicken Enchiladas Meal Kit,860,423,47,16,1,100,2520,70,NA,8,39,NA,NA,NA,Other
85
+
Chick Fil-A,Chicken Parmesan Meal Kit,720,279,31,15,0,120,1780,65,NA,7,48,NA,NA,NA,Other
86
+
Sonic,Hatch Green Chile Cheeseburger,710,380,43,17,2,120,1120,44,2,7,35,10,25,30,Other
87
+
Sonic,Jalapeno Burger,640,330,37,14,2,100,930,42,2,6,31,4,2,20,Other
88
+
Sonic,Jr. Burger,340,150,17,6,1,35,640,34,1,6,15,2,4,6,Other
89
+
Sonic,Jr. Chili Cheeseburger,410,220,24,9,0.5,55,730,32,1,4,20,7,1,15,Other
90
+
Sonic,Jr. Deluxe Burger,380,200,23,6,1,40,470,32,1,4,15,2,4,6,Other
91
+
Sonic,Jr. Deluxe Cheeseburger,450,250,28,9,1,60,800,33,1,4,19,6,4,15,Other
92
+
Sonic,Jr. Double Cheeseburger,600,350,38,16,2,110,1350,35,1,7,31,15,4,25,Other
93
+
Sonic,Sonic Bacon Cheeseburger (w/mayo),870,530,59,20,2,140,1350,45,2,7,39,10,8,30,Other
94
+
Sonic,Sonic Burger W/ Mustard,640,330,37,14,2,100,790,43,2,7,31,6,8,20,Other
95
+
Sonic,Sonic Burger W/ Ketchup,650,340,37,14,2,100,860,46,2,10,32,8,10,20,Other
96
+
Sonic,Sonic Burger W/ Mayonnaise,740,430,48,15,2,110,760,44,2,7,31,6,8,20,Other
97
+
Sonic,Sonic Cheeseburger W/ Mustard,710,380,43,17,2,120,1120,43,2,7,35,10,8,30,Other
98
+
Sonic,Sonic Cheeseburger W/ Ketchup,720,380,43,17,2,120,1190,47,2,10,35,15,10,30,Other
99
+
Sonic,Sonic Cheeseburger W/ Mayonnaise,800,480,54,18,2,130,1090,44,2,7,35,10,8,30,Other
100
+
Sonic,Super Sonic Bacon Double Cheeseburger (w/mayo),1280,830,92,36,4,260,1630,44,2,7,67,15,6,40,Other
101
+
Sonic,Super Sonic Double Cheeseburger W/ Mustard,1120,680,76,32,4,235,1550,44,2,8,63,15,8,40,Other
102
+
Sonic,Super Sonic Double Cheeseburger W/ Ketchup,1130,680,76,32,4,235,1620,47,2,11,63,20,10,40,Other
103
+
Sonic,Super Sonic Double Cheeseburger W/ Mayo,1220,780,87,34,4,245,1520,45,2,8,63,15,8,40,Other
104
+
Sonic,Super Sonic Jalapeno Double Cheeseburger,1120,680,76,32,4,235,1690,43,2,7,63,15,2,40,Other
105
+
Sonic,Veggie Burger W/ Ketchup,450,130,14,4,0,10,1410,67,5,11,15,6,8,25,Other
106
+
Sonic,Veggie Burger With Mustard,450,130,14,4,0,10,1350,64,5,8,15,6,8,27,Other
107
+
Sonic,Veggie Burger W/ Mustard,450,130,14,4,0,10,1300,64,5,8,15,6,8,25,Other
108
+
Sonic,Grilled Asiago Caesar Chicken Club Sandwich,610,270,30,7,0,110,1570,44,3,8,40,11,20,16,Other
109
+
Sonic,Crispy Asiago Caesar Chicken Club Sandwich,680,350,39,9,0,80,1120,53,4,7,31,11,7,16,Other
110
+
Sonic,Grilled Chicken Sandwich,430,180,20,4,0,80,940,33,2,6,28,6,8,10,Other
111
+
Sonic,Crispy Chicken Sandwich,570,300,33,5,0,45,1060,47,4,6,23,6,8,10,Other
112
+
Sonic,Chicken Strip Sandwich,450,220,24,4,0,35,740,43,1,4,19,0,0,4,Other
113
+
Sonic,3 Piece Crispy Chicken Tender Dinner,280,130,14,2.5,0,0,800,16,0,0,22,NA,NA,NA,Other
114
+
Sonic,5 Piece Crispy Chicken Tender Dinner,470,220,24,4.5,0,0,1340,26,0,0,37,NA,NA,NA,Other
115
+
Sonic,Deluxe Ultimate Chicken Sandwich,740,350,39,8,0,90,1550,63,4,12,33,10,8,15,Other
116
+
Sonic,Buffalo Dunked Ultimate Chicken Sandwich,1000,550,61,12,0.5,125,4520,70,5,12,23,NA,NA,NA,Other
117
+
Sonic,Garlic Parmesan Dunked Ultimate Chicken Sandwich,1350,900,100,17,0,190,2180,69,4,10,23,NA,NA,NA,Other
118
+
Sonic,Small Jumbo Popcorn Chicken,380,190,22,4,0,45,1250,27,3,1,18,0,0,2,Other
119
+
Sonic,Large Jumbo Popcorn Chicken,560,290,32,6,1,65,1890,41,5,2,27,0,0,4,Other
120
+
Sonic,Small Spicy Jumbo Popcorn Chicken,350,150,17,3,0,45,860,30,2,0,21,10,0,2,Other
121
+
Sonic,Large Spicy Jumbo Popcorn Chicken,610,270,30,5,0,80,1500,51,3,0,36,17,0,3,Other
122
+
Sonic,3 Piece Super Crunch Chicken Strip Dinner,970,410,46,8,1,55,2160,109,7,9,30,1,6,13,Other
123
+
Sonic,4 Piece Super Crunch Chicken Strip Dinner,1080,460,51,9,1,75,2390,118,8,9,37,1,7,13,Other
124
+
Sonic,5 Piece Super Crunch Chicken Strip Dinner,1190,510,57,10,1,90,2610,126,8,9,44,2,8,14,Other
125
+
Sonic,3 Piece Super Crunch Chicken Strips,330,140,16,3,0,55,670,25,2,0,22,1,2,1,Other
126
+
Sonic,4 Piece Super Crunch Chicken Strips,440,190,21,4,0,70,900,34,2,1,29,1,2,1,Other
127
+
Sonic,5 Piece Super Crunch Chicken Strips,550,240,26,5,0,90,1120,42,3,1,36,1,3,2,Other
128
+
Sonic,Traditional Ultimate Chicken Sandwich,730,350,39,8,0,90,1540,62,3,11,32,4,2,15,Other
129
+
Sonic,Ultimate Chicken Club,100,580,64,15,0.5,100,2070,65,4,12,39,15,8,30,Other
130
+
Sonic,"All Beef All-american Style Dog โ 6""",370,160,18,7,0,40,1180,40,1,15,12,2,4,8,Other
131
+
Sonic,"All Beef Chicago Dog โ 6""",430,180,20,7,0,40,2310,49,1,17,14,4,6,10,Other
132
+
Sonic,"All Beef Chili Cheese Coney โ 6""",410,230,26,11,0,65,1140,30,2,4,17,10,2,20,Other
133
+
Sonic,"All Beef New York Dog โ 6""",340,170,19,7,0,40,1250,30,3,4,13,2,10,8,Other
134
+
Sonic,"All Beef Regular Hot Dog โ 6""",320,160,18,7,0,40,870,27,1,3,11,0,2,8,Other
135
+
Sonic,Cheesy Bacon Pretzel Dog - 6 In.,500,240,26,10,0,50,1410,46,2,7,15,1,3,8,Other
136
+
Sonic,Corn Dog,210,100,11,4,0,20,530,23,2,4,6,0,0,4,Other
137
+
Sonic,Footlong Quarter Pound Coney,830,490,54,22,1,85,1940,54,3,9,30,15,4,30,Other
138
+
Sonic,The Original Pretzel Dog,320,160,18,7,0,35,910,27,1,2,11,0,0,4,Other
139
+
Arbys,Arby's Melt,330,100,11,4,0,30,920,40,2,5,18,2,0,8,Other
140
+
Arbys,Arby-Q Sandwich,400,90,10,3,0,30,1230,58,3,23,18,4,10,10,Other
141
+
Arbys,Beef 'n Cheddar Classic,450,180,20,6,1,50,1280,45,2,9,23,2,2,15,Other
142
+
Arbys,Beef 'n Cheddar Mid,630,290,32,11,1.5,100,2100,48,2,9,39,2,2,15,Other
143
+
Arbys,Bourbon BBQ Brisket Sandwich,650,300,33,12,1,105,1460,51,2,15,38,NA,NA,NA,Other
144
+
Arbys,Bourbon BBQ Chicken Sandwich,690,280,31,9,0,90,1990,66,3,16,38,NA,NA,NA,Other
145
+
Arbys,Bourbon BBQ Steak Sandwich,690,280,31,9,0,90,1990,66,3,16,38,NA,NA,NA,Other
146
+
Arbys,Buttermilk Buffalo Chicken Sandwich,540,220,24,4.5,0,60,2110,53,2,6,29,NA,NA,NA,Other
147
+
Arbys,Buttermilk Chicken Bacon & Swiss,650,280,31,9,0,90,1750,56,2,9,39,NA,NA,NA,Other
148
+
Arbys,Buttermilk Chicken Cordon Bleu Sandwich,690,310,35,10,0,110,2000,53,1,7,41,NA,NA,NA,Other
149
+
Arbys,Buttermilk Crispy Chicken Sandwich,550,230,26,4.5,0,60,1480,52,2,6,29,NA,NA,NA,Other
150
+
Arbys,Classic French Dip & Swiss/Au Jus,540,210,23,11,1,85,2500,50,2,3,35,2,8,15,Other
151
+
Arbys,Classic Roast Beef,360,120,14,5,0.5,50,970,37,2,5,23,0,0,6,Other
152
+
Arbys,Double Roast Beef,510,210,24,9,1.5,95,1610,38,2,5,38,0,0,6,Other
153
+
Arbys,Fire-Roasted Philly Steak,640,290,32,11,0.5,105,1950,46,3,4,42,NA,NA,NA,Other
154
+
Arbys,Grand Turkey Club,480,220,24,7,0,65,1610,37,2,9,30,15,10,15,Other
155
+
Arbys,Greek Gyro,710,390,44,13,0,75,1360,55,4,6,23,NA,NA,NA,Other
156
+
Arbys,Half Pound Beef 'n Cheddar Sandwich,740,350,39,14,2,130,2530,48,2,9,49,NA,NA,NA,Other
157
+
Arbys,Half Pound French Dip & Swiss,750,330,36,17,2,150,3350,51,2,3,55,NA,NA,NA,Other
158
+
Arbys,Half Pound Roast Beef Sandwich,610,270,30,12,2,130,2040,38,2,5,48,NA,NA,NA,Other
159
+
Arbys,Ham & Swiss Melt,300,80,9,4,0,35,1030,37,2,6,18,2,0,15,Other
160
+
Arbys,Loaded Italian Sandwich,680,360,40,14,0.5,100,2270,49,3,7,32,NA,NA,NA,Other
161
+
Arbys,Pecan Chicken Salad Flatbread,710,410,46,7,0.5,65,980,53,4,9,22,NA,NA,NA,Other
162
+
Arbys,Pecan Chicken Salad Sandwich,840,400,44,6,0.5,75,1210,81,6,20,33,10,8,25,Other
163
+
Arbys,2 piece Prime-Cut Chicken Tenders,240,100,11,1.5,0,30,640,19,1,0,16,0,0,2,Other
164
+
Arbys,3 piece Prime-Cut Chicken Tenders,360,150,17,2.5,0,45,950,28,2,0,23,0,4,2,Other
165
+
Arbys,5 piece Prime-Cut Chicken Tenders,600,250,28,4,0,75,1590,47,3,0,39,0,8,2,Other
166
+
Arbys,Reuben Sandwich,680,280,31,8,0.5,80,2420,62,4,5,37,6,20,35,Other
167
+
Arbys,Roast Beef Gyro,550,260,29,7,1,60,1290,48,3,5,24,10,15,10,Other
168
+
Arbys,Roast Turkey & Swiss Sandwich,710,260,28,7,0,65,1930,79,5,15,38,20,10,45,Other
169
+
Arbys,Roast Turkey & Swiss Wrap,520,240,27,9,0,65,1640,39,4,6,30,20,10,35,Other
170
+
Arbys,"Roast Turkey, Ranch & Bacon Sandwich",800,310,34,10,0.5,80,2420,79,5,16,45,20,10,45,Other
171
+
Arbys,"Roast Turkey, Ranch & Bacon Wrap",620,310,34,11,0.5,85,2130,39,4,6,37,20,10,30,Other
172
+
Arbys,Smoke Mountain w/ Beef Short Rib,740,320,35,13,1,125,2050,62,4,17,43,NA,NA,NA,Other
173
+
Arbys,Smokehouse Beef Short Rib Sandwich,590,250,59,10,1,75,1510,59,4,14,26,NA,NA,NA,Other
174
+
Arbys,Smokehouse Brisket,600,310,35,12,1,110,1240,42,2,7,33,4,8,20,Other
175
+
Arbys,Super Roast Beef,430,160,17,5,1,45,1060,45,3,11,23,10,10,8,Other
176
+
Arbys,Three Cheese Steak Sandwich,650,320,36,15,1,115,1760,44,2,9,30,NA,NA,NA,Other
177
+
Arbys,Triple Decker Sandwich,1030,459,51,17,1,155,2940,83,5,19,62,NA,NA,NA,Other
178
+
Arbys,Turkey Avocado Club,730,252,28,6,0,65,2140,80,6,16,41,NA,NA,NA,Other
179
+
Arbys,Turkey Gyro,470,180,20,3.5,0,45,1520,48,3,5,25,10,15,10,Other
180
+
Arbys,Ultimate BLT,980,495,55,14,0,85,2130,80,6,19,43,NA,NA,NA,Other
181
+
Arbys,Buffalo Chicken Slider,290,120,13,2,0,20,860,31,2,2,12,NA,NA,NA,Other
182
+
Arbys,Chicken Tender 'n Cheese Slider,290,110,12,3.5,0,25,720,30,1,1,15,NA,NA,NA,Other
183
+
Arbys,Corned Beef 'n Cheese Slider,220,80,9,3.5,0,30,890,21,1,1,14,NA,NA,NA,Other
184
+
Arbys,Ham 'n Cheese Slider,210,70,8,3,0,25,780,21,1,2,13,NA,NA,NA,Other
185
+
Arbys,Jalapeno Roast Beef 'n Cheese Slider,240,90,11,4.5,0,30,670,21,1,1,14,NA,NA,NA,Other
186
+
Arbys,Pizza Slider,300,150,17,6,0,35,930,23,1,2,13,NA,NA,NA,Other
187
+
Arbys,Roast Beef 'n Cheese Slider,240,90,11,4.5,0,30,670,21,1,1,14,NA,NA,NA,Other
188
+
Arbys,Turkey 'n Cheese Slider,200,60,7,2.5,0,25,760,21,1,2,14,NA,NA,NA,Other
189
+
Arbys,Chopped Side Salad,70,45,5,2.5,0,15,100,4,1,2,5,35,10,10,Other
190
+
Arbys,Crispy Chicken Farmhouse Salad,430,220,24,8,0,65,1000,26,4,4,28,60,20,25,Other
191
+
Arbys,Greek Gyro Salad,420,340,37,9,0,55,700,11,2,4,10,NA,NA,NA,Other
192
+
Arbys,Roast Turkey Farmhouse Salad,230,120,13,7,0,55,870,8,2,5,22,60,20,25,Other
193
+
Arbys,Super Greek Salad,720,480,53,15,0,85,1310,39,5,7,22,NA,NA,NA,Other
194
+
Burger King,American Brewhouse King,1550,1134,126,47,8,805,1820,21,3,7,134,NA,NA,NA,Other
195
+
Burger King,Bacon & Swiss Sourdough King,1000,585,65,24,3,200,1320,48,2,8,56,NA,NA,NA,Other
196
+
Burger King,Bacon Cheeseburger,330,140,16,7,0,55,830,32,1,7,18,NA,NA,NA,Other
197
+
Burger King,Bacon Cheeseburger Deluxe,290,120,14,6,0.5,40,720,28,1,7,12,NA,NA,NA,Other
198
+
Burger King,Bacon King,1040,630,48,28,2.5,220,1900,48,1,10,57,NA,NA,NA,Other
199
+
Burger King,Bacon King Jr,730,351,39,9,0,90,1930,63,0,16,32,NA,NA,NA,Other
200
+
Burger King,BBQ Bacon King,1100,675,75,29,3,220,1850,51,NA,13,57,NA,NA,NA,Other
201
+
Burger King,Cheeseburger,300,130,14,6,0,45,710,28,1,6,16,NA,NA,NA,Other
202
+
Burger King,Double Bacon Cheeseburger,520,280,31,14,1,105,1180,33,1,8,31,NA,NA,NA,Other
203
+
Burger King,Double Cheeseburger,450,230,26,12,1,95,960,29,1,6,26,NA,NA,NA,Other
204
+
Burger King,Double Hamburger,360,160,18,8,0,70,520,28,1,6,22,NA,NA,NA,Other
205
+
Burger King,Double Quarter Pound King,900,486,54,25,3,210,1740,50,2,11,56,NA,NA,NA,Other
206
+
Burger King,Extra Long Cheeseburger,580,300,33,13,1.5,85,1030,45,2,9,26,NA,NA,NA,Other
207
+
Burger King,Farmhouse King,1220,720,80,28,3,335,2050,62,NA,15,NA,NA,NA,NA,Other
208
+
Burger King,Hamburger,260,90,10,4,0,35,490,28,1,6,13,NA,NA,NA,Other
209
+
Burger King,Homestyle Cheeseburger,550,250,27,12,1.5,95,1140,48,2,10,30,NA,NA,NA,Other
210
+
Burger King,Jalapeno King Sandwich,990,585,65,24,3,205,1550,46,2,7,55,NA,NA,NA,Other
211
+
Burger King,Mushroom & Swiss King,940,567,63,21,2.5,175,1380,45,NA,8,49,NA,NA,NA,Other
212
+
Burger King,Rodeo Burger,310,110,13,4,0.5,25,450,38,1,9,9,NA,NA,NA,Other
213
+
Burger King,Rodeo King,1250,738,82,31,3.5,230,2270,69,3,14,60,NA,NA,NA,Other
214
+
Burger King,Sourdough King Single,730,387,43,16,1.5,125,1570,52,2,12,35,NA,NA,NA,Other
215
+
Burger King,Sourdough King Double,970,549,61,24,3,205,1640,52,2,12,55,NA,NA,NA,Other
216
+
Burger King,Steakhouse King,1100,666,74,24,1,180,1620,59,NA,13,50,NA,NA,NA,Other
217
+
Burger King,Bacon & Cheese Whopper,770,432,48,16,2,95,1360,47,2,9,29,NA,NA,NA,Other
218
+
Burger King,DOUBLE WHOPPER w/o Cheese,900,510,57,19,2,140,1050,51,3,11,47,NA,NA,NA,Other
219
+
Burger King,DOUBLE WHOPPER w/ Cheese,990,580,65,24,2,160,1480,53,3,11,52,NA,NA,NA,Other
220
+
Burger King,WHOPPER w/o Cheese,660,360,40,12,1.5,90,980,49,2,11,28,NA,NA,NA,Other
221
+
Burger King,WHOPPER w/ Cheese,760,430,47,16,1,100,1410,53,3,11,33,NA,NA,NA,Other
222
+
Burger King,WHOPPER JR. w/o Cheese,340,170,19,5,0,40,510,28,2,6,14,NA,NA,NA,Other
223
+
Burger King,WHOPPER JR. w/ Cheese,380,210,23,8,1,55,730,29,2,6,16,NA,NA,NA,Other
224
+
Burger King,Bacon Cheddar Ranch Chicken Salad w/ grilled Chicken & Dressing,590,360,40,12,0,150,1540,18,3,6,42,NA,NA,NA,Other
225
+
Burger King,Bacon Cheddar Ranch Chicken Salad w/ crispy Chicken & Dressing,720,450,50,13,0,120,1960,32,5,7,36,NA,NA,NA,Other
226
+
Burger King,Chicken BLT Salad w/ Grilled Chicken,550,330,37,10,0,115,1640,17,3,5,36,NA,NA,NA,Other
227
+
Burger King,Chicken BLT Salad w/ Crispy Chicken,690,430,48,12,1,100,1750,31,4,8,35,NA,NA,NA,Other
228
+
Burger King,Chicken Caesar Salad w/ Grilled Chicken,530,290,32,5,0,95,1640,26,3,6,35,NA,NA,NA,Other
229
+
Burger King,Chicken Caesar Salad w/ Crispy Chicken,670,380,43,7,0,80,1760,40,5,8,34,NA,NA,NA,Other
230
+
Burger King,"Chicken, Apple & Cranberry Salad w/ Grilled Chicken",560,270,30,7,0,90,980,40,4,34,29,NA,NA,NA,Other
231
+
Burger King,"Chicken, Apple & Cranberry Salad w/ Crispy Chicken",700,370,41,9,0,80,1090,54,5,37,28,NA,NA,NA,Other
232
+
Burger King,"Garden Grilled Chicken Salad w/ Grilled Chicken, no dressing",320,120,14,6,0,115,650,16,2,4,36,NA,NA,NA,Other
233
+
Burger King,"Garden Grilled Chicken Salad w/ Crispy Chicken, no dressing",450,220,24,7,0,85,1070,30,5,6,29,NA,NA,NA,Other
234
+
Burger King,Side Caesar Salad with dressing,220,180,20,4,0,10,540,7,2,3,6,NA,NA,NA,Other
235
+
Burger King,Side Garden Salad and Avocado Ranch Dressing,230,190,21,5,0,30,520,7,2,3,5,NA,NA,NA,Other
236
+
Burger King,Bacon Cheddar Ranch Crispy Chicken Sandwich,830,468,52,14,0.5,110,2100,57,NA,9,34,NA,NA,NA,Other
237
+
Burger King,BBQ Bacon Crispy Chicken Sandwich,440,243,27,4.5,0,15,630,44,NA,13,7,NA,NA,NA,Other
238
+
Burger King,Big Fish Sandwich,530,250,27,4.5,0,30,1360,54,2,7,17,NA,NA,NA,Other
239
+
Burger King,BK VEGGIE Burger,410,150,16,3,0,5,1030,44,7,8,22,NA,NA,NA,Other
240
+
Burger King,Chicken Burger,480,220,25,2.5,0,5,1160,42,2,10,22,NA,NA,NA,Other
241
+
Burger King,Chicken Cordon Bleu Sandwich,730,351,39,9,0,90,1930,63,NA,16,32,NA,NA,NA,Other
242
+
Burger King,Chicken Fries,290,150,17,3,1.5,40,780,18,1,1,16,NA,NA,NA,Other
243
+
Burger King,4 Piece Chicken Nuggets,190,100,11,2,0,25,310,10,1,0,10,NA,NA,NA,Other
244
+
Burger King,6 Piece Chicken Nuggets,290,150,17,3,0,40,460,15,1,0,15,NA,NA,NA,Other
245
+
Burger King,20 Piece Chicken Nuggets,950,500,55,11,0,130,1530,50,5,0,51,NA,NA,NA,Other
246
+
Burger King,Chicken Nuggets (10pc),470,260,29,5,0,50,890,34,5,0,21,NA,NA,NA,Other
247
+
Burger King,Chicken Parmesan Sandwich,570,225,25,8,0,70,1340,57,NA,9,32,NA,NA,NA,Other
248
+
Burger King,Crispy Buffalo Chicken Melt,580,252,28,8,0.5,70,2310,56,NA,8,30,NA,NA,NA,Other
249
+
Burger King,Crispy Chicken Jr.,430,250,28,4.5,0,30,760,34,2,4,12,NA,NA,NA,Other
250
+
Burger King,Crispy Chicken Sandwich,670,370,41,7,0.5,60,1070,54,2,7,23,NA,NA,NA,Other
251
+
Burger King,Grilled Chicken Sandwich,470,170,19,3.5,0,85,850,39,2,6,37,NA,NA,NA,Other
252
+
Burger King,Grilled Chili Cheese Dog,330,170,19,8,1,40,980,28,2,5,14,NA,NA,NA,Other
253
+
Burger King,Grilled Hot Dog,310,140,16,6,1,30,960,32,2,10,11,NA,NA,NA,Other
254
+
Burger King,Jalapeno Chicken Fries,300,160,18,3,0,40,950,19,1,1,15,NA,NA,NA,Other
255
+
Burger King,Original Chicken Sandwich,630,350,39,7,1,65,1390,46,3,4,24,NA,NA,NA,Other
256
+
Burger King,Pretzel Chicken Fries,340,189,21,3.5,0,45,1200,21,1,1,16,NA,NA,NA,Other
257
+
Burger King,Rodeo Crispy Chicken Sandwich,410,150,17,3,0,20,870,53,2,14,12,NA,NA,NA,Other
258
+
Burger King,Sourdough Chicken Club,840,459,51,12,1,95,1760,62,3,7,32,NA,NA,NA,Other
259
+
Burger King,4 Piece Spicy Chicken Nuggets,210,135,15,3,0,20,570,11,2,0,8,NA,NA,NA,Other
260
+
Burger King,Spicy Chicken Nuggets,530,333,37,7,0,55,1420,28,NA,0,20,NA,NA,NA,Other
261
+
Burger King,Spicy Crispy Chicken Jr.,410,220,25,4.5,0,35,850,35,2,5,12,NA,NA,NA,Other
262
+
Burger King,Spicy Crispy Chicken Sandwich,700,378,42,7,0,65,1140,57,3,8,25,NA,NA,NA,Other
263
+
Burger King,Spicy Crispy Jalapeno Chicken Sandwich,760,405,45,11,0,95,1720,58,3,8,32,NA,NA,NA,Other
264
+
Dairy Queen,1/2 lb. FlameThrowerยฎ GrillBurger,1000,660,74,26,2,170,1610,40,2,9,46,25,8,30,Other
265
+
Dairy Queen,1/2 lb. GrillBurger with Cheese,800,460,51,20,2,135,1280,44,3,13,40,25,6,35,Other
266
+
Dairy Queen,1/4 lb. Bacon Cheese GrillBurger,630,330,37,13,1,95,1250,44,2,13,30,20,6,25,Other
267
+
Dairy Queen,1/4 lb. GrillBurger with Cheese,540,270,30,11,1,70,1020,44,3,13,23,20,6,25,Other
268
+
Dairy Queen,1/4 lb. Mushroom Swiss GrillBurger,570,310,35,11,1,75,820,39,2,8,24,2,0,25,Other
269
+
Dairy Queen,Original Cheeseburger,400,160,18,9,1,65,930,34,1,8,19,10,0,10,Other
270
+
Dairy Queen,Original Double Cheeseburger,630,310,34,18,2,125,1240,34,1,9,34,15,0,20,Other
271
+
Dairy Queen,4 Piece Chicken Strip Basket w/ Country Gravy,1030,480,53,9,1,80,2780,105,9,4,35,2,0,10,Other
272
+
Dairy Queen,6 Piece Chicken Strip Basket w/ Country Gravy,1260,590,66,11,1,120,3500,121,12,4,49,2,0,10,Other
273
+
Dairy Queen,Bacon Cheese Dog,420,240,26,11,1,60,1140,26,1,3,19,NA,NA,NA,Other
274
+
Dairy Queen,Cheese Dog,390,220,24,11,1,50,1000,26,1,3,16,NA,NA,NA,Other
275
+
Dairy Queen,Chili Cheese Dog,380,220,24,11,1,55,900,23,1,3,16,10,0,15,Other
276
+
Dairy Queen,Chili Dog,330,180,20,8,1,40,1050,24,1,5,13,8,0,6,Other
277
+
Dairy Queen,Hot Dog,290,160,17,7,1,35,900,22,1,4,11,4,0,6,Other
278
+
Dairy Queen,Relish Dog,350,180,20,8,1,35,1000,30,1,6,13,NA,NA,NA,Other
279
+
Dairy Queen,Barbecue Pork Sandwich,310,80,9,3,0,50,830,41,2,9,17,10,4,4,Other
280
+
Dairy Queen,Breaded Mushrooms,250,80,9,1,0,0,500,36,2,1,7,0,2,2,Other
281
+
Dairy Queen,Regular Cheese Curds,550,410,45,25,0,150,900,0,0,0,35,30,0,100,Other
282
+
Dairy Queen,Large Cheese Curds,1050,670,75,43,1,180,2210,52,0,30,43,NA,NA,NA,Other
283
+
Dairy Queen,Chili Cheese Mega Dog,760,440,49,21,2,100,1570,48,2,6,32,NA,NA,NA,Other
284
+
Dairy Queen,Corn Dog,260,140,15,4,0,20,450,26,1,7,6,0,4,0,Other
285
+
Dairy Queen,Crispy Fish Sandwich,470,200,22,3,0,20,1210,53,2,7,17,10,2,6,Other
286
+
Dairy Queen,Deluxe Cheeseburger,400,160,18,9,1,65,930,35,1,9,20,20,6,10,Other
287
+
Dairy Queen,Deluxe Double Cheeseburger,640,310,34,18,2,125,1240,35,1,9,34,25,6,20,Other
288
+
Dairy Queen,Deluxe Double Hamburger,540,240,26,13,1,100,750,34,1,9,29,15,6,4,Other
289
+
Dairy Queen,Deluxe Hamburger,350,130,14,7,1,50,680,34,1,9,17,15,6,4,Other
290
+
Dairy Queen,DQ Ultimateยฎ Burger,780,430,48,22,2,150,1390,34,1,7,41,20,6,20,Other
291
+
Dairy Queen,Pork Tenderloin Sandwich,580,310,34,7,0,45,910,48,2,6,19,NA,NA,NA,Other
292
+
Dairy Queen,Steak Finger Basket,910,430,48,13,0.5,45,2210,95,5,2,23,NA,NA,NA,Other
293
+
Dairy Queen,3 chicken strips Chicken Strips,350,180,20,3,0,60,960,22,10,0,22,NA,NA,NA,Other
294
+
Dairy Queen,Chicken Bacon Ranch Sandwich,500,180,20,8,0,65,1190,45,3,3,33,NA,NA,NA,Other
295
+
Dairy Queen,Chicken Mozzarella Sandwich,640,220,25,8,0,60,1530,68,4,3,34,NA,NA,NA,Other
296
+
Dairy Queen,Crispy Chicken BLT Salad,520,280,31,10,0,100,1470,25,9,6,37,NA,NA,NA,Other
297
+
Dairy Queen,Crispy Chicken Garden Greens Salad,280,120,13,2,0,40,670,24,9,6,17,NA,NA,NA,Other
298
+
Dairy Queen,Crispy Chicken Sandwich,600,270,30,5,0,55,1250,59,7,8,24,10,6,15,Other
299
+
Dairy Queen,Crispy Chicken Wrap,350,190,21,5,0,35,820,30,2,1,12,10,2,10,Other
300
+
Dairy Queen,Grilled Chicken BLT Salad,380,170,19,9,0,100,1540,11,3,6,42,NA,NA,NA,Other
301
+
Dairy Queen,Grilled Chicken Garden Greens Salad,150,20,2,0.5,0,40,730,10,3,6,23,NA,NA,NA,Other
302
+
Dairy Queen,Grilled Chicken Sandwich,360,140,15,3,0,50,1040,32,1,5,25,10,8,6,Other
303
+
Dairy Queen,Grilled Chicken Wrap,280,130,15,4,0,30,800,22,1,1,15,10,4,10,Other
304
+
Dairy Queen,Side Salad,20,0,0,0,0,0,15,5,2,3,1,50,30,15,Other
305
+
Dairy Queen,Turkey BLT Sandwich,550,240,26,8,0,60,1420,45,3,3,30,NA,NA,NA,Other
306
+
Subway,"6"" B.L.T.",320,80,9,4,0,20,680,43,5,6,15,8,8,30,Other
307
+
Subway,Footlong B.L.T.,640,160,18,8,0,40,1360,86,10,12,30,16,16,60,Other
308
+
Subway,"6"" BBQ Rib Sandwich",430,160,18,6,0,50,590,47,5,8,19,8,20,30,Other
309
+
Subway,Footlong BBQ Rib Sandwich,860,320,36,12,0,100,1180,94,10,16,38,16,40,60,Other
310
+
Subway,"6"" Big Hot Pastrami",580,310,31,11,0,85,1470,47,5,7,29,10,45,40,Other
311
+
Subway,Footlong Big Hot Pastrami,1160,620,62,22,0,170,2940,94,10,14,58,20,90,80,Other
312
+
Subway,"6"" Big Philly Cheesesteak",500,150,17,9,1,85,1310,51,6,8,38,15,20,50,Other
313
+
Subway,Footlong Big Philly Cheesesteak,1000,300,34,18,2,170,2620,102,12,16,76,30,40,100,Other
314
+
Subway,Kids Mini Sub Black Forest Ham,180,20,3,0.5,0,10,450,30,3,5,10,6,15,20,Other
315
+
Subway,"6"" Black Forest Ham",290,40,5,1,0,20,830,46,5,8,18,8,20,30,Other
316
+
Subway,Footlong Black Forest Ham,580,80,10,2,0,40,1660,92,10,16,36,16,40,60,Other
317
+
Subway,"6"" Carved Turkey",330,45,5,1,0,45,890,45,5,7,25,8,20,30,Other
318
+
Subway,Footlong Carved Turkey,660,90,10,2,0,90,1780,90,10,14,50,16,40,60,Other
319
+
Subway,"6"" Carved Turkey & Bacon w/ Cheese",570,230,26,7,0,70,1600,46,5,8,33,10,20,40,Other
320
+
Subway,Footlong Carved Turkey & Bacon w/ Cheese,1140,460,52,14,0,140,3200,92,10,16,66,20,40,80,Other
321
+
Subway,"6"" Chicken & Bacon Ranch Melt",570,250,28,10,1,95,1080,47,5,8,35,15,25,50,Other
322
+
Subway,Footlong Chicken & Bacon Ranch Melt,1140,500,56,20,2,190,2160,94,10,16,70,30,50,100,Other
323
+
Subway,"6"" Chicken Pizziola Melt",460,140,16,6,0,80,1140,49,6,9,32,15,30,45,Other
324
+
Subway,Footlong Chicken Pizziola Melt,920,280,32,12,0,160,2280,98,12,18,64,30,60,90,Other
325
+
Subway,"6"" Cold Cut Combo",370,120,13,4,0,50,1140,46,5,7,18,10,20,35,Other
326
+
Subway,Footlong Cold Cut Combo,740,240,26,8,0,100,2280,92,10,14,36,20,40,70,Other
327
+
Subway,"6"" Corned Beef Reuben",470,130,15,4.5,0,85,1770,45,7,12,39,10,35,20,Other
328
+
Subway,Footlong Corned Beef Reuben,940,260,30,9,0,170,3540,90,14,24,78,20,70,40,Other
329
+
Subway,"6"" Italian B.M.T.",410,150,16,6,0,45,1300,46,5,8,20,8,20,30,Other
330
+
Subway,Footlong Italian B.M.T.,820,300,32,12,0,90,2600,92,10,16,40,16,40,60,Other
331
+
Subway,"6"" Italian Hero",550,260,29,10,0,75,1470,47,5,9,26,10,20,40,Other
332
+
Subway,Footlong Italian Hero,1100,520,58,20,0,150,2940,94,10,18,52,20,40,80,Other
333
+
Subway,"6"" Meatball Marinara",480,160,18,7,1,30,950,59,8,12,21,25,35,35,Other
334
+
Subway,Footlong Meatball Marinara,960,320,36,14,2,60,1900,118,16,24,42,50,70,70,Other
335
+
Subway,"6"" Oven Roasted Chicken",320,40,5,2,0,25,640,47,5,8,23,8,30,30,Other
336
+
Subway,Footlong Oven Roasted Chicken,640,80,10,4,0,50,1280,44,10,16,46,16,60,60,Other
337
+
Subway,Kids Mini Sub Roast Beef,200,25,3,1,0,25,390,30,4,5,14,6,15,20,Other
338
+
Subway,"6"" Roast Beef",320,40,5,2,0,40,700,45,5,7,24,8,20,30,Other
339
+
Subway,Footlong Roast Beef,640,80,10,4,0,80,1400,90,10,14,48,16,40,60,Other
340
+
Subway,"6"" Rotisserie Style Chicken",350,50,6,1.5,0,50,540,44,5,7,29,8,20,30,Other
341
+
Subway,Footlong Rotisserie Style Chicken,700,100,12,3,0,100,1080,88,10,14,58,16,40,60,Other
342
+
Subway,"6"" Spicy Italian",480,220,24,9,1,50,1520,46,5,8,20,8,20,30,Other
343
+
Subway,Footlong Spicy Italian,960,440,48,18,2,100,3040,92,10,16,40,16,40,60,Other
344
+
Subway,"6"" Steak and Cheese",380,90,10,5,0,50,1060,48,5,8,26,10,20,40,Other
345
+
Subway,Footlong Steak and Cheese,760,180,20,10,0,100,2120,96,10,16,52,20,40,80,Other
346
+
Subway,"6"" Subway Club",310,40,5,2,0,40,880,46,5,7,23,8,20,30,Other
347
+
Subway,Footlong Subway Club,620,80,10,4,0,80,1760,92,10,14,46,16,40,60,Other
348
+
Subway,"6"" Subway Melt (includes cheese)",370,100,11,5,0,45,1210,47,5,8,23,10,20,40,Other
349
+
Subway,Footlong Subway Melt (includes cheese),740,200,22,10,0,90,1420,94,10,16,46,20,40,80,Other
350
+
Subway,"6"" Subway Seafood Sensation",420,170,19,3,0,20,690,51,5,8,13,10,20,35,Other
351
+
Subway,Footlong Subway Seafood Sensation,840,340,38,6,0,40,1380,102,10,16,26,20,40,70,Other
352
+
Subway,"6"" Sweet Onion Chicken Teriyaki",380,40,5,1,0,50,900,59,5,18,26,8,30,35,Other
353
+
Subway,Footlong Sweet Onion Chicken Teriyaki,760,80,10,2,0,100,1800,118,10,36,52,16,60,70,Other
354
+
Subway,"6"" Tuna",470,210,24,4,0,30,620,44,5,6,20,8,20,30,Other
355
+
Subway,Footlong Tuna,940,420,48,8,0,60,1240,88,10,12,40,16,40,60,Other
356
+
Subway,"6"" Turkey & Bacon Avocado",390,110,13,3.5,0,30,860,49,8,7,22,10,200,30,Other
357
+
Subway,Footlong Turkey & Bacon Avocado,780,220,26,7,0,60,1720,98,16,14,44,20,400,60,Other
358
+
Subway,Kids Mini Sub Turkey Breast,180,20,2,0.5,0,10,380,30,3,5,10,6,15,20,Other
359
+
Subway,"6"" Turkey Breast",280,30,4,1,0,20,810,46,5,7,18,8,20,30,Other
360
+
Subway,Footlong Turkey Breast,560,60,8,2,0,40,1620,92,10,14,36,16,40,60,Other
361
+
Subway,"6"" Turkey Breast & Ham",280,35,4,1,0,20,820,46,5,8,18,8,20,30,Other
362
+
Subway,Footlong Turkey Breast & Ham,560,70,8,2,0,40,1640,92,10,16,36,16,40,60,Other
363
+
Subway,"6"" Turkey Italiano Melt (with Provolone)",490,210,24,9,1,50,1480,47,5,8,24,10,20,45,Other
364
+
Subway,Footlong Turkey Italiano Melt (with Provolone),980,420,48,18,2,100,2960,94,10,16,48,20,40,90,Other
365
+
Subway,Kids Mini Sub Veggie Delite,150,15,2,0,0,0,190,29,3,4,6,6,15,20,Other
366
+
Subway,"6"" Veggie Delite",230,20,3,1,0,0,310,44,5,6,8,8,20,30,Other
367
+
Subway,Footlong Veggie Delite,460,40,6,2,0,0,620,88,10,12,16,16,40,60,Other
368
+
Subway,"6"" Veggie Patty",390,70,7,1,0,10,800,56,8,8,23,15,20,35,Other
369
+
Subway,Footlong Veggie Patty,780,140,14,2,0,20,1600,112,16,16,46,30,20,70,Other
370
+
Subway,Autumn Carved Turkey Salad,300,80,9,3,0,60,1120,26,3,22,25,40,40,15,Other
371
+
Subway,B.L.T. Salad,150,70,8,4,0,20,420,10,4,5,10,50,50,6,Other
372
+
Subway,Big Hot Pastrami Melt Salad,400,300,29,11,0,85,1250,12,4,4,23,25,70,10,Other
373
+
Subway,Big Philly Cheesesteak Salad,330,140,16,8,1,85,1080,17,5,6,32,60,50,25,Other
374
+
Subway,Black Forest Ham Salad,110,25,3,1,0,20,590,11,4,6,12,25,45,4,Other
375
+
Subway,Buffalo Chicken Salad (with Ranch dressing),360,230,26,4,0,60,1100,13,4,6,20,50,60,8,Other
376
+
Subway,Carved Turkey & Bacon w/ Cheese Salad,280,110,12,4.5,0,65,1320,11,4,5,28,50,50,15,Other
377
+
Subway,Carved Turkey Salad,150,30,4,0,0,45,680,8,3,3,19,40,40,6,Other
378
+
Subway,Chicken & Bacon Ranch Melt Salad (includes Ranch dressing),510,340,38,12,1,100,1040,14,4,7,30,60,60,30,Other
379
+
Subway,Cold Cut Combo Salad,180,95,11,4,0,45,820,12,4,5,12,50,50,10,Other
380
+
Subway,Double Chicken Salad,220,35,5,1.5,0,100,490,10,4,4,36,50,60,8,Other
381
+
Subway,Italian B.M.T.ยฎ Salad,230,135,15,6,0,45,1060,12,4,6,14,50,50,6,Other
382
+
Subway,Italian Hero Salad,230,140,15,5,0,45,1060,13,4,8,14,40,60,4,Other
383
+
Subway,Meatball Marinara Salad,310,150,17,7,1,30,720,25,6,10,16,60,70,10,Other
384
+
Subway,Oven Roasted Chicken Salad,140,25,3,0.5,0,50,280,10,4,4,19,50,60,8,Other
385
+
Subway,Roast Beef Salad,140,30,4,1,0,40,450,10,4,5,18,25,45,4,Other
386
+
Subway,Spicy Italian Salad,310,205,23,9,1,50,1280,11,4,6,15,50,50,8,Other
387
+
Subway,Steak & Cheese Salad,210,75,8,4,0,50,830,14,4,6,20,50,50,15,Other
388
+
Subway,Subway Club Salad,140,30,4,1,0,40,640,11,4,5,17,25,45,6,Other
389
+
Subway,Subway Meltยฎ Salad,200,85,10,5,0,45,910,13,4,6,18,50,50,15,Other
390
+
Subway,Sweet Onion Chicken Teriyaki Salad,200,25,3,1,0,50,660,24,4,16,20,25,50,6,Other
391
+
Subway,Tuna Salad,310,215,24,4,0,40,370,10,4,4,15,50,50,6,Other
392
+
Subway,Turkey Breast & Ham Salad,110,20,3,1,0,25,580,11,4,5,12,25,45,6,Other
393
+
Subway,Turkey Breast Salad,110,20,2,1,0,20,570,11,4,5,12,25,45,6,Other
394
+
Subway,Veggie Delite Salad,50,10,1,0,0,0,65,9,4,4,3,25,45,4,Other
395
+
Subway,Chipotle Southwest Steak & Cheese Wrap,760,330,37,12,1,100,2250,65,4,7,43,15,45,30,Other
396
+
Subway,Rotisserie-Style Chicken Caesar Wrap,730,310,34,10,0.5,135,1900,53,3,4,55,15,8,45,Other
397
+
Subway,"Turkey, Bacon & Guacamole Wrap",810,380,42,13,0.5,75,2970,62,3,6,43,10,30,30,Other
398
+
Subway,Cheese & Veggies Pizza,740,230,25,11,0,50,1270,100,5,9,36,35,30,60,Other
399
+
Subway,Cheese Pizza,680,200,22,9,0,40,1070,96,4,7,32,25,4,45,Other
400
+
Subway,Pepperoni Pizza,790,290,32,13,0,60,1350,96,4,8,38,30,4,60,Other
401
+
Subway,Sausage Pizza,820,310,34,14,0,70,1420,97,4,8,39,30,4,60,Other
402
+
Taco Bell,1/2 lb.* Cheesy Potato Burrito,540,230,26,7,1,45,1360,59,7,4,19,NA,NA,NA,Other
403
+
Taco Bell,1/2 lb.* Combo Burrito,460,170,18,7,1,45,1320,53,9,3,21,NA,NA,NA,Other
404
+
Taco Bell,7-Layer Burrito,510,170,19,7,0,20,1090,68,11,4,16,NA,NA,NA,Other
405
+
Taco Bell,Bean Burrito,370,100,11,4,0,5,960,56,9,3,13,NA,NA,NA,Other
406
+
Taco Bell,Beefy 5-Layer Burrito,550,200,22,8,0,35,1270,68,8,5,19,NA,NA,NA,Other
407
+
Taco Bell,Beefy Fritosยฎ Burrito,440,160,18,5,0,20,1030,55,4,3,13,NA,NA,NA,Other
408
+
Taco Bell,Black Bean Burrito,410,110,12,4,0,10,1100,62,8,3,14,NA,NA,NA,Other
409
+
Taco Bell,Burrito Supremeยฎ โ Beef,420,140,16,7,0,35,1090,53,8,5,16,NA,NA,NA,Other
410
+
Taco Bell,Burrito Supremeยฎ - Chicken,390,110,12,5,0,40,1050,52,7,5,19,NA,NA,NA,Other
411
+
Taco Bell,Burrito Supremeยฎ - Steak,390,120,13,5,0,30,1090,52,7,5,17,NA,NA,NA,Other
412
+
Taco Bell,Cantina Power Burrito - Chicken,760,240,27,6,0,60,1960,96,12,7,32,NA,NA,NA,Other
413
+
Taco Bell,Cantina Power Burrito - Steak,780,250,28,7,0,50,1900,98,13,7,33,NA,NA,NA,Other
414
+
Taco Bell,Cantina Power Burrito - Veggie,740,230,26,5,0,10,1750,107,17,8,20,NA,NA,NA,Other
415
+
Taco Bell,Cheesy Bean and Rice Burrito,420,160,17,3.5,0,0,930,55,6,4,11,NA,NA,NA,Other
416
+
Taco Bell,Chili Cheese Burrito,380,150,17,8,1,35,930,41,5,2,16,NA,NA,NA,Other
417
+
Taco Bell,Chicken Crunchy Cheesy Core Burrito,610,210,24,9,0,55,1510,74,5,5,25,10,4,35,Other
418
+
Taco Bell,Steak Crunchy Cheesy Core Burrito,610,220,24,9,0,50,1520,75,5,5,25,10,4,40,Other
419
+
Taco Bell,Beef Crunchy Cheesy Core Burrito,630,240,26,10,0.5,45,1530,76,7,5,22,15,4,35,Other
420
+
Taco Bell,Loaded Taco Burrito,550,260,29,9,0.5,50,1130,52,7,4,20,15,6,20,Other
421
+
Taco Bell,Chicken Quesarito,620,270,30,10,0,60,1440,64,4,4,24,NA,NA,NA,Other
422
+
Taco Bell,Steak Quesarito,630,280,31,11,0.5,65,1410,64,3,4,25,NA,NA,NA,Other
423
+
Taco Bell,Beef Quesarito,650,300,34,12,0.5,60,1450,65,6,5,22,NA,NA,NA,Other
424
+
Taco Bell,Shredded Chicken Burrito,400,160,18,4.5,0,30,960,45,3,3,16,NA,NA,NA,Other
425
+
Taco Bell,Smothered Burrito - Beef,710,320,35,13,1,75,2260,70,10,4,28,NA,NA,NA,Other
426
+
Taco Bell,Smothered Burrito - Shredded Chicken,650,250,28,10,0,70,2230,67,8,4,34,NA,NA,NA,Other
427
+
Taco Bell,Smothered Burrito - Steak,670,260,29,11,0.5,80,2080,68,7,4,35,NA,NA,NA,Other
428
+
Taco Bell,Chicken Spicy Cheesy Core Burrito,540,180,20,8,0,55,1740,66,5,5,24,15,8,35,Other
429
+
Taco Bell,Steak Spicy Cheesy Core Burrito,550,190,21,9,0,50,1750,66,5,5,24,10,6,35,Other
430
+
Taco Bell,Beef Spicy Cheesy Core Burrito,570,210,23,10,0.5,45,1760,68,7,5,22,15,6,35,Other
431
+
Taco Bell,Triple Melt Burrito,410,140,16,6,0,30,1030,50,4,3,15,6,2,20,Other
432
+
Taco Bell,XXL Grilled Stuft Burrito - Beef,880,380,42,14,1,75,2020,94,12,6,31,NA,NA,NA,Other
433
+
Taco Bell,XXL Grilled Stuft Burrito - Chicken,830,320,35,11,0,85,1940,91,10,6,37,NA,NA,NA,Other
434
+
Taco Bell,XXL Grilled Stuft Burrito - Steak,820,320,36,12,1,70,2020,91,10,7,33,NA,NA,NA,Other
435
+
Taco Bell,Chicken Soft Taco,170,50,6,3,0,30,460,18,1,1,12,NA,NA,NA,Other
436
+
Taco Bell,Cool Ranchยฎ Doritosยฎ Double Deckerยฎ Taco,320,120,14,5,0,25,770,36,6,2,13,NA,NA,NA,Other
437
+
Taco Bell,Cool Ranchยฎ Doritosยฎ Locos Taco,160,90,10,3.5,0,25,350,13,2,1,8,NA,NA,NA,Other
438
+
Taco Bell,Cool Ranchยฎ Doritosยฎ Locos Taco Supreme,200,100,12,4.5,0,35,370,15,3,3,9,NA,NA,NA,Other
439
+
Taco Bell,Crunchy Taco,170,90,10,4,0,25,290,12,3,1,8,NA,NA,NA,Other
440
+
Taco Bell,Crunchy Taco Supremeยฎ,200,110,12,5,0,35,320,15,3,2,9,NA,NA,NA,Other
441
+
Taco Bell,Double Deckerยฎ Taco,320,120,14,5,0,25,640,37,7,2,13,NA,NA,NA,Other
442
+
Taco Bell,DOUBLE DECKERยฎ Taco Supremeยฎ,350,140,16,6,0,35,670,40,7,3,14,NA,NA,NA,Other
443
+
Taco Bell,Spicy Sweet Double Stacked Taco,340,160,18,7,0,35,640,32,4,6,12,10,2,15,Other
444
+
Taco Bell,Cool Ranch Habanero Double Stacked Taco,350,180,20,8,0.5,40,630,30,4,3,13,15,2,20,Other
445
+
Taco Bell,Nacho Crunch Double Stacked Taco,380,170,19,6,0,35,650,39,5,2,13,8,2,20,Other
446
+
Taco Bell,Fiery Doritosยฎ Double Deckerยฎ Taco,320,120,13,5,0,25,770,36,7,2,14,NA,NA,NA,Other
447
+
Taco Bell,Fiery Doritosยฎ Locos Taco,170,90,10,3.5,0,25,370,12,3,1,8,NA,NA,NA,Other
448
+
Taco Bell,Fiery Doritosยฎ Locos Taco Supreme,200,110,12,5,0,30,390,15,3,2,9,NA,NA,NA,Other
449
+
Taco Bell,Grilled Steak Soft Taco,250,130,14,4,0,30,550,19,2,2,11,NA,NA,NA,Other
450
+
Taco Bell,Nacho Cheese Doritosยฎ Double Deckerยฎ Taco,320,120,13,5,0,25,760,36,7,2,14,NA,NA,NA,Other
451
+
Taco Bell,Nacho Cheese Doritosยฎ Locos Tacos,170,80,9,4,0,25,340,13,2,1,8,NA,NA,NA,Other
452
+
Taco Bell,Nacho Cheese Doritosยฎ Locos Tacos Supreme,200,100,11,5,0,35,370,15,3,2,9,NA,NA,NA,Other
453
+
Taco Bell,Soft Taco Supremeยฎ โ Beef,230,100,11,5,0,35,530,22,3,3,10,NA,NA,NA,Other
454
+
Taco Bell,Soft Taco-Beef,200,80,9,4,0,25,510,19,3,1,10,NA,NA,NA,Other
455
+
Taco Bell,Spicy Potato Soft Taco,250,120,13,3,0,10,510,28,3,1,6,NA,NA,NA,Other
456
+
Taco Bell,Chalupa Supremeยฎ - Chicken,340,160,18,4,0,40,530,29,3,4,16,NA,NA,NA,Other
457
+
Taco Bell,Chalupa Supremeยฎ - Steak,340,170,18,4,0,30,570,29,3,4,14,NA,NA,NA,Other
458
+
Taco Bell,Chalupa SupremeยฎโBeef,370,190,21,5,0,30,570,31,4,4,13,NA,NA,NA,Other
459
+
Taco Bell,Double Chalupa,600,310,35,8,0.5,50,1010,50,6,5,21,15,4,15,Other
460
+
Taco Bell,Wild Naked Chicken Chalupa,420,250,28,6,0,65,1070,23,4,2,19,6,4,6,Other
461
+
Taco Bell,Mild Naked Chicken Chalupa,440,270,30,7,0,70,1090,22,3,1,20,6,4,6,Other
462
+
Taco Bell,Spicy Double Chalupa,600,310,35,8,0.5,50,1240,52,7,5,21,15,8,15,Other
463
+
Taco Bell,Fresco Bean Burrito,350,80,9,3,0,0,950,57,9,3,11,NA,NA,NA,Other
464
+
Taco Bell,Fresco Burrito Supremeยฎ โ Chicken,340,80,8,3,0,25,1020,50,7,4,17,NA,NA,NA,Other
465
+
Taco Bell,Fresco Burrito Supremeยฎ โ Steak,340,80,9,3,0,15,1060,50,7,4,15,NA,NA,NA,Other
466
+
Taco Bell,Fresco Chicken Soft Taco,150,35,4,1,0,25,460,18,2,2,11,NA,NA,NA,Other
467
+
Taco Bell,Fresco Crunchy Taco,140,70,8,2,0,20,290,13,3,1,6,NA,NA,NA,Other
468
+
Taco Bell,Fresco Grilled Steak Soft Taco,150,35,4,2,0,15,500,19,2,2,9,NA,NA,NA,Other
469
+
Taco Bell,Fresco Soft Taco,170,60,7,3,0,20,500,20,3,2,8,NA,NA,NA,Other
470
+
Taco Bell,Cheesy Gordita Crunch,490,260,29,10,1,55,810,39,5,6,20,NA,NA,NA,Other
471
+
Taco Bell,Doritosยฎ Cheesy Gordita Crunch - Cool Ranch,490,250,28,10,1,55,890,40,5,5,20,NA,NA,NA,Other
472
+
Taco Bell,Doritosยฎ Cheesy Gordita Crunch - Fiery,490,250,28,10,1,55,890,40,5,4,20,NA,NA,NA,Other
473
+
Taco Bell,Doritosยฎ Cheesy Gordita Crunch - Nacho Cheese,490,250,28,10,1,55,880,40,5,5,20,NA,NA,NA,Other
474
+
Taco Bell,Double Cheesy Gordita Crunch,570,290,32,12,1,70,1110,44,7,5,25,15,2,30,Other
475
+
Taco Bell,Gordita Supremeยฎ โ Beef,300,120,14,5,0,30,550,31,4,6,13,NA,NA,NA,Other
476
+
Taco Bell,Gordita Supremeยฎ - Chicken,270,90,10,4,0,40,510,29,2,6,16,NA,NA,NA,Other
477
+
Taco Bell,Gordita Supremeยฎ - Steak,270,90,11,4,0,30,550,29,2,6,14,NA,NA,NA,Other
478
+
Taco Bell,Nacho Fries Bellgrande,710,360,41,6,0,30,1420,73,10,4,13,10,4,8,Other
479
+
Taco Bell,Nachos BellGrandeยฎ,760,360,39,6,0,30,1100,82,13,5,18,NA,NA,NA,Other
480
+
Taco Bell,Nachos Supreme,430,210,23,5,0,30,690,44,7,3,12,NA,NA,NA,Other
481
+
Taco Bell,Triple Layer Nachos,320,140,15,1.5,0,0,600,41,6,2,7,NA,NA,NA,Other
482
+
Taco Bell,Triple Melt Nachos,260,140,16,4.5,0,30,550,19,3,1,10,6,0,10,Other
483
+
Taco Bell,Beefy Cheddar Crunchwrap Slider,410,170,19,6,0,25,960,46,4,3,14,NA,NA,NA,Other
484
+
Taco Bell,Beefy Mini Quesadilla,210,110,12,4,0,25,560,17,3,1,9,NA,NA,NA,Other
485
+
Taco Bell,Beefy Nacho Griller,420,170,19,4.5,0,20,870,49,5,3,12,NA,NA,NA,Other
486
+
Taco Bell,BLT Crunchwrap Slider,430,210,23,5,0,20,900,43,3,4,12,NA,NA,NA,Other
487
+
Taco Bell,Cantina Power Bowl - Chicken,560,200,22,4,0,60,1520,64,9,4,26,NA,NA,NA,Other
488
+
Taco Bell,Cantina Power Bowl - Steak,580,210,23,4,0,50,1460,66,10,4,27,NA,NA,NA,Other
489
+
Taco Bell,Cantina Power Bowl - Veggie,540,190,21,3,0,10,1310,75,14,4,14,NA,NA,NA,Other
490
+
Taco Bell,Cheese Quesadilla,480,240,27,11,1,50,1000,40,4,3,19,NA,NA,NA,Other
491
+
Taco Bell,Cheese Roll-Up,190,80,9,5,0,20,450,18,2,1,9,NA,NA,NA,Other
492
+
Taco Bell,Chicken Quesadilla,520,250,28,12,1,75,1210,41,4,3,27,NA,NA,NA,Other
493
+
Taco Bell,Chickstar,620,340,37,8,0,50,1290,53,4,4,17,8,6,15,Other
494
+
Taco Bell,Chili Cheese Burrito,380,150,17,8,1,35,930,41,5,2,16,NA,NA,NA,Other
495
+
Taco Bell,Chipotle Crispy Chicken Griller,290,170,18,3,0,25,640,22,1,1,9,NA,NA,NA,Other
496
+
Taco Bell,Crispy Chicken Quesadilla,650,340,37,13,0.5,75,1480,51,5,3,26,10,2,45,Other
497
+
Taco Bell,Crunchwrap Supremeยฎ,540,190,21,6,0,30,1110,71,7,7,16,NA,NA,NA,Other
498
+
Taco Bell,Double Tostada,270,100,11,4,0,15,650,32,8,2,12,NA,NA,NA,Other
499
+
Taco Bell,Express Taco Salad w/ Chips,580,260,29,9,1,60,1270,59,8,7,23,NA,NA,NA,Other
500
+
Taco Bell,Loaded Potato Griller,470,200,22,6,0,25,1120,55,4,5,13,NA,NA,NA,Other
501
+
Taco Bell,Mexican Pizza,540,270,31,8,1,40,860,47,7,2,20,NA,NA,NA,Other
502
+
Taco Bell,MexiMeltยฎ,270,130,14,7,1,40,740,21,3,2,14,NA,NA,NA,Other
503
+
Taco Bell,Steak Quesalupa,440,210,23,10,0.5,60,840,36,3,3,22,15,6,35,Other
504
+
Taco Bell,Chicken Quesalupa,440,200,23,10,0.5,60,840,37,3,3,22,15,8,35,Other
505
+
Taco Bell,Beef Quesalupa,460,240,26,11,1,50,890,38,4,3,19,15,6,35,Other
506
+
Taco Bell,Shredded Chicken Mini Quesadilla,180,70,8,2.5,0,25,540,15,2,1,12,NA,NA,NA,Other
507
+
Taco Bell,Spicy Chicken Crunchwrap Slider,400,180,20,4,0,25,900,42,3,3,15,NA,NA,NA,Other
508
+
Taco Bell,Spicy Tostada,200,90,10,2.5,0,10,440,22,4,1,7,NA,NA,NA,Other
509
+
Taco Bell,Stacker,390,170,18,8,0.5,40,1050,39,4,3,18,8,2,30,Other
510
+
Taco Bell,Steak Quesadilla,520,250,28,12,1,65,1250,41,4,3,25,NA,NA,NA,Other
511
+
Taco Bell,Original Triple Double Crunchwrap,700,270,30,9,0.5,45,1550,85,9,7,23,15,6,25,Other
512
+
Taco Bell,Spicy Triple Double Crunchwrap,780,340,38,10,0.5,50,1850,87,9,8,23,20,10,25,Other
513
+
Taco Bell,Express Taco Salad w/ Chips,580,260,29,9,1,60,1270,59,8,7,23,NA,NA,NA,Other
514
+
Taco Bell,Fiesta Taco Salad-Beef,780,380,42,10,1,60,1340,74,11,7,26,NA,NA,NA,Other
515
+
Taco Bell,Fiesta Taco Salad-Chicken,720,320,35,7,0,70,1260,70,8,8,32,NA,NA,NA,Other
516
+
Taco Bell,Fiesta Taco Salad-Steak,720,320,36,8,1,55,1340,70,8,8,28,NA,NA,NA,Other
+52
python/oct15/fast-food-nutrition/main.py
+52
python/oct15/fast-food-nutrition/main.py
···
1
+
import pandas as pd
2
+
import matplotlib.pyplot as plt
3
+
import seaborn as sns
4
+
5
+
def loadFastFoodDatabase():
6
+
df = pd.read_csv('fast-food-nutrition/fastfood.csv')
7
+
return df
8
+
9
+
def highest_cal_item(df: pd.DataFrame) -> tuple[str, int, str]:
10
+
cals = df["calories"].astype(int)
11
+
item = df.iloc[cals.argmax()]
12
+
return (item["item"], item["calories"], item["restaurant"])
13
+
14
+
def cal_fat_correlation(df: pd.DataFrame) -> float:
15
+
corr = df["calories"].corr(df["total_fat"])
16
+
return corr
17
+
18
+
def scatter_plot(df: pd.DataFrame) -> None:
19
+
sns.scatterplot(x=df["calories"], y=df["total_fat"])
20
+
plt.show()
21
+
pass
22
+
23
+
def high_rest_avg(df: pd.DataFrame) -> tuple[str, float]:
24
+
avgs: list[tuple[str, float]] = []
25
+
for restaurant in df["restaurant"].unique():
26
+
items = df[df["restaurant"] == restaurant]
27
+
avg = items["calories"].astype(int).sum()/len(items)
28
+
avgs.append((restaurant, avg))
29
+
highest = avgs[0]
30
+
for (r, avg) in avgs[1:]:
31
+
if avg > highest[1]:
32
+
highest = (r, avg)
33
+
return highest
34
+
35
+
def percent_500(df: pd.DataFrame) -> float:
36
+
sodium = df["sodium"]
37
+
df = df.assign(above_500=sodium > 500)
38
+
# df["above_500"].value_counts().plot.pie(labels=["Above 500mg", "Below 500mg"])
39
+
# plt.show()
40
+
return len(df[df["above_500"]])/len(sodium)
41
+
42
+
print("Running test cases...", end="")
43
+
try:
44
+
df = loadFastFoodDatabase()
45
+
assert(highest_cal_item(df) == ("20 piece Buttermilk Crispy Chicken Tenders", 2430, "Mcdonalds")) # Q1
46
+
assert(cal_fat_correlation(df) == 0.9004936961298484) # Q2. This means that fats highly contribute to calorie count
47
+
# scatter_plot(df) # Q3
48
+
assert(high_rest_avg(df) == ("Mcdonalds", 640.3508771929825)) # Q4
49
+
assert(percent_500(df) == 0.9145631067961165) # Q5
50
+
print("Passed!")
51
+
except:
52
+
print("Failed :(")
python/oct15/sunrise-sunset/Figure_1.png
python/oct15/sunrise-sunset/Figure_1.png
This is a binary file and will not be displayed.
python/oct15/sunrise-sunset/Figure_2.png
python/oct15/sunrise-sunset/Figure_2.png
This is a binary file and will not be displayed.
+72
python/oct15/sunrise-sunset/main.py
+72
python/oct15/sunrise-sunset/main.py
···
1
+
import pandas as pd
2
+
import requests
3
+
from geopy.geocoders import Nominatim
4
+
from geopy.location import Location
5
+
import datetime
6
+
import seaborn as sns
7
+
import matplotlib.pyplot as plt
8
+
9
+
def sunrise_time(location_name: str, date: str|None|tuple[str,str] = None) -> list[dict[str, str]]:
10
+
nominatim = Nominatim(user_agent="CMU_Bootcamp")
11
+
geo: Location | None = nominatim.geocode(location_name)
12
+
if not geo:
13
+
raise KeyError("Location not found")
14
+
params = {
15
+
"lng": geo.longitude,
16
+
"lat": geo.latitude
17
+
}
18
+
19
+
if isinstance(date, tuple):
20
+
params["date_start"] = date[0],
21
+
params["date_end"] = date[1]
22
+
else:
23
+
params["date"] = date if date else "today"
24
+
25
+
out = requests.get(
26
+
"https://api.sunrisesunset.io/json",
27
+
params
28
+
)
29
+
j = out.json()['results']
30
+
res = j if isinstance(j, list) else [j]
31
+
return [{
32
+
"sunrise": r["sunrise"],
33
+
"sunset": r["sunset"],
34
+
"date": r["date"]
35
+
} for r in res]
36
+
37
+
def dayLength(sunrise: str, sunset: str) -> float:
38
+
sr = datetime.datetime.strptime(sunrise, "%I:%M:%S %p")
39
+
ss = datetime.datetime.strptime(sunset, "%I:%M:%S %p")
40
+
srhr = round(sr.hour + sr.minute/60, 1)
41
+
sshr = round(ss.hour + ss.minute/60, 1)
42
+
length = sshr - srhr
43
+
return round(length, 1)
44
+
45
+
location = "5000 Forbes Ave, Pittsburgh"
46
+
print("Running checks...")
47
+
try:
48
+
compare_date = "2024-08-02"
49
+
a = sunrise_time("Juneau, Alaska", compare_date)
50
+
b = sunrise_time("Miami, Florida", compare_date)
51
+
print(f"Juneau, Alaska {a[0]["sunrise"]}\nMiami, Florida {b[0]["sunrise"]}")
52
+
df = pd.DataFrame(sunrise_time("Seattle, Washington", ("2023-01-01", "2023-12-31")))
53
+
print(f"Latest: {df["sunrise"].max()} | Earliest: {df["sunrise"].min()}")
54
+
df = df.assign(dayLengthInHours=df.apply(lambda row: dayLength(row["sunrise"], row["sunset"]), axis=1))
55
+
print(f"Longest: {df["dayLengthInHours"].max()} | Shortest: {df['dayLengthInHours'].min()}")
56
+
# ax = sns.lineplot(data=df["dayLengthInHours"])
57
+
# ax.set(xlabel="", ylabel="", xticklabels=[])
58
+
# plt.show()
59
+
df = df.assign(month=df.apply(lambda row: datetime.datetime.strptime(row["date"], "%Y-%m-%d").strftime("%B"), axis=1))
60
+
print(df)
61
+
avgs: dict[str, float] = {}
62
+
for month in df["month"].unique():
63
+
this_month = pd.DataFrame(df[df["month"] == month])
64
+
total = this_month["dayLengthInHours"].astype(float).sum()
65
+
avg = round(total / len(this_month), 1)
66
+
avgs[month] = avg
67
+
# ax = sns.barplot(data=avgs)
68
+
# ax.tick_params(axis="x", rotation=45)
69
+
# plt.show()
70
+
print("Passed!")
71
+
except:
72
+
print("Failed :(")
+245
python/oct15/tic-tac-toe/main.py
+245
python/oct15/tic-tac-toe/main.py
···
1
+
# Tic-Tac-Toe
2
+
# by David Kosbie
3
+
4
+
# This implements a basic game of TicTacToe.
5
+
6
+
# This version does not save or load the game (that is left for you to do!)
7
+
8
+
import json
9
+
from cmu_graphics import *
10
+
import math
11
+
import os
12
+
13
+
def onAppStart(app):
14
+
# Set the model values (in the app object) that never change.
15
+
app.rows = 3
16
+
app.cols = 3
17
+
app.boardBounds = (50, 75, 350, 375) # left, top, right, bottom
18
+
app.cellBorderWidth = 2
19
+
resetApp(app)
20
+
21
+
def resetApp(app):
22
+
app.selection = None
23
+
app.board = [[None]*app.cols for row in range(app.rows)]
24
+
app.turn = 'X'
25
+
app.message = "X's turn"
26
+
app.turnCount = 0
27
+
app.gameOver = False
28
+
app.winningCells = None
29
+
30
+
def saveGame(app):
31
+
with open("savedGame.txt", "w") as f:
32
+
json.dump({
33
+
"board": app.board,
34
+
"turn": app.turn,
35
+
"gameOver": app.gameOver,
36
+
"winningCells": app.winningCells
37
+
}, f)
38
+
39
+
def loadGame(app):
40
+
with open("savedGame.txt", "r") as f:
41
+
game = json.load(f)
42
+
app.selection = None
43
+
app.board = game["board"]
44
+
app.turn = game["turn"]
45
+
app.message = f"{app.turn}'s turn"
46
+
flat_list = []
47
+
for r in app.board:
48
+
for c in r:
49
+
if c != None:
50
+
flat_list.append(1)
51
+
app.turn_count = sum(flat_list)
52
+
app.gameOver = game["gameOver"]
53
+
app.winningCells = game["winningCells"]
54
+
55
+
56
+
def onKeyPress(app, key):
57
+
if key == 's':
58
+
saveGame(app)
59
+
elif key == 'l':
60
+
loadGame(app)
61
+
elif (app.gameOver) and (key == 'r'):
62
+
resetApp(app)
63
+
64
+
def onMousePress(app, mouseX, mouseY):
65
+
# Always clear the selection on any mouse press.
66
+
app.selection = None
67
+
# Then, make the move, but only if the game is not over, and the move is legal
68
+
# (that is, it's in an empty cell).
69
+
if not app.gameOver:
70
+
cell = getCell(app, mouseX, mouseY)
71
+
if cell != None:
72
+
row, col = cell
73
+
if app.board[row][col] == None:
74
+
makeMove(app, row, col)
75
+
76
+
def onMouseMove(app, mouseX, mouseY):
77
+
if app.gameOver:
78
+
return
79
+
# Set the cell selection as the mouse is moved, but only
80
+
# if there is a selected cell, and that cell on the board is empty.
81
+
# Otherwise, clear the cell selection.
82
+
selectedCell = getCell(app, mouseX, mouseY)
83
+
if selectedCell == None:
84
+
app.selection = None
85
+
else:
86
+
row, col = selectedCell
87
+
if app.board[row][col] == None:
88
+
app.selection = selectedCell
89
+
else:
90
+
app.selection = None
91
+
92
+
def makeMove(app, row, col):
93
+
# We already know that this is a legal move, so set the board
94
+
# to the current player, add one to the turn count, check if the
95
+
# game is over, and if not, change turns.
96
+
app.board[row][col] = app.turn
97
+
app.turnCount += 1
98
+
checkForGameOver(app)
99
+
if not app.gameOver:
100
+
changeTurns(app)
101
+
102
+
def checkForGameOver(app):
103
+
# Check if the game is over (tie or win), and if so, set app.gameOver to
104
+
# True and set the app.message as appropriate.
105
+
# First check for a tie game. If it is, set
106
+
if app.turnCount == app.rows * app.cols:
107
+
app.gameOver = True
108
+
app.message = 'Tie game!'
109
+
# It's not a tie game, so check if there are 3 in a row on the board,
110
+
# in a search that is similar to wordSearch:
111
+
else:
112
+
directions = [ (0, 1), # right
113
+
(1, 0), # down
114
+
(1, 1), # right-down diagonal
115
+
(1, -1) # right-up diagonal
116
+
]
117
+
for startRow in range(app.rows):
118
+
for startCol in range(app.cols):
119
+
for drow,dcol in directions:
120
+
winner = checkForWin(app, startRow, startCol, drow, dcol)
121
+
if winner != None:
122
+
app.gameOver = True
123
+
app.message = f'{winner} wins!'
124
+
return
125
+
126
+
def checkForWin(app, startRow, startCol, drow, dcol):
127
+
# Check for a winner (3 in a row) starting from (startRow, startCol) and
128
+
# heading in the direction (drow, dcol). Return the winner if there
129
+
# is one, otherwise None. Also, so that we can draw the line through
130
+
# the winning 3-in-a-row run, store the winning cells in the order
131
+
# they appear in app.winningCells.
132
+
player = app.board[startRow][startCol]
133
+
if player == None:
134
+
return None
135
+
winLength = 3
136
+
winningCells = [ ]
137
+
for i in range(winLength):
138
+
row = startRow + i * drow
139
+
col = startCol + i * dcol
140
+
if ((row < 0) or (row >= app.rows) or
141
+
(col < 0) or (col >= app.cols)):
142
+
# we went off the board
143
+
return None
144
+
if app.board[row][col] != player:
145
+
return None
146
+
winningCells.append((row, col))
147
+
app.winningCells = winningCells
148
+
return player
149
+
150
+
def changeTurns(app):
151
+
# Change the turn from 'X' to 'O' or 'O' to 'X',
152
+
# and set the app.message as appropriate.
153
+
app.turn = 'O' if (app.turn == 'X') else 'X'
154
+
app.message = f"{app.turn}'s turn"
155
+
156
+
def redrawAll(app):
157
+
drawLabel('Tic-Tac-Toe', 200, 20, size=16, bold=True)
158
+
drawLabel('Press s to save game, l to load game', 200, 40, size=14)
159
+
drawAppMessage(app)
160
+
drawBoard(app)
161
+
drawWinningLine(app)
162
+
163
+
def drawAppMessage(app):
164
+
# Draw the app.message, and if the game is over, make the message red
165
+
# and add a note to press r to restart.
166
+
if app.gameOver:
167
+
message = app.message + ' (press r to restart)'
168
+
color = 'red'
169
+
else:
170
+
message = app.message
171
+
color = 'black'
172
+
drawLabel(message, 200, 60, size=14, fill=color)
173
+
174
+
def drawBoard(app):
175
+
# first draw each cell (with single-thickness):
176
+
for row in range(app.rows):
177
+
for col in range(app.cols):
178
+
drawCell(app, row, col)
179
+
# then draw the board outline (with double-thickness):
180
+
x0, y0, x1, y1 = app.boardBounds
181
+
drawRect(x0, y0, x1-x0, y1-y0,
182
+
fill=None, border='black',
183
+
borderWidth=2*app.cellBorderWidth)
184
+
185
+
def drawCell(app, row, col):
186
+
x0, y0, x1, y1 = getCellBounds(app, row, col)
187
+
color = 'cyan' if (row, col) == app.selection else None
188
+
drawRect(x0, y0, x1-x0, y1-y0,
189
+
fill=color, border='black', borderWidth=app.cellBorderWidth)
190
+
label = app.board[row][col]
191
+
if label != None:
192
+
cx = x0 + (x1 - x0)/2
193
+
cy = y0 + (y1 - y0)/2
194
+
drawLabel(label, cx, cy, size=24, bold=True)
195
+
196
+
def drawWinningLine(app):
197
+
# If there is a winner, then app.winningCells will contain the
198
+
# cells in order, so draw a line from the center of the first cell
199
+
# to the center of the last cell.
200
+
if app.winningCells != None:
201
+
cx0, cy0 = getCellCenter(app, app.winningCells[0])
202
+
cx1, cy1 = getCellCenter(app, app.winningCells[-1])
203
+
drawLine(cx0, cy0, cx1, cy1)
204
+
205
+
def getCellCenter(app, cell):
206
+
# Return the center of the given cell, a (row, col) tuple.
207
+
row, col = cell
208
+
x0, y0, x1, y1 = getCellBounds(app, row, col)
209
+
cx = (x0 + x1) / 2
210
+
cy = (y0 + y1) / 2
211
+
return cx, cy
212
+
213
+
def getCellBounds(app, row, col):
214
+
boardX0, boardY0, boardX1, boardY1 = app.boardBounds
215
+
cellWidth, cellHeight = getCellSize(app)
216
+
x0 = boardX0 + col * cellWidth
217
+
y0 = boardY0 + row * cellHeight
218
+
x1 = x0 + cellWidth
219
+
y1 = y0 + cellHeight
220
+
return (x0, y0, x1, y1)
221
+
222
+
def getCellSize(app):
223
+
boardX0, boardY0, boardX1, boardY1 = app.boardBounds
224
+
boardWidth = boardX1 - boardX0
225
+
boardHeight = boardY1 - boardY0
226
+
cellWidth = boardWidth / app.cols
227
+
cellHeight = boardHeight / app.rows
228
+
return (cellWidth, cellHeight)
229
+
230
+
def getCell(app, x, y):
231
+
boardX0, boardY0, boardX1, boardY1 = app.boardBounds
232
+
dx = x - boardX0
233
+
dy = y - boardY0
234
+
cellWidth, cellHeight = getCellSize(app)
235
+
row = math.floor(dy / cellHeight)
236
+
col = math.floor(dx / cellWidth)
237
+
if (0 <= row < app.rows) and (0 <= col < app.cols):
238
+
return (row, col)
239
+
else:
240
+
return None
241
+
242
+
def main():
243
+
runApp()
244
+
245
+
main()
+24
react/.gitignore
+24
react/.gitignore
···
1
+
# Logs
2
+
logs
3
+
*.log
4
+
npm-debug.log*
5
+
yarn-debug.log*
6
+
yarn-error.log*
7
+
pnpm-debug.log*
8
+
lerna-debug.log*
9
+
10
+
node_modules
11
+
dist
12
+
dist-ssr
13
+
*.local
14
+
15
+
# Editor directories and files
16
+
.vscode/*
17
+
!.vscode/extensions.json
18
+
.idea
19
+
.DS_Store
20
+
*.suo
21
+
*.ntvs*
22
+
*.njsproj
23
+
*.sln
24
+
*.sw?
+75
react/README.md
+75
react/README.md
···
1
+
# React + TypeScript + Vite
2
+
3
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+
Currently, two official plugins are available:
6
+
7
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+
## React Compiler
11
+
12
+
The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information.
13
+
14
+
Note: This will impact Vite dev & build performances.
15
+
16
+
## Expanding the ESLint configuration
17
+
18
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
19
+
20
+
```js
21
+
export default defineConfig([
22
+
globalIgnores(['dist']),
23
+
{
24
+
files: ['**/*.{ts,tsx}'],
25
+
extends: [
26
+
// Other configs...
27
+
28
+
// Remove tseslint.configs.recommended and replace with this
29
+
tseslint.configs.recommendedTypeChecked,
30
+
// Alternatively, use this for stricter rules
31
+
tseslint.configs.strictTypeChecked,
32
+
// Optionally, add this for stylistic rules
33
+
tseslint.configs.stylisticTypeChecked,
34
+
35
+
// Other configs...
36
+
],
37
+
languageOptions: {
38
+
parserOptions: {
39
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
40
+
tsconfigRootDir: import.meta.dirname,
41
+
},
42
+
// other options...
43
+
},
44
+
},
45
+
])
46
+
```
47
+
48
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
49
+
50
+
```js
51
+
// eslint.config.js
52
+
import reactX from 'eslint-plugin-react-x'
53
+
import reactDom from 'eslint-plugin-react-dom'
54
+
55
+
export default defineConfig([
56
+
globalIgnores(['dist']),
57
+
{
58
+
files: ['**/*.{ts,tsx}'],
59
+
extends: [
60
+
// Other configs...
61
+
// Enable lint rules for React
62
+
reactX.configs['recommended-typescript'],
63
+
// Enable lint rules for React DOM
64
+
reactDom.configs.recommended,
65
+
],
66
+
languageOptions: {
67
+
parserOptions: {
68
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
69
+
tsconfigRootDir: import.meta.dirname,
70
+
},
71
+
// other options...
72
+
},
73
+
},
74
+
])
75
+
```
+699
react/bun.lock
+699
react/bun.lock
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"workspaces": {
4
+
"": {
5
+
"name": "react",
6
+
"dependencies": {
7
+
"@tailwindcss/vite": "^4.1.17",
8
+
"draft-js": "^0.11.7",
9
+
"react": "^19.2.0",
10
+
"react-dom": "^19.2.0",
11
+
"react-router": "^7.9.6",
12
+
"tailwindcss": "^4.1.17",
13
+
},
14
+
"devDependencies": {
15
+
"@eslint/js": "^9.39.1",
16
+
"@happy-dom/global-registrator": "^20.0.10",
17
+
"@testing-library/jest-dom": "^6.9.1",
18
+
"@testing-library/react": "^16.3.0",
19
+
"@types/bun": "latest",
20
+
"@types/draft-js": "^0.11.20",
21
+
"@types/node": "^24.10.0",
22
+
"@types/react": "^19.2.2",
23
+
"@types/react-dom": "^19.2.2",
24
+
"@vitejs/plugin-react": "^5.1.0",
25
+
"babel-plugin-react-compiler": "^1.0.0",
26
+
"eslint": "^9.39.1",
27
+
"eslint-plugin-react-hooks": "^7.0.1",
28
+
"eslint-plugin-react-refresh": "^0.4.24",
29
+
"globals": "^16.5.0",
30
+
"typescript": "~5.9.3",
31
+
"typescript-eslint": "^8.46.3",
32
+
"vite": "^7.2.2",
33
+
},
34
+
},
35
+
},
36
+
"packages": {
37
+
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
38
+
39
+
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
40
+
41
+
"@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
42
+
43
+
"@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
44
+
45
+
"@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
46
+
47
+
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
48
+
49
+
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
50
+
51
+
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
52
+
53
+
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
54
+
55
+
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
56
+
57
+
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
58
+
59
+
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
60
+
61
+
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
62
+
63
+
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
64
+
65
+
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
66
+
67
+
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
68
+
69
+
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
70
+
71
+
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
72
+
73
+
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
74
+
75
+
"@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
76
+
77
+
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
78
+
79
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
80
+
81
+
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
82
+
83
+
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
84
+
85
+
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
86
+
87
+
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
88
+
89
+
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
90
+
91
+
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
92
+
93
+
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
94
+
95
+
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
96
+
97
+
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
98
+
99
+
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
100
+
101
+
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
102
+
103
+
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
104
+
105
+
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
106
+
107
+
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
108
+
109
+
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
110
+
111
+
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
112
+
113
+
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
114
+
115
+
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
116
+
117
+
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
118
+
119
+
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
120
+
121
+
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
122
+
123
+
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
124
+
125
+
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
126
+
127
+
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
128
+
129
+
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
130
+
131
+
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
132
+
133
+
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
134
+
135
+
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
136
+
137
+
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
138
+
139
+
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
140
+
141
+
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
142
+
143
+
"@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
144
+
145
+
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
146
+
147
+
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
148
+
149
+
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.10", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.10" } }, "sha512-GU0UBt9lJKhZlY/U0Bivj9ZVepDIQoAUupAAl/90THG4/urkzXNglkVYETsnt2pGBDgQ+4vBjMAbLu6XzcKcQA=="],
150
+
151
+
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
152
+
153
+
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
154
+
155
+
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
156
+
157
+
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
158
+
159
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
160
+
161
+
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
162
+
163
+
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
164
+
165
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
166
+
167
+
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
168
+
169
+
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
170
+
171
+
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
172
+
173
+
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
174
+
175
+
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.47", "", {}, "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw=="],
176
+
177
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.2", "", { "os": "android", "cpu": "arm" }, "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA=="],
178
+
179
+
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.2", "", { "os": "android", "cpu": "arm64" }, "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g=="],
180
+
181
+
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ=="],
182
+
183
+
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw=="],
184
+
185
+
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA=="],
186
+
187
+
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA=="],
188
+
189
+
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg=="],
190
+
191
+
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q=="],
192
+
193
+
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA=="],
194
+
195
+
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ=="],
196
+
197
+
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ=="],
198
+
199
+
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g=="],
200
+
201
+
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA=="],
202
+
203
+
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ=="],
204
+
205
+
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w=="],
206
+
207
+
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw=="],
208
+
209
+
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA=="],
210
+
211
+
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.2", "", { "os": "none", "cpu": "arm64" }, "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A=="],
212
+
213
+
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA=="],
214
+
215
+
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg=="],
216
+
217
+
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw=="],
218
+
219
+
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA=="],
220
+
221
+
"@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
222
+
223
+
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="],
224
+
225
+
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="],
226
+
227
+
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="],
228
+
229
+
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="],
230
+
231
+
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="],
232
+
233
+
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="],
234
+
235
+
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="],
236
+
237
+
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="],
238
+
239
+
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="],
240
+
241
+
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="],
242
+
243
+
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="],
244
+
245
+
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="],
246
+
247
+
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
248
+
249
+
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.17", "", { "dependencies": { "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "tailwindcss": "4.1.17" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA=="],
250
+
251
+
"@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
252
+
253
+
"@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="],
254
+
255
+
"@testing-library/react": ["@testing-library/react@16.3.0", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw=="],
256
+
257
+
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
258
+
259
+
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
260
+
261
+
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
262
+
263
+
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
264
+
265
+
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
266
+
267
+
"@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],
268
+
269
+
"@types/draft-js": ["@types/draft-js@0.11.20", "", { "dependencies": { "@types/react": "*", "immutable": "~3.7.4" } }, "sha512-bZHtHxXnCu4wlUXlDWrIlJSG2LJ6wcycSWoxcTCcGd0cVOm35p0vh87qpIPzGK2NALMMvJhQXdS330iYB3iGlw=="],
270
+
271
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
272
+
273
+
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
274
+
275
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
276
+
277
+
"@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="],
278
+
279
+
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
280
+
281
+
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
282
+
283
+
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.47.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/type-utils": "8.47.0", "@typescript-eslint/utils": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.47.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA=="],
284
+
285
+
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.47.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ=="],
286
+
287
+
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.47.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.47.0", "@typescript-eslint/types": "^8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA=="],
288
+
289
+
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0" } }, "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg=="],
290
+
291
+
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.47.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g=="],
292
+
293
+
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/utils": "8.47.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A=="],
294
+
295
+
"@typescript-eslint/types": ["@typescript-eslint/types@8.47.0", "", {}, "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A=="],
296
+
297
+
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.47.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.47.0", "@typescript-eslint/tsconfig-utils": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg=="],
298
+
299
+
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.47.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ=="],
300
+
301
+
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ=="],
302
+
303
+
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.1", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.47", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA=="],
304
+
305
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
306
+
307
+
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
308
+
309
+
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
310
+
311
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
312
+
313
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
314
+
315
+
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
316
+
317
+
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
318
+
319
+
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
320
+
321
+
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
322
+
323
+
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
324
+
325
+
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.29", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA=="],
326
+
327
+
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
328
+
329
+
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
330
+
331
+
"browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
332
+
333
+
"bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],
334
+
335
+
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
336
+
337
+
"caniuse-lite": ["caniuse-lite@1.0.30001755", "", {}, "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA=="],
338
+
339
+
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
340
+
341
+
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
342
+
343
+
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
344
+
345
+
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
346
+
347
+
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
348
+
349
+
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
350
+
351
+
"core-js": ["core-js@3.47.0", "", {}, "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg=="],
352
+
353
+
"cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
354
+
355
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
356
+
357
+
"css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
358
+
359
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
360
+
361
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
362
+
363
+
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
364
+
365
+
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
366
+
367
+
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
368
+
369
+
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
370
+
371
+
"draft-js": ["draft-js@0.11.7", "", { "dependencies": { "fbjs": "^2.0.0", "immutable": "~3.7.4", "object-assign": "^4.1.1" }, "peerDependencies": { "react": ">=0.14.0", "react-dom": ">=0.14.0" } }, "sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg=="],
372
+
373
+
"electron-to-chromium": ["electron-to-chromium@1.5.255", "", {}, "sha512-Z9oIp4HrFF/cZkDPMpz2XSuVpc1THDpT4dlmATFlJUIBVCy9Vap5/rIXsASP1CscBacBqhabwh8vLctqBwEerQ=="],
374
+
375
+
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
376
+
377
+
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
378
+
379
+
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
380
+
381
+
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
382
+
383
+
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
384
+
385
+
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
386
+
387
+
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="],
388
+
389
+
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
390
+
391
+
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
392
+
393
+
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
394
+
395
+
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
396
+
397
+
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
398
+
399
+
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
400
+
401
+
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
402
+
403
+
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
404
+
405
+
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
406
+
407
+
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
408
+
409
+
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
410
+
411
+
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
412
+
413
+
"fbjs": ["fbjs@2.0.0", "", { "dependencies": { "core-js": "^3.6.4", "cross-fetch": "^3.0.4", "fbjs-css-vars": "^1.0.0", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^0.7.18" } }, "sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ=="],
414
+
415
+
"fbjs-css-vars": ["fbjs-css-vars@1.0.2", "", {}, "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="],
416
+
417
+
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
418
+
419
+
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
420
+
421
+
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
422
+
423
+
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
424
+
425
+
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
426
+
427
+
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
428
+
429
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
430
+
431
+
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
432
+
433
+
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
434
+
435
+
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
436
+
437
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
438
+
439
+
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
440
+
441
+
"happy-dom": ["happy-dom@20.0.10", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" } }, "sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g=="],
442
+
443
+
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
444
+
445
+
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
446
+
447
+
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
448
+
449
+
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
450
+
451
+
"immutable": ["immutable@3.7.6", "", {}, "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw=="],
452
+
453
+
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
454
+
455
+
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
456
+
457
+
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
458
+
459
+
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
460
+
461
+
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
462
+
463
+
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
464
+
465
+
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
466
+
467
+
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
468
+
469
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
470
+
471
+
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
472
+
473
+
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
474
+
475
+
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
476
+
477
+
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
478
+
479
+
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
480
+
481
+
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
482
+
483
+
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
484
+
485
+
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
486
+
487
+
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
488
+
489
+
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
490
+
491
+
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
492
+
493
+
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
494
+
495
+
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
496
+
497
+
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
498
+
499
+
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
500
+
501
+
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
502
+
503
+
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
504
+
505
+
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
506
+
507
+
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
508
+
509
+
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
510
+
511
+
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
512
+
513
+
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
514
+
515
+
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
516
+
517
+
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
518
+
519
+
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
520
+
521
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
522
+
523
+
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
524
+
525
+
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
526
+
527
+
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
528
+
529
+
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
530
+
531
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
532
+
533
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
534
+
535
+
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
536
+
537
+
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
538
+
539
+
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
540
+
541
+
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
542
+
543
+
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
544
+
545
+
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
546
+
547
+
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
548
+
549
+
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
550
+
551
+
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
552
+
553
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
554
+
555
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
556
+
557
+
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
558
+
559
+
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
560
+
561
+
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
562
+
563
+
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
564
+
565
+
"promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
566
+
567
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
568
+
569
+
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
570
+
571
+
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
572
+
573
+
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
574
+
575
+
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
576
+
577
+
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
578
+
579
+
"react-router": ["react-router@7.9.6", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA=="],
580
+
581
+
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
582
+
583
+
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
584
+
585
+
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
586
+
587
+
"rollup": ["rollup@4.53.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.2", "@rollup/rollup-android-arm64": "4.53.2", "@rollup/rollup-darwin-arm64": "4.53.2", "@rollup/rollup-darwin-x64": "4.53.2", "@rollup/rollup-freebsd-arm64": "4.53.2", "@rollup/rollup-freebsd-x64": "4.53.2", "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", "@rollup/rollup-linux-arm-musleabihf": "4.53.2", "@rollup/rollup-linux-arm64-gnu": "4.53.2", "@rollup/rollup-linux-arm64-musl": "4.53.2", "@rollup/rollup-linux-loong64-gnu": "4.53.2", "@rollup/rollup-linux-ppc64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-musl": "4.53.2", "@rollup/rollup-linux-s390x-gnu": "4.53.2", "@rollup/rollup-linux-x64-gnu": "4.53.2", "@rollup/rollup-linux-x64-musl": "4.53.2", "@rollup/rollup-openharmony-arm64": "4.53.2", "@rollup/rollup-win32-arm64-msvc": "4.53.2", "@rollup/rollup-win32-ia32-msvc": "4.53.2", "@rollup/rollup-win32-x64-gnu": "4.53.2", "@rollup/rollup-win32-x64-msvc": "4.53.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g=="],
588
+
589
+
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
590
+
591
+
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
592
+
593
+
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
594
+
595
+
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
596
+
597
+
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
598
+
599
+
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
600
+
601
+
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
602
+
603
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
604
+
605
+
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
606
+
607
+
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
608
+
609
+
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
610
+
611
+
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
612
+
613
+
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
614
+
615
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
616
+
617
+
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
618
+
619
+
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
620
+
621
+
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
622
+
623
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
624
+
625
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
626
+
627
+
"typescript-eslint": ["typescript-eslint@8.47.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/parser": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/utils": "8.47.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q=="],
628
+
629
+
"ua-parser-js": ["ua-parser-js@0.7.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg=="],
630
+
631
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
632
+
633
+
"update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
634
+
635
+
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
636
+
637
+
"vite": ["vite@7.2.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ=="],
638
+
639
+
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
640
+
641
+
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
642
+
643
+
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
644
+
645
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
646
+
647
+
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
648
+
649
+
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
650
+
651
+
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
652
+
653
+
"zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="],
654
+
655
+
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
656
+
657
+
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
658
+
659
+
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
660
+
661
+
"@happy-dom/global-registrator/@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="],
662
+
663
+
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
664
+
665
+
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
666
+
667
+
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
668
+
669
+
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
670
+
671
+
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
672
+
673
+
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
674
+
675
+
"@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
676
+
677
+
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
678
+
679
+
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
680
+
681
+
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
682
+
683
+
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
684
+
685
+
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
686
+
687
+
"happy-dom/@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="],
688
+
689
+
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
690
+
691
+
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
692
+
693
+
"@happy-dom/global-registrator/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
694
+
695
+
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
696
+
697
+
"happy-dom/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
698
+
}
699
+
}
+23
react/eslint.config.js
+23
react/eslint.config.js
···
1
+
import js from '@eslint/js'
2
+
import globals from 'globals'
3
+
import reactHooks from 'eslint-plugin-react-hooks'
4
+
import reactRefresh from 'eslint-plugin-react-refresh'
5
+
import tseslint from 'typescript-eslint'
6
+
import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+
export default defineConfig([
9
+
globalIgnores(['dist']),
10
+
{
11
+
files: ['**/*.{ts,tsx}'],
12
+
extends: [
13
+
js.configs.recommended,
14
+
tseslint.configs.recommended,
15
+
reactHooks.configs.flat.recommended,
16
+
reactRefresh.configs.vite,
17
+
],
18
+
languageOptions: {
19
+
ecmaVersion: 2020,
20
+
globals: globals.browser,
21
+
},
22
+
},
23
+
])
+13
react/index.html
+13
react/index.html
···
1
+
<!doctype html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8" />
5
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+
<title>react</title>
8
+
</head>
9
+
<body>
10
+
<div id="root"></div>
11
+
<script type="module" src="/src/main.tsx"></script>
12
+
</body>
13
+
</html>
+40
react/package.json
+40
react/package.json
···
1
+
{
2
+
"name": "react-blog",
3
+
"private": true,
4
+
"version": "0.0.0",
5
+
"type": "module",
6
+
"scripts": {
7
+
"dev": "vite",
8
+
"build": "tsc -b && vite build",
9
+
"lint": "eslint .",
10
+
"preview": "vite preview"
11
+
},
12
+
"dependencies": {
13
+
"@tailwindcss/vite": "^4.1.17",
14
+
"draft-js": "^0.11.7",
15
+
"react": "^19.2.0",
16
+
"react-dom": "^19.2.0",
17
+
"react-router": "^7.9.6",
18
+
"tailwindcss": "^4.1.17"
19
+
},
20
+
"devDependencies": {
21
+
"@eslint/js": "^9.39.1",
22
+
"@happy-dom/global-registrator": "^20.0.10",
23
+
"@testing-library/jest-dom": "^6.9.1",
24
+
"@testing-library/react": "^16.3.0",
25
+
"@types/bun": "latest",
26
+
"@types/draft-js": "^0.11.20",
27
+
"@types/node": "^24.10.0",
28
+
"@types/react": "^19.2.2",
29
+
"@types/react-dom": "^19.2.2",
30
+
"@vitejs/plugin-react": "^5.1.0",
31
+
"babel-plugin-react-compiler": "^1.0.0",
32
+
"eslint": "^9.39.1",
33
+
"eslint-plugin-react-hooks": "^7.0.1",
34
+
"eslint-plugin-react-refresh": "^0.4.24",
35
+
"globals": "^16.5.0",
36
+
"typescript": "~5.9.3",
37
+
"typescript-eslint": "^8.46.3",
38
+
"vite": "^7.2.2"
39
+
}
40
+
}
+3
react/preload.ts
+3
react/preload.ts
+61
react/src/App.tsx
+61
react/src/App.tsx
···
1
+
import { posts } from "./lib/post";
2
+
import { BlogPostList } from "./components/BlogPostList";
3
+
import { Link } from "react-router";
4
+
import { useState } from "react";
5
+
6
+
export function App() {
7
+
const [searchBarDisplay, displaySearchBar] = useState(false);
8
+
return (
9
+
<>
10
+
<title>Posts</title>
11
+
<div className="w-screen p-5 flex flex-col items-center gap-10">
12
+
<nav className="flex justify-between items-center w-full sticky top-0">
13
+
<h1 className="text-3xl font-bold text-left">Blog App</h1>
14
+
{searchBarDisplay ? (
15
+
<>
16
+
<input
17
+
type="text"
18
+
placeholder="Search..."
19
+
className="border border-gray-300 rounded px-2 py-1"
20
+
onChange={(e) => {
21
+
// Implement search functionality here
22
+
}}
23
+
/>
24
+
<button
25
+
className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center"
26
+
onClick={() => displaySearchBar(false)}
27
+
>
28
+
Close
29
+
</button>
30
+
</>
31
+
) : (
32
+
<button
33
+
className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center"
34
+
onClick={() => displaySearchBar(true)}
35
+
>
36
+
Search
37
+
</button>
38
+
)}
39
+
<div className="flex w-full justify-end items-center">
40
+
<Link to="/post" className="w-1/3">
41
+
<div className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center">
42
+
New Post
43
+
</div>
44
+
</Link>
45
+
</div>
46
+
</nav>
47
+
<div className="flex flex-col gap-4 md:grid md:grid-cols-3 items-center justify-between w-full">
48
+
<div className="flex w-full justify-end items-center">
49
+
<h1 className="text-5xl font-bold text-center">Posts</h1>
50
+
<Link to="/post" className="w-1/3">
51
+
<div className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center">
52
+
New Post
53
+
</div>
54
+
</Link>
55
+
</div>
56
+
</div>
57
+
<BlogPostList posts={posts} />
58
+
</div>
59
+
</>
60
+
);
61
+
}
+17
react/src/component.test.tsx
+17
react/src/component.test.tsx
···
1
+
/// <reference lib="dom" />
2
+
3
+
import { test, expect } from "bun:test";
4
+
import { render, screen } from "@testing-library/react";
5
+
import "@testing-library/jest-dom";
6
+
import { App } from "./App";
7
+
import { BrowserRouter } from "react-router";
8
+
import { posts } from "./lib/post";
9
+
10
+
test("renders app page", () => {
11
+
render(
12
+
<BrowserRouter>
13
+
<App />
14
+
</BrowserRouter>,
15
+
);
16
+
expect(screen.getAllByRole("heading")).toHaveLength(posts.length + 1);
17
+
});
+95
react/src/components/BlogPostDetail.tsx
+95
react/src/components/BlogPostDetail.tsx
···
1
+
import { useParams, Outlet } from "react-router";
2
+
import { posts } from "../lib/post";
3
+
import { Link } from "react-router";
4
+
import { ContentState, convertFromRaw, Editor, EditorState } from "draft-js";
5
+
import { useState } from "react";
6
+
import { useNavigate } from "react-router";
7
+
8
+
export function BlogPostDetail({
9
+
deletePost,
10
+
}: {
11
+
deletePost: (id: number) => void;
12
+
}) {
13
+
const { postId } = useParams();
14
+
const post = posts.find((post) => post.id === parseInt(postId!));
15
+
const [editorState, setEditorState] = useState(() => {
16
+
try {
17
+
const data = JSON.parse(`"${post?.content ?? ""}"`);
18
+
return EditorState.createWithContent(convertFromRaw(data));
19
+
} catch {
20
+
console.log("fallback");
21
+
return EditorState.createWithContent(
22
+
ContentState.createFromText(post?.content ?? ""),
23
+
);
24
+
}
25
+
});
26
+
const navigate = useNavigate();
27
+
28
+
if (!post) {
29
+
return <div>Post not found</div>;
30
+
}
31
+
32
+
const formattedDate = new Date(post.datePosted).toLocaleDateString("en-US", {
33
+
month: "long",
34
+
day: "numeric",
35
+
year: "numeric",
36
+
});
37
+
38
+
return (
39
+
<>
40
+
<title>{post.title}</title>
41
+
<div className="md:grid md:grid-cols-3 flex flex-col w-full">
42
+
<Link
43
+
to="/"
44
+
className="text-gray-700 dark:text-gray-200 hover:text-gray-400 flex justify-center items-center w-16 mb-4 md:mb-0"
45
+
>
46
+
Home
47
+
</Link>
48
+
<h1 className="text-3xl md:text-4xl font-bold text-center mb-4 md:mb-0">
49
+
{post.title}
50
+
</h1>
51
+
<div className="flex md:justify-end items-center">
52
+
<Link to={`/post?postId=${post.id}`} className="w-1/3">
53
+
<div className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center">
54
+
Edit Post
55
+
</div>
56
+
</Link>
57
+
</div>
58
+
</div>
59
+
<div className="flex flex-col gap-1 md:gap-2.5 justify-center items-center mb-1.5 md:mb-2.5">
60
+
<p className="text-gray-700 dark:text-gray-400 text-sm md:text-lg">
61
+
By: {post.author}
62
+
</p>
63
+
<p className="text-gray-600 dark:text-gray-500 text-xs md:text-base">
64
+
Published on {formattedDate}
65
+
</p>
66
+
</div>
67
+
<div className="text-sm md:text-lg md:w-3xl w-full md:mb-10">
68
+
<Editor
69
+
editorState={editorState}
70
+
onChange={setEditorState}
71
+
readOnly={true}
72
+
/>
73
+
</div>
74
+
<button
75
+
className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded cursor-pointer w-full md:w-3xl"
76
+
onClick={() => {
77
+
if (window.confirm("Are you sure you want to delete this post?")) {
78
+
deletePost(post.id);
79
+
navigate("/");
80
+
}
81
+
}}
82
+
>
83
+
Delete
84
+
</button>
85
+
</>
86
+
);
87
+
}
88
+
89
+
export function PostLayout() {
90
+
return (
91
+
<div className="flex flex-col justify-center gap-3.5 md:gap-5 items-center p-5 w-screen">
92
+
<Outlet />
93
+
</div>
94
+
);
95
+
}
+255
react/src/components/BlogPostForm.tsx
+255
react/src/components/BlogPostForm.tsx
···
1
+
import { posts, type BlogPost } from "../lib/post";
2
+
import { useRef, useState } from "react";
3
+
import { useNavigate } from "react-router";
4
+
import { useSearchParams } from "react-router";
5
+
import {
6
+
ContentState,
7
+
convertFromRaw,
8
+
convertToRaw,
9
+
Editor,
10
+
EditorState,
11
+
RichUtils,
12
+
} from "draft-js";
13
+
import "draft-js/dist/Draft.css";
14
+
15
+
export function BlogPostForm({
16
+
post,
17
+
onSubmit,
18
+
}: {
19
+
post: BlogPost | null;
20
+
onSubmit: (post: BlogPost) => void;
21
+
}) {
22
+
const [postState, setPostState] = useState({
23
+
id: post?.id ?? posts.length,
24
+
title: post?.title ?? "",
25
+
summary: post?.summary ?? "",
26
+
content: post?.content ?? "",
27
+
author: post?.author ?? "",
28
+
datePosted: post?.datePosted ?? new Date().toISOString().split("T")[0],
29
+
});
30
+
const [missing, setMissing] = useState<string[]>([]);
31
+
const [contentState, setContentState] = useState<EditorState>(() => {
32
+
if (post?.content) {
33
+
try {
34
+
const rawContent = JSON.parse(post.content);
35
+
return EditorState.createWithContent(convertFromRaw(rawContent));
36
+
} catch {
37
+
// Fallback to plain text if JSON parsing fails
38
+
return EditorState.createWithContent(
39
+
ContentState.createFromText(post.content),
40
+
);
41
+
}
42
+
}
43
+
return EditorState.createEmpty();
44
+
});
45
+
46
+
const editorRef = useRef<Editor>(null);
47
+
48
+
const navigate = useNavigate();
49
+
50
+
const handleChange = (
51
+
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
52
+
) => {
53
+
const { name, value } = event.target;
54
+
setPostState((prevState) => ({ ...prevState, [name]: value }));
55
+
};
56
+
57
+
const handleMissing = () => {
58
+
const missingFields = Object.entries(postState)
59
+
.map(([key, value]) => (value === "" ? key : null))
60
+
.filter((key) => key !== null);
61
+
setMissing(missingFields);
62
+
};
63
+
64
+
const handleContentChange = (content: EditorState) => {
65
+
setContentState(content);
66
+
const rawContent = convertToRaw(content.getCurrentContent());
67
+
setPostState((prevState) => ({
68
+
...prevState,
69
+
content: JSON.stringify(rawContent),
70
+
}));
71
+
};
72
+
73
+
const handleInlineStyle = (style: string) => {
74
+
setContentState((prevState) =>
75
+
RichUtils.toggleInlineStyle(prevState, style),
76
+
);
77
+
};
78
+
79
+
const handleSubmit = (event: React.MouseEvent<HTMLButtonElement>) => {
80
+
event.preventDefault();
81
+
82
+
if (
83
+
!postState.title ||
84
+
!postState.summary ||
85
+
!postState.content ||
86
+
!postState.author
87
+
) {
88
+
handleMissing();
89
+
return;
90
+
}
91
+
92
+
onSubmit(postState);
93
+
navigate("/");
94
+
};
95
+
96
+
return (
97
+
<form className="flex flex-col gap-4 dark:bg-slate-600 p-10 rounded-lg md:w-4xl w-md">
98
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
99
+
Title:
100
+
<input
101
+
type="text"
102
+
name="title"
103
+
className="border-gray-400 md:col-start-2 md:col-span-5 border rounded h-8 py-1 px-2 w-full"
104
+
value={postState.title}
105
+
onChange={handleChange}
106
+
required
107
+
/>
108
+
</label>
109
+
{missing.includes("title") && (
110
+
<p className="text-red-500">Title is required</p>
111
+
)}
112
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
113
+
Summary:
114
+
<textarea
115
+
name="summary"
116
+
className="border-gray-400 md:col-start-2 md:col-span-5 border rounded min-h-16 h-auto py-1 px-2 w-full"
117
+
value={postState.summary}
118
+
onChange={handleChange}
119
+
required
120
+
/>
121
+
</label>
122
+
{missing.includes("summary") && (
123
+
<p className="text-red-500">Summary is required</p>
124
+
)}
125
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
126
+
Content:
127
+
</label>
128
+
<div className="md:grid md:grid-cols-6">
129
+
<div className="md:col-start-2 md:col-span-5">
130
+
<div className="flex gap-2 mb-2 border-b pb-2">
131
+
<button
132
+
type="button"
133
+
onMouseDown={(e) => {
134
+
e.preventDefault();
135
+
handleInlineStyle("BOLD");
136
+
}}
137
+
className={`px-3 py-1 border rounded ${
138
+
contentState.getCurrentInlineStyle().has("BOLD")
139
+
? "bg-blue-500 text-white"
140
+
: "bg-gray-500"
141
+
}`}
142
+
>
143
+
<strong>B</strong>
144
+
</button>
145
+
<button
146
+
type="button"
147
+
onMouseDown={(e) => {
148
+
e.preventDefault();
149
+
handleInlineStyle("ITALIC");
150
+
}}
151
+
className={`px-3 py-1 border rounded ${
152
+
contentState.getCurrentInlineStyle().has("ITALIC")
153
+
? "bg-blue-500 text-white"
154
+
: "bg-gray-500"
155
+
}`}
156
+
>
157
+
<em>I</em>
158
+
</button>
159
+
<button
160
+
type="button"
161
+
onMouseDown={(e) => {
162
+
e.preventDefault();
163
+
handleInlineStyle("UNDERLINE");
164
+
}}
165
+
className={`px-3 py-1 border rounded ${
166
+
contentState.getCurrentInlineStyle().has("UNDERLINE")
167
+
? "bg-blue-500 text-white"
168
+
: "bg-gray-500"
169
+
}`}
170
+
>
171
+
<u>U</u>
172
+
</button>
173
+
</div>
174
+
175
+
{/* Editor */}
176
+
<div
177
+
className="border-gray-400 border rounded p-2 cursor-text min-h-48 pointer-events-auto select-text"
178
+
onMouseDown={(e) => {
179
+
if (e.target === e.currentTarget) {
180
+
e.preventDefault();
181
+
editorRef.current?.focus();
182
+
}
183
+
}}
184
+
>
185
+
<Editor
186
+
ref={editorRef}
187
+
editorState={contentState}
188
+
onChange={handleContentChange}
189
+
placeholder="Write your content here..."
190
+
/>
191
+
</div>
192
+
</div>
193
+
</div>
194
+
{missing.includes("content") && (
195
+
<p className="text-red-500">Content is required</p>
196
+
)}
197
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
198
+
Author:
199
+
<input
200
+
type="text"
201
+
name="author"
202
+
className="border-gray-400 md:col-start-2 md:col-span-5 border rounded h-8 py-1 px-2 w-full"
203
+
value={postState.author}
204
+
onChange={handleChange}
205
+
required
206
+
/>
207
+
</label>
208
+
{missing.includes("author") && (
209
+
<p className="text-red-500">Author is required</p>
210
+
)}
211
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
212
+
Date Posted:
213
+
<input
214
+
type="date"
215
+
name="datePosted"
216
+
value={postState.datePosted}
217
+
onChange={handleChange}
218
+
required
219
+
/>
220
+
</label>
221
+
<button
222
+
type="button"
223
+
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded cursor-pointer"
224
+
onClick={handleSubmit}
225
+
>
226
+
{post ? "Save Post" : "Create Post"}
227
+
</button>
228
+
</form>
229
+
);
230
+
}
231
+
232
+
export function NewPostLayout() {
233
+
const searchParams = useSearchParams()[0];
234
+
const postId = parseInt(searchParams.get("postId") ?? "-1");
235
+
const post =
236
+
postId < 0 || postId >= posts.length
237
+
? null
238
+
: posts.find((p) => p.id === postId)!;
239
+
240
+
return (
241
+
<div className="flex flex-col gap-4 items-center justify-center dark:bg-slate-700 p-10 h-screen">
242
+
<h1 className="text-2xl font-bold">New Post</h1>
243
+
<BlogPostForm
244
+
post={post}
245
+
onSubmit={(post) => {
246
+
if (post.id < posts.length) {
247
+
posts[post.id] = post;
248
+
} else {
249
+
posts.push(post);
250
+
}
251
+
}}
252
+
/>
253
+
</div>
254
+
);
255
+
}
+33
react/src/components/BlogPostItem.tsx
+33
react/src/components/BlogPostItem.tsx
···
1
+
import { Link } from "react-router";
2
+
3
+
export function BlogPostItem({
4
+
title,
5
+
idx,
6
+
datePosted,
7
+
summary,
8
+
}: {
9
+
title: string;
10
+
idx: number;
11
+
datePosted: string;
12
+
summary: string;
13
+
}) {
14
+
return (
15
+
<>
16
+
<Link to={`/entries/${idx}`}>
17
+
<div className="border border-gray-300 p-3.5 md:p-5 rounded-md flex flex-col gap justify-center items-center max-w-lg">
18
+
<div className="flex flex-row gap-4 justify-center items-center">
19
+
<p className="text-gray-500">#{idx + 1}</p>
20
+
<h2 className="text-xl md:text-2xl dark:text-gray-300 text-gray-900 font-bold ">
21
+
{title}
22
+
</h2>
23
+
</div>
24
+
<p className="text-gray-500 text-sm">Posted on {datePosted}</p>
25
+
<div className="h-3" />
26
+
<p className="text-left w-full">
27
+
{summary.length > 100 ? summary.substring(0, 100) + "..." : summary}
28
+
</p>
29
+
</div>
30
+
</Link>
31
+
</>
32
+
);
33
+
}
+26
react/src/components/BlogPostList.tsx
+26
react/src/components/BlogPostList.tsx
···
1
+
import type { BlogPost } from "../lib/post";
2
+
import { BlogPostItem } from "./BlogPostItem";
3
+
4
+
export function BlogPostList({ posts }: { posts: BlogPost[] }) {
5
+
if (!posts.length) {
6
+
return <p className="text-lg text-center">No blog posts available</p>;
7
+
}
8
+
return (
9
+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 items-center justify-center gap-5">
10
+
{posts
11
+
.sort(
12
+
(a, b) =>
13
+
new Date(b.datePosted).getTime() - new Date(a.datePosted).getTime(),
14
+
)
15
+
.map((post, idx) => (
16
+
<BlogPostItem
17
+
key={idx}
18
+
idx={post.id}
19
+
title={post.title}
20
+
summary={post.summary}
21
+
datePosted={post.datePosted}
22
+
/>
23
+
))}
24
+
</div>
25
+
);
26
+
}
+22
react/src/index.css
+22
react/src/index.css
···
1
+
@import "tailwindcss";
2
+
:root {
3
+
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
4
+
line-height: 1.5;
5
+
font-weight: 400;
6
+
7
+
color-scheme: light dark;
8
+
color: rgba(255, 255, 255, 0.87);
9
+
background-color: #242424;
10
+
11
+
font-synthesis: none;
12
+
text-rendering: optimizeLegibility;
13
+
-webkit-font-smoothing: antialiased;
14
+
-moz-osx-font-smoothing: grayscale;
15
+
}
16
+
17
+
@media (prefers-color-scheme: light) {
18
+
:root {
19
+
color: #213547;
20
+
background-color: #ffffff;
21
+
}
22
+
}
+39
react/src/lib/post.ts
+39
react/src/lib/post.ts
···
1
+
export interface BlogPost {
2
+
id: number;
3
+
datePosted: string;
4
+
title: string;
5
+
author: string;
6
+
summary: string;
7
+
content: string;
8
+
}
9
+
10
+
export const posts: BlogPost[] = [
11
+
{
12
+
id: 0,
13
+
datePosted: "2025-11-15",
14
+
title: "My First Blog Post",
15
+
author: "Samuel Shuert",
16
+
summary: "First blog post",
17
+
content:
18
+
"This is my first blog post. I am excited to share my thoughts with the world! lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
19
+
},
20
+
{
21
+
id: 1,
22
+
datePosted: "2025-11-17",
23
+
title: "My Second Blog Post",
24
+
author: "Samuel Shuert",
25
+
summary: "Another post",
26
+
content:
27
+
"This is my second blog post. I am excited to share my thoughts with the world!",
28
+
},
29
+
{
30
+
id: 2,
31
+
datePosted: "2025-11-18",
32
+
title: "My Third Blog Post",
33
+
author: "Samuel Shuert",
34
+
summary:
35
+
"The third blog post lorem ipsum dolor sit amet consectetur adipiscing elit The third blog post lorem ipsum dolor sit amet consectetur adipiscing elit",
36
+
content:
37
+
"This is my third blog post. I am excited to share my thoughts with the world!",
38
+
},
39
+
];
+32
react/src/main.tsx
+32
react/src/main.tsx
···
1
+
import { StrictMode } from "react";
2
+
import { createRoot } from "react-dom/client";
3
+
import { BrowserRouter, Routes, Route } from "react-router";
4
+
import "./index.css";
5
+
import { App } from "./App.tsx";
6
+
import { BlogPostDetail, PostLayout } from "./components/BlogPostDetail.tsx";
7
+
import { NewPostLayout } from "./components/BlogPostForm.tsx";
8
+
import { posts } from "./lib/post.ts";
9
+
10
+
const deletePost = (postId: number) => {
11
+
const index = posts.findIndex((post) => post.id === postId);
12
+
if (index !== -1) {
13
+
posts.splice(index, 1);
14
+
}
15
+
};
16
+
17
+
createRoot(document.getElementById("root")!).render(
18
+
<StrictMode>
19
+
<BrowserRouter>
20
+
<Routes>
21
+
<Route index element={<App />} />
22
+
<Route path="entries" element={<PostLayout />}>
23
+
<Route
24
+
path=":postId"
25
+
element={<BlogPostDetail deletePost={deletePost} />}
26
+
/>
27
+
</Route>
28
+
<Route path="post" element={<NewPostLayout />} />
29
+
</Routes>
30
+
</BrowserRouter>
31
+
</StrictMode>,
32
+
);
+28
react/tsconfig.app.json
+28
react/tsconfig.app.json
···
1
+
{
2
+
"compilerOptions": {
3
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+
"target": "ES2022",
5
+
"useDefineForClassFields": true,
6
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+
"module": "ESNext",
8
+
"types": ["vite/client", "@types/bun"],
9
+
"skipLibCheck": true,
10
+
11
+
/* Bundler mode */
12
+
"moduleResolution": "bundler",
13
+
"allowImportingTsExtensions": true,
14
+
"verbatimModuleSyntax": true,
15
+
"moduleDetection": "force",
16
+
"noEmit": true,
17
+
"jsx": "react-jsx",
18
+
19
+
/* Linting */
20
+
"strict": true,
21
+
"noUnusedLocals": true,
22
+
"noUnusedParameters": true,
23
+
"erasableSyntaxOnly": true,
24
+
"noFallthroughCasesInSwitch": true,
25
+
"noUncheckedSideEffectImports": true
26
+
},
27
+
"include": ["src"]
28
+
}
+7
react/tsconfig.json
+7
react/tsconfig.json
+26
react/tsconfig.node.json
+26
react/tsconfig.node.json
···
1
+
{
2
+
"compilerOptions": {
3
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+
"target": "ES2023",
5
+
"lib": ["ES2023"],
6
+
"module": "ESNext",
7
+
"types": ["@types/bun"],
8
+
"skipLibCheck": true,
9
+
10
+
/* Bundler mode */
11
+
"moduleResolution": "bundler",
12
+
"allowImportingTsExtensions": true,
13
+
"verbatimModuleSyntax": true,
14
+
"moduleDetection": "force",
15
+
"noEmit": true,
16
+
17
+
/* Linting */
18
+
"strict": true,
19
+
"noUnusedLocals": true,
20
+
"noUnusedParameters": true,
21
+
"erasableSyntaxOnly": true,
22
+
"noFallthroughCasesInSwitch": true,
23
+
"noUncheckedSideEffectImports": true
24
+
},
25
+
"include": ["vite.config.ts"]
26
+
}
+21
react/vite.config.ts
+21
react/vite.config.ts
···
1
+
import { defineConfig } from "vite";
2
+
import react from "@vitejs/plugin-react";
3
+
import tailwindcss from "@tailwindcss/vite";
4
+
5
+
// https://vite.dev/config/
6
+
export default defineConfig({
7
+
define: {
8
+
global: "globalThis",
9
+
},
10
+
server: {
11
+
allowedHosts: ["project.coded.codes"],
12
+
},
13
+
plugins: [
14
+
react({
15
+
babel: {
16
+
plugins: [["babel-plugin-react-compiler"]],
17
+
},
18
+
}),
19
+
tailwindcss(),
20
+
],
21
+
});
+34
server/.gitignore
+34
server/.gitignore
···
1
+
# dependencies (bun install)
2
+
node_modules
3
+
4
+
# output
5
+
out
6
+
dist
7
+
*.tgz
8
+
9
+
# code coverage
10
+
coverage
11
+
*.lcov
12
+
13
+
# logs
14
+
logs
15
+
_.log
16
+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17
+
18
+
# dotenv environment variable files
19
+
.env
20
+
.env.development.local
21
+
.env.test.local
22
+
.env.production.local
23
+
.env.local
24
+
25
+
# caches
26
+
.eslintcache
27
+
.cache
28
+
*.tsbuildinfo
29
+
30
+
# IntelliJ based IDEs
31
+
.idea
32
+
33
+
# Finder (MacOS) folder config
34
+
.DS_Store
+15
server/README.md
+15
server/README.md
+1
server/books.json
+1
server/books.json
···
1
+
[{"id":"9780553212471","title":"Frankenstein","author":"Mary Shelley"},{"id":"9780060935467","title":"To Kill a Mockingbird","author":"Harper Lee"},{"id":"9780141439518","title":"Pride and Prejudice","author":"Jane Austen"}]
+288
server/books.ts
+288
server/books.ts
···
1
+
import express, {
2
+
type NextFunction,
3
+
type Request,
4
+
type Response,
5
+
} from "express";
6
+
import { writeFile, readFile, exists } from "fs/promises";
7
+
8
+
const ISBN13 =
9
+
/^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)[\d-]+$/;
10
+
11
+
interface Book {
12
+
id: string;
13
+
title: string;
14
+
author: string;
15
+
}
16
+
17
+
const initBooks: () => Promise<void> = async () => {
18
+
await writeFile(
19
+
"books.json",
20
+
JSON.stringify([
21
+
{
22
+
id: "9780553212471",
23
+
title: "Frankenstein",
24
+
author: "Mary Shelley",
25
+
},
26
+
{
27
+
id: "9780060935467",
28
+
title: "To Kill a Mockingbird",
29
+
author: "Harper Lee",
30
+
},
31
+
{
32
+
id: "9780141439518",
33
+
title: "Pride and Prejudice",
34
+
author: "Jane Austen",
35
+
},
36
+
]),
37
+
);
38
+
};
39
+
40
+
enum ErrorType {
41
+
NotFound,
42
+
InvalidId,
43
+
BadData,
44
+
AlreadyExists,
45
+
}
46
+
47
+
class BookError extends Error {
48
+
public readonly status: number;
49
+
constructor(err: ErrorType) {
50
+
let msg: string;
51
+
let st: number;
52
+
switch (err) {
53
+
case ErrorType.NotFound:
54
+
msg = "Book {{id}} not found";
55
+
st = 404;
56
+
break;
57
+
case ErrorType.InvalidId:
58
+
msg = "Invalid book id ({{id}}) [must be ISBN-13 formatted]";
59
+
st = 400;
60
+
break;
61
+
case ErrorType.BadData:
62
+
msg = "Invalid book data";
63
+
st = 400;
64
+
break;
65
+
case ErrorType.AlreadyExists:
66
+
msg = "Book with id {{id}} already exists";
67
+
st = 409;
68
+
break;
69
+
}
70
+
super(msg);
71
+
this.name = "BookError";
72
+
this.status = st;
73
+
}
74
+
}
75
+
76
+
const getBooks: () => Promise<Book[]> = async () => {
77
+
if (!(await exists("books.json"))) {
78
+
await initBooks();
79
+
}
80
+
const file = await readFile("books.json", "utf-8");
81
+
if (file.length < 4) {
82
+
await initBooks();
83
+
return await getBooks();
84
+
}
85
+
return JSON.parse(file);
86
+
};
87
+
88
+
const updateBook = async (task: Book): Promise<void> => {
89
+
const books = await getBooks();
90
+
const index = books.findIndex((b) => b.id === task.id);
91
+
if (index !== -1) {
92
+
books[index] = task;
93
+
} else {
94
+
books.push(task);
95
+
}
96
+
await writeFile("books.json", JSON.stringify(books));
97
+
};
98
+
99
+
const removeBook = async (id: string): Promise<void> => {
100
+
const books = await getBooks();
101
+
const index = books.findIndex((b) => b.id === id);
102
+
if (index !== -1) {
103
+
books.splice(index, 1);
104
+
await writeFile("books.json", JSON.stringify(books));
105
+
}
106
+
};
107
+
108
+
class BadDataIssues extends Error {
109
+
missingKeys: string[];
110
+
extraKeys: string[];
111
+
badValues: [string, string][];
112
+
113
+
constructor(
114
+
missingKeys: string[],
115
+
extraKeys: string[],
116
+
badValues: [string, string][],
117
+
) {
118
+
super("Bad data issues");
119
+
this.missingKeys = missingKeys;
120
+
this.extraKeys = extraKeys;
121
+
this.badValues = badValues;
122
+
}
123
+
}
124
+
125
+
const keyTypes = {
126
+
id: "ISBN13 code",
127
+
title: "string",
128
+
author: "string",
129
+
};
130
+
131
+
const validateBook = (task: { [key: string]: any }): Book => {
132
+
let missingKeys = ["id", "title", "author"].filter(
133
+
(key) => !Object.keys(task).includes(key),
134
+
);
135
+
let extraKeys = Object.keys(task).filter(
136
+
(key) => !["id", "title", "author"].includes(key),
137
+
);
138
+
let badValues = Object.entries(task)
139
+
.filter(([key, value]) => {
140
+
if (key === "id") return typeof value !== "string" || !ISBN13.test(value);
141
+
if (key === "title") return typeof value !== "string";
142
+
if (key === "author") return typeof value !== "string";
143
+
return false;
144
+
})
145
+
.map(
146
+
([key, _value]) =>
147
+
[key, keyTypes[key as keyof typeof keyTypes]] as [string, string],
148
+
);
149
+
if (missingKeys.length > 0 || extraKeys.length > 0 || badValues.length > 0) {
150
+
throw new BadDataIssues(missingKeys, extraKeys, badValues);
151
+
}
152
+
return task as Book;
153
+
};
154
+
155
+
const auth = async (req: Request, res: Response, next: NextFunction) => {
156
+
if (req.method === "GET") {
157
+
next();
158
+
return;
159
+
}
160
+
if (!req.headers.authorization) {
161
+
res.status(401).json({ error: "Unauthorized" });
162
+
return;
163
+
}
164
+
let token = req.headers.authorization.split(" ")[1];
165
+
if (token !== "password1!") {
166
+
res.status(401).json({ error: "Unauthorized" });
167
+
return;
168
+
}
169
+
next();
170
+
};
171
+
172
+
const errorHandler = (
173
+
err: Error,
174
+
_req: Request,
175
+
res: Response,
176
+
_next: NextFunction,
177
+
) => {
178
+
if (err instanceof BookError) {
179
+
let msg = err.message.replace("{{id}}", res.locals.id ?? "");
180
+
181
+
let obj: Map<string, any> = new Map<string, any>([
182
+
["error", `${err.name}: ${msg}`],
183
+
]);
184
+
185
+
if (res.locals.bdi) {
186
+
if (res.locals.bdi.missingKeys.length > 0) {
187
+
obj.set("missingKeys", res.locals.bdi.missingKeys);
188
+
}
189
+
if (res.locals.bdi.extraKeys.length > 0) {
190
+
obj.set("extraKeys", res.locals.bdi.extraKeys);
191
+
}
192
+
if (res.locals.bdi.badValues.length > 0) {
193
+
obj.set("badValues", res.locals.bdi.badValues);
194
+
}
195
+
}
196
+
197
+
res.status(err.status).json(Object.fromEntries(obj.entries()));
198
+
} else {
199
+
console.error(err.stack);
200
+
res.status(500).json({ error: "Internal Server Error" });
201
+
}
202
+
};
203
+
204
+
const router = express.Router();
205
+
206
+
router.use(express.json());
207
+
router.use((req, res, next) => {
208
+
console.log(`Recieved a ${req.method} request to ${req.url}`);
209
+
next();
210
+
});
211
+
router.use(auth);
212
+
213
+
router.get("/", async (_req, res) => {
214
+
res.json(await getBooks());
215
+
});
216
+
217
+
router.post("/", async (req, res) => {
218
+
const books = await getBooks();
219
+
try {
220
+
const bookData = validateBook(req.body);
221
+
res.locals.id = bookData.id;
222
+
if (books.filter((b) => b.id === bookData.id).length > 0) {
223
+
throw new BookError(ErrorType.AlreadyExists);
224
+
}
225
+
await updateBook(bookData);
226
+
res.status(201).json(bookData);
227
+
} catch (err) {
228
+
if (err instanceof BookError) {
229
+
throw err;
230
+
} else if (err instanceof BadDataIssues) {
231
+
res.locals.bdi = err;
232
+
throw new BookError(ErrorType.BadData);
233
+
} else {
234
+
res.status(500).json({ error: "Internal Server Error" });
235
+
}
236
+
}
237
+
});
238
+
239
+
router.get("/:id", async (req, res) => {
240
+
res.locals.id = req.params.id;
241
+
if (!ISBN13.test(req.params.id)) {
242
+
throw new BookError(ErrorType.InvalidId);
243
+
}
244
+
const books = await getBooks();
245
+
const book = books.find((b) => b.id == req.params.id);
246
+
if (!book) throw new BookError(ErrorType.NotFound);
247
+
res.json(book);
248
+
});
249
+
250
+
router.put("/:id", async (req, res) => {
251
+
res.locals.id = req.params.id;
252
+
if (!ISBN13.test(req.params.id)) {
253
+
throw new BookError(ErrorType.InvalidId);
254
+
}
255
+
const books = await getBooks();
256
+
const book = books.find((b) => b.id == req.params.id);
257
+
if (!book) throw new BookError(ErrorType.NotFound);
258
+
const bookData = validateBook(req.body);
259
+
await updateBook(bookData);
260
+
res.sendStatus(204);
261
+
});
262
+
263
+
router.delete("/reset", async (_req, res) => {
264
+
await initBooks();
265
+
res.sendStatus(204);
266
+
});
267
+
268
+
router.delete("/:id", async (req, res) => {
269
+
res.locals.id = req.params.id;
270
+
if (!ISBN13.test(req.params.id)) {
271
+
throw new BookError(ErrorType.InvalidId);
272
+
}
273
+
const books = await getBooks();
274
+
const book = books.find((b) => b.id == req.params.id);
275
+
if (!book) throw new BookError(ErrorType.NotFound);
276
+
await removeBook(book.id);
277
+
res.sendStatus(204);
278
+
});
279
+
280
+
router.all("/{*splat}", async (req, res) => {
281
+
res
282
+
.status(404)
283
+
.json({ error: `path: ${req.method} at /${req.params.splat} Not Found` });
284
+
});
285
+
286
+
router.use(errorHandler);
287
+
288
+
export default router;
+177
server/bun.lock
+177
server/bun.lock
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"workspaces": {
4
+
"": {
5
+
"name": "server",
6
+
"dependencies": {
7
+
"@types/express": "^5.0.6",
8
+
"express": "^5.2.1",
9
+
},
10
+
"devDependencies": {
11
+
"@types/bun": "latest",
12
+
},
13
+
"peerDependencies": {
14
+
"typescript": "^5",
15
+
},
16
+
},
17
+
},
18
+
"packages": {
19
+
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
20
+
21
+
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
22
+
23
+
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
24
+
25
+
"@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="],
26
+
27
+
"@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.0", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA=="],
28
+
29
+
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
30
+
31
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
32
+
33
+
"@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
34
+
35
+
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
36
+
37
+
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
38
+
39
+
"@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="],
40
+
41
+
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
42
+
43
+
"body-parser": ["body-parser@2.2.1", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
44
+
45
+
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
46
+
47
+
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
48
+
49
+
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
50
+
51
+
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
52
+
53
+
"content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
54
+
55
+
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
56
+
57
+
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
58
+
59
+
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
60
+
61
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
62
+
63
+
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
64
+
65
+
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
66
+
67
+
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
68
+
69
+
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
70
+
71
+
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
72
+
73
+
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
74
+
75
+
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
76
+
77
+
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
78
+
79
+
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
80
+
81
+
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
82
+
83
+
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
84
+
85
+
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
86
+
87
+
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
88
+
89
+
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
90
+
91
+
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
92
+
93
+
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
94
+
95
+
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
96
+
97
+
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
98
+
99
+
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
100
+
101
+
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
102
+
103
+
"iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
104
+
105
+
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
106
+
107
+
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
108
+
109
+
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
110
+
111
+
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
112
+
113
+
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
114
+
115
+
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
116
+
117
+
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
118
+
119
+
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
120
+
121
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
122
+
123
+
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
124
+
125
+
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
126
+
127
+
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
128
+
129
+
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
130
+
131
+
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
132
+
133
+
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
134
+
135
+
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
136
+
137
+
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
138
+
139
+
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
140
+
141
+
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
142
+
143
+
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
144
+
145
+
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
146
+
147
+
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
148
+
149
+
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
150
+
151
+
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
152
+
153
+
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
154
+
155
+
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
156
+
157
+
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
158
+
159
+
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
160
+
161
+
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
162
+
163
+
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
164
+
165
+
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
166
+
167
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
168
+
169
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
170
+
171
+
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
172
+
173
+
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
174
+
175
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
176
+
}
177
+
}
+13
server/index.ts
+13
server/index.ts
···
1
+
import express from "express";
2
+
import tasks from "./tasks.ts";
3
+
import books from "./books.ts";
4
+
5
+
const app = express();
6
+
const port = process.env["NODE_PORT"] ?? 5173;
7
+
8
+
app.use("/tasks", tasks);
9
+
app.use("/books", books);
10
+
11
+
app.listen(port, () => {
12
+
console.log(`Server listening on port ${port}`);
13
+
});
+16
server/package.json
+16
server/package.json
···
1
+
{
2
+
"name": "server",
3
+
"module": "index.ts",
4
+
"type": "module",
5
+
"private": true,
6
+
"devDependencies": {
7
+
"@types/bun": "latest"
8
+
},
9
+
"peerDependencies": {
10
+
"typescript": "^5"
11
+
},
12
+
"dependencies": {
13
+
"@types/express": "^5.0.6",
14
+
"express": "^5.2.1"
15
+
}
16
+
}
server/tasks.json
server/tasks.json
This is a binary file and will not be displayed.
+146
server/tasks.ts
+146
server/tasks.ts
···
1
+
import express from "express";
2
+
import { readFile, writeFile, exists } from "fs/promises";
3
+
4
+
const router = express.Router();
5
+
6
+
interface Task {
7
+
id: string;
8
+
title: string;
9
+
completed: boolean;
10
+
}
11
+
12
+
const getTasks: () => Promise<Task[]> = async () => {
13
+
if (!(await exists("tasks.json"))) {
14
+
await writeFile("tasks.json", JSON.stringify([]));
15
+
}
16
+
const file = await readFile("tasks.json", "utf-8");
17
+
if (file.length === 0) {
18
+
return [];
19
+
}
20
+
return JSON.parse(file);
21
+
};
22
+
23
+
const updateTask = async (task: Task): Promise<void> => {
24
+
const tasks = await getTasks();
25
+
const index = tasks.findIndex((t) => t.id === task.id);
26
+
if (index !== -1) {
27
+
tasks[index] = task;
28
+
} else {
29
+
tasks.push(task);
30
+
}
31
+
await writeFile("tasks.json", JSON.stringify(tasks));
32
+
};
33
+
34
+
const removeTask = async (id: string): Promise<void> => {
35
+
const tasks = await getTasks();
36
+
const index = tasks.findIndex((t) => t.id === id);
37
+
if (index !== -1) {
38
+
tasks.splice(index, 1);
39
+
await writeFile("tasks.json", JSON.stringify(tasks));
40
+
}
41
+
};
42
+
43
+
router.use(express.json());
44
+
45
+
router.get("/", async (_req, res) => {
46
+
res.json(await getTasks());
47
+
});
48
+
49
+
router.post("/", async (req, res) => {
50
+
if (!(typeof req.body.title === "string")) {
51
+
res.status(400).send("Invalid title");
52
+
return;
53
+
}
54
+
const newTask = {
55
+
id: Math.random().toString(16).substring(2, 8),
56
+
title: req.body.title,
57
+
completed: false,
58
+
};
59
+
await updateTask(newTask);
60
+
res.json(newTask).status(201);
61
+
});
62
+
63
+
router.get("/:id", async (req, res) => {
64
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
65
+
if (!task) {
66
+
res.status(404).send("Task not found");
67
+
} else {
68
+
res.json(task);
69
+
}
70
+
});
71
+
72
+
router.put("/:id", async (req, res) => {
73
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
74
+
if (!task) {
75
+
res.status(404).send("Task not found");
76
+
} else {
77
+
const missing = [];
78
+
if (req.body.title === undefined) missing.push("title");
79
+
if (req.body.completed === undefined) missing.push("completed");
80
+
if (missing.length > 0) {
81
+
res
82
+
.status(400)
83
+
.send(
84
+
`Missing field${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`,
85
+
);
86
+
}
87
+
const badTypes = [];
88
+
if (!(typeof req.body.title === "string")) badTypes.push("title");
89
+
if (!(typeof req.body.completed === "boolean")) badTypes.push("completed");
90
+
if (badTypes.length > 0) {
91
+
res
92
+
.status(400)
93
+
.send(
94
+
`Invalid type${badTypes.length > 1 ? "s" : ""}: ${badTypes.join(", ")}`,
95
+
);
96
+
return;
97
+
}
98
+
task.title = req.body.title ?? task.title;
99
+
task.completed = req.body.completed ?? task.completed;
100
+
await updateTask(task);
101
+
res.json(task);
102
+
}
103
+
});
104
+
105
+
router.patch("/:id", async (req, res) => {
106
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
107
+
if (!task) {
108
+
res.status(404).send("Task not found");
109
+
} else {
110
+
const badTypes = [];
111
+
if (
112
+
Object.keys(req.body).includes("title") &&
113
+
!(typeof req.body.title === "string")
114
+
)
115
+
badTypes.push("title");
116
+
if (
117
+
Object.keys(req.body).includes("completed") &&
118
+
!(typeof req.body.completed === "boolean")
119
+
)
120
+
badTypes.push("completed");
121
+
if (badTypes.length > 0) {
122
+
res
123
+
.status(400)
124
+
.send(
125
+
`Invalid type${badTypes.length > 1 ? "s" : ""}: ${badTypes.join(", ")}`,
126
+
);
127
+
return;
128
+
}
129
+
task.title = req.body.title ?? task.title;
130
+
task.completed = req.body.completed ?? task.completed;
131
+
await updateTask(task);
132
+
res.json(task);
133
+
}
134
+
});
135
+
136
+
router.delete("/:id", async (req, res) => {
137
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
138
+
if (!task) {
139
+
res.status(404).send("Task not found");
140
+
} else {
141
+
await removeTask(task.id);
142
+
res.status(204).send();
143
+
}
144
+
});
145
+
146
+
export default router;
+29
server/tsconfig.json
+29
server/tsconfig.json
···
1
+
{
2
+
"compilerOptions": {
3
+
// Environment setup & latest features
4
+
"lib": ["ESNext"],
5
+
"target": "ESNext",
6
+
"module": "Preserve",
7
+
"moduleDetection": "force",
8
+
"jsx": "react-jsx",
9
+
"allowJs": true,
10
+
11
+
// Bundler mode
12
+
"moduleResolution": "bundler",
13
+
"allowImportingTsExtensions": true,
14
+
"verbatimModuleSyntax": true,
15
+
"noEmit": true,
16
+
17
+
// Best practices
18
+
"strict": true,
19
+
"skipLibCheck": true,
20
+
"noFallthroughCasesInSwitch": true,
21
+
"noUncheckedIndexedAccess": true,
22
+
"noImplicitOverride": true,
23
+
24
+
// Some stricter flags (disabled by default)
25
+
"noUnusedLocals": false,
26
+
"noUnusedParameters": false,
27
+
"noPropertyAccessFromIndexSignature": false
28
+
}
29
+
}
+36
ts/bun.lock
+36
ts/bun.lock
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"workspaces": {
4
+
"": {
5
+
"name": "ts",
6
+
"dependencies": {
7
+
"glob-to-regex.js": "^1.2.0",
8
+
},
9
+
"devDependencies": {
10
+
"@types/bun": "latest",
11
+
},
12
+
"peerDependencies": {
13
+
"typescript": "^5",
14
+
},
15
+
},
16
+
},
17
+
"packages": {
18
+
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
19
+
20
+
"@types/node": ["@types/node@24.8.1", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q=="],
21
+
22
+
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
23
+
24
+
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
25
+
26
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
27
+
28
+
"glob-to-regex.js": ["glob-to-regex.js@1.2.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ=="],
29
+
30
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
31
+
32
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
33
+
34
+
"undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
35
+
}
36
+
}
+1
ts/index.ts
+1
ts/index.ts
···
1
+
console.log("Nothing actually here");
+6
ts/oct21/collatz.ts
+6
ts/oct21/collatz.ts
+8
ts/oct21/evenDigits.ts
+8
ts/oct21/evenDigits.ts
+6
ts/oct21/fizzBuzz.ts
+6
ts/oct21/fizzBuzz.ts
+23
ts/oct21/index.test.ts
+23
ts/oct21/index.test.ts
···
1
+
import { expect, test } from "bun:test";
2
+
3
+
import { fizzBuzz } from "./fizzBuzz";
4
+
import { collatz } from "./collatz";
5
+
import { evenDigits } from "./evenDigits";
6
+
7
+
test("FizzBuzz", () => {
8
+
expect(fizzBuzz(1)).toBe("1");
9
+
expect(fizzBuzz(3)).toBe("fizz");
10
+
expect(fizzBuzz(5)).toBe("buzz");
11
+
expect(fizzBuzz(15)).toBe("fizzBuzz");
12
+
});
13
+
14
+
test("Collatz", () => {
15
+
expect(collatz(3)).toEqual([3, 10, 5, 16, 8, 4, 2, 1]);
16
+
expect(collatz(1)).toEqual([1, 4, 2, 1]);
17
+
expect(collatz(4)).toEqual([4, 2, 1]);
18
+
});
19
+
20
+
test("All Even Digits", () => {
21
+
expect(evenDigits(2486)).toBe(true);
22
+
expect(evenDigits(1234)).toBe(false);
23
+
});
+15
ts/package.json
+15
ts/package.json
+48
ts/searchEngine/crawler.test.ts
+48
ts/searchEngine/crawler.test.ts
···
1
+
import { describe, it, beforeEach, expect } from "bun:test";
2
+
import { SearchIndex } from ".";
3
+
import { Crawler, RobotsParser } from "./crawler";
4
+
import { sleep } from "bun";
5
+
6
+
describe("Robots Parser", () => {
7
+
it("should parse robots.txt file", () => {
8
+
const robotsTxt = `
9
+
User-agent: *
10
+
User-agent: crawl
11
+
Disallow: /admin
12
+
Allow: /public
13
+
14
+
User-Agent: crawl
15
+
Disallow: /no-robots
16
+
`;
17
+
const robotsParser = new RobotsParser(robotsTxt);
18
+
const { allows, disallows } = robotsParser.getUrlsForUA("crawl");
19
+
const urls = {
20
+
allows,
21
+
disallows,
22
+
};
23
+
expect(allows.has("/public")).toBe(true);
24
+
expect(disallows.has("/admin")).toBe(true);
25
+
expect(RobotsParser.checkUserAgent(urls, "/admin")).toBe(false);
26
+
expect(RobotsParser.checkUserAgent(urls, "/public")).toBe(true);
27
+
});
28
+
});
29
+
30
+
describe("Crawler", () => {
31
+
let crawler: Crawler;
32
+
beforeEach(() => {
33
+
crawler = new Crawler("SmartFridge", new SearchIndex());
34
+
});
35
+
36
+
it("should crawl a page", () => {
37
+
const url = new URL("https://google.com");
38
+
crawler.crawl(url);
39
+
crawler.on("storePage", (url) => {
40
+
console.log(`Page stored: ${url}`);
41
+
sleep(4000).then(() => {
42
+
crawler.emit("stop");
43
+
expect(crawler.index.size()).toBe(1);
44
+
});
45
+
});
46
+
// expect(crawler.index).toBe(1);
47
+
});
48
+
});
+211
ts/searchEngine/crawler.ts
+211
ts/searchEngine/crawler.ts
···
1
+
import { SearchIndex } from ".";
2
+
import { toRegex } from "glob-to-regex.js";
3
+
import { EventEmitter } from "node:events";
4
+
5
+
interface RobotUrls {
6
+
allows: Set<string>;
7
+
disallows: Set<string>;
8
+
}
9
+
10
+
export class RobotsParser {
11
+
disallow: Map<string, Set<string>> = new Map();
12
+
allow: Map<string, Set<string>> = new Map();
13
+
14
+
constructor(text: string) {
15
+
const lines = text
16
+
.split("\n")
17
+
.filter((l) => !/^\s*#.*$/.test(l)) // remove full-line comments
18
+
.map((l) => l.replace(/\s*#.*$/, "")); // remove end-of-line comments
19
+
lines.push("");
20
+
21
+
const blocks: Array<Array<string>> = [];
22
+
let current_block: Array<string> = [];
23
+
lines.forEach((line) => {
24
+
if (line == "") {
25
+
if (current_block.length == 0) return; // ignore consecutive empty lines
26
+
blocks.push(current_block);
27
+
current_block = new Array();
28
+
} else {
29
+
current_block.push(line);
30
+
}
31
+
});
32
+
33
+
blocks.forEach((block) => {
34
+
let uas: string[] = [];
35
+
let disallows: string[] = [];
36
+
let allows: string[] = [];
37
+
block.forEach((line) => {
38
+
line = line.trim().toLowerCase();
39
+
const fields: Array<string> = line.split(/\s*:\s*/);
40
+
if (fields.length < 2) return;
41
+
if (fields[0] == "user-agent") {
42
+
uas.push(fields[1]!);
43
+
} else if (fields[0] == "disallow") {
44
+
disallows.push(fields[1]!);
45
+
} else if (fields[0] == "allow") {
46
+
allows.push(fields[1]!);
47
+
}
48
+
});
49
+
uas.forEach((ua) => {
50
+
ua = ua.toLowerCase();
51
+
this.disallow.set(
52
+
ua,
53
+
new Set([...(this.disallow.get(ua) || []), ...disallows]),
54
+
);
55
+
this.allow.set(
56
+
ua,
57
+
new Set([...(this.allow.get(ua) || []), ...allows]),
58
+
);
59
+
});
60
+
});
61
+
}
62
+
63
+
static checkUserAgent(urls: RobotUrls, url: string): boolean {
64
+
const { allows, disallows } = urls;
65
+
const allowed = allows
66
+
.values()
67
+
.map((allow) => {
68
+
const regex = toRegex(allow);
69
+
return regex.test(url);
70
+
})
71
+
.reduce((acc, curr) => acc || curr, false);
72
+
if (allowed) {
73
+
return true;
74
+
}
75
+
const disallowed = disallows
76
+
.values()
77
+
.map((disallow) => {
78
+
const regex = toRegex(disallow);
79
+
return regex.test(url);
80
+
})
81
+
.reduce((acc, curr) => acc || curr, false);
82
+
return !disallowed;
83
+
}
84
+
85
+
getUrlsForUA(ua: string): RobotUrls {
86
+
ua = ua.toLowerCase();
87
+
const allowUAs = this.allow
88
+
.keys()
89
+
.filter((key) => toRegex(key).test(ua));
90
+
const disallowUAs = this.disallow
91
+
.keys()
92
+
.filter((key) => toRegex(key).test(ua));
93
+
let allows = new Set<string>();
94
+
let disallows = new Set<string>();
95
+
96
+
allowUAs.forEach((ua) => {
97
+
const allow = this.allow.get(ua);
98
+
if (allow) {
99
+
allows = allows.union(allow);
100
+
}
101
+
});
102
+
disallowUAs.forEach((ua) => {
103
+
const disallow = this.disallow.get(ua);
104
+
if (disallow) {
105
+
disallows = disallows.union(disallow);
106
+
}
107
+
});
108
+
return {
109
+
allows,
110
+
disallows,
111
+
};
112
+
}
113
+
}
114
+
115
+
const urlRegex = /https?:\/\/[^\s\"]+/g;
116
+
export class Crawler extends EventEmitter {
117
+
private robots: Map<string, RobotUrls> = new Map(); // hostname, robots allowed and disallowed for the sepcified UA
118
+
private visited: Set<URL> = new Set(); // URLS
119
+
120
+
constructor(
121
+
private readonly UA: string,
122
+
public index: SearchIndex,
123
+
) {
124
+
super();
125
+
this.on("addURL", (url: URL) => {
126
+
console.log(`Adding URL: ${url}`);
127
+
void this.processPage(url);
128
+
});
129
+
this.once("stop", () => {
130
+
this.removeAllListeners();
131
+
});
132
+
}
133
+
134
+
private async checkDisallowed(url: URL): Promise<boolean> {
135
+
const robots =
136
+
this.robots.get(url.hostname) || (await this.getRobotsTxt(url));
137
+
return !RobotsParser.checkUserAgent(robots, url.toString());
138
+
}
139
+
140
+
private async getRobotsTxt(url: URL): Promise<RobotUrls> {
141
+
const robotsTxtUrl = new URL(
142
+
`${url.protocol}//${url.hostname}/robots.txt`,
143
+
);
144
+
145
+
const response = await fetch(robotsTxtUrl, {
146
+
headers: {
147
+
"User-Agent": this.UA,
148
+
},
149
+
});
150
+
if (response.status !== 200)
151
+
return { allows: new Set(), disallows: new Set() };
152
+
if (!response.headers.get("content-type")?.startsWith("text/plain"))
153
+
return { allows: new Set(), disallows: new Set() };
154
+
const robotsTxt = await response.text();
155
+
const parsed = new RobotsParser(robotsTxt);
156
+
const forUA = parsed.getUrlsForUA(this.UA);
157
+
this.robots.set(url.hostname, forUA);
158
+
return forUA;
159
+
}
160
+
161
+
private async addOutlinks(html: string): Promise<void> {
162
+
const links = html.matchAll(urlRegex);
163
+
if (!links) return;
164
+
for (const [link, ..._] of links) {
165
+
console.log(link);
166
+
const url = new URL(link);
167
+
if (await this.checkDisallowed(url)) {
168
+
this.emit("addURL", url);
169
+
}
170
+
}
171
+
}
172
+
173
+
// private getText(html: string): string {
174
+
// const parser = new DOMParser();
175
+
// const doc = parser.parseFromString(html, "text/html");
176
+
// return doc.body.textContent || "";
177
+
// }
178
+
179
+
private async getPage(url: URL) {
180
+
if (this.visited.has(url)) return;
181
+
if (await this.checkDisallowed(url)) return;
182
+
const page = await fetch(url);
183
+
this.visited.add(url);
184
+
if (!page.ok) return;
185
+
if (!page.headers.get("Content-Type")?.startsWith("text/html")) return;
186
+
187
+
return await page.text();
188
+
}
189
+
190
+
private async processPage(url: URL) {
191
+
const page = await this.getPage(url);
192
+
if (!page) return;
193
+
await this.addOutlinks(page);
194
+
this.index.addPage(url.toString(), page);
195
+
this.emit("storePage", url);
196
+
}
197
+
198
+
crawl(url_str: string | URL) {
199
+
this.emit("addURL", new URL(url_str));
200
+
}
201
+
}
202
+
203
+
let crawler = new Crawler("SmartFridge", new SearchIndex());
204
+
205
+
const url = new URL("https://example.com");
206
+
crawler.crawl(url);
207
+
crawler.on("storePage", (url) => {
208
+
console.log(`Page stored: ${url}`);
209
+
console.log("entries:", crawler.index.size());
210
+
crawler.emit("stop");
211
+
});
+138
ts/searchEngine/index.test.ts
+138
ts/searchEngine/index.test.ts
···
1
+
import { describe, it, beforeEach, expect } from "bun:test";
2
+
import { SearchIndex } from ".";
3
+
import { Crawler } from "./crawler";
4
+
5
+
describe("Search Index", () => {
6
+
let index: SearchIndex;
7
+
beforeEach(() => {
8
+
index = new SearchIndex();
9
+
});
10
+
11
+
it("should add a new page to the index", () => {
12
+
index.addPage(
13
+
"https://www.example.com",
14
+
"This is a sample webpage about dogs",
15
+
);
16
+
expect(index.getPagesForKeyword("dogs")).toContain(
17
+
"https://www.example.com",
18
+
);
19
+
});
20
+
it("should return an empty list for the keyword", () => {
21
+
expect(index.getPagesForKeyword("pineapple")).toBeEmpty();
22
+
});
23
+
it("should update a page in the index", () => {
24
+
index.addPage(
25
+
"https://www.example.com",
26
+
"This is a sample web page about dogs",
27
+
);
28
+
index.updatePage(
29
+
"https://www.example.com",
30
+
"This is a sample web page about cats",
31
+
);
32
+
expect(index.getPagesForKeyword("dogs")).not.toContain(
33
+
"https://www.example.com",
34
+
);
35
+
expect(index.getPagesForKeyword("cats")).toContain(
36
+
"https://www.example.com",
37
+
);
38
+
});
39
+
it("should remove a page from the index", () => {
40
+
index.addPage(
41
+
"https://www.example.com",
42
+
"This is a sample web page about cats",
43
+
);
44
+
index.removePage("https://www.example.com");
45
+
expect(index.getPagesForKeyword("cats")).not.toContain(
46
+
"https://www.example.com",
47
+
);
48
+
});
49
+
it("should return relevant pages for a keyword", () => {
50
+
index.addPage(
51
+
"https://www.example.com",
52
+
"This is a sample web page about cats",
53
+
);
54
+
expect(index.getPagesForKeyword("cats")).toContain(
55
+
"https://www.example.com",
56
+
);
57
+
});
58
+
it("should return multiple relavent pages that share a keyword", () => {
59
+
index.addPage(
60
+
"https://www.pineapple-world.com",
61
+
"We have lots of pineapples. You've never seen this many pineapples before.",
62
+
);
63
+
index.addPage(
64
+
"https://www.pineapple-is-my-favorite-fruit.com",
65
+
"I love pineapples, it's all I eat. I mean I REALLY LOVE PINEAPPLES",
66
+
);
67
+
68
+
expect(index.getPagesForKeyword("pineapples")).toBeArrayOfSize(2);
69
+
});
70
+
});
71
+
72
+
describe("Search Algorithm", () => {
73
+
let index: SearchIndex;
74
+
beforeEach(() => {
75
+
index = new SearchIndex();
76
+
index.addPage(
77
+
"https://www.beans.com",
78
+
"beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans",
79
+
);
80
+
index.addPage("https://www.beans-are-ok.com", "beans are ok I guess");
81
+
index.addPage("https://testsite.com", "beans");
82
+
index.addPage(
83
+
"https://www.example.com/cats",
84
+
"This is a sample web page about cats",
85
+
);
86
+
index.addPage(
87
+
"https://www.example.com/dogs",
88
+
"This is a sample web page about dogs and training",
89
+
);
90
+
index.addPage(
91
+
"https://www.training.com",
92
+
"This is a general training website",
93
+
);
94
+
index.addPage(
95
+
"https://www.pineapple-world.com",
96
+
"We have lots of pineapples. You've never seen this many pineapples before.",
97
+
);
98
+
index.addPage(
99
+
"https://www.pineapple-is-my-favorite-fruit.com",
100
+
"I love pineapples, it's all I eat. I mean I REALLY LOVE PINEAPPLES",
101
+
);
102
+
index.addPage(
103
+
"https://www.example.com/ml",
104
+
"This is a page about machine learning",
105
+
);
106
+
});
107
+
108
+
it("should return relevant pages for a single keyword search", () => {
109
+
const results = index.search("cats");
110
+
expect(results).toContain("https://www.example.com/cats");
111
+
});
112
+
113
+
it("should return relevant pages for a multi keyword search", () => {
114
+
const results = index.search("dogs training");
115
+
expect(results).toContainAllValues([
116
+
"https://www.example.com/dogs",
117
+
"https://www.training.com",
118
+
]);
119
+
expect(results.indexOf("https://www.example.com/dogs")).toBe(0);
120
+
expect(results.indexOf("https://www.training.com")).toBe(1);
121
+
});
122
+
123
+
it("should return relevant pages for a phrase search", () => {
124
+
const results = index.search("machine learning");
125
+
expect(results).toContain("https://www.example.com/ml");
126
+
});
127
+
128
+
it("should rank results properly by relevance", () => {
129
+
const results = index.search("beans");
130
+
expect(results.indexOf("https://www.beans.com")).toBe(0);
131
+
expect(results.indexOf("https://www.beans-are-ok.com")).toBe(1);
132
+
expect(results.indexOf("https://testsite.com")).toBe(2);
133
+
const results2 = index.search("beans beans");
134
+
expect(results2.indexOf("https://www.beans.com")).toBe(0);
135
+
expect(results2.indexOf("https://www.beans-are-ok.com")).toBe(1);
136
+
expect(results.indexOf("https://testsite.com")).toBe(2);
137
+
});
138
+
});
+221
ts/searchEngine/index.ts
+221
ts/searchEngine/index.ts
···
1
+
const articles = ["a", "an", "the", "this"];
2
+
3
+
const prepositions = [
4
+
"in",
5
+
"on",
6
+
"at",
7
+
"to",
8
+
"from",
9
+
"by",
10
+
"with",
11
+
"for",
12
+
"of",
13
+
];
14
+
15
+
const conjunctions = ["and", "or", "but", "yet", "so"];
16
+
17
+
const pronouns = ["i", "me", "he", "she", "it", "we", "they", "you"];
18
+
19
+
const auxiliaryVerbs = [
20
+
"is",
21
+
"are",
22
+
"am",
23
+
"be",
24
+
"been",
25
+
"being",
26
+
"has",
27
+
"have",
28
+
"had",
29
+
"do",
30
+
"does",
31
+
"did",
32
+
];
33
+
34
+
const commonVerbs = ["go", "get", "make", "take", "see", "come", "think"];
35
+
36
+
const adverbs = ["very", "more", "most", "also", "just", "only"];
37
+
38
+
const otherCommon = [
39
+
"not",
40
+
"no",
41
+
"yes",
42
+
"some",
43
+
"any",
44
+
"all",
45
+
"each",
46
+
"every",
47
+
"what",
48
+
"which",
49
+
"who",
50
+
"when",
51
+
"where",
52
+
"why",
53
+
"how",
54
+
];
55
+
56
+
const contractions = [
57
+
"its",
58
+
"youve",
59
+
"youre",
60
+
"weve",
61
+
"were",
62
+
"itd",
63
+
"youd",
64
+
"yall",
65
+
];
66
+
67
+
const stopWords = new Set([
68
+
...articles,
69
+
...prepositions,
70
+
...adverbs,
71
+
...otherCommon,
72
+
...commonVerbs,
73
+
...conjunctions,
74
+
...pronouns,
75
+
...auxiliaryVerbs,
76
+
...contractions,
77
+
]);
78
+
79
+
export class SearchIndex {
80
+
private index: Map<string, [string, number][]>;
81
+
82
+
constructor() {
83
+
this.index = new Map<string, [string, number][]>();
84
+
}
85
+
86
+
private getPhrases(
87
+
words: string[],
88
+
filter: boolean[],
89
+
): Map<string, number> {
90
+
const wordGroups: string[][] = [];
91
+
let currentSlice: string[] = [];
92
+
for (const [index, val] of filter.entries()) {
93
+
if (val) {
94
+
currentSlice.push(words[index]!);
95
+
continue;
96
+
}
97
+
if (currentSlice.length > 1) wordGroups.push(currentSlice);
98
+
currentSlice = [];
99
+
}
100
+
const subPhrases: string[] = wordGroups.flatMap((group) =>
101
+
this.getSubPhrases(group),
102
+
);
103
+
const subPhraseDict = new Map<string, number>();
104
+
for (const sp of subPhrases) {
105
+
subPhraseDict.set(sp, (subPhraseDict.get(sp) || 0) + 1);
106
+
}
107
+
return subPhraseDict;
108
+
}
109
+
110
+
private getSubPhrases(phrase: string[]): string[] {
111
+
const subPhrases: string[] = [phrase.join(" ")];
112
+
for (let i = 2; i < phrase.length; i++) {
113
+
for (let offset = 0; offset + i < phrase.length + 1; offset++) {
114
+
const subPhrase = phrase.slice(offset, offset + i).join(" ");
115
+
subPhrases.push(subPhrase);
116
+
}
117
+
}
118
+
return subPhrases;
119
+
}
120
+
121
+
private extractKeywords(pageContent: string): Map<string, number> {
122
+
let words: string[] = pageContent
123
+
.split(/\s+/)
124
+
.map((str) => str.replaceAll(/[^\w]+/g, "").toLowerCase())
125
+
.filter((str) => str.length > 0);
126
+
words = [...words, "a"];
127
+
const filter = words.map((word) => !stopWords.has(word));
128
+
const keywords = new Set<string>(words).difference(stopWords);
129
+
const keywordMap = new Map<string, number>(
130
+
keywords.values().map((kw) => [kw, 0]),
131
+
);
132
+
133
+
for (let word of words) {
134
+
if (keywords.has(word)) {
135
+
keywordMap.set(word, (keywordMap.get(word) || 0) + 1);
136
+
}
137
+
}
138
+
const phrases = this.getPhrases(words, filter);
139
+
return new Map([...keywordMap, ...phrases]);
140
+
}
141
+
142
+
addPage(url: string, pageContent: string): void {
143
+
let keywords = this.extractKeywords(pageContent);
144
+
for (let [kw, count] of keywords.entries()) {
145
+
if (this.index.has(kw)) {
146
+
let prev = this.index.get(kw)!;
147
+
prev.push([url, count]);
148
+
this.index.set(kw, prev);
149
+
} else {
150
+
this.index.set(kw, [[url, count]]);
151
+
}
152
+
}
153
+
}
154
+
155
+
updatePage(url: string, pageContent: string): void {
156
+
this.removePage(url);
157
+
this.addPage(url, pageContent);
158
+
}
159
+
160
+
removePage(url: string): void {
161
+
this.index.entries().forEach(([keyword, urls]) => {
162
+
const index = urls.findIndex(([u, _]) => u === url);
163
+
if (index >= 0) {
164
+
urls.splice(index, 1);
165
+
if (urls.length === 0) {
166
+
this.index.delete(keyword);
167
+
}
168
+
}
169
+
});
170
+
}
171
+
172
+
checkPage(search: string): boolean {
173
+
for (const urls of this.index.values()) {
174
+
for (const [url, _] of urls) {
175
+
if (search === url) {
176
+
return true;
177
+
}
178
+
}
179
+
}
180
+
return false;
181
+
}
182
+
183
+
size() {
184
+
return this.index.size;
185
+
}
186
+
187
+
getPagesForKeyword(keyword: string): string[] {
188
+
const pages = this.index.get(keyword);
189
+
if (!pages) {
190
+
return [];
191
+
}
192
+
return Array.from(pages)
193
+
.sort((a, b) => a[1] - b[1])
194
+
.map(([url, _]) => url);
195
+
}
196
+
197
+
search(query: string): string[] {
198
+
const urls = new Map<string, number>();
199
+
const keys = this.extractKeywords(query);
200
+
for (const [key, count] of keys.entries()) {
201
+
const pages = this.index.get(key);
202
+
if (!pages) continue;
203
+
for (const [url, v] of pages) {
204
+
urls.set(
205
+
url,
206
+
urls.has(url)
207
+
? (urls.get(url) || 0) + v * count
208
+
: v * count,
209
+
);
210
+
}
211
+
}
212
+
urls.forEach((value, key) => {
213
+
if (key.includes(query)) {
214
+
value += 10;
215
+
}
216
+
});
217
+
return Array.from(urls.entries())
218
+
.sort((a, b) => b[1] - a[1])
219
+
.map(([url, _]) => url);
220
+
}
221
+
}
ts/searchEngine/mainLoop.plan
ts/searchEngine/mainLoop.plan
This is a binary file and will not be displayed.
+29
ts/tsconfig.json
+29
ts/tsconfig.json
···
1
+
{
2
+
"compilerOptions": {
3
+
// Environment setup & latest features
4
+
"lib": ["ESNext"],
5
+
"target": "ESNext",
6
+
"module": "Preserve",
7
+
"moduleDetection": "force",
8
+
"jsx": "react-jsx",
9
+
"allowJs": true,
10
+
11
+
// Bundler mode
12
+
"moduleResolution": "bundler",
13
+
"allowImportingTsExtensions": true,
14
+
"verbatimModuleSyntax": true,
15
+
"noEmit": true,
16
+
17
+
// Best practices
18
+
"strict": true,
19
+
"skipLibCheck": true,
20
+
"noFallthroughCasesInSwitch": true,
21
+
"noUncheckedIndexedAccess": true,
22
+
"noImplicitOverride": true,
23
+
24
+
// Some stricter flags (disabled by default)
25
+
"noUnusedLocals": false,
26
+
"noUnusedParameters": false,
27
+
"noPropertyAccessFromIndexSignature": false
28
+
}
29
+
}