Convert backend/web from submodule to regular directory

This commit is contained in:
2026-08-08 23:26:14 +02:00
parent 102a316d42
commit 0010c92103
89 changed files with 6559 additions and 1 deletions
Submodule backend/web deleted from fd6aeabbd5
+28
View File
@@ -0,0 +1,28 @@
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-*
# SQLite
*.db
*.db.wal
.fontello
+1
View File
@@ -0,0 +1 @@
engine-strict=true
+9
View File
@@ -0,0 +1,9 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb
# Miscellaneous
/static/
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"svelte.svelte-vscode",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss"
]
}
+14
View File
@@ -0,0 +1,14 @@
{
"files.associations": {
"*.css": "tailwindcss"
},
"svelte/no-navigation-without-resolve": [
"error",
{
"ignoreGoto": false,
"ignoreLinks": false,
"ignorePushState": false,
"ignoreReplaceState": false
}
]
}
+30
View File
@@ -0,0 +1,30 @@
FONT_DIR ?= ./static/assets/fontello
FONTELLO_HOST ?= https://fontello.com
fontopen:
@if test ! `which curl` ; then \
echo 'Install curl first.' >&2 ; \
exit 128 ; \
fi
curl --silent --show-error --fail --output .fontello \
--form "config=@${FONT_DIR}/config.json" \
${FONTELLO_HOST}
xdg-open ${FONTELLO_HOST}/`cat .fontello`
fontsave:
@if test ! `which unzip` ; then \
echo 'Install unzip first.' >&2 ; \
exit 128 ; \
fi
@if test ! -e .fontello ; then \
echo 'Run `make fontopen` first.' >&2 ; \
exit 128 ; \
fi
rm -rf .fontello.src .fontello.zip
curl --silent --show-error --fail --output .fontello.zip \
${FONTELLO_HOST}/`cat .fontello`/get
unzip .fontello.zip -d .fontello.src
rm -rf ${FONT_DIR}
mv `find ./.fontello.src -maxdepth 1 -name 'fontello-*'` ${FONT_DIR}
rm -rf .fontello.src .fontello.zip
+42
View File
@@ -0,0 +1,42 @@
# 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
npx sv create my-app
```
To recreate this project with the same configuration:
```sh
# recreate this project
npx sv@0.17.0 create --template minimal --types ts --add prettier eslint tailwindcss="plugins:typography,forms" better-auth="demo:password,github" drizzle="database:sqlite+sqlite:turso" --no-install ./
```
## 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.
+382
View File
@@ -0,0 +1,382 @@
{
"auditReportVersion": 2,
"vulnerabilities": {
"@chevrotain/cst-dts-gen": {
"name": "@chevrotain/cst-dts-gen",
"severity": "moderate",
"isDirect": false,
"via": [
"@chevrotain/gast",
"lodash"
],
"effects": [
"chevrotain"
],
"range": "10.0.0 - 10.5.0",
"nodes": [
"node_modules/@chevrotain/cst-dts-gen"
],
"fixAvailable": {
"name": "auth",
"version": "1.2.3",
"isSemVerMajor": true
}
},
"@chevrotain/gast": {
"name": "@chevrotain/gast",
"severity": "moderate",
"isDirect": false,
"via": [
"lodash"
],
"effects": [
"@chevrotain/cst-dts-gen",
"chevrotain"
],
"range": "<=10.5.0",
"nodes": [
"node_modules/@chevrotain/gast"
],
"fixAvailable": {
"name": "auth",
"version": "1.2.3",
"isSemVerMajor": true
}
},
"@esbuild-kit/core-utils": {
"name": "@esbuild-kit/core-utils",
"severity": "moderate",
"isDirect": false,
"via": [
"esbuild"
],
"effects": [
"@esbuild-kit/esm-loader"
],
"range": "*",
"nodes": [
"node_modules/@esbuild-kit/core-utils"
],
"fixAvailable": {
"name": "drizzle-kit",
"version": "0.18.1",
"isSemVerMajor": true
}
},
"@esbuild-kit/esm-loader": {
"name": "@esbuild-kit/esm-loader",
"severity": "moderate",
"isDirect": false,
"via": [
"@esbuild-kit/core-utils"
],
"effects": [
"drizzle-kit"
],
"range": "*",
"nodes": [
"node_modules/@esbuild-kit/esm-loader"
],
"fixAvailable": {
"name": "drizzle-kit",
"version": "0.18.1",
"isSemVerMajor": true
}
},
"@mrleebo/prisma-ast": {
"name": "@mrleebo/prisma-ast",
"severity": "moderate",
"isDirect": false,
"via": [
"chevrotain"
],
"effects": [
"auth"
],
"range": "0.4.2 - 0.13.1",
"nodes": [
"node_modules/@mrleebo/prisma-ast"
],
"fixAvailable": {
"name": "auth",
"version": "1.2.3",
"isSemVerMajor": true
}
},
"@sveltejs/adapter-auto": {
"name": "@sveltejs/adapter-auto",
"severity": "low",
"isDirect": true,
"via": [
"@sveltejs/kit"
],
"effects": [],
"range": "1.0.0-next.0 - 8.0.0-next.0",
"nodes": [
"node_modules/@sveltejs/adapter-auto"
],
"fixAvailable": {
"name": "@sveltejs/adapter-auto",
"version": "0.0.1",
"isSemVerMajor": true
}
},
"@sveltejs/kit": {
"name": "@sveltejs/kit",
"severity": "low",
"isDirect": true,
"via": [
"cookie"
],
"effects": [
"@sveltejs/adapter-auto",
"better-auth"
],
"range": "1.0.0-next.0 - 2.70.2",
"nodes": [
"node_modules/@sveltejs/kit"
],
"fixAvailable": {
"name": "@sveltejs/kit",
"version": "0.0.30",
"isSemVerMajor": true
}
},
"auth": {
"name": "auth",
"severity": "moderate",
"isDirect": true,
"via": [
"@mrleebo/prisma-ast",
"better-auth"
],
"effects": [],
"range": ">=1.5.0-beta.15",
"nodes": [
"node_modules/auth"
],
"fixAvailable": {
"name": "auth",
"version": "1.2.3",
"isSemVerMajor": true
}
},
"better-auth": {
"name": "better-auth",
"severity": "low",
"isDirect": true,
"via": [
"@sveltejs/kit"
],
"effects": [
"auth"
],
"range": "1.3.10-beta.1 - 1.3.21 || 1.4.0-beta.1 - 1.4.0-beta.28 || >=1.4.4-beta.1",
"nodes": [
"node_modules/better-auth"
],
"fixAvailable": {
"name": "better-auth",
"version": "1.4.3",
"isSemVerMajor": true
}
},
"chevrotain": {
"name": "chevrotain",
"severity": "moderate",
"isDirect": false,
"via": [
"@chevrotain/cst-dts-gen",
"@chevrotain/gast",
"lodash"
],
"effects": [
"@mrleebo/prisma-ast"
],
"range": "10.0.0 - 10.5.0",
"nodes": [
"node_modules/chevrotain"
],
"fixAvailable": {
"name": "auth",
"version": "1.2.3",
"isSemVerMajor": true
}
},
"cookie": {
"name": "cookie",
"severity": "low",
"isDirect": false,
"via": [
{
"source": 1103907,
"name": "cookie",
"dependency": "cookie",
"title": "cookie accepts cookie name, path, and domain with out of bounds characters",
"url": "https://github.com/advisories/GHSA-pxg6-pf52-xh8x",
"severity": "low",
"cwe": [
"CWE-74"
],
"cvss": {
"score": 0,
"vectorString": null
},
"range": "<0.7.0"
}
],
"effects": [
"@sveltejs/kit"
],
"range": "<0.7.0",
"nodes": [
"node_modules/cookie"
],
"fixAvailable": {
"name": "@sveltejs/kit",
"version": "0.0.30",
"isSemVerMajor": true
}
},
"drizzle-kit": {
"name": "drizzle-kit",
"severity": "moderate",
"isDirect": true,
"via": [
"@esbuild-kit/esm-loader"
],
"effects": [],
"range": "0.19.0 - 1.0.0-beta.1-fd8bfcc",
"nodes": [
"node_modules/drizzle-kit"
],
"fixAvailable": {
"name": "drizzle-kit",
"version": "0.18.1",
"isSemVerMajor": true
}
},
"esbuild": {
"name": "esbuild",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1102341,
"name": "esbuild",
"dependency": "esbuild",
"title": "esbuild enables any website to send any requests to the development server and read the response",
"url": "https://github.com/advisories/GHSA-67mh-4wv8-2f99",
"severity": "moderate",
"cwe": [
"CWE-346"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:N/A:N"
},
"range": "<=0.24.2"
}
],
"effects": [
"@esbuild-kit/core-utils"
],
"range": "<=0.24.2",
"nodes": [
"node_modules/@esbuild-kit/core-utils/node_modules/esbuild"
],
"fixAvailable": {
"name": "drizzle-kit",
"version": "0.18.1",
"isSemVerMajor": true
}
},
"lodash": {
"name": "lodash",
"severity": "high",
"isDirect": false,
"via": [
{
"source": 1115806,
"name": "lodash",
"dependency": "lodash",
"title": "lodash vulnerable to Code Injection via `_.template` imports key names",
"url": "https://github.com/advisories/GHSA-r5fr-rjxr-66jc",
"severity": "high",
"cwe": [
"CWE-94"
],
"cvss": {
"score": 8.1,
"vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H"
},
"range": ">=4.0.0 <=4.17.23"
},
{
"source": 1115810,
"name": "lodash",
"dependency": "lodash",
"title": "lodash vulnerable to Prototype Pollution via array path bypass in `_.unset` and `_.omit`",
"url": "https://github.com/advisories/GHSA-f23m-r3pf-42rh",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 6.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L"
},
"range": "<=4.17.23"
},
{
"source": 1120370,
"name": "lodash",
"dependency": "lodash",
"title": "Lodash has Prototype Pollution Vulnerability in `_.unset` and `_.omit` functions",
"url": "https://github.com/advisories/GHSA-xxjr-mmjv-4gpg",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 6.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L"
},
"range": ">=4.0.0 <=4.17.22"
}
],
"effects": [
"@chevrotain/cst-dts-gen",
"@chevrotain/gast",
"chevrotain"
],
"range": "<=4.17.23",
"nodes": [
"node_modules/lodash"
],
"fixAvailable": {
"name": "auth",
"version": "1.2.3",
"isSemVerMajor": true
}
}
},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 4,
"moderate": 9,
"high": 1,
"critical": 0,
"total": 14
},
"dependencies": {
"prod": 2,
"dev": 451,
"optional": 139,
"peer": 0,
"peerOptional": 0,
"total": 452
}
}
}
+50
View File
@@ -0,0 +1,50 @@
import prettier from 'eslint-config-prettier';
import path from 'node:path';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import { defineConfig, includeIgnoreFile } from 'eslint/config';
import globals from 'globals';
import ts from 'typescript-eslint';
const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
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: {
"svelte/no-navigation-without-resolve": [
"error",
{
"ignoreGoto": true,
"ignoreLinks": true,
"ignorePushState": true,
"ignoreReplaceState": true
}
],
// 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
}
}
},
{
// Override or add rule settings here, such as:
// 'svelte/button-has-type': 'error'
rules: {}
}
);
+3980
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
{
"name": "web",
"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",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio",
"auth:schema": "auth generate --config src/lib/server/auth.ts --output src/lib/server/db/auth.schema.ts --yes"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@libsql/client": "^0.17.3",
"@sveltejs/adapter-auto": "^7.0.1",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.70.2",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"@tailwindcss/forms": "^0.5.11",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.3.0",
"@types/node": "^24",
"eslint": "^10.4.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-svelte": "^3.19.0",
"globals": "^17.6.0",
"prettier": "^3.8.3",
"prettier-plugin-svelte": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.8.0",
"svelte": "^5.56.1",
"svelte-check": "^4.6.0",
"tailwindcss": "^4.3.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.1",
"vite": "^8.2.0"
},
"overrides": {
"cookie": "^0.7.0",
"esbuild": "^0.25.0"
},
"dependencies": {
"oidc-client-ts": "^3.5.0"
}
}
+12
View File
@@ -0,0 +1,12 @@
/** @type {import("prettier").Config} */
const config = {
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'
};
export default config;
+16
View File
@@ -0,0 +1,16 @@
import type { User, Session } from 'better-auth';
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
interface Locals { user?: User; session?: Session }
// interface Error {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="text-scale" content="scale" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+62
View File
@@ -0,0 +1,62 @@
import { goto } from "$app/navigation";
import { getToken, logout } from "$lib/state/auth.svelte"
const BASE_URL = import.meta.env.VITE_API_URL;
interface RequestOptions extends RequestInit {
auth?: boolean; // true par défaut
}
class ApiError extends Error {
constructor(public status: number, public body: string) {
super(`API Error ${status}`);
}
}
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
const { auth = true, headers, ...rest } = options;
const requestHeaders: HeadersInit = {
'Content-Type': 'application/json',
...headers,
};
const fetchHeaders = new Headers(requestHeaders)
if (auth) {
const token = getToken()
if (token) {
fetchHeaders.append('Authorization', `Bearer ${token}`)
}
}
const res = await fetch(`${BASE_URL}${path}`, {
...rest,
headers: fetchHeaders,
});
if (res.status === 401 ) {
logout()
goto("/login")
//throw new ApiError(401, await safeJson(res));
}
if (!res.ok) {
throw new ApiError(res.status, await safeJson(res));
}
if (res.status === 204) return undefined as T;
return res.json();
}
async function safeJson(res: Response) {
try { return await res.json(); } catch { return null; }
}
export const api = {
get: <T>(path: string, opts?: RequestOptions) => request<T>(path, { ...opts, method: 'GET' }),
post: <T>(path: string, body?: unknown, opts?: RequestOptions) =>
request<T>(path, { ...opts, method: 'POST', body: JSON.stringify(body) }),
put: <T>(path: string, body?: unknown, opts?: RequestOptions) =>
request<T>(path, { ...opts, method: 'PUT', body: JSON.stringify(body) }),
delete: <T>(path: string, opts?: RequestOptions) => request<T>(path, { ...opts, method: 'DELETE' }),
};
+33
View File
@@ -0,0 +1,33 @@
import { api } from '../client';
import { type User } from "$lib/types/api"
export interface Credentials {
email: string
password: string
}
export interface UserReponse {
user : User
accessToken: string
refreshToken: string
expiresAt: Date
}
export interface RegistrationData {
method: string // "password"
email: string
firstname: string
lastname: string
password: string
}
export const authApi = {
register: (data: RegistrationData) => api.post<User>('/auth/register', data),
status: () => api.get<User>('/auth/status'),
login: (data: Credentials) => api.post<UserReponse>('/auth/login', data),
// getAll: () => api.get<User[]>('/users'),
// getById: (id: string) => api.get<User>(`/users/${id}`),
// create: (data: Partial<User>) => api.post<User>('/users', data),
// update: (id: string, data: Partial<User>) => api.put<User>(`/users/${id}`, data),
// remove: (id: string) => api.delete<void>(`/users/${id}`),
};
@@ -0,0 +1,7 @@
import { api } from '../client';
import { type User } from "$lib/types/api"
export const userApi = {
me: () => api.get<User>('/user/me'),
update: (id: string, data: Partial<User>) => api.put<User>(`/user/${id}`, data),
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="13mm"
height="13mm"
viewBox="0 0 13 13"
version="1.1"
id="svg1"
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
sodipodi:docname="favicon.svg"
inkscape:export-filename="../backend/web/src/lib/assets/logo-txt.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="1.4431818"
inkscape:cx="287.90552"
inkscape:cy="214.80315"
inkscape:window-width="2560"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:14.1111px;font-family:pixelart;-inkscape-font-specification:pixelart;writing-mode:lr-tb;direction:ltr;fill:#37abc8;stroke-width:0.264583"
x="2.7499995"
y="11.549997"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.1111px;font-family:'GohuFont 11 Nerd Font';-inkscape-font-specification:'GohuFont 11 Nerd Font';fill:#0ea5e9;fill-opacity:1;stroke-width:0.264583"
x="2.7499995"
y="11.549997">l</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,13 @@
<script lang="ts">
type Props = {
errors: Map<string,string>
key: string
}
let { errors, key }: Props = $props();
</script>
{#if errors.has(key)}
<span class="text-red-700 dark:text-red-400 text-xs">{errors.get(key)}</span>
{:else}
<span class="text-xs">&nbsp;</span>
{/if}
@@ -0,0 +1,13 @@
<script lang="ts">
import {uiState} from "$lib/state/ui.svelte"
let {label='', icon='', link=''} = $props();
</script>
<div class="my-2 block">
<a class="p-2 w-full text-left menuitem block" href={link}>
<i class={[icon]}></i>
{#if uiState.menuContentVisible}
<span class="ml-4">{label}</span>
{/if}
</a>
</div>
@@ -0,0 +1,55 @@
<script lang="ts">
import { uiState } from "$lib/state/ui.svelte"
import { auth } from "$lib/state/auth.svelte"
import Menu from "$lib/components/Menu.svelte"
import { logout } from "$lib/state/auth.svelte"
import logo from "$lib/assets/logo-txt.png"
let {handleMenuDesktop, handleMenuMobile} = $props();
const handleLogout = () => {
logout()
}
</script>
<div class="flex flex-row italic mb-4">
{#if uiState.menuContentVisible}
<div class="flex-1 p-3 font openonly">
<img src={logo} class="h-5" alt="logo"/>
</div>
{/if}
<button id="hamburgerDesktop" onclick={handleMenuDesktop} title="Open menu">
<i class="icon-menu pointer block p-2"></i>
</button>
<button id="hamburgerMobile" onclick={handleMenuMobile} title="Open menu">
<i class="icon-menu pointer block p-2"></i>
</button>
</div>
<div id="menu" class="flex-1 overflow-y-scroll">
<Menu label="Conversation" icon="icon-chat" link="/"/>
<Menu label="Tâches" icon="icon-tasks" link="/tasks"/>
<Menu label="Connaissances" icon="icon-graduation-cap" link="/kb"/>
<Menu label="Historique" icon="icon-history" link="/history"/>
{#if auth.user?.administrator}
<Menu label="Paramétrage" icon="icon-cog" link="/settings"/>
{/if}
</div>
<div id="profile" class="w-full flex flex-row items-center border-t border-stone-200 dark:border-stone-800">
{#if uiState.menuContentVisible}
<div class="p-4 flex-1 profile">
<div>Fabien Masson</div>
<div class="flex">
<a href="/profile" class="text-xs flex-1">Modifier</a>
</div>
</div>
<div class="p-2">
<button class="w-full bt" onclick={handleLogout} title="Déconecter">
<i class="icon-logout"></i>
</button>
</div>
{:else}
<Menu label="User" icon="icon-user" link="/profile"/>
{/if}
</div>
+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+26
View File
@@ -0,0 +1,26 @@
import { type User } from "$lib/types/api"
type AuthState = {
user: User | null;
status: 'loading' | 'authenticated' | 'unauthenticated';
//accessToken: string | null
}
export const auth: AuthState = $state({
user: null,
status: 'loading',
//accessToken: null
})
export const logout = () => {
localStorage.removeItem("accessToken")
auth.status = 'unauthenticated'
}
export const getToken = (): string | null => {
return localStorage.getItem("accessToken")
}
export const setToken = (token : string) => {
localStorage.setItem("accessToken", token)
}
@@ -0,0 +1,20 @@
let messageIndex = 0
interface Message {
id : number
type: 'success' | 'failure'
text: string,
status: 'appearing' | 'visible' | 'disappearing'
}
export const messages: Array<Message> = $state([])
export const putMessage = (type: 'success' | 'failure', text: string)=> {
messages.push({
id: messageIndex++,
type: type,
text: text,
status: 'appearing'
})
}
+5
View File
@@ -0,0 +1,5 @@
export const uiState = $state({
sidebarDesktopOpen: true,
sidebarMobileOpen: false,
menuContentVisible: true
});
+10
View File
@@ -0,0 +1,10 @@
export interface User {
id: string
firstname: string
lastname: string
email: string
administrator: boolean
theme: string
lang: string
_version: string
}
+17
View File
@@ -0,0 +1,17 @@
export const applyTheme = () => {
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
(!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches),
);
}
export const themeToLocalStorage = (theme: string) => {
if (theme=='light') {
localStorage.setItem('theme','light')
} else if (theme=='dark') {
localStorage.setItem('theme','dark')
} else {
localStorage.removeItem('theme')
}
}
@@ -0,0 +1,5 @@
const emailRegex: RegExp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
export function validateEmail(input: string): boolean {
return emailRegex.test(input);
}
@@ -0,0 +1,7 @@
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Conversation' };
}
@@ -0,0 +1,80 @@
<script lang="ts">
import type { LayoutProps } from './$types';
import { page } from '$app/state';
import {uiState} from "$lib/state/ui.svelte"
import { auth } from '$lib/state/auth.svelte'
import Sidebar from "$lib/components/Sidebar.svelte"
import { goto } from '$app/navigation'
import { onMount } from "svelte";
import { applyTheme, themeToLocalStorage } from '$lib/ui/themeswitcher';
let { data, children }: LayoutProps = $props();
const handleMenuDesktop = () => {
uiState.sidebarDesktopOpen = !uiState.sidebarDesktopOpen
if (uiState.sidebarDesktopOpen) {
setTimeout(() => { uiState.menuContentVisible = uiState.sidebarDesktopOpen }, 200)
} else {
uiState.menuContentVisible = uiState.sidebarDesktopOpen
}
}
const handleMenuMobile = () => {
uiState.sidebarMobileOpen = !uiState.sidebarMobileOpen
if (uiState.sidebarMobileOpen) {
setTimeout(() => { uiState.menuContentVisible = uiState.sidebarMobileOpen }, 200)
} else {
uiState.menuContentVisible = uiState.sidebarMobileOpen
}
}
const handleResize = () => {
if (window.innerWidth < 640) {
uiState.menuContentVisible = uiState.sidebarMobileOpen
} else {
uiState.menuContentVisible = uiState.sidebarDesktopOpen
}
}
onMount(async () => {
handleResize()
if (data.me) {
auth.user = data.me
themeToLocalStorage(auth.user.theme)
applyTheme()
auth.status = 'authenticated'
} else {
auth.status = 'unauthenticated'
}
})
$effect(() => {
if (auth.status == 'unauthenticated') {
goto("/login")
}
});
</script>
<svelte:window onresize={handleResize} />
<div id="outer" class={[uiState.sidebarDesktopOpen ? "desktopMenuOpen" : "desktopMenuClosed", uiState.sidebarMobileOpen ? "mobileMenuOpen" : "mobileMenuClosed" ]}>
<div id="sidebar" class="flex flex-col">
<Sidebar {handleMenuDesktop} {handleMenuMobile}/>
</div>
<div id="main">
<div class="flex flex-col h-screen ">
<div class={["header", page.data.theme]}>
<h1 class="flex">
<span class="flex-1">{page.data.title}</span>
{#if page.data.back}
<span><a href={page.data.back} title="retour"><i class="icon-left-big"></i></a></span>
{/if}
</h1>
</div>
<div class="overflow-scroll flex-1 p-2">
{@render children()}
</div>
</div>
</div>
</div>
@@ -0,0 +1,8 @@
import { userApi } from "$lib/api/endpoints/user"
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async () => {
return {
me: await userApi.me()
}
}
@@ -0,0 +1,6 @@
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Historique' };
}
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Base de connaissance' };
}
@@ -0,0 +1,109 @@
<script lang="ts">
import { SvelteMap } from 'svelte/reactivity';
import type { PageProps } from './$types';
import { type User } from '$lib/types/api'
import { userApi } from '$lib/api/endpoints/user'
import FormError from '$lib/components/FormError.svelte';
import { applyTheme, themeToLocalStorage } from '$lib/ui/themeswitcher';
import { validateEmail } from '$lib/utilities/validators'
let { data }: PageProps = $props();
let form : User = $state(data.me)
let errors = new SvelteMap<string,string>()
$effect(()=>{
themeToLocalStorage(form.theme)
applyTheme()
})
let submitUser = (e: Event) => {
e.preventDefault()
if (!validateEmail(form.email)) {
errors.set("email","Adresse email invalide !")
}
if (!form.firstname) {
errors.set("firstname","Le prénom est obligatoire !")
}
if (!form.lastname) {
errors.set("lastname","Le nom est obligatoire !")
}
if (errors.size==0) {
userApi.update(form.id, form)
.then(()=>{ console.log('success')})
.catch((err: string)=>{ console.error(err)})
}
}
</script>
<div class="mb-4 formgroup">
<h2>Mon compte</h2>
<form onsubmit={submitUser} onchange={()=>{errors.clear()}}>
<label for="email" class="block my-1 flex-1">Email</label>
<input
id="email"
type="text"
bind:value={form.email}
class="textinput"
autofocus
/>
<FormError key="email" errors={errors} />
<div class="flex">
<div class="flex-1 mr-2">
<label for="firstname" class="block my-1 flex-1">Prénom</label>
<input
id="firstname"
type="text"
bind:value={form.firstname}
class="textinput"
/>
<FormError key="firstname" errors={errors} />
</div>
<div class="flex-1 ml-2">
<label for="lastname" class="block my-1 flex-1">Nom</label>
<input
id="lastname"
type="text"
bind:value={form.lastname}
class="textinput"
/>
<FormError key="lastname" errors={errors} />
</div>
</div>
<div class="flex">
<div class="flex-1 mr-2">
<label for="theme" class="block my-1 flex-1">Thème</label>
<select bind:value={form.theme} class="textinput">
<option value="auto">Automatic</option>
<option value="dark">Sombre</option>
<option value="light">Clair</option>
</select>
<FormError key="theme" errors={errors} />
</div>
<div class="flex-1 ml-2">
<label for="lang" class="block my-1 flex-1">Langue</label>
<select bind:value={form.lang} class="textinput">
<option value="en">English</option>
<option value="fr">Français</option>
</select>
<FormError key="lang" errors={errors} />
</div>
</div>
<button class="bt w-full">Enregistrer</button>
</form>
</div>
<div class="mb-4 formgroup">
<h2>Canaux de communication</h2>
</div>
@@ -0,0 +1,9 @@
import { userApi } from "$lib/api/endpoints/user"
import type { PageLoad } from './$types';
export const load: PageLoad = async () => {
return {
title: 'Profil',
me: await userApi.me()
}
}
@@ -0,0 +1,58 @@
<div class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
<a class="bt p-5 text-center"
title="Modèles"
href="/settings/models">
Modèles
<span class="ml-2 opacity-50 text-xs">mistral-medium-latest</span>
</a>
<a class="bt p-5 text-center"
title="Outils"
href="/settings/tools"
>
Outils
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">12</span>
</a>
<a class="bt p-5 text-center"
title="Skills"
href="/settings/skills">
Skills
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">3</span>
</a>
<a class="bt p-5 text-center"
title="Canaux de communication"
href="/settings/channels"
>
Canaux de communication
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">12</span>
</a>
<a class="bt p-5 text-center"
title="Bases de connaissance"
href="/settings/kb">
Bases de connaissance
</a>
<a class="bt p-5 text-center"
title="Agents"
href="/settings/agents">
Agents
</a>
<a class="bt p-5 text-center"
title="Mémoire"
href="/settings/memory">
Mémoire
<span class="ml-2 opacity-50 text-xs">standard</span>
</a>
<a class="bt p-5 text-center"
title="Utilisateurs"
href="/settings/users">
Utilisateurs
<span class="ml-2 text-xs bg-red-900 text-red-300 rounded-lg p-1">1</span>
</a>
</div>
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Paramétrage', theme: "settings" };
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Agents',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Canaux de communication',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Base de connaissance',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Mémoire',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Modèles',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Skills',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Outils',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1 @@
<div>Modèles</div>
@@ -0,0 +1,7 @@
export function load() {
return {
title: 'Paramétrage : Utilisateurs',
back: "/settings",
theme: "settings"
};
}
@@ -0,0 +1,3 @@
<div>
Tâches
</div>
@@ -0,0 +1,3 @@
export function load() {
return { title: 'Tâches' };
}
@@ -0,0 +1,105 @@
<script lang="ts">
import logo from "$lib/assets/logo-txt.png"
import { auth, setToken } from '$lib/state/auth.svelte';
import { goto } from '$app/navigation';
import { authApi } from "$lib/api/endpoints/auth"
type Credentials = {
email: string
validemail: boolean
password : string
}
let credentials: Credentials = $state({
email: "",
password: "",
validemail: false
})
const formSubmitEmail = () => {
credentials.validemail = true
}
const formSubmitPass = (e: Event) => {
e.preventDefault()
authApi.login(credentials)
.then((res)=>{
auth.user = res.user
setToken(res.accessToken)
goto("/")
})
.catch((err)=>{
console.log(err)
})
}
</script>
<div class="h-full w-full flex items-center public">
<div class="m-auto w-96 max-w-full p-4">
<img src={logo} class="h-10 mb-4" alt="logo"/>
<h1>Identifiez-vous ...</h1>
{#if !credentials.validemail}
<form onsubmit={formSubmitEmail}>
<div class="flex">
<label for="email" class="block mb-2 flex-1">Email</label>
<a href="/recover-password" class="text-xs">Mot de passe oublié ?</a>
</div>
<div class="flex flex-row">
<input
type="text"
bind:value={credentials.email}
class="textinput mr-1"
autofocus
/>
<button class="bt" title="page suivante">
<i class="icon-right-big"></i>
</button>
</div>
</form>
<div class="my-4 text-xs">
<a href="/register">Pas encore de compte ? Incrivez-vous.</a>
</div>
{:else}
<form onsubmit={formSubmitPass}>
<div class="flex">
<label for="email" class="block mb-2 flex-1">Mot de passe</label>
</div>
<div class="flex flex-row">
<input
type="password"
bind:value={credentials.password}
class="textinput mr-1"
autofocus
/>
<button class="bt">
Entrer
</button>
</div>
<a href="/login" class="text-xs" onclick={()=>{credentials.validemail=false}}> Retour</a>
</form>
{/if}
<hr class="my-4">
<div class="grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] gap-4">
<button class="bt p-5 text-center" title="Connexion avec Google">
<i class="icon-google"></i>
</button>
<button class="bt p-5 text-center" title="Connexion avec Facebook">
<i class="icon-facebook"></i>
</button>
<button class="bt p-5 text-center" title="Connexion avec Github">
<i class="icon-github"></i>
</button>
<button class="bt p-5 text-center" title="Connexion avec Apple">
<i class="icon-apple"></i>
</button>
</div>
</div>
</div>
@@ -0,0 +1,120 @@
<script lang="ts">
import logo from "$lib/assets/logo-txt.png"
import { SvelteMap } from 'svelte/reactivity';
import { goto } from '$app/navigation';
import { validateEmail } from '$lib/utilities/validators'
import FormError from "$lib/components/FormError.svelte";
import { authApi } from "$lib/api/endpoints/auth"
type RegisterForm = {
email: string
firstname: string
lastname: string
password : string
verifPassword: string
}
let errors = new SvelteMap<string,string>()
let form: RegisterForm = $state({
email: "",
firstname: "",
lastname: "",
password: "",
verifPassword: ""
})
const handleSubmit = (e: Event) => {
e.preventDefault()
errors.clear()
if (!validateEmail(form.email)) {
errors.set("email","Adresse email invalide !")
}
if (form.password!==form.verifPassword) {
errors.set("verifPassword","Les mots de pass ne correspondent pas.")
}
if (errors.size==0) {
let data = {
method: "password",
email: form.email,
firstname: form.firstname,
lastname: form.lastname,
password: form.password
}
authApi.register(data)
.then(()=>{ goto("/") })
.catch((reason: string)=>{ errors.set("submit","Erreur: "+reason) })
}
}
</script>
<div class="h-full w-full flex items-center public">
<div class="m-auto w-96 max-w-full p-4">
<img src={logo} class="h-10 mb-4" alt="logo"/>
<h1>Inscription</h1>
<form onsubmit={handleSubmit} onchange={()=>{errors.clear()}}>
<label for="email" class="block my-1 flex-1">Email</label>
<input
id="email"
type="text"
bind:value={form.email}
class="textinput"
autofocus
/>
<FormError key="email" errors={errors} />
<label for="firstname" class="block my-1 flex-1">Prénom</label>
<input
id="firstname"
type="text"
bind:value={form.firstname}
class="textinput"
/>
<FormError key="firstname" {errors} />
<label for="lastname" class="block my-1 flex-1">Nom</label>
<input
id="lastname"
type="text"
bind:value={form.lastname}
class="textinput"
/>
<FormError key="lastname" {errors} />
<label for="password" class="block my-1 flex-1">Mot de passe</label>
<input
id="password"
type="password"
bind:value={form.password}
class="textinput"
/>
<FormError key="password" {errors} />
<label for="verifpassword" class="block my-1 flex-1">Vérification du mot de passe</label>
<input
id="verifpassword"
type="password"
bind:value={form.verifPassword}
class="textinput"
/>
<FormError key="verifPassword" {errors} />
<button class="bt my-2 w-full ">
Enregistrer
</button>
<FormError key="submit" {errors} />
</form>
</div>
</div>
+2
View File
@@ -0,0 +1,2 @@
export const ssr = false;
export const prerender = false;
+18
View File
@@ -0,0 +1,18 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
import { applyTheme } from '$lib/ui/themeswitcher';
import { onMount } from 'svelte';
let { children } = $props();
onMount(()=>{ applyTheme() })
</script>
<svelte:head>
<title>Lasssistanoque</title>
<link rel="icon" href={favicon} />
<link rel="stylesheet" href="/assets/fontello/css/fontello.css">
</svelte:head>
{@render children()}
+107
View File
@@ -0,0 +1,107 @@
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
@custom-variant dark (&:where(.dark, .dark *));
html {
@apply h-full;
}
body {
@apply h-full bg-stone-100 text-stone-900 dark:bg-stone-900 dark:text-stone-200;
}
label {
@apply text-xs text-stone-600 uppercase dark:text-stone-400;
}
hr {
@apply border-stone-200 dark:border-stone-800;
}
h2 {
@apply mb-4 font-bold;
}
.textinput {
@apply w-full rounded bg-white p-1 text-stone-900 dark:bg-stone-700 dark:text-stone-100;
}
.pointer {
@apply cursor-pointer;
}
.bt {
@apply cursor-pointer rounded bg-stone-200 px-4 py-2 hover:bg-stone-300 dark:bg-stone-800 dark:hover:bg-stone-700;
}
.menuitem {
@apply text-stone-800 text-stone-950 dark:text-stone-300 hover:dark:text-stone-50;
}
.header {
@apply p-2 text-lg;
h1 {
@apply rounded bg-stone-200 px-2 font-bold text-sky-600 dark:bg-stone-800 dark:text-sky-200;
}
&.settings {
h1 {
@apply rounded border-none bg-sky-200 px-2 text-sky-700 dark:bg-sky-950 dark:text-sky-300;
}
}
}
.public h1 {
@apply mb-4 text-3xl font-bold;
}
.profile a,
.public a {
@apply text-sky-500;
}
.formgroup {
@apply rounded border border-stone-300 bg-stone-100 p-2 dark:border-stone-700 dark:bg-stone-900;
}
#outer {
@apply flex h-full flex-row;
}
#main {
@apply flex w-full flex-col;
}
#sidebar {
@apply min-w-64 border-r border-r-stone-200 transition-all duration-300 dark:border-stone-800;
}
#content {
@apply p-2;
}
#hamburgerDesktop,
#hamburgerMobile {
@apply hidden;
}
@media (width >= 640px) {
#hamburgerDesktop {
@apply inline;
}
.desktopMenuClosed {
#sidebar {
@apply w-10 min-w-10;
}
}
}
@media (width < 640px) {
#hamburgerMobile {
@apply inline;
}
#main {
@apply ml-10;
}
#sidebar {
@apply fixed left-0 h-full w-full bg-stone-100 dark:bg-stone-900;
}
.mobileMenuClosed {
#sidebar {
@apply fixed w-10 min-w-10;
}
}
}
@@ -0,0 +1,12 @@
Font license info
## Font Awesome
Copyright (C) 2016 by Dave Gandy
Author: Dave Gandy
License: SIL ()
Homepage: http://fortawesome.github.com/Font-Awesome/
@@ -0,0 +1,75 @@
This webfont is generated by https://fontello.com open source project.
================================================================================
Please, note, that you should obey original font licenses, used to make this
webfont pack. Details available in LICENSE.txt file.
- Usually, it's enough to publish content of LICENSE.txt file somewhere on your
site in "About" section.
- If your project is open-source, usually, it will be ok to make LICENSE.txt
file publicly available in your repository.
- Fonts, used in Fontello, don't require a clickable link on your site.
But any kind of additional authors crediting is welcome.
================================================================================
Comments on archive content
---------------------------
- /font/* - fonts in different formats
- /css/* - different kinds of css, for all situations. Should be ok with
twitter bootstrap. Also, you can skip <i> style and assign icon classes
directly to text elements, if you don't mind about IE7.
- demo.html - demo file, to show your webfont content
- LICENSE.txt - license info about source fonts, used to build your one.
- config.json - keeps your settings. You can import it back into fontello
anytime, to continue your work
Why so many CSS files ?
-----------------------
Because we like to fit all your needs :)
- basic file, <your_font_name>.css - is usually enough, it contains @font-face
and character code definitions
- *-ie7.css - if you need IE7 support, but still don't wish to put char codes
directly into html
- *-codes.css and *-ie7-codes.css - if you like to use your own @font-face
rules, but still wish to benefit from css generation. That can be very
convenient for automated asset build systems. When you need to update font -
no need to manually edit files, just override old version with archive
content. See fontello source code for examples.
- *-embedded.css - basic css file, but with embedded WOFF font, to avoid
CORS issues in Firefox and IE9+, when fonts are hosted on the separate domain.
We strongly recommend to resolve this issue by `Access-Control-Allow-Origin`
server headers. But if you ok with dirty hack - this file is for you. Note,
that data url moved to separate @font-face to avoid problems with <IE9, when
string is too long.
- animate.css - use it to get ideas about spinner rotation animation.
Attention for server setup
--------------------------
You MUST setup server to reply with proper `mime-types` for font files -
otherwise some browsers will fail to show fonts.
Usually, `apache` already has necessary settings, but `nginx` and other
webservers should be tuned. Here is list of mime types for our file extensions:
- `application/vnd.ms-fontobject` - eot
- `application/x-font-woff` - woff
- `application/x-font-ttf` - ttf
- `image/svg+xml` - svg
@@ -0,0 +1,118 @@
{
"name": "",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "dcedf50ab1ede3283d7a6c70e2fe32f3",
"css": "chat",
"code": 59392,
"src": "fontawesome"
},
{
"uid": "31972e4e9d080eaa796290349ae6c1fd",
"css": "users",
"code": 59393,
"src": "fontawesome"
},
{
"uid": "38575a803c4da31ce20d77e1e1236bcb",
"css": "paper-plane",
"code": 61912,
"src": "fontawesome"
},
{
"uid": "5bb103cd29de77e0e06a52638527b575",
"css": "wrench",
"code": 59394,
"src": "fontawesome"
},
{
"uid": "20fc52f9a88bb7bda023ef209acac095",
"css": "graduation-cap",
"code": 61853,
"src": "fontawesome"
},
{
"uid": "e99461abfef3923546da8d745372c995",
"css": "cog",
"code": 59395,
"src": "fontawesome"
},
{
"uid": "8b80d36d4ef43889db10bc1f0dc9a862",
"css": "user",
"code": 59396,
"src": "fontawesome"
},
{
"uid": "9396b2d8849e0213a0f11c5fd7fcc522",
"css": "tasks",
"code": 61614,
"src": "fontawesome"
},
{
"uid": "d4816c0845aa43767213d45574b3b145",
"css": "history",
"code": 61914,
"src": "fontawesome"
},
{
"uid": "559647a6f430b3aeadbecd67194451dd",
"css": "menu",
"code": 61641,
"src": "fontawesome"
},
{
"uid": "41087bc74d4b20b55059c60a33bf4008",
"css": "edit",
"code": 59397,
"src": "fontawesome"
},
{
"uid": "ad6b3fbb5324abe71a9c0b6609cbb9f1",
"css": "right-big",
"code": 59398,
"src": "fontawesome"
},
{
"uid": "f06fe7ff18d1c591bc1183cb3ab105e9",
"css": "google",
"code": 61856,
"src": "fontawesome"
},
{
"uid": "8e04c98c8f5ca0a035776e3001ad2638",
"css": "facebook",
"code": 61594,
"src": "fontawesome"
},
{
"uid": "e9fa538fd5913046497ac148e27cd8ea",
"css": "apple",
"code": 61817,
"src": "fontawesome"
},
{
"uid": "5e0a374728ffa8d0ae1f331a8f648231",
"css": "github",
"code": 61715,
"src": "fontawesome"
},
{
"uid": "0d20938846444af8deb1920dc85a29fb",
"css": "logout",
"code": 59399,
"src": "fontawesome"
},
{
"uid": "555ef8c86832e686fef85f7af2eb7cde",
"css": "left-big",
"code": 59400,
"src": "fontawesome"
}
]
}
+85
View File
@@ -0,0 +1,85 @@
/*
Animation example, for spinners
*/
.animate-spin {
-moz-animation: spin 2s infinite linear;
-o-animation: spin 2s infinite linear;
-webkit-animation: spin 2s infinite linear;
animation: spin 2s infinite linear;
display: inline-block;
}
@-moz-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-webkit-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-o-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-ms-keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes spin {
0% {
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(359deg);
-o-transform: rotate(359deg);
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@@ -0,0 +1,19 @@
.icon-chat:before { content: '\e800'; } /* '' */
.icon-users:before { content: '\e801'; } /* '' */
.icon-wrench:before { content: '\e802'; } /* '' */
.icon-cog:before { content: '\e803'; } /* '' */
.icon-user:before { content: '\e804'; } /* '' */
.icon-edit:before { content: '\e805'; } /* '' */
.icon-right-big:before { content: '\e806'; } /* '' */
.icon-logout:before { content: '\e807'; } /* '' */
.icon-left-big:before { content: '\e808'; } /* '' */
.icon-facebook:before { content: '\f09a'; } /* '' */
.icon-tasks:before { content: '\f0ae'; } /* '' */
.icon-menu:before { content: '\f0c9'; } /* '' */
.icon-github:before { content: '\f113'; } /* '' */
.icon-apple:before { content: '\f179'; } /* '' */
.icon-graduation-cap:before { content: '\f19d'; } /* '' */
.icon-google:before { content: '\f1a0'; } /* '' */
.icon-paper-plane:before { content: '\f1d8'; } /* '' */
.icon-history:before { content: '\f1da'; } /* '' */
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
.icon-chat { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-users { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-user { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-right-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-left-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
.icon-facebook { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf09a;&nbsp;'); }
.icon-tasks { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ae;&nbsp;'); }
.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0c9;&nbsp;'); }
.icon-github { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf113;&nbsp;'); }
.icon-apple { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf179;&nbsp;'); }
.icon-graduation-cap { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf19d;&nbsp;'); }
.icon-google { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1a0;&nbsp;'); }
.icon-paper-plane { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1d8;&nbsp;'); }
.icon-history { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1da;&nbsp;'); }
+30
View File
@@ -0,0 +1,30 @@
[class^="icon-"], [class*=" icon-"] {
font-family: 'fontello';
font-style: normal;
font-weight: normal;
/* fix buttons height */
line-height: 1em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
}
.icon-chat { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-users { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-wrench { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-user { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-edit { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-right-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-left-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
.icon-facebook { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf09a;&nbsp;'); }
.icon-tasks { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ae;&nbsp;'); }
.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0c9;&nbsp;'); }
.icon-github { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf113;&nbsp;'); }
.icon-apple { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf179;&nbsp;'); }
.icon-graduation-cap { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf19d;&nbsp;'); }
.icon-google { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1a0;&nbsp;'); }
.icon-paper-plane { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1d8;&nbsp;'); }
.icon-history { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1da;&nbsp;'); }
+74
View File
@@ -0,0 +1,74 @@
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?64785462');
src: url('../font/fontello.eot?64785462#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?64785462') format('woff2'),
url('../font/fontello.woff?64785462') format('woff'),
url('../font/fontello.ttf?64785462') format('truetype'),
url('../font/fontello.svg?64785462#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?64785462#fontello') format('svg');
}
}
*/
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "fontello";
font-style: normal;
font-weight: normal;
speak: never;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-chat:before { content: '\e800'; } /* '' */
.icon-users:before { content: '\e801'; } /* '' */
.icon-wrench:before { content: '\e802'; } /* '' */
.icon-cog:before { content: '\e803'; } /* '' */
.icon-user:before { content: '\e804'; } /* '' */
.icon-edit:before { content: '\e805'; } /* '' */
.icon-right-big:before { content: '\e806'; } /* '' */
.icon-logout:before { content: '\e807'; } /* '' */
.icon-left-big:before { content: '\e808'; } /* '' */
.icon-facebook:before { content: '\f09a'; } /* '' */
.icon-tasks:before { content: '\f0ae'; } /* '' */
.icon-menu:before { content: '\f0c9'; } /* '' */
.icon-github:before { content: '\f113'; } /* '' */
.icon-apple:before { content: '\f179'; } /* '' */
.icon-graduation-cap:before { content: '\f19d'; } /* '' */
.icon-google:before { content: '\f1a0'; } /* '' */
.icon-paper-plane:before { content: '\f1d8'; } /* '' */
.icon-history:before { content: '\f1da'; } /* '' */
@@ -0,0 +1,280 @@
<!DOCTYPE html>
<html>
<head>
<!--[if lt IE 9]><script language="javascript" type="text/javascript" src="//html5shim.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
<meta charset="UTF-8">
<style>
html {
font-size: 100%;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
a:focus {
outline: thin dotted #333;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
a:hover,
a:active {
outline: 0;
}
input {
margin: 0;
font-size: 100%;
vertical-align: middle;
*overflow: visible;
line-height: normal;
}
input::-moz-focus-inner {
padding: 0;
border: 0;
}
body {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
color: #333;
background-color: #fff;
}
a {
color: #08c;
text-decoration: none;
}
a:hover {
color: #005580;
text-decoration: underline;
}
.row {
margin-left: -20px;
*zoom: 1;
}
.row:before,
.row:after {
display: table;
content: "";
line-height: 0;
}
.row:after {
clear: both;
}
.span3 {
float: left;
min-height: 1px;
margin-left: 20px;
width: 220px;
}
.container {
width: 940px;
margin-right: auto;
margin-left: auto;
*zoom: 1;
}
.container:before,
.container:after {
display: table;
content: "";
line-height: 0;
}
.container:after {
clear: both;
}
small {
font-size: 85%;
}
h1 {
margin: 10px 0;
font-family: inherit;
font-weight: bold;
line-height: 20px;
color: inherit;
text-rendering: optimizelegibility;
line-height: 40px;
font-size: 38.5px;
}
h1 small {
font-weight: normal;
line-height: 1;
color: #999;
font-size: 24.5px;
}
body {
margin-top: 90px;
}
.header {
position: fixed;
top: 0;
left: 50%;
margin-left: -480px;
background-color: #fff;
border-bottom: 1px solid #ddd;
padding-top: 10px;
z-index: 10;
}
.footer {
color: #ddd;
font-size: 12px;
text-align: center;
margin-top: 20px;
}
.footer a {
color: #ccc;
text-decoration: underline;
}
.the-icons {
font-size: 14px;
line-height: 24px;
}
.switch {
position: absolute;
right: 0;
bottom: 10px;
color: #666;
}
.switch input {
margin-right: 0.3em;
}
.codesOn .i-name {
display: none;
}
.codesOn .i-code {
display: inline;
}
.i-code {
display: none;
}
@font-face {
font-family: 'fontello';
src: url('./font/fontello.eot?72761720');
src: url('./font/fontello.eot?72761720#iefix') format('embedded-opentype'),
url('./font/fontello.woff?72761720') format('woff'),
url('./font/fontello.ttf?72761720') format('truetype'),
url('./font/fontello.svg?72761720#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
.demo-icon {
font-family: "fontello";
font-style: normal;
font-weight: normal;
speak: never;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* You can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
</style>
<link rel="stylesheet" href="css/animation.css"><!--[if IE 7]><link rel="stylesheet" href="css/" + font.fontname + "-ie7.css"><![endif]-->
<script>
function toggleCodes(on) {
var obj = document.getElementById('icons');
if (on) {
obj.className += ' codesOn';
} else {
obj.className = obj.className.replace(' codesOn', '');
}
}
</script>
</head>
<body>
<div class="container header">
<h1>fontello <small>font demo</small></h1>
<label class="switch">
<input type="checkbox" onclick="toggleCodes(this.checked)">show codes
</label>
</div>
<div class="container" id="icons">
<div class="row">
<div class="span3" title="Code: 0xe800">
<i class="demo-icon icon-chat">&#xe800;</i> <span class="i-name">icon-chat</span><span class="i-code">0xe800</span>
</div>
<div class="span3" title="Code: 0xe801">
<i class="demo-icon icon-users">&#xe801;</i> <span class="i-name">icon-users</span><span class="i-code">0xe801</span>
</div>
<div class="span3" title="Code: 0xe802">
<i class="demo-icon icon-wrench">&#xe802;</i> <span class="i-name">icon-wrench</span><span class="i-code">0xe802</span>
</div>
<div class="span3" title="Code: 0xe803">
<i class="demo-icon icon-cog">&#xe803;</i> <span class="i-name">icon-cog</span><span class="i-code">0xe803</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xe804">
<i class="demo-icon icon-user">&#xe804;</i> <span class="i-name">icon-user</span><span class="i-code">0xe804</span>
</div>
<div class="span3" title="Code: 0xe805">
<i class="demo-icon icon-edit">&#xe805;</i> <span class="i-name">icon-edit</span><span class="i-code">0xe805</span>
</div>
<div class="span3" title="Code: 0xe806">
<i class="demo-icon icon-right-big">&#xe806;</i> <span class="i-name">icon-right-big</span><span class="i-code">0xe806</span>
</div>
<div class="span3" title="Code: 0xe807">
<i class="demo-icon icon-logout">&#xe807;</i> <span class="i-name">icon-logout</span><span class="i-code">0xe807</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xe808">
<i class="demo-icon icon-left-big">&#xe808;</i> <span class="i-name">icon-left-big</span><span class="i-code">0xe808</span>
</div>
<div class="span3" title="Code: 0xf09a">
<i class="demo-icon icon-facebook">&#xf09a;</i> <span class="i-name">icon-facebook</span><span class="i-code">0xf09a</span>
</div>
<div class="span3" title="Code: 0xf0ae">
<i class="demo-icon icon-tasks">&#xf0ae;</i> <span class="i-name">icon-tasks</span><span class="i-code">0xf0ae</span>
</div>
<div class="span3" title="Code: 0xf0c9">
<i class="demo-icon icon-menu">&#xf0c9;</i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf113">
<i class="demo-icon icon-github">&#xf113;</i> <span class="i-name">icon-github</span><span class="i-code">0xf113</span>
</div>
<div class="span3" title="Code: 0xf179">
<i class="demo-icon icon-apple">&#xf179;</i> <span class="i-name">icon-apple</span><span class="i-code">0xf179</span>
</div>
<div class="span3" title="Code: 0xf19d">
<i class="demo-icon icon-graduation-cap">&#xf19d;</i> <span class="i-name">icon-graduation-cap</span><span class="i-code">0xf19d</span>
</div>
<div class="span3" title="Code: 0xf1a0">
<i class="demo-icon icon-google">&#xf1a0;</i> <span class="i-name">icon-google</span><span class="i-code">0xf1a0</span>
</div>
</div>
<div class="row">
<div class="span3" title="Code: 0xf1d8">
<i class="demo-icon icon-paper-plane">&#xf1d8;</i> <span class="i-name">icon-paper-plane</span><span class="i-code">0xf1d8</span>
</div>
<div class="span3" title="Code: 0xf1da">
<i class="demo-icon icon-history">&#xf1da;</i> <span class="i-name">icon-history</span><span class="i-code">0xf1da</span>
</div>
</div>
</div>
<div class="container footer">Generated by <a href="https://fontello.com">fontello.com</a></div>
</body>
</html>
Binary file not shown.
@@ -0,0 +1,46 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2026 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="chat" unicode="&#xe800;" d="M786 421q0-77-53-143t-143-104-197-38q-48 0-98 9-70-49-155-72-21-5-48-9h-2q-6 0-12 5t-6 12q-1 1-1 3t1 4 1 3l1 3t2 3 2 3 3 3 2 2q3 3 13 14t15 16 12 17 14 21 11 25q-69 40-108 98t-40 125q0 78 53 144t143 104 197 38 197-38 143-104 53-144z m214-142q0-67-40-126t-108-98q5-14 11-25t14-21 13-16 14-17 13-14q0 0 2-2t3-3 2-3 2-3l1-3t1-3 1-4-1-3q-2-8-7-13t-12-4q-28 4-48 9-86 23-156 72-50-9-98-9-151 0-263 74 32-3 49-3 90 0 172 25t148 72q69 52 107 119t37 141q0 43-13 85 72-39 114-99t42-128z" horiz-adv-x="1000" />
<glyph glyph-name="users" unicode="&#xe801;" d="M331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 24 12q34 0 62-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
<glyph glyph-name="wrench" unicode="&#xe802;" d="M214 29q0 14-10 25t-25 10-25-10-11-25 11-25 25-11 25 11 10 25z m360 234l-381-381q-21-20-50-20-29 0-51 20l-59 61q-21 20-21 50 0 29 21 51l380 380q22-55 64-97t97-64z m354 243q0-22-13-59-27-75-92-122t-144-46q-104 0-177 73t-73 177 73 176 177 74q32 0 67-10t60-26q9-6 9-15t-9-16l-163-94v-125l108-60q2 2 44 27t75 45 40 20q8 0 13-5t5-14z" horiz-adv-x="928.6" />
<glyph glyph-name="cog" unicode="&#xe803;" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 16 20 16h124q7 0 13-5t7-12l15-103q28-9 51-20l79 59q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-12 0-7-4-13-9-12-29-37t-30-40q15-28 23-54l102-16q7-1 12-7t4-13z" horiz-adv-x="857.1" />
<glyph glyph-name="user" unicode="&#xe804;" d="M714 69q0-60-35-104t-84-44h-476q-49 0-84 44t-35 104q0 48 5 90t17 85 33 73 52 50 76 19q73-72 174-72t175 72q42 0 75-19t52-50 33-73 18-85 4-90z m-143 495q0-88-62-151t-152-63-151 63-63 151 63 152 151 63 152-63 62-152z" horiz-adv-x="714.3" />
<glyph glyph-name="edit" unicode="&#xe805;" d="M496 189l64 65-85 85-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-8-8-18-4-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160-375-375h-161v160z m248-73l-51-52-161 161 51 52q16 15 38 15t38-15l85-85q16-16 16-38t-16-38z" horiz-adv-x="1000" />
<glyph glyph-name="right-big" unicode="&#xe806;" d="M821 314q0-30-20-50l-363-364q-22-20-51-20-29 0-50 20l-42 42q-22 21-22 51t22 51l163 163h-393q-29 0-47 21t-18 51v71q0 30 18 51t47 20h393l-163 165q-22 20-22 50t22 50l42 42q21 21 50 21 29 0 51-21l363-363q20-20 20-51z" horiz-adv-x="857.1" />
<glyph glyph-name="logout" unicode="&#xe807;" d="M357 46q0-2 1-11t0-14-2-14-5-11-12-3h-178q-67 0-114 47t-47 114v392q0 67 47 114t114 47h178q8 0 13-5t5-13q0-2 1-11t0-15-2-13-5-11-12-3h-178q-37 0-63-26t-27-64v-392q0-37 27-63t63-27h174t6 0 7-2 4-3 4-5 1-8z m518 304q0-14-11-25l-303-304q-11-10-25-10t-25 10-11 25v161h-250q-14 0-25 11t-11 25v214q0 15 11 25t25 11h250v161q0 14 11 25t25 10 25-10l303-304q11-10 11-25z" horiz-adv-x="928.6" />
<glyph glyph-name="left-big" unicode="&#xe808;" d="M857 350v-71q0-30-18-51t-47-21h-393l164-164q21-20 21-50t-21-50l-42-43q-21-20-51-20-29 0-50 20l-364 364q-20 21-20 50 0 29 20 51l364 363q21 21 50 21 29 0 51-21l42-41q21-22 21-51t-21-51l-164-164h393q29 0 47-20t18-51z" horiz-adv-x="857.1" />
<glyph glyph-name="facebook" unicode="&#xf09a;" d="M535 843v-147h-87q-48 0-65-20t-17-60v-106h164l-22-165h-142v-424h-171v424h-142v165h142v122q0 104 58 161t155 57q82 0 127-7z" horiz-adv-x="571.4" />
<glyph glyph-name="tasks" unicode="&#xf0ae;" d="M571 64h358v72h-358v-72z m-214 286h572v71h-572v-71z m357 286h215v71h-215v-71z m286-465v-142q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v142q0 15 11 26t25 10h928q15 0 25-10t11-26z m0 286v-143q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v143q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 286v-143q0-14-11-25t-25-11h-928q-15 0-25 11t-11 25v143q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="menu" unicode="&#xf0c9;" d="M857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
<glyph glyph-name="github" unicode="&#xf113;" d="M357 171q0-22-7-45t-24-43-40-19-41 19-24 43-7 45 7 46 24 43 41 19 40-19 24-43 7-46z m357 0q0-22-7-45t-24-43-40-19-41 19-24 43-7 45 7 46 24 43 41 19 40-19 24-43 7-46z m90 0q0 67-39 114t-104 47q-23 0-109-12-40-6-88-6t-87 6q-85 12-109 12-66 0-104-47t-39-114q0-49 18-85t45-58 68-33 78-17 83-4h94q46 0 83 4t78 17 69 33 45 58 18 85z m125 99q0-116-34-185-22-43-59-74t-79-48-95-27-96-12-93-3q-43 0-79 2t-82 7-85 17-77 29-67 45-48 64q-35 69-35 185 0 132 76 221-15 45-15 95 0 64 28 121 61 0 106-22t106-69q82 20 172 20 83 0 157-18 58 46 104 67t105 22q29-57 29-121 0-49-15-94 76-89 76-222z" horiz-adv-x="928.6" />
<glyph glyph-name="apple" unicode="&#xf179;" d="M777 172q-21-70-68-139-72-110-144-110-27 0-78 18-48 18-84 18-34 0-79-19-45-19-74-19-85 0-168 145-82 146-82 281 0 127 63 208 63 81 159 81 40 0 98-17 58-17 77-17 25 0 80 19 57 19 97 19 66 0 119-36 29-20 58-56-44-37-64-66-36-52-36-115 0-69 38-125t88-70z m-209 655q0-34-17-76-16-42-52-77-30-30-60-40-20-7-58-10 2 83 44 143 41 60 139 83 1-2 2-6t1-6q0-2 0-6t1-5z" horiz-adv-x="785.7" />
<glyph glyph-name="graduation-cap" unicode="&#xf19d;" d="M990 384l10-177q2-38-46-71t-131-52-180-20-180 20-131 52-46 71l10 177 320-101q12-4 27-4t27 4z m296 180q0-12-13-17l-625-196q-2-1-5-1t-6 1l-364 115q-24-19-39-63t-19-99q35-20 35-61 0-39-32-60l32-242q1-7-4-13-5-7-14-7h-107q-8 0-13 7-6 6-5 13l33 242q-33 21-33 60 0 41 36 62 7 115 55 184l-186 58q-12 5-12 17t12 18l625 196q3 1 6 1t5-1l625-196q13-5 13-18z" horiz-adv-x="1285.7" />
<glyph glyph-name="google" unicode="&#xf1a0;" d="M429 411h404q7-37 7-71 0-121-51-216t-145-149-215-54q-88 0-167 34t-137 91-91 137-34 167 34 167 91 137 137 91 167 34q167 0 287-113l-117-112q-68 67-170 67-72 0-133-37t-97-98-36-136 36-136 97-98 133-37q48 0 89 14t67 33 46 46 28 49 13 43h-243v147z" horiz-adv-x="857.1" />
<glyph glyph-name="paper-plane" unicode="&#xf1d8;" d="M984 844q19-13 15-36l-142-857q-3-16-18-25-8-5-18-5-6 0-13 3l-253 104-135-165q-10-13-27-13-7 0-12 2-11 4-17 13t-7 21v195l482 590-596-516-221 91q-20 8-22 30-1 23 18 33l928 536q9 5 18 5 11 0 20-6z" horiz-adv-x="1000" />
<glyph glyph-name="history" unicode="&#xf1da;" d="M857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z m-357 161v-250q0-8-5-13t-13-5h-178q-8 0-13 5t-5 13v35q0 8 5 13t13 5h125v197q0 8 5 13t12 5h36q8 0 13-5t5-13z" horiz-adv-x="857.1" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:
+20
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
}
+30
View File
@@ -0,0 +1,30 @@
import tailwindcss from '@tailwindcss/vite';
import adapter from '@sveltejs/adapter-static';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
server: {
host: "0.0.0.0"
},
plugins: [
tailwindcss(),
sveltekit({
compilerOptions: {
// Force runes mode for the project, except for libraries. Can be removed in svelte 6.
runes: ({ filename }) => filename.split(/[/\\]/).includes('node_modules') ? undefined : true
},
// 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({
pages: '../internal/http/web',
assets: '../internal/http/web',
fallback: 'index.html', // <-- vérifie que c'est bien là et pas oublié/mal placé
precompress: false,
strict: true
}),
})
]
});