Feat: restructure and add basic web ui

This commit is contained in:
Dorian Zedler 2023-12-11 18:04:58 +01:00
parent c55f84d690
commit e16904fae9
Signed by: dorian
GPG key ID: 989DE36109AFA354
31 changed files with 4793 additions and 10 deletions

13
.eslintignore Normal file
View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

31
.eslintrc.cjs Normal file
View file

@ -0,0 +1,31 @@
/** @type { import("eslint").Linter.FlatConfig } */
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
.npmrc Normal file
View file

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

13
.prettierignore Normal file
View file

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

15
.prettierrc Normal file
View file

@ -0,0 +1,15 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

38
README.md Normal file
View file

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
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:
```bash
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://kit.svelte.dev/docs/adapters) for your target environment.

View file

@ -1,3 +0,0 @@
[submodule "lib/esp-nimble-cpp"]
path = lib/esp-nimble-cpp
url = https://itsblue.dev/ScStw/esp-nimble-cpp.git

10
firmware/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

View file

@ -14,9 +14,9 @@
#define TAG "main" #define TAG "main"
#define TRIGGER_PIN GPIO_NUM_23 #define TRIGGER_PIN GPIO_NUM_23
#define BLE_SERVICE_UUID "deea3136-d728-4f23-823a-1042909dd100" #define BLE_SERVICE_UUID "deea3136-d728-4f23-823a-104290000000"
#define BLE_CURRENTTIME_CHARACTERISTIC_UUID "deea3136-d728-4f23-823a-1042909dd101" #define BLE_CURRENTTIME_CHARACTERISTIC_UUID "deea3136-d728-4f23-823a-104290000001"
#define BLE_LASTTRIGGERTIME_CHARACTERISTIC_UUID "deea3136-d728-4f23-823a-1042909dd102" #define BLE_LASTTRIGGERTIME_CHARACTERISTIC_UUID "deea3136-d728-4f23-823a-104290000002"
extern "C" extern "C"
{ {
@ -29,6 +29,7 @@ NimBLECharacteristic *bleCurrentTimeCharacteristic;
NimBLECharacteristic *bleLastTriggerTimeCharacteristic; NimBLECharacteristic *bleLastTriggerTimeCharacteristic;
QueueHandle_t triggerQueue; QueueHandle_t triggerQueue;
uint64_t lastTriggerTime = 0;
class LocalServerCallbacks : public NimBLEServerCallbacks class LocalServerCallbacks : public NimBLEServerCallbacks
{ {
@ -56,15 +57,22 @@ class LocalCharacteristicCallbacks : public NimBLECharacteristicCallbacks
{ {
void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
{ {
bleCurrentTimeCharacteristic->setValue(esp_timer_get_time()); bleCurrentTimeCharacteristic->setValue(esp_timer_get_time() / 1000);
bleCurrentTimeCharacteristic->notify(); bleCurrentTimeCharacteristic->notify();
ESP_LOGI(TAG, "Characteristic written!"); // ESP_LOGI(TAG, "Characteristic written!");
} }
}; };
void handleTrigger(void *) void handleTrigger(void *)
{ {
uint64_t currentTime = esp_timer_get_time(); uint64_t currentTime = esp_timer_get_time() / 1000;
if (currentTime - lastTriggerTime < 1000)
{
return;
}
lastTriggerTime = currentTime;
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(triggerQueue, &currentTime, &xHigherPriorityTaskWoken); xQueueSendFromISR(triggerQueue, &currentTime, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) if (xHigherPriorityTaskWoken)
@ -123,7 +131,7 @@ void app_main()
{ {
bleLastTriggerTimeCharacteristic->setValue(currentTime); bleLastTriggerTimeCharacteristic->setValue(currentTime);
bleLastTriggerTimeCharacteristic->notify(); bleLastTriggerTimeCharacteristic->notify();
ESP_LOGI(TAG, "Characteristic written!"); // ESP_LOGI(TAG, "Characteristic written!");
} }
} }
} }

4057
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

43
package.json Normal file
View file

@ -0,0 +1,43 @@
{
"name": "bluetooth-buzzer",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.27.4",
"@types/ua-parser-js": "^0.7.39",
"@types/web-bluetooth": "^0.0.20",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.28.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-svelte": "^2.30.0",
"flowbite": "^2.2.0",
"flowbite-svelte": "^0.44.20",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1",
"prettier": "^3.0.0",
"prettier-plugin-tailwindcss": "^0.4.1",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"tailwindcss": "^3.3.2",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"ua-parser-js": "^1.0.37"
}
}

13
postcss.config.cjs Normal file
View file

@ -0,0 +1,13 @@
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer
]
};
module.exports = config;

12
src/app.d.ts vendored Normal file
View file

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

16
src/app.html Normal file
View file

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover"
class="bg-white dark:bg-gray-900 text-gray-600 dark:text-gray-400 antialiased">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

4
src/app.pcss Normal file
View file

@ -0,0 +1,4 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;

150
src/lib/bluetooth.ts Normal file
View file

@ -0,0 +1,150 @@
import { bluetoothState, buzzerState } from '../stores';
import { Ntp } from './ntp';
export type DeepronTimerButton = 'OK' | 'ESC' | '+' | '-' | 'RDY' | 'RST' | 'FULL_RESET';
const BLUETOOTH_BASE_UUID = 'deea3136-d728-4f23-823a-104290';
const BLUETOOTH_BUZZER_SERVICE_BASE_UUID = BLUETOOTH_BASE_UUID + '00';
const BLUETOOTH_BUZZER_SERVICE_UUID = BLUETOOTH_BUZZER_SERVICE_BASE_UUID + '0000';
let bluetoothDevice: BluetoothDevice | undefined = undefined;
let bluetoothService: BluetoothRemoteGATTService | undefined = undefined;
let bluetoothCharacteristics: {
currentTime: BluetoothRemoteGATTCharacteristic | undefined;
lastTriggerTime: BluetoothRemoteGATTCharacteristic | undefined;
} = {
currentTime: undefined,
lastTriggerTime: undefined
};
let ntp: Ntp | undefined = undefined;
let timeSyncResponse: ((time: bigint) => void) | undefined = undefined;
let timeSyncInterval: number | undefined = undefined;
function checkAvailability() {
if (!navigator.bluetooth) {
bluetoothState.set('UNAVAILABLE');
return false;
}
bluetoothState.set('DISCONNECTED');
return true;
}
async function startBluetooth() {
bluetoothState.set('CONNECTING');
try {
bluetoothDevice = await navigator.bluetooth.requestDevice({
filters: [
{
services: [BLUETOOTH_BUZZER_SERVICE_UUID]
}
],
optionalServices: [BLUETOOTH_BUZZER_SERVICE_UUID]
});
} catch (error) {
console.error(error);
bluetoothState.set('DISCONNECTED');
return;
}
console.log('> Requested ' + bluetoothDevice.name + ' (' + bluetoothDevice.id + ')');
let server;
try {
server = await bluetoothDevice.gatt!.connect();
} catch (error) {
console.log('> Error connecting to ' + bluetoothDevice.name + ' (' + bluetoothDevice.id + ')');
bluetoothState.set('DISCONNECTED');
return;
}
bluetoothDevice.addEventListener('gattserverdisconnected', onDisconnected);
bluetoothService = await server.getPrimaryService(BLUETOOTH_BUZZER_SERVICE_UUID);
bluetoothCharacteristics.currentTime = await bluetoothService.getCharacteristic(BLUETOOTH_BUZZER_SERVICE_BASE_UUID + '000' + (1));
await bluetoothCharacteristics.currentTime.startNotifications();
bluetoothCharacteristics.currentTime.addEventListener('characteristicvaluechanged', handleNotifications);
bluetoothCharacteristics.lastTriggerTime = await bluetoothService.getCharacteristic(BLUETOOTH_BUZZER_SERVICE_BASE_UUID + '000' + (2));
await bluetoothCharacteristics.lastTriggerTime.startNotifications();
bluetoothCharacteristics.lastTriggerTime.addEventListener('characteristicvaluechanged', handleNotifications);
ntp = new Ntp();
timeSyncInterval = setInterval(doOneTimeSync, 1000);
bluetoothState.set('CONNECTED');
}
function onDisconnected() {
console.log('> Bluetooth Device disconnected');
bluetoothDevice = undefined;
bluetoothService = undefined;
ntp = undefined;
timeSyncResponse = undefined;
if (timeSyncInterval !== undefined)
clearInterval(timeSyncInterval);
bluetoothState.set('DISCONNECTED');
}
function handleNotifications(this: BluetoothRemoteGATTCharacteristic, event: Event) {
if (!event.target) return;
const characteristic = event.target as BluetoothRemoteGATTCharacteristic;
if (characteristic.uuid == bluetoothCharacteristics.currentTime?.uuid)
handleNewCurrentTime(new DataView(characteristic.value!.buffer, 0).getBigUint64(0, true));
else if (characteristic.uuid == bluetoothCharacteristics.lastTriggerTime?.uuid)
handleNewLastTriggerTime(new DataView(characteristic.value!.buffer, 0).getBigUint64(0, true));
}
async function doOneTimeSync() {
if (ntp === undefined) return false;
if (bluetoothCharacteristics.currentTime === undefined) return false;
if (timeSyncResponse !== undefined) return false;
let sentAt = performance.now();
const remoteTime = new Promise<bigint>((resolve) => {
timeSyncResponse = resolve;
});
await bluetoothCharacteristics.currentTime.writeValueWithResponse(new ArrayBuffer(0));
let time = await remoteTime;
let receivedAt = performance.now();
ntp.handleTimeSync(BigInt(Math.floor(sentAt)), BigInt(Math.floor(receivedAt)), time);
buzzerState.update(state => {
state.offset = ntp!.currentOffset();
return state;
});
}
function handleNewCurrentTime(time: bigint) {
if (timeSyncResponse === undefined)
return;
timeSyncResponse(time);
timeSyncResponse = undefined;
}
function handleNewLastTriggerTime(time: bigint) {
if (ntp === undefined) return;
const currentOffset = ntp.currentOffset();
if (currentOffset === undefined) return;
buzzerState.update(state => {
state.lastTriggerTime = time - currentOffset;
state.connectionQuality = ntp!.currentAcceptanceRate();
return state;
});
}
export { checkAvailability, startBluetooth, doOneTimeSync };

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.

108
src/lib/ntp.ts Normal file
View file

@ -0,0 +1,108 @@
export class Ntp {
__currentOffset: bigint | undefined;
__currentFluctuation: bigint | undefined;
// between 0 and 100
__currentAcceptanceRate: bigint | undefined;
__acceptedFluctuationFactor: bigint = 4n;
__latestOffsets: bigint[] = [];
__latestFluctuations: bigint[] = [];
__latestAcceptanceResults: bigint[] = [];
constructor() {
this.__currentOffset = undefined;
this.__currentFluctuation = undefined;
this.__latestOffsets = [];
this.__latestFluctuations = [];
this.__latestAcceptanceResults = [];
}
currentOffset() {
return this.__currentOffset;
}
currentAcceptanceRate() {
return this.__currentAcceptanceRate;
}
handleTimeSync(sentAt: bigint, receivedAt: bigint, time: bigint) {
let roundTripTime = receivedAt - sentAt;
let newOffset = time + roundTripTime / 2n - receivedAt;
this.__handleNewOffset(newOffset);
}
__handleNewOffset(newOffset: bigint) {
const offsetInMargin = this.__isOffsetInMargin(newOffset);
this.__handleAcceptanceResult(offsetInMargin);
if (!offsetInMargin) {
return;
}
this.__currentOffset = this.__pushValueAndCaluclateAverage(this.__latestOffsets, newOffset);
}
__handleAcceptanceResult(result: boolean) {
this.__currentAcceptanceRate = this.__pushValueAndCaluclateAverage(
this.__latestAcceptanceResults,
result ? 100n : 0n
);
if (this.__currentAcceptanceRate > 90n) {
this.__acceptedFluctuationFactor = 4n;
}
else if (this.__currentAcceptanceRate > 80n) {
this.__acceptedFluctuationFactor = 6n;
}
else if (this.__currentAcceptanceRate > 50n) {
this.__acceptedFluctuationFactor = 10n;
} else {
this.__acceptedFluctuationFactor = 100n;
}
}
__isOffsetInMargin(newOffset: bigint) {
if (!this.__currentOffset) {
return true;
}
let fluctuation = this.__currentOffset - newOffset;
if (fluctuation < 0) {
fluctuation = -fluctuation;
}
if (
this.__currentFluctuation &&
this.__latestFluctuations.length > 5 &&
fluctuation > this.__currentFluctuation * this.__acceptedFluctuationFactor
) {
return false;
}
this.__currentFluctuation = this.__pushValueAndCaluclateAverage(this.__latestFluctuations, fluctuation);
if (this.__latestFluctuations.length < 10) {
return true;
}
return fluctuation < this.__currentFluctuation * 2n;
};
__pushValueAndCaluclateAverage(values: bigint[], newValue: bigint): bigint {
values.push(newValue);
if (values.length > 10) {
values.shift();
}
let sum = 0n;
for (let i = 0; i < values.length; i++) {
sum += values[i];
}
return sum / BigInt(values.length);
};
}

5
src/lib/types.ts Normal file
View file

@ -0,0 +1,5 @@
export interface BuzzerState {
offset?: bigint;
lastTriggerTime?: bigint;
connectionQuality?: bigint;
}

View file

@ -0,0 +1,5 @@
<script>
import '../app.pcss';
</script>
<slot />

2
src/routes/+layout.ts Normal file
View file

@ -0,0 +1,2 @@
export const prerender = false;
export const ssr = false;

46
src/routes/+page.svelte Normal file
View file

@ -0,0 +1,46 @@
<script lang="ts">
import { checkAvailability, doOneTimeSync, startBluetooth } from '$lib/bluetooth';
import { A, Alert, Button } from 'flowbite-svelte';
import { bluetoothState } from '../stores';
import { UAParser } from 'ua-parser-js';
import Timer from './Timer.svelte';
const uap = new UAParser();
$: {
checkAvailability();
}
</script>
<div class="p-8 flex flex-col">
{#if $bluetoothState == 'DISCONNECTED'}
<Button
on:click={() => {
startBluetooth();
}}>Connect</Button
>
{:else if $bluetoothState == 'UNAVAILABLE'}
<span
class="text-xl text-white rounded-md font-bold border-primary-600 border-4 border-r-4 p-3"
>
Your Browser is not compatible with this website, as it does not support <A
class="font-bold"
href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API#browser_compatibility"
>
web bluetooth
</A>.
</span>
<span class="text-xl mt-3">
Please use a browser which supports web bluetooth, for example
{#if uap.getOS().name === 'iOS'}
<A href="https://apps.apple.com/us/app/bluefy-web-ble-browser/id1492822055">Bluefy</A>
{:else}
<A href="https://www.google.com/chrome/">Google Chrome</A>
{/if}.
</span>
{:else if $bluetoothState == 'CONNECTED'}
<Timer />
{:else}
<Alert color="yellow">Connecting...</Alert>
{/if}
</div>

2
src/routes/+page.ts Normal file
View file

@ -0,0 +1,2 @@
export const prerender = false;
export const ssr = false;

69
src/routes/Timer.svelte Normal file
View file

@ -0,0 +1,69 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { buzzerState } from '../stores';
import { Button } from 'flowbite-svelte';
let timerStartedAt: bigint | undefined;
let timerStoppedAt: bigint | undefined;
let currentTimerValue: bigint | undefined;
let timerInterval: number | undefined;
onMount(() => {
timerInterval = setInterval(() => {
if (timerStartedAt) {
let now = BigInt(Math.floor(performance.now()));
const timerValue = (timerStoppedAt ?? now) - timerStartedAt;
currentTimerValue =
timerStoppedAt === undefined ? timerValue - (timerValue % 100n) : timerValue;
}
}, 100);
});
onDestroy(() => {
clearInterval(timerInterval);
});
const getTimerColor = (timerStartedAt?: bigint, timerStoppedAt?: bigint) => {
if (timerStartedAt === undefined) {
return 'white';
} else if (timerStartedAt !== undefined && timerStoppedAt === undefined) {
return 'red';
} else if (timerStartedAt !== undefined && timerStoppedAt !== undefined) {
return 'green';
} else {
return 'white';
}
};
$: {
if (!timerStartedAt || !$buzzerState.lastTriggerTime) {
break $;
}
if ($buzzerState.lastTriggerTime > timerStartedAt) {
timerStoppedAt = $buzzerState.lastTriggerTime;
}
}
</script>
<div class="flex flex-col items-center gap-3">
<div class="w-full flex flex-row flex-wrap gap-3 justify-center">
<div class="flex flex-grow flex-col items-center">
<span
class="text-9xl font-bold"
style="color: {getTimerColor(timerStartedAt, timerStoppedAt)};"
>
{(Number(currentTimerValue ?? 0) / 1000).toFixed(3)}
</span>
</div>
</div>
</div>
<Button
on:click={() => {
timerStartedAt = BigInt(Math.floor(performance.now()));
timerStoppedAt = undefined;
}}>Start!</Button
>

7
src/stores.ts Normal file
View file

@ -0,0 +1,7 @@
import type { BuzzerState } from '$lib/types';
import { writable } from 'svelte/store';
const bluetoothState = writable<'UNAVAILABLE' | 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED'>('DISCONNECTED');
let buzzerState = writable<BuzzerState>({});
export { bluetoothState, buzzerState }

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

18
svelte.config.js Normal file
View file

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

65
tailwind.config.cjs Normal file
View file

@ -0,0 +1,65 @@
const config = {
content: [
'./src/**/*.{html,js,svelte,ts}',
'./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}'
],
theme: {
extend: {
colors: {
'dark-white': '#F7F7F7',
black: '#111827',
'dark-grey': '#1F2937',
grey: '#374151',
'light-grey': '#6B7280',
red: '#DA3C2B',
yellow: '#DD972A',
green: '#057A55',
primary: {
50: '#FFF5F2',
100: '#FFF1EE',
200: '#FFE4DE',
300: '#FFD5CC',
400: '#FFBCAD',
500: '#FE795D',
600: '#EF562F',
700: '#EB4F27',
800: '#CC4522',
900: '#A5371B'
}
},
fontFamily: {
Roboto: ['Roboto', 'sans-serif'],
Raleway: ['Raleway', 'sans-serif']
},
animation: {
blink: 'blink 4s linear infinite',
eyes: 'eyes 10s ease-in-out infinite'
},
keyframes: {
blink: {
'0%, 96%': { transform: 'scaleY(1)' },
'98%': { transform: 'scaleY(0.1)' }
},
eyes: {
'0%, 25%, 50%, 75%': { transform: 'translate(0, 0)' },
'30%, 45%': { transform: 'translate(-10px, 0)' },
'80%, 95%': { transform: 'translate(10px, 0)' }
}
},
boxShadow: {
'box-light': '0 10px 50px 0 rgba(17, 24, 39, .05)',
'box-dark': '0 10px 50px 0 rgba(247, 247, 247, .02)'
}
}
},
safelist: [
],
plugins: [require('flowbite/plugin')],
darkMode: 'media'
};
module.exports = config;

18
tsconfig.json Normal file
View file

@ -0,0 +1,18 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

6
vite.config.ts Normal file
View file

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