init commit

This commit is contained in:
Nonimart 2026-02-09 16:37:57 +01:00
parent 6c8f86e804
commit 12e020138c
22 changed files with 741 additions and 76 deletions

0
README.md Normal file → Executable file
View File

26
app/components/Nav.jsx Normal file
View File

@ -0,0 +1,26 @@
import Link from "next/link";
export default function Nav() {
return (
<div className='heading menu flex justify-between items-center px-8 py-8'>
<h1 className='text-3xl font-bold'>
<Link href='/'>Deligraph</Link>
</h1>
<ul className='flex gap-4 items-center'>
<li>
<Link href='/projects'>Portfolio</Link>
</li>
<li>
<Link href='/projects-clients-side'>Projets Clients Side</Link>
</li>
<li>
<Link href='/projects-server-side'>Projets Server Side</Link>
</li>
<li>
<Link href='/contact'>Contact</Link>
</li>
</ul>
</div>
);
}

View File

@ -0,0 +1,35 @@
export default async function PortfolioGridServerSide({ apiUrl = "https://deligraph.com/wp-json/wp/v2/portfolio" }) {
let posts = [];
let error = null;
try {
posts = await fetchPortfolioPosts();
} catch (err) {
error = err.message;
}
if (error) {
return (
<div className='flex justify-center items-center py-12'>
<p className='text-red-500 text-lg'>Erreur: {error}</p>
</div>
);
}
return (
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 px-8 py-8'>
{posts.map((post) => (
<article
key={post.id}
className='bg-white dark:bg-neutral-800 rounded-lg p-6 shadow-md hover:shadow-lg transition-shadow'>
<h3 className='text-xl font-bold mb-2 text-neutral-800 dark:text-white'>
{post.title.rendered}
</h3>
<p className='text-neutral-600 dark:text-neutral-300 line-clamp-3'>
{post.content.rendered}
</p>
</article>
))}
</div>
);
}

View File

@ -0,0 +1,66 @@
"use client";
import { useState, useEffect } from "react";
export default function PostGridClientSide({ apiUrl = "https://deligraph.com/wp-json/wp/v2/portfolio" }) {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchPosts = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
const data = await response.json();
setPosts(data);
} catch (err) {
setError(err.message);
console.error("Erreur lors du fetch:", err);
} finally {
setLoading(false);
}
};
fetchPosts();
}, [apiUrl]);
if (loading) {
return (
<div className='flex justify-center items-center py-12'>
<p className='text-lg'>Chargement des posts...</p>
</div>
);
}
if (error) {
return (
<div className='flex justify-center items-center py-12'>
<p className='text-red-500 text-lg'>Erreur: {error}</p>
</div>
);
}
console.log(posts);
return (
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 px-8 py-8'>
{posts.map((post) => (
<article
key={post.id}
className='bg-white dark:bg-neutral-800 rounded-lg p-6 shadow-md hover:shadow-lg transition-shadow'>
<h3 className='text-xl font-bold mb-2 text-neutral-800 dark:text-white'>
{post.title.rendered}
</h3>
<p className='text-neutral-600 dark:text-neutral-300 line-clamp-3'>
{post.content.rendered}
</p>
</article>
))}
</div>
);
}

View File

@ -0,0 +1,66 @@
import Link from "next/link";
import Image from "next/image";
import { fetchPortfolioPosts } from "../utils/useWordpress";
export default async function PortfolioGridServerSide() {
let posts = [];
let error = null;
try {
// fetchPortfolioPosts récupère automatiquement les images de couverture
const result = await fetchPortfolioPosts({
perPage: 10,
fetchOptions: {
next: { revalidate: 3600 }, // Revalide toutes les heures
},
});
posts = result.posts;
} catch (err) {
error = err.message;
}
if (error) {
return (
<div className='flex justify-center items-center py-12'>
<p className='text-red-500 text-lg'>Erreur: {error}</p>
</div>
);
}
return (
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 px-8 py-8'>
{posts.map((post) => {
return (
<Link href={`/projects/${post.slug}`} key={post.id}>
<article className='bg-white dark:bg-neutral-800 rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-shadow'>
{post.featuredImageUrl && (
<div className='relative w-full h-48 overflow-hidden'>
<Image
src={post.featuredImageUrl}
alt={post.title.rendered || "Image du projet"}
fill
className='object-cover'
sizes='(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw'
/>
</div>
)}
<div className='p-6'>
<h3 className='text-xl font-bold mb-2 text-neutral-800 dark:text-white'>
{post.title.rendered}
</h3>
<div
className='text-neutral-600 dark:text-neutral-300 line-clamp-3'
dangerouslySetInnerHTML={{
__html:
post.excerpt?.rendered ||
post.content.rendered?.substring(0, 150) + "...",
}}
/>
</div>
</article>
</Link>
);
})}
</div>
);
}

