Merge pull request #2 from NabilOuldHamou/graphical-rework

Refonte Graphique du site
This commit is contained in:
Nabil Ould Hamou 2024-01-07 04:22:55 +01:00 committed by GitHub
commit 1e2c511bb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1309 additions and 196 deletions

13
components.json Normal file
View file

@ -0,0 +1,13 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"style": "new-york",
"tailwind": {
"config": "tailwind.config.js",
"css": "src/app.css",
"baseColor": "zinc"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils"
}
}

417
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -32,6 +32,14 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"svelte-icons": "^2.1.0" "@radix-ui/react-slot": "^1.0.2",
"bits-ui": "^0.13.3",
"clsx": "^2.1.0",
"mode-watcher": "^0.1.2",
"radix-icons-svelte": "^1.2.1",
"svelte-icons": "^2.1.0",
"tailwind-merge": "^2.2.0",
"tailwind-variants": "^0.1.20",
"vaul-svelte": "^0.0.4"
} }
} }

View file

@ -1,8 +1,78 @@
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap');
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
.content-container { @layer base {
height: calc(100vh - theme('spacing.16')); :root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 0 0% 98%;
--ring: 240 10% 3.9%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--ring: 240 4.9% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
} }

View file

@ -2,25 +2,39 @@ const projects = [
{ {
"name": "Portfolio", "name": "Portfolio",
"stack": ["SvelteKit", "TypeScript", "TailwindCSS"], "stack": ["SvelteKit", "TypeScript", "TailwindCSS"],
"description": "SvelteKit project to learn svelte and also update my personal website.", "shortDesc": "Personal portfolio made using SvelteKit for learning purposes.",
"description": "This project is the website that you are actually using. This is my personal portfolio and used " +
"this project as an occasion to learn SvelteKit. In order to have an easier time with the design and make something " +
"good looking I also used TailwindCSS and Shadcn-svelte which is a binding for Shadcn/ui in Svelte. This project was " +
"the perfect occasion for me to learn and get more familiar with SvelteKit which is now my favorite framework for web developpment.",
"url": "https://github.com/nabilouldhamou/portfolio" "url": "https://github.com/nabilouldhamou/portfolio"
}, },
{ {
"name": "Green Plates", "name": "Green Plates",
"stack": ["GoLang", "GinGonic", "SvelteKit", "TypeScript"], "stack": ["Go", "GinGonic", "SvelteKit", "TypeScript"],
"description": "Recipe website with custom made backend using GoLang.", "shortDesc": "Recipe website with custom made backend using Go.",
"description": "Green Plates is a recipe sharing website made in collaboration with my friend @BenGregory23. " +
"This website is also made with SvelteKit for the frontend but also features a custom made high performance backend using Go and GinGonic. " +
"The website is still in a work in progress but the backend 99% done, only missing file fetching after the files are uploaded to the server.",
"url": "https://github.com/NabilOuldHamou?tab=repositories&q=green-plates&type=&language=&sort=" "url": "https://github.com/NabilOuldHamou?tab=repositories&q=green-plates&type=&language=&sort="
}, },
{ {
"name": "AnimeWorld", "name": "AnimeWorld",
"stack": ["Symfony 6", "PHP", "Nginx"], "stack": ["Symfony 6", "PHP", "Nginx"],
"description": "University group project for the Web Server Programming class. I took the initiative to deploy the website on my VPS.", "shortDesc": "University group project for my Web Server Programming class.",
"description": "For my Web Server Programming class, we were tasked with creating a website using Symfony 6 and other libraries, " +
"we decided to make an anime rating website, where users can create their own accounts are rate animes using a grade from 0 to 5 and also leave a comment. " +
"This project was the occasion for me to endorse a bit as a group leader by planning and taking care of the GitHub repository by giving some rules to follow to not compromise the repository. " +
"This was also the occasion for me to work once again on deploying a website on a Linux VPS by using Nginx and having to come up with the configuration needed for our website and also configure mailer, DNS and other domain related stuff.",
"url": "https://github.com/luxray555/projetsymfony" "url": "https://github.com/luxray555/projetsymfony"
}, },
{ {
"name": "EMG reader", "name": "EMG reader",
"stack": ["C", "Arduino"], "stack": ["C", "Arduino"],
"description": "EMG reader for a friend in Biomedical Masters. The source code is private.", "shortDesc": "EMG reader for a friend in Biomedical Masters.",
"description": "A friend in Biomedical Masters degree asked for my help to conceive a program, that would allow him to read the data from EMG using an Arduino board. " +
"I proceeded to write a program that can read the data from the EMG and send it to a computer where that data can be read and processed accordingly. " +
"My friend did not release the source code thus the project is considered proprietary.",
"url": "" "url": ""
} }
] ]

