diff --git a/jsrepo.json b/jsrepo.json new file mode 100644 index 0000000..0ed3231 --- /dev/null +++ b/jsrepo.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://unpkg.com/jsrepo@2.3.1/schemas/project-config.json", + "repos": ["@ieedan/shadcn-svelte-extras"], + "includeTests": false, + "watermark": true, + "formatter": "prettier", + "configFiles": {}, + "paths": { + "*": "$lib/blocks", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "actions": "$lib/actions", + "hooks": "$lib/hooks" + } +} diff --git a/package.json b/package.json index 4412fe9..5bc5dbb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.1", "type": "module", "scripts": { - "dev": "concurrently \"convex dev\" \"vite dev\"", + "dev": "concurrently -n \"convex,vite\" -c \"blue.bold,green.bold\" \"convex dev\" \"vite dev\"", "build": "vite build", "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", @@ -19,6 +19,7 @@ "devDependencies": { "@eslint/compat": "^1.2.5", "@eslint/js": "^9.18.0", + "@lucide/svelte": "^0.515.0", "@playwright/test": "^1.49.1", "@sveltejs/adapter-auto": "^6.0.0", "@sveltejs/kit": "^2.16.0", @@ -26,11 +27,17 @@ "@tailwindcss/vite": "^4.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/svelte": "^5.2.4", + "bits-ui": "^2.6.2", + "clsx": "^2.1.1", + "concurrently": "^9.1.2", + "convex": "^1.24.8", + "convex-svelte": "^0.0.11", "eslint": "^9.18.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-svelte": "^3.0.0", "globals": "^16.0.0", "jsdom": "^26.0.0", + "melt": "^0.35.0", "mode-watcher": "^1.0.8", "prettier": "^3.4.2", "prettier-plugin-svelte": "^3.3.3", @@ -38,15 +45,13 @@ "runed": "^0.28.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", + "tailwind-merge": "^3.3.1", + "tailwind-variants": "^1.0.0", "tailwindcss": "^4.0.0", "typescript": "^5.0.0", "typescript-eslint": "^8.20.0", "vite": "^6.2.6", - "vitest": "^3.2.3", - "concurrently": "^9.1.2", - "convex": "^1.24.8", - "convex-svelte": "^0.0.11", - "melt": "^0.35.0" + "vitest": "^3.2.3" }, "pnpm": { "onlyBuiltDependencies": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40a9417..65c28a6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@eslint/js': specifier: ^9.18.0 version: 9.28.0 + '@lucide/svelte': + specifier: ^0.515.0 + version: 0.515.0(svelte@5.34.1) '@playwright/test': specifier: ^1.49.1 version: 1.53.0 @@ -35,6 +38,12 @@ importers: '@testing-library/svelte': specifier: ^5.2.4 version: 5.2.8(svelte@5.34.1)(vite@6.3.5(@types/node@24.0.1)(jiti@2.4.2)(lightningcss@1.30.1))(vitest@3.2.3(@types/node@24.0.1)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)) + bits-ui: + specifier: ^2.6.2 + version: 2.6.2(@internationalized/date@3.8.2)(svelte@5.34.1) + clsx: + specifier: ^2.1.1 + version: 2.1.1 concurrently: specifier: ^9.1.2 version: 9.1.2 @@ -83,6 +92,12 @@ importers: svelte-check: specifier: ^4.0.0 version: 4.2.1(picomatch@4.0.2)(svelte@5.34.1)(typescript@5.8.3) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + tailwind-variants: + specifier: ^1.0.0 + version: 1.0.0(tailwindcss@4.1.10) tailwindcss: specifier: ^4.0.0 version: 4.1.10 @@ -531,6 +546,9 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@internationalized/date@3.8.2': + resolution: {integrity: sha512-/wENk7CbvLbkUvX1tu0mwq49CVkkWpkXubGel6birjRPyo6uQ4nQpnq5xZu823zRCwwn82zgHrvgF1vZyvmVgA==} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -557,6 +575,11 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@lucide/svelte@0.515.0': + resolution: {integrity: sha512-CEAyqcZmNBfYzVgaRmK2RFJP5tnbXxekRyDk0XX/eZQRfsJmkDvmQwXNX8C869BgNeryzmrRyjHhUL6g9ZOHNA==} + peerDependencies: + svelte: ^5 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -714,6 +737,9 @@ packages: svelte: ^5.0.0 vite: ^6.0.0 + '@swc/helpers@0.5.17': + resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@tailwindcss/node@4.1.10': resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==} @@ -991,6 +1017,13 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bits-ui@2.6.2: + resolution: {integrity: sha512-OlPSUAT+ENhtRarPjABljca1cCljyoAqOZKfgjCB8PxQii2fL0AKnzObhnEdhZKwYdpXczEtNOYqUUNYwliaWA==} + engines: {node: '>=20', pnpm: '>=8.7.0'} + peerDependencies: + '@internationalized/date': ^3.8.1 + svelte: ^5.33.0 + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1954,6 +1987,12 @@ packages: peerDependencies: svelte: ^5.0.0 + svelte-toolbelt@0.9.1: + resolution: {integrity: sha512-wBX6MtYw/kpht80j5zLpxJyR9soLizXPIAIWEVd9llAi17SR44ZdG291bldjB7r/K5duC0opDFcuhk2cA1hb8g==} + engines: {node: '>=18', pnpm: '>=8.7.0'} + peerDependencies: + svelte: ^5.30.2 + svelte@5.34.1: resolution: {integrity: sha512-jWNnN2hZFNtnzKPptCcJHBWrD9CtbHPDwIRIODufOYaWkR0kLmAIlM384lMt4ucwuIRX4hCJwD2D8ZtEcGJQ0Q==} engines: {node: '>=18'} @@ -1961,6 +2000,21 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tailwind-merge@3.0.2: + resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==} + + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + + tailwind-variants@1.0.0: + resolution: {integrity: sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==} + engines: {node: '>=16.x', pnpm: '>=7.x'} + peerDependencies: + tailwindcss: '*' + tailwindcss@4.1.10: resolution: {integrity: sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==} @@ -2493,6 +2547,10 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@internationalized/date@3.8.2': + dependencies: + '@swc/helpers': 0.5.17 + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -2518,6 +2576,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@lucide/svelte@0.515.0(svelte@5.34.1)': + dependencies: + svelte: 5.34.1 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2647,6 +2709,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@swc/helpers@0.5.17': + dependencies: + tslib: 2.8.1 + '@tailwindcss/node@4.1.10': dependencies: '@ampproject/remapping': 2.3.0 @@ -2941,6 +3007,18 @@ snapshots: balanced-match@1.0.2: {} + bits-ui@2.6.2(@internationalized/date@3.8.2)(svelte@5.34.1): + dependencies: + '@floating-ui/core': 1.7.1 + '@floating-ui/dom': 1.7.1 + '@internationalized/date': 3.8.2 + css.escape: 1.5.1 + esm-env: 1.2.2 + runed: 0.28.0(svelte@5.34.1) + svelte: 5.34.1 + svelte-toolbelt: 0.9.1(svelte@5.34.1) + tabbable: 6.2.0 + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -3836,6 +3914,13 @@ snapshots: style-to-object: 1.0.9 svelte: 5.34.1 + svelte-toolbelt@0.9.1(svelte@5.34.1): + dependencies: + clsx: 2.1.1 + runed: 0.28.0(svelte@5.34.1) + style-to-object: 1.0.9 + svelte: 5.34.1 + svelte@5.34.1: dependencies: '@ampproject/remapping': 2.3.0 @@ -3855,6 +3940,17 @@ snapshots: symbol-tree@3.2.4: {} + tabbable@6.2.0: {} + + tailwind-merge@3.0.2: {} + + tailwind-merge@3.3.1: {} + + tailwind-variants@1.0.0(tailwindcss@4.1.10): + dependencies: + tailwind-merge: 3.0.2 + tailwindcss: 4.1.10 + tailwindcss@4.1.10: {} tapable@2.2.2: {} diff --git a/src/app.css b/src/app.css index 2a14cb0..f7ce0eb 100644 --- a/src/app.css +++ b/src/app.css @@ -1,5 +1,7 @@ @import 'tailwindcss'; +@custom-variant dark (&:where(.dark, .dark *)); + :root { --background: oklch(0.9754 0.0084 325.6414); --foreground: oklch(0.3257 0.1161 325.0372); @@ -153,4 +155,4 @@ body { @apply bg-background text-foreground; } -} \ No newline at end of file +} diff --git a/src/lib/actions/active.svelte.ts b/src/lib/actions/active.svelte.ts new file mode 100644 index 0000000..98aab91 --- /dev/null +++ b/src/lib/actions/active.svelte.ts @@ -0,0 +1,110 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +import { page } from '$app/state'; +import { untrack } from 'svelte'; +import { createAttachmentKey } from 'svelte/attachments'; + +export type Options = { + /** Determines if the route should be active for subdirectories. + * + * @default true + */ + activeForSubdirectories?: boolean; + /** Determines if the href of the `` tag is a `#` route + * + * @default false + */ + isHash?: boolean; + /** Determines if the href of the `` tag is a search route + * + * @default false + */ + isSearch?: boolean; + url: URL; +}; + +/** Sets the `data-active` attribute on an `` tag based on its 'active' state. + * + * @param node + * @param opts + * + * ## Usage + * ```svelte + * Route + * ``` + */ +export function active(node: HTMLAnchorElement, opts: Omit = {}) { + checkIsActive(node.href, { ...opts, url: page.url }).toString(); + + $effect(() => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + page.url; + + untrack(() => { + node.setAttribute( + 'data-active', + checkIsActive(node.href, { ...opts, url: page.url }).toString() + ); + }); + }); +} + +/** Sets the `data-active` attribute on an `` tag based on its 'active' state. + * + * @param opts + * @returns + * + * ## Usage + * ```svelte + * Route + * ``` + */ +export function attachActive(opts: Omit = {}) { + return { + [createAttachmentKey()]: (node: HTMLAnchorElement) => active(node, opts) + }; +} + +export const checkIsActive = ( + nodeHref: string, + { activeForSubdirectories, url, isHash, isSearch }: Options +): boolean => { + let href: string = new URL(nodeHref).pathname; + + if (isHash) { + href = new URL(nodeHref).hash; + } + + let searchParamName: string | undefined = undefined; + let searchParamValue: string | undefined = undefined; + + if (isSearch) { + const tempUrl = new URL(nodeHref); + + for (const [key, value] of tempUrl.searchParams.entries()) { + searchParamName = key; + searchParamValue = value; + } + + href = new URL(nodeHref).search; + } + + const samePath = href === url.pathname; + + const isParentRoute: boolean = + (activeForSubdirectories == undefined || activeForSubdirectories) && + url.pathname.startsWith(href ?? ''); + + const isHashRoute: boolean = + isHash == true && (url.hash == href || ((href == '#' || href == '#/') && url.hash == '')); + + const isSearchRoute: boolean = + isSearch === true && + searchParamName !== undefined && + searchParamValue !== undefined && + (url.searchParams.get(searchParamName) ?? '/') === searchParamValue; + + return samePath || isParentRoute || isHashRoute || isSearchRoute; +}; diff --git a/src/lib/backend/convex/schema.ts b/src/lib/backend/convex/schema.ts index 72ef662..0d164b0 100644 --- a/src/lib/backend/convex/schema.ts +++ b/src/lib/backend/convex/schema.ts @@ -5,4 +5,4 @@ export default defineSchema({ user_keys: defineTable({ openRouter: v.string() }) -}); \ No newline at end of file +}); diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte new file mode 100644 index 0000000..4981447 --- /dev/null +++ b/src/lib/components/ui/button/button.svelte @@ -0,0 +1,128 @@ + + + + + + + + { + onclick?.(e); + + if (type === undefined) return; + + if (onClickPromise) { + loading = true; + + await onClickPromise(e); + + loading = false; + } + }} +> + {#if type !== undefined && loading} +
+
+ +
+
+ Loading + {/if} + {@render children?.()} +
diff --git a/src/lib/components/ui/button/index.ts b/src/lib/components/ui/button/index.ts new file mode 100644 index 0000000..34c3e59 --- /dev/null +++ b/src/lib/components/ui/button/index.ts @@ -0,0 +1,27 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + type AnchorElementProps, + type ButtonElementProps, + type ButtonPropsWithoutHTML, + buttonVariants +} from './button.svelte'; + +export { + Root, + type ButtonProps as Props, + // + Root as Button, + buttonVariants, + type ButtonProps, + type ButtonSize, + type ButtonVariant, + type AnchorElementProps, + type ButtonElementProps, + type ButtonPropsWithoutHTML +}; diff --git a/src/lib/components/ui/card/card-content.svelte b/src/lib/components/ui/card/card-content.svelte new file mode 100644 index 0000000..ce8f1e8 --- /dev/null +++ b/src/lib/components/ui/card/card-content.svelte @@ -0,0 +1,10 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/card/card-description.svelte b/src/lib/components/ui/card/card-description.svelte new file mode 100644 index 0000000..7f14121 --- /dev/null +++ b/src/lib/components/ui/card/card-description.svelte @@ -0,0 +1,10 @@ + + +

+ {@render children?.()} +

diff --git a/src/lib/components/ui/card/card-header.svelte b/src/lib/components/ui/card/card-header.svelte new file mode 100644 index 0000000..981f958 --- /dev/null +++ b/src/lib/components/ui/card/card-header.svelte @@ -0,0 +1,10 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/card/card-title.svelte b/src/lib/components/ui/card/card-title.svelte new file mode 100644 index 0000000..f1bd93b --- /dev/null +++ b/src/lib/components/ui/card/card-title.svelte @@ -0,0 +1,10 @@ + + +

+ {@render children?.()} +

diff --git a/src/lib/components/ui/card/card.svelte b/src/lib/components/ui/card/card.svelte new file mode 100644 index 0000000..c3512cf --- /dev/null +++ b/src/lib/components/ui/card/card.svelte @@ -0,0 +1,10 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/card/index.ts b/src/lib/components/ui/card/index.ts new file mode 100644 index 0000000..675bdd9 --- /dev/null +++ b/src/lib/components/ui/card/index.ts @@ -0,0 +1,7 @@ +import Root from './card.svelte'; +import Header from './card-header.svelte'; +import Title from './card-title.svelte'; +import Description from './card-description.svelte'; +import Content from './card-content.svelte'; + +export { Root, Header, Title, Description, Content }; diff --git a/src/lib/components/ui/input/index.ts b/src/lib/components/ui/input/index.ts new file mode 100644 index 0000000..aef418b --- /dev/null +++ b/src/lib/components/ui/input/index.ts @@ -0,0 +1,3 @@ +import Input from './input.svelte'; + +export { Input }; diff --git a/src/lib/components/ui/input/input.svelte b/src/lib/components/ui/input/input.svelte new file mode 100644 index 0000000..106d948 --- /dev/null +++ b/src/lib/components/ui/input/input.svelte @@ -0,0 +1,14 @@ + + + diff --git a/src/lib/components/ui/light-switch/index.ts b/src/lib/components/ui/light-switch/index.ts new file mode 100644 index 0000000..695e77e --- /dev/null +++ b/src/lib/components/ui/light-switch/index.ts @@ -0,0 +1,7 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +import LightSwitch from './light-switch.svelte'; + +export { LightSwitch }; diff --git a/src/lib/components/ui/light-switch/light-switch.svelte b/src/lib/components/ui/light-switch/light-switch.svelte new file mode 100644 index 0000000..504b348 --- /dev/null +++ b/src/lib/components/ui/light-switch/light-switch.svelte @@ -0,0 +1,18 @@ + + + + + diff --git a/src/lib/components/ui/light-switch/types.ts b/src/lib/components/ui/light-switch/types.ts new file mode 100644 index 0000000..97fc777 --- /dev/null +++ b/src/lib/components/ui/light-switch/types.ts @@ -0,0 +1,7 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +export type LightSwitchProps = { + variant?: 'outline' | 'ghost'; +}; diff --git a/src/lib/components/ui/link/index.ts b/src/lib/components/ui/link/index.ts new file mode 100644 index 0000000..a8191a8 --- /dev/null +++ b/src/lib/components/ui/link/index.ts @@ -0,0 +1,3 @@ +import Link from './link.svelte'; + +export { Link }; diff --git a/src/lib/components/ui/link/link.svelte b/src/lib/components/ui/link/link.svelte new file mode 100644 index 0000000..a000d6e --- /dev/null +++ b/src/lib/components/ui/link/link.svelte @@ -0,0 +1,10 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/tabs/index.ts b/src/lib/components/ui/tabs/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/index.ts b/src/lib/index.ts deleted file mode 100644 index 856f2b6..0000000 --- a/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/src/lib/utils/utils.ts b/src/lib/utils/utils.ts new file mode 100644 index 0000000..85bb323 --- /dev/null +++ b/src/lib/utils/utils.ts @@ -0,0 +1,17 @@ +/* + Installed from @ieedan/shadcn-svelte-extras +*/ + +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChild = T extends { child?: any } ? Omit : T; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type WithoutChildren = T extends { children?: any } ? Omit : T; +export type WithoutChildrenOrChild = WithoutChildren>; +export type WithElementRef = T & { ref?: U | null }; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index dc1529b..8d6c4c5 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,5 +5,5 @@ let { children } = $props(); - + {@render children()} diff --git a/src/routes/account/+layout.svelte b/src/routes/account/+layout.svelte index 49f0bbf..ccd0e3a 100644 --- a/src/routes/account/+layout.svelte +++ b/src/routes/account/+layout.svelte @@ -1,4 +1,73 @@ +
+
+ + + Back to Chat + +
+ + +
+
+
+ +
+
+ {#each navigation as tab (tab)} + + {tab.title} + + {/each} +
+ {@render children?.()} +
+
+
diff --git a/src/routes/account/api-keys/+page.svelte b/src/routes/account/api-keys/+page.svelte index e69de29..c7f2d94 100644 --- a/src/routes/account/api-keys/+page.svelte +++ b/src/routes/account/api-keys/+page.svelte @@ -0,0 +1,30 @@ + + + + + + Open Router + + API Key for OpenRouter. + + +
+ + + Get your API key from + + OpenRouter + + +
+
+ +
+
+