30
app/contact/page.jsx Normal file
View File

@ -0,0 +1,30 @@
"use client";
import Link from "next/link";
import { useState } from "react";
export default function Home() {
return (
<main>
<div className='heading menu flex justify-between items-center px-8 py-8'>
<h1 className='text-3xl font-bold'>
{" "}
<Link href='/'>Deligraph</Link>
</h1>
<ul className='flex gap-4 items-center'>
<li>
<Link href='/projects-clients-side'>Projets Clients Side</Link>
</li>
<li>
<Link href='/projects-server-side'>Projets Server Side</Link>
</li>
<li>
<Link href='/contact'>Contact</Link>
</li>
</ul>
</div>
<h2>prooooojets</h2>
</main>
);
}

View File

@ -1,26 +1,42 @@
@import "tailwindcss"; @import "tailwindcss";
:root { :root {
--background: #ffffff; --background: #ffffff;
--foreground: #171717; --foreground: #171717;
} }
@theme inline { @theme inline {
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans); --font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--background: #0a0a0a; --background: #0a0a0a;
--foreground: #ededed; --foreground: #ededed;
} }
} }
body { body {
background: var(--background); background: var(--background);
color: var(--foreground); color: var(--foreground);
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
&:has(main[data-theme="dark"]) {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
&:has(main[data-theme="light"]) {
--background: white;
--foreground: #000;
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
background: white;
color: #000;
} }

View File

@ -1,65 +1,55 @@
"use client";
import Link from "next/link";
import { useState, useEffect } from "react";
import Image from "next/image"; import Image from "next/image";
export default function Home() { export default function Home() {
return ( const [theme, setTheme] = useState<"light" | "dark">("light");
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start"> useEffect(() => {
<Image // Vérifier les préférences système uniquement côté client
className="dark:invert" const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
src="/next.svg" const initialTheme = isDarkMode ? "dark" : "light";
alt="Next.js logo" setTheme(initialTheme);
width={100} document.documentElement.classList.toggle("dark", initialTheme === "dark");
height={20} }, []);
priority
/> const toggleTheme = () => {
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left"> const newTheme = theme === "light" ? "dark" : "light";
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50"> setTheme(newTheme);
To get started, edit the page.tsx file. document.documentElement.classList.toggle("dark", newTheme === "dark");
</h1> };
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400"> return (
Looking for a starting point or more instructions? Head over to{" "} <main data-theme={theme === "dark" ? "dark" : "light"}>
<a <div className='heading menu flex justify-between items-center px-8 py-8'>
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" <h1 className='text-3xl font-bold '>
className="font-medium text-zinc-950 dark:text-zinc-50" <Link href='/'>Deligraph</Link>
> </h1>
Templates
</a>{" "} <ul className='flex gap-4 items-center'>
or the{" "} <li>
<a <Link href='/projects-clients-side'>Projets Clients Side</Link>
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" </li>
className="font-medium text-zinc-950 dark:text-zinc-50" <li>
> <Link href='/projects-server-side'>Projets Server Side</Link>
Learning </li>
</a>{" "} <li>
center. <Link href='/contact'>Contact</Link>
</p> </li>
</div> <button
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row"> className={`px-4 py-2 rounded-2xl flex items-center gap-2 ${theme === "light" ? "bg-neutral-800 text-white" : "bg-white text-neutral-800"} cursor-pointer`}
<a onClick={toggleTheme}>
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]" <Image
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" src={"/icon-switch.svg"}
target="_blank" className={theme === "light" ? "invert" : ""}
rel="noopener noreferrer" alt='sun'
> width={20}
<Image height={20}
className="dark:invert" />
src="/vercel.svg" Switch to {theme === "light" ? "dark" : "light"}
alt="Vercel logomark" </button>
width={16} </ul>
height={16} </div>
/> </main>
Deploy Now );
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
</div>
);
} }

View File

