initial commit

This commit is contained in:
whaffman 2026-01-08 13:58:16 +01:00
commit 663cb518f9
31 changed files with 4670 additions and 0 deletions

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

9
.prettierignore Normal file
View File

@ -0,0 +1,9 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
# Miscellaneous
/static/

16
.prettierrc Normal file
View File

@ -0,0 +1,16 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
],
"tailwindStylesheet": "./src/routes/layout.css"
}

7
.vscode/mcp.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"servers": {
"svelte": {
"url": "https://mcp.svelte.dev/mcp"
}
}
}

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"files.associations": {
"*.css": "tailwindcss"
}
}

23
AGENTS.md Normal file
View File

@ -0,0 +1,23 @@
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
## Available MCP Tools:
### 1. list-sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get-documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
### 3. svelte-autofixer
Analyzes Svelte code and returns issues and suggestions.
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
### 4. playground-link
Generates a Svelte Playground link with the provided code.
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# sv
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```sh
# create a new project in the current directory
npx sv create
# create a new project in my-app
npx sv create my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```sh
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```sh
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

41
eslint.config.js Normal file
View File

@ -0,0 +1,41 @@
import prettier from 'eslint-config-prettier';
import { fileURLToPath } from 'node:url';
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';
const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
export default defineConfig(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
prettier,
...svelte.configs.prettier,
{
languageOptions: { globals: { ...globals.browser, ...globals.node } },
rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off'
}
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
svelteConfig
}
}
}
);

3980
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
package.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "daisy-test",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint ."
},
"devDependencies": {
"@eslint/compat": "^1.4.0",
"@eslint/js": "^9.39.1",
"@sveltejs/adapter-auto": "^7.0.0",
"@sveltejs/kit": "^2.49.1",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tailwindcss/vite": "^4.1.18",
"@types/node": "^24",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.13.1",
"globals": "^16.5.0",
"prettier": "^3.7.4",
"prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"svelte": "^5.45.6",
"svelte-check": "^4.3.4",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"typescript-eslint": "^8.48.1",
"vite": "^7.2.6",
"vite-plugin-devtools-json": "^1.0.0"
},
"dependencies": {
"daisyui": "^5.5.14"
}
}

6
src/app.css Normal file
View File

@ -0,0 +1,6 @@
@import "tailwindcss";
@plugin "daisyui";
body {
@apply bg-black;
}

13
src/app.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

11
src/app.html Normal file
View File

@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,226 @@
<script lang="ts">
import { onMount } from 'svelte';
let canvas: HTMLCanvasElement;
let ctx: CanvasRenderingContext2D;
let animationId: number;
interface Snake {
x: number;
y: number;
angle: number;
color: string;
trail: { x: number; y: number }[];
alive: boolean;
turnBias: number; // Unique personality trait
aggressiveness: number; // How much they turn randomly
}
let snakes: Snake[] = [];
const SPEED = 2;
const TURN_SPEED = 0.05;
const LINE_WIDTH = 4;
const CANVAS_WIDTH = 300;
const CANVAS_HEIGHT = 300;
function initSnakes() {
snakes = [
{
x: CANVAS_WIDTH * 0.3,
y: CANVAS_HEIGHT * 0.4,
angle: Math.random() * Math.PI * 2,
color: '#00ff00',
trail: [],
alive: true,
turnBias: (Math.random() - 0.5) * 0.02,
aggressiveness: 0.8 + Math.random() * 0.4
},
{
x: CANVAS_WIDTH * 0.7,
y: CANVAS_HEIGHT * 0.6,
angle: Math.random() * Math.PI * 2,
color: '#0000ff',
trail: [],
alive: true,
turnBias: (Math.random() - 0.5) * 0.02,
aggressiveness: 0.8 + Math.random() * 0.4
}
];
}
function checkCollision(snake: Snake): boolean {
// Check wall collision
if (snake.x < 0 || snake.x > CANVAS_WIDTH || snake.y < 0 || snake.y > CANVAS_HEIGHT) {
return true;
}
// Check collision with own trail (after some length)
if (snake.trail.length > 100) {
for (let i = 0; i < snake.trail.length - 50; i++) {
const dist = Math.hypot(snake.x - snake.trail[i].x, snake.y - snake.trail[i].y);
if (dist < LINE_WIDTH) {
return true;
}
}
}
// Check collision with other snakes' trails
for (const otherSnake of snakes) {
if (otherSnake === snake) continue;
if (otherSnake.trail.length < 30) continue; // Give other snake time to get away
for (const point of otherSnake.trail) {
const dist = Math.hypot(snake.x - point.x, snake.y - point.y);
if (dist < LINE_WIDTH) {
return true;
}
}
}
return false;
}
function checkDangerAhead(snake: Snake, distance: number = 80): number {
// Check in five directions for more awareness
const directions = [-0.6, -0.3, 0, 0.3, 0.6]; // left 34°, left 17°, straight, right 17°, right 34°
const scores = [0, 0, 0, 0, 0];
for (let d = 0; d < directions.length; d++) {
const checkAngle = snake.angle + directions[d];
// Check multiple points ahead for better space detection
for (let dist = 20; dist <= distance; dist += 20) {
const checkX = snake.x + Math.cos(checkAngle) * dist;
const checkY = snake.y + Math.sin(checkAngle) * dist;
// Check wall danger
if (checkX < 30 || checkX > CANVAS_WIDTH - 30 || checkY < 30 || checkY > CANVAS_HEIGHT - 30) {
scores[d] += 100;
}
// Check collision with trails
for (const otherSnake of snakes) {
const trailToCheck = otherSnake === snake ? snake.trail.slice(0, -60) : otherSnake.trail;
for (const point of trailToCheck) {
const distToPoint = Math.hypot(checkX - point.x, checkY - point.y);
if (distToPoint < LINE_WIDTH * 8) {
scores[d] += (1 - distToPoint / (LINE_WIDTH * 8)) * 30;
}
}
}
}
}
// Find the direction with the most open space (lowest score)
let bestDirection = 2; // default to straight
let lowestScore = scores[2];
for (let i = 0; i < scores.length; i++) {
if (scores[i] < lowestScore) {
lowestScore = scores[i];
bestDirection = i;
}
}
// Return turn direction based on best option
if (bestDirection === 2) return 0; // straight
if (bestDirection < 2) return -1; // turn left
return 1; // turn right
}
function updateSnake(snake: Snake) {
if (!snake.alive) return;
// Check for danger ahead and find open space
const bestDirection = checkDangerAhead(snake);
// Steer towards open space
snake.angle += bestDirection * TURN_SPEED * 4;
// Apply turn bias (personality) - reduced influence
snake.angle += snake.turnBias * 0.5;
// Less random turning since we're seeking open space
if (Math.random() < 0.01 * snake.aggressiveness) {
snake.angle += (Math.random() - 0.5) * TURN_SPEED * 4;
}
// Move forward
snake.x += Math.cos(snake.angle) * SPEED;
snake.y += Math.sin(snake.angle) * SPEED;
// Add to trail
snake.trail.push({ x: snake.x, y: snake.y });
// Check collision
if (checkCollision(snake)) {
snake.alive = false;
}
}
function draw() {
if (!ctx) return;
// Clear canvas
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Draw snakes
for (const snake of snakes) {
if (snake.trail.length < 2) continue;
ctx.strokeStyle = snake.color;
ctx.lineWidth = LINE_WIDTH;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.moveTo(snake.trail[0].x, snake.trail[0].y);
for (let i = 1; i < snake.trail.length; i++) {
ctx.lineTo(snake.trail[i].x, snake.trail[i].y);
}
ctx.stroke();
}
}
function animate() {
// Update all snakes
for (const snake of snakes) {
updateSnake(snake);
}
draw();
// Check if any snake died
const anyDead = snakes.some((s) => !s.alive);
if (anyDead) {
// Restart after a short delay
setTimeout(() => {
initSnakes();
animate(); // Restart animation loop
}, 1000);
} else {
animationId = requestAnimationFrame(animate);
}
}
onMount(() => {
ctx = canvas.getContext('2d')!;
initSnakes();
animate();
return () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
};
});
</script>
<div class="w-full flex justify-center items-center">
<canvas
bind:this={canvas}
width={CANVAS_WIDTH}
height={CANVAS_HEIGHT}
class="border border-gray-700 rounded-lg shadow-xl max-w-full h-auto"
></canvas>
</div>

View File

@ -0,0 +1,35 @@
<script lang="ts">
interface NavItem {
label: string;
href: string;
}
const {
name,
items,
loginHref,
loginLabel
}: {
name: string;
items: NavItem[];
loginHref: string;
loginLabel: string;
} = $props();
</script>
<nav class="navbar">
<div class="navbar-start">
<a class="btn btn-ghost text-xl normal-case" href="/">{name}</a>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
{#each items as item}
<li><a href={item.href}>{item.label}</a></li>
{/each}
</ul>
</div>
<div class="navbar-end">
<a class="btn" href={loginHref}>{loginLabel}</a>
</div>
</nav>

1
src/lib/index.ts Normal file
View File

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

16
src/routes/+error.svelte Normal file
View File

@ -0,0 +1,16 @@
<script lang="ts">
import { page } from '$app/stores';
</script>
<div class="flex min-h-screen flex-col items-center justify-center gap-12 p-8">
<div class="relative w-fit">
<h1 class="text-9xl font-bold">{$page.status}</h1>
<p
class="text-error absolute top-1/2 left-1/2 -translate-y-1/2 text-5xl font-black whitespace-nowrap italic"
style="transform: skewY(-5deg);"
>
{$page.error?.message}
</p>
</div>
<a href="/" class="btn btn-primary btn-lg">Go Home</a>
</div>

40
src/routes/+layout.svelte Normal file
View File

@ -0,0 +1,40 @@
<script lang="ts">
import '../app.css';
import favicon from '$lib/assets/favicon.svg';
import NavBar from '$lib/components/NavBar.svelte';
let { children } = $props();
</script>
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
<div class="flex min-h-screen w-full flex-col text-white relative overflow-hidden">
<!-- Background gradients -->
<div class="absolute inset-0 z-0 pointer-events-none">
<div class="absolute -top-[25vw] -right-[25vw] w-[50vw] h-[50vw] bg-green-500 rounded-full blur-[100px] opacity-30"></div>
<div class="absolute -bottom-[25vw] -left-[25vw] w-[50vw] h-[50vw] bg-blue-500 rounded-full blur-[100px] opacity-30"></div>
</div>
<header class="">
<NavBar
name="Achtung!"
items={[
{ label: 'Home', href: '/' },
{ label: 'About', href: '/about' },
{ label: 'Contact', href: '/contact' }
]}
loginHref="/login"
loginLabel="Login"
/>
</header>
{@render children()}
<footer class="">
<p class="py-4 text-center text-sm text-gray-300">
Transcendence - Achtung! &copy; 2024 qbeukelman, hesmolde, fras, qmennen & whaffman.
</p>
</footer>
</div>

79
src/routes/+page.svelte Normal file
View File

@ -0,0 +1,79 @@
<script lang="ts">
import KurveDemo from '$lib/components/KurveDemo.svelte';
interface Player {
name: string;
rating: number;
avatarUrl?: string;
}
let topPlayers: Player[] = [
{ name: 'Hein', rating: 2500, avatarUrl: '/avatars/hein.png' },
{ name: 'Quenten', rating: 2350, avatarUrl: '/avatars/quenten.png' },
{ name: 'Ferry', rating: 2200, avatarUrl: '/avatars/ferry.png' },
{ name: 'Quentin', rating: 2100, avatarUrl: '/avatars/quentin.png' },
{ name: 'Willem', rating: 2000, avatarUrl: '/avatars/willem.png' },
{ name: 'Johnny', rating: 1900 },
{ name: 'Alice', rating: 1800 },
{ name: 'Bob', rating: 1700 }
];
</script>
<main class="flex flex-1 flex-col items-center gap-8 p-8 lg:flex-row lg:justify-around">
<!-- Hero Section -->
<div class="hero flex items-center justify-center lg:w-1/2">
<div class="hero-content flex flex-col gap-6 text-center">
<div class="flex-start max-w-2xl">
<h1 class="text-8xl font-bold italic">ACHTUNG!</h1>
<p class="py-6 text-lg text-red-800">The Kurve Web Edition</p>
</div>
<div class="flex w-1/2 flex-col gap-6">
<button class="btn bg-slate-200 text-black">Login with Email</button>
<button class="btn bg-slate-200 text-black">Login with 42</button>
<button class="btn bg-slate-200 text-black">Login with Google</button>
</div>
</div>
</div>
<!-- Ranking Table Card Section -->
<div class="flex w-full items-center justify-center lg:w-1/2">
<div
class="card bg-base-100 w-3/4 max-w-2xl rounded-4xl border border-gray-700 opacity-90 shadow-xl shadow-gray-900"
>
<div class="card-body justify-center">
<h2 class="card-title mb-4 w-full justify-center text-center text-2xl">TOP PLAYERS</h2>
<div class="overflow-x-auto">
<table class="table w-full text-center text-xl">
<tbody>
{#each topPlayers.slice(0, 6) as player, index (player.name)}
<tr class="">
<td class="py-4">
<div class="avatar mx-auto">
{#if player.avatarUrl}
<div class="w-20 rounded-full">
<img src={player.avatarUrl} alt="Avatar of {player.name}" />
</div>
{:else}
<div class="skeleton w-20 rounded-full"></div>
{/if}
{#if index === 0}
<div
class="absolute text-2xl"
style="transform: translate(-25%, -45%) rotate(-35deg);"
>
👑
</div>
{/if}
</div>
</td>
<td class="py-4 text-left">{player.name}</td>
<td class="text-success py-4 font-bold">{player.rating}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>

1
src/routes/layout.css Normal file
View File

@ -0,0 +1 @@
@import 'tailwindcss';

BIN
static/avatars/ferry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
static/avatars/hein.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
static/avatars/quenten.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
static/avatars/quentin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
static/avatars/willem.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

3
static/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:

27
svelte.config.js Normal file
View File

@ -0,0 +1,27 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
adapter: adapter(),
},
compilerOptions: {
runes: true,
}
};
export default config;

20
tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}

6
vite.config.ts Normal file
View File

@ -0,0 +1,6 @@
import devtoolsJson from 'vite-plugin-devtools-json';
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({ plugins: [tailwindcss(), sveltekit(), devtoolsJson()] });