View file

@ -0,0 +1,29 @@
<script lang="ts">
import { Button } from "@/components/ui/button";
import * as Drawer from "@/components/ui/drawer";
import {HamburgerMenu} from "radix-icons-svelte";
import routes from "@/Routes";
export let path;
</script>
<Drawer.Root>
<Drawer.Trigger asChild let:builder>
<Button class="sm:hidden" variant="ghost" builders={[builder]}><HamburgerMenu class="w-6 h-6" /></Button>
</Drawer.Trigger>
<Drawer.Content>
<div class="mx-auto w-full max-w-sm mb-10">
<Drawer.Header>
<Drawer.Title>nbiloh.me</Drawer.Title>
</Drawer.Header>
<div class="p-4 pb-0">
<div class="flex flex-col gap-4 items-center justify-center space-x-2 text-muted-foreground">
{#each routes as route}
<Drawer.Close asChild let:builder>
<Button builders={[builder]} variant="ghost" class={`cursor-pointer ${path === route.href ? 'text-foreground font-bold' : 'hover:text-foreground'}`} href={route.href}>{route.name}</Button>
</Drawer.Close>
{/each}
</div>
</div>
</div>
</Drawer.Content>
</Drawer.Root>

View file

@ -0,0 +1,17 @@
<script lang="ts">
import { Button } from "@/components/ui/button";
import { Sun, Moon } from "radix-icons-svelte";
import {ModeWatcher, toggleMode} from "mode-watcher";
</script>
<ModeWatcher />
<Button on:click={toggleMode} variant="outline" size="icon">
<Sun
class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
/>
<Moon
class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
/>
<span class="sr-only">Toggle theme</span>
</Button>

View file

@ -1,15 +1,19 @@
<script> <script lang="ts">
import routes from '$lib/Routes' import routes from '$lib/Routes'
export let path; import LightSwitch from "@/components/LightSwitch.svelte";
import HamburgerMenu from "@/components/HamburgerMenu.svelte";
export let path: string;
</script> </script>
<div class='h-14 w-full top-0 left-0 sticky flex justify-center items-center bg-dark-charcoal-gray'> <div class='h-14 w-full top-0 left-0 sticky flex justify-center items-center bg-background'>
<div class='pt-4 w-4/5 flex justify-between'> <div class='pt-4 w-4/5 flex justify-between items-center'>
<img alt="Logo" src="/images/console.png" class='w-12 h-12' /> <a href="/" class="font-bold">nbiloh.me</a>
<div class='flex items-center justify-between sm:gap-x-8 gap-x-4 font-medium text-lg'> <div class='hidden sm:visible sm:flex items-center justify-between sm:gap-x-8 gap-x-4 font-medium text-lg text-muted-foreground'>
{#each routes as route} {#each routes as route}
<a class={`cursor-pointer ${path === route.href ? 'text-accent' : 'hover:text-accent'}`} href={route.href}>{route.name}</a> <a class={`cursor-pointer ${path === route.href ? 'text-foreground' : 'hover:text-foreground'}`} href={route.href}>{route.name}</a>
{/each} {/each}
</div> </div>
<HamburgerMenu path={path} />
<LightSwitch />
</div> </div>
</div> </div>

View file

@ -1,14 +1,57 @@
<script lang="ts"> <script lang="ts">
import * as Card from "@/components/ui/card";
import {Button, buttonVariants} from "@/components/ui/button";
import * as Dialog from "@/components/ui/dialog"
import {EyeNone, GithubLogo} from "radix-icons-svelte";
export let name: string; export let name: string;
export let stack: string; export let stack: string;
export let shortDescription: string;
export let description: string; export let description: string;
export let url: string; export let url: string;
</script> </script>
<a target='_blank' href='{url}'> <Card.Root class="max-w-xs w-[320px]">
<div class='rounded-md border border-gray-200 max-w-xl text-center flex flex-col items-center justify-center flex-wrap hover:border-accent transition ease-in-out duration-500'> <Card.Header>
<h1 class='text-blue-300 text-xl font-medium'>{name}</h1> <Card.Title class="font-bold">{name}</Card.Title>
<h3 class='text-accent w-11/12 break-words'>{stack}</h3> <Card.Description>{stack}</Card.Description>
<p class='w-4/5'>{description}</p> </Card.Header>
</div> <Card.Content>
</a> <p>{shortDescription}</p>
</Card.Content>
<Card.Footer class="flex justify-between items-center">
{#if url === ""}
<Button disabled="true" variant="outline">
<EyeNone class="mr-2 h-4 w-4" />
Proprietary
</Button>
{:else }
<Button href={url} target="_blank" variant="outline">
<GithubLogo class="mr-2 h-4 w-4" />
View on GitHub
</Button>
{/if}
<Dialog.Root>
<Dialog.Trigger class={buttonVariants({ variant: "default" })}>View More</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>{name}</Dialog.Title>
<Dialog.Description>{stack}</Dialog.Description>
</Dialog.Header>
<p>{description}</p>
<Dialog.Footer>
{#if url === ""}
<Button disabled="true" variant="outline">
<EyeNone class="mr-2 h-4 w-4" />
Proprietary
</Button>
{:else }
<Button href={url} target="_blank" variant="outline">
<GithubLogo class="mr-2 h-4 w-4" />
View on GitHub
</Button>
{/if}
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
</Card.Footer>
</Card.Root>

View file

@ -0,0 +1,19 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = AvatarPrimitive.FallbackProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<AvatarPrimitive.Fallback
class={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...$$restProps}
>
<slot />
</AvatarPrimitive.Fallback>

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = AvatarPrimitive.ImageProps;
let className: $$Props["class"] = undefined;
export let src: $$Props["src"] = undefined;
export let alt: $$Props["alt"] = undefined;
export { className as class };
</script>
<AvatarPrimitive.Image
{src}
{alt}
class={cn("aspect-square h-full w-full", className)}
{...$$restProps}
/>

View file

@ -0,0 +1,21 @@
<script lang="ts">
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = AvatarPrimitive.Props;
let className: $$Props["class"] = undefined;
export let delayMs: $$Props["delayMs"] = undefined;
export { className as class };
</script>
<AvatarPrimitive.Root
{delayMs}
class={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...$$restProps}
>
<slot />
</AvatarPrimitive.Root>

View file

@ -0,0 +1,13 @@
import Root from "./avatar.svelte";
import Image from "./avatar-image.svelte";
import Fallback from "./avatar-fallback.svelte";
export {
Root,
Image,
Fallback,
//
Root as Avatar,
Image as AvatarImage,
Fallback as AvatarFallback
};

View file

@ -0,0 +1,25 @@
<script lang="ts">
import { Button as ButtonPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
import { buttonVariants, type Props, type Events } from ".";
type $$Props = Props;
type $$Events = Events;
let className: $$Props["class"] = undefined;
export let variant: $$Props["variant"] = "default";
export let size: $$Props["size"] = "default";
export let builders: $$Props["builders"] = [];
export { className as class };
</script>
<ButtonPrimitive.Root
{builders}
class={cn(buttonVariants({ variant, size, className }))}
type="button"
{...$$restProps}
on:click
on:keydown
>
<slot />
</ButtonPrimitive.Root>

View file

@ -0,0 +1,52 @@
import type { Button as ButtonPrimitive } from "bits-ui";
import { tv, type VariantProps } from "tailwind-variants";
import Root from "./button.svelte";
const buttonVariants = tv({
base: "inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline"
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
});
type Variant = VariantProps<typeof buttonVariants>["variant"];
type Size = VariantProps<typeof buttonVariants>["size"];
type Props = ButtonPrimitive.Props & {
variant?: Variant;
size?: Size;
};
type Events = ButtonPrimitive.Events;
export {
Root,
type Props,
type Events,
//
Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants
};

View file

@ -0,0 +1,13 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("p-6 pt-0", className)} {...$$restProps}>
<slot />
</div>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils";
type $$Props = HTMLAttributes<HTMLParagraphElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<p class={cn("text-sm text-muted-foreground", className)} {...$$restProps}>
<slot />
</p>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex items-center p-6 pt-0", className)} {...$$restProps}>
<slot />
</div>

View file

@ -0,0 +1,13 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div class={cn("flex flex-col space-y-1.5 p-6", className)} {...$$restProps}>
<slot />
</div>

View file

@ -0,0 +1,21 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils";
import type { HeadingLevel } from ".";
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
tag?: HeadingLevel;
};
let className: $$Props["class"] = undefined;
export let tag: $$Props["tag"] = "h3";
export { className as class };
</script>
<svelte:element
this={tag}
class={cn("font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</svelte:element>

View file

@ -0,0 +1,25 @@
<script lang="ts">
import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/utils";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...$$restProps}
on:click
on:focusin
on:focusout
on:mouseenter
on:mouseleave
>
<slot />
</div>

View file

@ -0,0 +1,24 @@
import Root from "./card.svelte";
import Content from "./card-content.svelte";
import Description from "./card-description.svelte";
import Footer from "./card-footer.svelte";
import Header from "./card-header.svelte";
import Title from "./card-title.svelte";
export {
Root,
Content,
Description,
Footer,
Header,
Title,
//
Root as Card,
Content as CardContent,
Description as CardDescription,
Footer as CardFooter,
Header as CardHeader,
Title as CardTitle
};
export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";

View file

@ -0,0 +1,36 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import * as Dialog from ".";
import { cn, flyAndScale } from "$lib/utils";
import { Cross2 } from "radix-icons-svelte";
type $$Props = DialogPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = flyAndScale;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 200
};
export { className as class };
</script>
<Dialog.Portal>
<Dialog.Overlay />
<DialogPrimitive.Content
{transition}
{transitionConfig}
class={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg sm:rounded-lg md:w-full",
className
)}
{...$$restProps}
>
<slot />
<DialogPrimitive.Close
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<Cross2 class="h-4 w-4" />
<span class="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</Dialog.Portal>

View file

@ -0,0 +1,16 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = DialogPrimitive.DescriptionProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Description
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Description>

View file

@ -0,0 +1,19 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...$$restProps}
>
<slot />
</div>

View file

@ -0,0 +1,16 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
{...$$restProps}
>
<slot />
</div>

View file

@ -0,0 +1,24 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
import { fade } from "svelte/transition";
type $$Props = DialogPrimitive.OverlayProps;
let className: $$Props["class"] = undefined;
export let transition: $$Props["transition"] = fade;
export let transitionConfig: $$Props["transitionConfig"] = {
duration: 150
};
export { className as class };
</script>
<DialogPrimitive.Overlay
{transition}
{transitionConfig}
class={cn(
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm ",
className
)}
{...$$restProps}
/>

View file

@ -0,0 +1,9 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
type $$Props = DialogPrimitive.PortalProps;
</script>
<DialogPrimitive.Portal {...$$restProps}>
<slot />
</DialogPrimitive.Portal>

View file

@ -0,0 +1,16 @@
<script lang="ts">
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = DialogPrimitive.TitleProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DialogPrimitive.Title
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</DialogPrimitive.Title>

View file

@ -0,0 +1,34 @@
import { Dialog as DialogPrimitive } from "bits-ui";
const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;
import Title from "./dialog-title.svelte";
import Portal from "./dialog-portal.svelte";
import Footer from "./dialog-footer.svelte";
import Header from "./dialog-header.svelte";
import Overlay from "./dialog-overlay.svelte";
import Content from "./dialog-content.svelte";
import Description from "./dialog-description.svelte";
export {
Root,
Title,
Portal,
Footer,
Header,
Trigger,
Overlay,
Content,
Description,
//
Root as Dialog,
Title as DialogTitle,
Portal as DialogPortal,
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription
};

View file

@ -0,0 +1,24 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import DrawerOverlay from "./drawer-overlay.svelte";
import { cn } from "$lib/utils";
type $$Props = DrawerPrimitive.ContentProps;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DrawerPrimitive.Portal>
<DrawerOverlay />
<DrawerPrimitive.Content
class={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className
)}
{...$$restProps}
>
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
<slot />
</DrawerPrimitive.Content>
</DrawerPrimitive.Portal>

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import { cn } from "$lib/utils";
type $$Props = DrawerPrimitive.DescriptionProps;
export let el: $$Props["el"] = undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DrawerPrimitive.Description
bind:el
class={cn("text-sm text-muted-foreground", className)}
{...$$restProps}
>
<slot />
</DrawerPrimitive.Description>

View file

@ -0,0 +1,20 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement> & {
el?: HTMLDivElement;
};
export let el: $$Props["el"] = undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
bind:this={el}
class={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...$$restProps}
>
<slot />
</div>

View file

@ -0,0 +1,19 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement> & {
el?: HTMLDivElement;
};
export let el: $$Props["el"] = undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
bind:this={el}
class={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
{...$$restProps}
>
<slot />
</div>

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import { cn } from "$lib/utils";
type $$Props = DrawerPrimitive.OverlayProps;
export let el: $$Props["el"] = undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DrawerPrimitive.Overlay
bind:el
class={cn("fixed inset-0 z-50 bg-black/80", className)}
{...$$restProps}
>
<slot />
</DrawerPrimitive.Overlay>

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import { cn } from "$lib/utils";
type $$Props = DrawerPrimitive.TitleProps;
export let el: $$Props["el"] = undefined;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<DrawerPrimitive.Title
bind:el
class={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...$$restProps}
>
<slot />
</DrawerPrimitive.Title>

View file

@ -0,0 +1,17 @@
<script lang="ts">
import { Drawer as DrawerPrimitive } from "vaul-svelte";
type $$Props = DrawerPrimitive.Props;
export let shouldScaleBackground: $$Props["shouldScaleBackground"] = true;
export let open: $$Props["open"] = false;
export let activeSnapPoint: $$Props["activeSnapPoint"] = undefined;
</script>
<DrawerPrimitive.Root
{shouldScaleBackground}
bind:open
bind:activeSnapPoint
{...$$restProps}
>
<slot />
</DrawerPrimitive.Root>

View file

@ -0,0 +1,37 @@
import { Drawer as DrawerPrimitive } from "vaul-svelte";
import Root from "./drawer.svelte";
import Content from "./drawer-content.svelte";
import Description from "./drawer-description.svelte";
import Overlay from "./drawer-overlay.svelte";
import Footer from "./drawer-footer.svelte";
import Header from "./drawer-header.svelte";
import Title from "./drawer-title.svelte";
const Trigger = DrawerPrimitive.Trigger;
const Portal = DrawerPrimitive.Portal;
const Close = DrawerPrimitive.Close;
export {
Root,
Content,
Description,
Overlay,
Footer,
Header,
Title,
Trigger,
Portal,
Close,
//
Root as Drawer,
Content as DrawerContent,
Description as DrawerDescription,
Overlay as DrawerOverlay,
Footer as DrawerFooter,
Header as DrawerHeader,
Title as DrawerTitle,
Trigger as DrawerTrigger,
Portal as DrawerPortal,
Close as DrawerClose
};

62
src/lib/utils.ts Normal file
View file

@ -0,0 +1,62 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { cubicOut } from "svelte/easing";
import type { TransitionConfig } from "svelte/transition";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
type FlyAndScaleParams = {
y?: number;
x?: number;
start?: number;
duration?: number;
};
export const flyAndScale = (
node: Element,
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
): TransitionConfig => {
const style = getComputedStyle(node);
const transform = style.transform === "none" ? "" : style.transform;
const scaleConversion = (
valueA: number,
scaleA: [number, number],
scaleB: [number, number]
) => {
const [minA, maxA] = scaleA;
const [minB, maxB] = scaleB;
const percentage = (valueA - minA) / (maxA - minA);
const valueB = percentage * (maxB - minB) + minB;
return valueB;
};
const styleToString = (
style: Record<string, number | string | undefined>
): string => {
return Object.keys(style).reduce((str, key) => {
if (style[key] === undefined) return str;
return str + `${key}:${style[key]};`;
}, "");
};
return {
duration: params.duration ?? 200,
delay: 0,
css: (t) => {
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
return styleToString({
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
opacity: t
});
},
easing: cubicOut
};
};

View file

@ -1,4 +1,4 @@
<div class='flex flex-col items-center justify-center w-full h-full'> <div class='flex flex-col items-center justify-center w-full h-full'>
<h1 class='text-lg text-accent font-bold'>404</h1> <h1 class='text-lg text-foreground font-bold'>404</h1>
<h3>This page does not exist.</h3> <h3>This page does not exist.</h3>
</div> </div>

View file

@ -9,12 +9,4 @@
<section class='content-container'> <section class='content-container'>
<slot /> <slot />
</section> </section>
</main> </main>
<style lang="postcss">
:global(html) {
font-family: theme(fontFamily.fira-code);
background-color: theme(colors.dark-charcoal-gray);
color: theme(colors.white-smoke);
}
</style>

View file

@ -1,18 +1,44 @@
<script lang="ts"> <script lang="ts">
import FaGithub from 'svelte-icons/fa/FaGithub.svelte' import {Button} from '@/components/ui/button'
import FaTwitter from 'svelte-icons/fa/FaTwitter.svelte'; import { GithubLogo } from "radix-icons-svelte";
import FaRegFileAlt from 'svelte-icons/fa/FaRegFileAlt.svelte'; import { TwitterLogo } from "radix-icons-svelte";
import { FileText } from "radix-icons-svelte";
import * as Card from "@/components/ui/card";
import * as Avatar from "@/components/ui/avatar"
</script> </script>
<div class='flex flex-col justify-center items-center h-full font-bold text-center cursor-default'> <div class='flex flex-col justify-center items-center h-full text-center cursor-default mt-20'>
<div class='w-3/5'>
<h1 class='text-xl sm:text-4xl'>Hi I'm Nabil 👋</h1> <Card.Root class="max-w-xs sm:max-w-sm ease-in-out duration-500">
<p class='text-lg sm:text-2xl'>A <span class='text-accent'>Computer Science</span> student 🧑🏽‍💻 interested in <span class='text-accent'>embedded systems engineering</span> 🔋 Currently based in France 🇫🇷</p> <Card.Header class="flex items-center">
</div> <Card.Title>Nabil Ould Hamou</Card.Title>
<Card.Description>Computer Science Student</Card.Description>
<div class='w-3/5 h-10 flex mt-8 justify-evenly'> <Avatar.Root class="h-32 w-32">
<a class='hover:text-accent cursor-pointer' href="https://github.com/nabilouldhamou" target="_blank"><FaGithub /></a> <Avatar.Image src="https://github.com/nabilouldhamou.png" alt="@nabilouldhamou" />
<a class='hover:text-accent cursor-pointer' href="https://twitter.com/nbil_o" target="_blank"><FaTwitter /></a> <Avatar.Fallback>NOH</Avatar.Fallback>
<a class='hover:text-accent cursor-pointer' href="/CV.pdf" target="_blank"><FaRegFileAlt /></a> </Avatar.Root>
</div> </Card.Header>
<Card.Content>
<p class="text-muted-foreground">Hi I am <span class="text-foreground font-bold">Nabil</span> 👋</p>
<p class="text-muted-foreground">I am a <span class="text-foreground">computer science student</span> 👨‍💻 interested in <span class="text-foreground">embedded systems engineering</span> 📟.</p>
</Card.Content>
<Card.Footer class="flex flex-col justify-center gap-2">
<div class="flex justify-center gap-1.5 sm:gap-3">
<Button href="https://github.com/nabilouldhamou" target='_blank' variant="outline">
<GithubLogo class="mr-2 h-4 w-4" />
Github
</Button>
<Button href="/CV.pdf" target='_blank' variant="outline">
<FileText class="mr-2 h-4 w-4" />
CV
</Button>
<Button href="https://x.com/nbil_o" target='_blank' variant="outline">
<TwitterLogo class="mr-2 h-4 w-4" />
Twitter
</Button>
</div>
<p class="font-bold">Contact: ouldhamounabil@gmail.com</p>
</Card.Footer>
</Card.Root>
</div> </div>

View file

@ -1,7 +1,7 @@
<div class='flex flex-col justify-center items-center pt-6'> <div class='flex flex-col justify-center items-center pt-6'>
<div class='text-justify w-4/5 lg:w-3/5'> <div class='text-justify w-4/5 lg:w-3/5'>
<h3 class='text-accent md:text-4xl text-3xl pb-5 font-bold'>About Me</h3> <h1 class='md:text-4xl text-3xl pb-5 font-bold'>About Me</h1>
<p class='sm:text-xl text-md'>Hello I am <span class='text-accent'>Nabil Ould Hamou</span>, a <span class='text-accent'>computer science student</span>. <p class='text-muted-foreground text-lg'>I am <span class='text-foreground font-bold'>Nabil Ould Hamou</span>, a <span class='text-foreground font-bold'>computer science student</span>.
I am currently studying computer science and making my way to become an embedded systems engineer. Since a very young age I always I am currently studying computer science and making my way to become an embedded systems engineer. Since a very young age I always
loved tinkering, especially with electronics, anything from game consoles to computers. This curiosity led me into pursuing my dream loved tinkering, especially with electronics, anything from game consoles to computers. This curiosity led me into pursuing my dream
of become a computer scientist.</p> of become a computer scientist.</p>

View file

@ -3,13 +3,15 @@
import projects from '$lib/Projects' import projects from '$lib/Projects'
</script> </script>
<div class='flex flex-col items-center'> <div class='flex flex-col justify-center items-center pt-6'>
<div class='w-4/5 lg:w-3/5'>
<h1 class='md:text-4xl text-3xl pb-5 font-bold'>Projects</h1>
</div>
<h1 class='text-accent text-4xl font-bold my-4'>Projects</h1> <div class='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6'>
<div class='flex flex-col gap-6 w-4/5 mb-6 items-center justify-center'>
{#each projects as project} {#each projects as project}
<Project name={project.name} stack={project.stack} description={project.description} url={project.url} /> <Project name={project.name} stack={project.stack} shortDescription={project.shortDesc} description={project.description} url={project.url} />
{/each} {/each}
</div> </div>
</div> </div>

View file

@ -11,7 +11,10 @@ const config = {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // 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. // 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. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter() adapter: adapter(),
alias: {
"@/*": "./src/lib",
},
} }
}; };