@ -0,0 +1,13 @@
import Link from "next/link";
import PostGridClientSide from "../components/PostGridClientSide";
import Nav from "../components/Nav";
export default function Projects() {
return (
<main>
<Nav />
<h2 className='px-8 text-2xl font-bold mb-4'>Projets</h2>
<PostGridClientSide />
</main>
);
}

View File

@ -0,0 +1,13 @@
import Link from "next/link";
import PostGridServerSide from "../components/PostGridServerSide";
import Nav from "../components/Nav";
export default function Projects() {
return (
<main>
<Nav />
<h2 className='px-8 text-2xl font-bold mb-4'>Projets</h2>
<PostGridServerSide />
</main>
);
}

View File

@ -0,0 +1,74 @@
import { notFound } from "next/navigation";
import Image from "next/image";
import Link from "next/link";
import Nav from "../../components/Nav";
import { fetchPortfolioPostBySlug, fetchMedia } from "../../utils/useWordpress";
export default async function ProjectDetail({ params }) {
const { slug } = params;
if (!slug) {
notFound();
}
let post = null;
let featuredImageUrl = null;
try {
// Récupérer le post par son slug
post = await fetchPortfolioPostBySlug(slug, {
next: { revalidate: 3600 }, // Revalide toutes les heures
});
if (!post) {
notFound();
}
// Récupérer l'image featured si elle existe
if (post.featured_media) {
featuredImageUrl = await fetchMedia(post.featured_media, {
next: { revalidate: 3600 },
});
}
} catch (error) {
console.error("Erreur lors de la récupération du projet:", error);
notFound();
}
return (
<main>
<Nav />
<article className='max-w-4xl mx-auto px-8 py-8'>
<Link
href='/projects'
className='inline-block mb-6 text-blue-600 dark:text-blue-400 hover:underline'>
Retour aux projets
</Link>
{featuredImageUrl && (
<div className='relative w-full h-96 mb-8 rounded-lg overflow-hidden'>
<Image
src={featuredImageUrl}
alt={post.title?.rendered || "Image du projet"}
fill
className='object-cover'
priority
sizes='(max-width: 768px) 100vw, 896px'
/>
</div>
)}
<h1 className='text-4xl font-bold mb-6 text-neutral-800 dark:text-white'>
{post.title?.rendered || "Sans titre"}
</h1>
<div
className='prose prose-lg dark:prose-invert max-w-none text-neutral-700 dark:text-neutral-300'
dangerouslySetInnerHTML={{
__html: post.content?.rendered || "",
}}
/>
</article>
</main>
);
}

View File

@ -0,0 +1,21 @@
import Link from "next/link";
export default function NotFound() {
return (
<main className='px-8 py-8'>
<div className='max-w-4xl mx-auto text-center'>
<h1 className='text-4xl font-bold mb-4 text-neutral-800 dark:text-white'>
404 - Projet non trouvé
</h1>
<p className='text-neutral-600 dark:text-neutral-300 mb-6'>
Le projet que vous recherchez n'existe pas ou a été supprimé.
</p>
<Link
href='/projects'
className='inline-block px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors'>
Retour aux projets
</Link>
</div>
</main>
);
}

12
app/projects/page.jsx Normal file
View File

@ -0,0 +1,12 @@
import PortfolioGridServerSide from "../components/PostGridServerSide";
import Nav from "../components/Nav";
export default function Projects() {
return (
<main>
<Nav />
<h2 className='px-8 text-2xl font-bold mb-4'>Portfolio</h2>
<PortfolioGridServerSide />
</main>
);
}

281
app/utils/useWordpress.js Normal file
View File

@ -0,0 +1,281 @@
/**
* Fonctions utilitaires pour récupérer des données depuis WordPress REST API
* Compatible avec Next.js (client et serveur)
*/
const WORDPRESS_API_BASE = "https://deligraph.com/wp-json/wp/v2";
const PORTFOLIO_ENDPOINT = `${WORDPRESS_API_BASE}/portfolio`;
/**
* Construit une URL avec des paramètres de requête
* @param {string} baseUrl - URL de base
* @param {Object} params - Paramètres de requête
* @returns {string} URL complète avec paramètres
*/
function buildUrl(baseUrl, params = {}) {
const url = new URL(baseUrl);
Object.keys(params).forEach((key) => {
if (params[key] !== undefined && params[key] !== null) {
url.searchParams.append(key, params[key]);
}
});
return url.toString();
}
/**
* Récupère les informations d'un média WordPress par son ID
* @param {number} mediaId - ID du média WordPress
* @param {Object} fetchOptions - Options supplémentaires pour fetch
* @returns {Promise<string|null>} URL de l'image ou null si non trouvé
*/
export async function fetchMedia(mediaId, fetchOptions = {}) {
if (!mediaId) {
return null;
}
const url = `${WORDPRESS_API_BASE}/media/${mediaId}`;
try {
const response = await fetch(url, {
...fetchOptions,
});
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`Erreur HTTP: ${response.status} - ${response.statusText}`);
}
const data = await response.json();
// Retourner l'URL source de l'image
return data.source_url || null;
} catch (error) {
console.error(`Erreur lors de la récupération du média ID "${mediaId}":`, error);
return null; // Retourner null au lieu de throw pour éviter de casser le rendu
}
}
/**
* Récupère les posts de type portfolio depuis WordPress
* @param {Object} options - Options de récupération
* @param {number} options.perPage - Nombre de posts par page (défaut: 10)
* @param {number} options.page - Numéro de page (défaut: 1)
* @param {string} options.search - Terme de recherche
* @param {number} options.categories - ID de catégorie
* @param {string} options.order - Ordre de tri (asc/desc, défaut: desc)
* @param {string} options.orderby - Champ de tri (date, title, etc., défaut: date)
* @param {Object} options.fetchOptions - Options supplémentaires pour fetch (utile pour revalidate côté serveur)
* @returns {Promise<Array>} Tableau de posts portfolio
*/
export async function fetchPortfolioPosts(options = {}) {
const {
perPage = 10,
page = 1,
search,
categories,
order = "desc",
orderby = "date",
fetchOptions = {},
} = options;
const params = {
per_page: perPage,
page: page,
order: order,
orderby: orderby,
};
if (search) {
params.search = search;
}
if (categories) {
params.categories = categories;
}
const url = buildUrl(PORTFOLIO_ENDPOINT, params);
try {
const response = await fetch(url, {
...fetchOptions,
});
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status} - ${response.statusText}`);
}
const data = await response.json();
// Récupérer les images de couverture pour chaque post en parallèle
const postsWithImages = await Promise.all(
data.map(async (post) => {
if (post.featured_media) {
try {
const mediaUrl = await fetchMedia(post.featured_media, fetchOptions);
return { ...post, featuredImageUrl: mediaUrl };
} catch (error) {
console.error(`Erreur lors de la récupération de l'image pour le post ${post.id}:`, error);
return { ...post, featuredImageUrl: null };
}
}
return { ...post, featuredImageUrl: null };
}),
);
return {
posts: postsWithImages,
totalPages: parseInt(response.headers.get("X-WP-TotalPages") || "1", 10),
total: parseInt(response.headers.get("X-WP-Total") || "0", 10),
};
} catch (error) {
console.error("Erreur lors de la récupération des posts portfolio:", error);
throw error;
}
}
/**
* Récupère un post portfolio spécifique par son slug
* @param {string} slug - Slug du post
* @param {Object} fetchOptions - Options supplémentaires pour fetch
* @returns {Promise<Object|null>} Post portfolio ou null si non trouvé
*/
export async function fetchPortfolioPostBySlug(slug, fetchOptions = {}) {
if (!slug) {
throw new Error("Le slug est requis");
}
const url = buildUrl(PORTFOLIO_ENDPOINT, { slug });
console.log("URL de recherche:", url);
try {
const response = await fetch(url, {
...fetchOptions,
});
console.log("Statut de la réponse:", response.status, response.statusText);
if (!response.ok) {
if (response.status === 404) {
console.log("Aucun post trouvé avec le paramètre slug, tentative avec fallback...");
// Fallback: récupérer tous les posts et filtrer par slug
return await fetchPortfolioPostBySlugFallback(slug, fetchOptions);
}
throw new Error(`Erreur HTTP: ${response.status} - ${response.statusText}`);
}
const data = await response.json();
console.log(
"Données reçues:",
Array.isArray(data) ? `${data.length} résultat(s)` : "Format inattendu",
data,
);
// L'API WordPress retourne un tableau même pour un seul résultat
if (Array.isArray(data)) {
if (data.length > 0) {
return data[0];
}
// Si le tableau est vide, essayer le fallback
console.log("Tableau vide, tentative avec fallback...");
return await fetchPortfolioPostBySlugFallback(slug, fetchOptions);
}
// Si ce n'est pas un tableau, retourner directement (cas où l'API retourne un objet unique)
return data;
} catch (error) {
console.error(`Erreur lors de la récupération du post portfolio "${slug}":`, error);
// En cas d'erreur, essayer le fallback
try {
return await fetchPortfolioPostBySlugFallback(slug, fetchOptions);
} catch {
throw error; // Relancer l'erreur originale
}
}
}
/**
* Fallback: récupère tous les posts et filtre par slug
* @param {string} slug - Slug du post
* @param {Object} fetchOptions - Options supplémentaires pour fetch
* @returns {Promise<Object|null>} Post portfolio ou null si non trouvé
*/
async function fetchPortfolioPostBySlugFallback(slug, fetchOptions = {}) {
console.log("Utilisation du fallback pour le slug:", slug);
try {
const allPosts = await fetchAllPortfolioPosts({ fetchOptions });
const post = allPosts.find((p) => p.slug === slug);
if (post) {
console.log("Post trouvé via fallback");
return post;
}
console.log("Aucun post trouvé avec le slug:", slug);
return null;
} catch (error) {
console.error("Erreur dans le fallback:", error);
throw error;
}
}
/**
* Récupère un post portfolio par son ID
* @param {number} id - ID du post
* @param {Object} fetchOptions - Options supplémentaires pour fetch
* @returns {Promise<Object|null>} Post portfolio ou null si non trouvé
*/
export async function fetchPortfolioPostById(id, fetchOptions = {}) {
if (!id) {
throw new Error("L'ID est requis");
}
const url = `${PORTFOLIO_ENDPOINT}/${id}`;
try {
const response = await fetch(url, {
...fetchOptions,
});
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`Erreur HTTP: ${response.status} - ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`Erreur lors de la récupération du post portfolio ID "${id}":`, error);
throw error;
}
}
/**
* Récupère tous les posts portfolio (sans pagination)
* @param {Object} options - Options de récupération (mêmes que fetchPortfolioPosts)
* @returns {Promise<Array>} Tableau de tous les posts portfolio
*/
export async function fetchAllPortfolioPosts(options = {}) {
const allPosts = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const result = await fetchPortfolioPosts({
...options,
page,
perPage: 100, // Maximum recommandé par WordPress REST API
});
allPosts.push(...result.posts);
if (page >= result.totalPages) {
hasMore = false;
} else {
page++;
}
}
return allPosts;
}

0
eslint.config.mjs Normal file → Executable file
View File

20
next.config.ts Normal file → Executable file
View File

@ -1,7 +1,25 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ // Configuration pour l'export statique
output: "export",
images: {
remotePatterns: [
{
protocol: "https",
hostname: "deligraph.com",
pathname: "/wp-content/**",
},
],
// Pour l'export statique, désactiver l'optimisation d'images Next.js
// ou utiliser unloader externe
unoptimized: true,
},
// Désactiver les fonctionnalités qui ne fonctionnent pas en mode statique
trailingSlash: true, // Optionnel : ajoute un slash à la fin des URLs
}; };
export default nextConfig; export default nextConfig;

2
package.json Normal file → Executable file
View File

@ -24,4 +24,4 @@
"typescript": "^5" "typescript": "^5"
}, },
"packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c" "packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c"
} }

0
pnpm-lock.yaml Normal file → Executable file
View File

0
pnpm-workspace.yaml Normal file → Executable file
View File

0
postcss.config.mjs Normal file → Executable file
View File

4
public/icon-switch.svg Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" width="87.5" height="87.5" viewBox="0 0 87.5 87.5">
<path d="M43.8,0C19.6,0,0,19.6,0,43.8s19.6,43.8,43.8,43.8,43.8-19.6,43.8-43.8S67.9,0,43.8,0ZM43.8,81.2c-20.7,0-37.5-16.8-37.5-37.5S23.1,6.2,43.8,6.2s37.5,16.8,37.5,37.5-16.8,37.5-37.5,37.5ZM78.1,43.8c0,9.2-3.6,17.8-10.1,24.3-5.6,5.6-13,9.1-20.9,9.9l-3.4.3V9.2l3.4.3c7.8.8,15.3,4.3,20.9,9.9,6.5,6.5,10.1,15.1,10.1,24.3h0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 498 B

0
tsconfig.json Normal file → Executable file
View File