View file

@ -1,20 +1,64 @@
import { fontFamily } from "tailwindcss/defaultTheme";
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { const config = {
content: ['./src/**/*.{html,js,svelte,ts}'], darkMode: ["class"],
purge: ["./src/**/*.svelte"], content: ["./src/**/*.{html,js,svelte,ts}"],
darkMode: true, safelist: ["dark"],
theme: { theme: {
extend: { container: {
colors: { center: true,
'dark-charcoal-gray': '#111111', padding: "2rem",
'white-smoke': '#efefef', screens: {
'accent': '#f9f871', "2xl": "1400px"
'lighter-gray': '#333333', }
}, },
fontFamily: { extend: {
'fira-code': ['Fira Code', 'monospace'] colors: {
} border: "hsl(var(--border) / <alpha-value>)",
} input: "hsl(var(--input) / <alpha-value>)",
}, ring: "hsl(var(--ring) / <alpha-value>)",
plugins: [] background: "hsl(var(--background) / <alpha-value>)",
}; foreground: "hsl(var(--foreground) / <alpha-value>)",
primary: {
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
},
secondary: {
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
},
destructive: {
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
},
muted: {
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
},
accent: {
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
},
popover: {
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
},
card: {
DEFAULT: "hsl(var(--card) / <alpha-value>)",
foreground: "hsl(var(--card-foreground) / <alpha-value>)"
}
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)"
},
fontFamily: {
sans: [...fontFamily.sans]
}
}
},
};
export default config;