added content

This commit is contained in:
Nico 2022-12-28 10:36:15 +01:00
parent bf2221b9a9
commit 9b91b02d90
79 changed files with 3053 additions and 284 deletions

View File

@ -1,10 +1,10 @@
type DefaultLangCode = "fr" type DefaultLangCode = "fr"
type SupportedLangCode = "en" type SupportedLangCode = "en"
type LangCode = DefaultLangCode | SupportedLangCode type LangCode = DefaultLangCode | SupportedLangCode
type RouteUri = | "/articles/[slug]" | "/articles" | "/tags/[tag]" | "/tags" | "/" type RouteUri = | "/articles/[slug]" | "/articles" | "/agments/[slug]" | "/agments" | "/tags/[tag]" | "/tags" | "/"
type RouteParams = {"/articles/[slug]": { "slug": string; }; "/articles": undefined; "/tags/[tag]": { "tag": string; }; "/tags": undefined; "/": undefined; } type RouteParams = {"/articles/[slug]": { "slug": string; }; "/articles": undefined; "/agments/[slug]": { "slug": string; }; "/agments": undefined; "/tags/[tag]": { "tag": string; }; "/tags": undefined; "/": undefined; }
type TranslationPath = "accueil" | "tagline" | "copyright" | "contact.title" | "contact.email" | "contact.tel" | "contenuVide" | "header.skipLink" | "header.mainNav" | "header.homeLink" | "sitemap" | "prevNext.contenus" | "prevNext.precedent" | "prevNext.suivant" | "article.titre" | "article.tagline" | "article.published" | "meta.publication" | "meta.modification" | "meta.credit" | "fragments.titre" | "fragments.tagline" | "projet.titre" | "projet.tagline" | "projet.cta" | "projet.lienTitle" | "projet.fenetre" | "erreur.introuvable" | "erreur.autre" | "erreur.lienRetour" | "seo.article.title" | "seo.article.description" | "seo.projet.title" | "seo.projet.description" | "seo.code.title" | "seo.code.description" | "index.articles.pageName" | "index.articles.subtitle" | "index.title" | "index.subtitle" | "index.quoi" | "index.comment" | "index.opensource" | "index.writing" | "index.latestProjects" | "index.latestArticles" | "index.scihub" | "contact.contenuVide" type TranslationPath = "accueil" | "tagline" | "copyright" | "contact.title" | "contact.email" | "contact.tel" | "contenuVide" | "header.skipLink" | "header.mainNav" | "header.homeLink" | "sitemap" | "prevNext.contenus" | "prevNext.precedent" | "prevNext.suivant" | "article.titre" | "article.tagline" | "article.published" | "meta.publication" | "meta.modification" | "meta.credit" | "fragments.titre" | "fragments.tagline" | "projet.titre" | "projet.tagline" | "projet.cta" | "projet.lienTitle" | "projet.fenetre" | "erreur.introuvable" | "erreur.autre" | "erreur.lienRetour" | "seo.article.title" | "seo.article.description" | "seo.projet.title" | "seo.projet.description" | "seo.code.title" | "seo.code.description" | "index.articles.pageName" | "index.articles.subtitle" | "index.fragments.pageName" | "index.fragments.subtitle" | "index.title" | "index.subtitle" | "index.quoi" | "index.comment" | "index.opensource" | "index.writing" | "index.latestProjects" | "index.latestArticles" | "index.allProjects" | "index.allArticles" | "index.latestSnippets" | "index.allSnippets" | "index.toc" | "contact.contenuVide"
type TranslationOptions = { "accueil": {} | undefined; "tagline": {} | undefined; "copyright": {} | undefined; "contact.title": {} | undefined; "contact.email": {} | undefined; "contact.tel": {} | undefined; "contenuVide": {} | undefined; "header.skipLink": {} | undefined; "header.mainNav": {} | undefined; "header.homeLink": {} | undefined; "sitemap": {} | undefined; "prevNext.contenus": {} | undefined; "prevNext.precedent": {} | undefined; "prevNext.suivant": {} | undefined; "article.titre": {} | undefined; "article.tagline": {} | undefined; "article.published": { datetime: unknown; options: unknown; }; "meta.publication": {} | undefined; "meta.modification": {} | undefined; "meta.credit": {} | undefined; "fragments.titre": {} | undefined; "fragments.tagline": {} | undefined; "projet.titre": {} | undefined; "projet.tagline": {} | undefined; "projet.cta": {} | undefined; "projet.lienTitle": {} | undefined; "projet.fenetre": {} | undefined; "erreur.introuvable": {} | undefined; "erreur.autre": {} | undefined; "erreur.lienRetour": {} | undefined; "seo.article.title": {} | undefined; "seo.article.description": {} | undefined; "seo.projet.title": {} | undefined; "seo.projet.description": {} | undefined; "seo.code.title": {} | undefined; "seo.code.description": {} | undefined; "index.articles.pageName": {} | undefined; "index.articles.subtitle": {} | undefined; "index.title": {} | undefined; "index.subtitle": {} | undefined; "index.quoi": {} | undefined; "index.comment": {} | undefined; "index.opensource": {} | undefined; "index.writing": {} | undefined; "index.latestProjects": {} | undefined; "index.latestArticles": {} | undefined; "index.scihub": {} | undefined; "contact.contenuVide": {} | undefined; } type TranslationOptions = { "accueil": {} | undefined; "tagline": {} | undefined; "copyright": {} | undefined; "contact.title": {} | undefined; "contact.email": {} | undefined; "contact.tel": {} | undefined; "contenuVide": {} | undefined; "header.skipLink": {} | undefined; "header.mainNav": {} | undefined; "header.homeLink": {} | undefined; "sitemap": {} | undefined; "prevNext.contenus": {} | undefined; "prevNext.precedent": {} | undefined; "prevNext.suivant": {} | undefined; "article.titre": {} | undefined; "article.tagline": {} | undefined; "article.published": { datetime: unknown; options: unknown; }; "meta.publication": {} | undefined; "meta.modification": {} | undefined; "meta.credit": {} | undefined; "fragments.titre": {} | undefined; "fragments.tagline": {} | undefined; "projet.titre": {} | undefined; "projet.tagline": {} | undefined; "projet.cta": {} | undefined; "projet.lienTitle": {} | undefined; "projet.fenetre": {} | undefined; "erreur.introuvable": {} | undefined; "erreur.autre": {} | undefined; "erreur.lienRetour": {} | undefined; "seo.article.title": {} | undefined; "seo.article.description": {} | undefined; "seo.projet.title": {} | undefined; "seo.projet.description": {} | undefined; "seo.code.title": {} | undefined; "seo.code.description": {} | undefined; "index.articles.pageName": {} | undefined; "index.articles.subtitle": {} | undefined; "index.fragments.pageName": {} | undefined; "index.fragments.subtitle": {} | undefined; "index.title": {} | undefined; "index.subtitle": {} | undefined; "index.quoi": {} | undefined; "index.comment": {} | undefined; "index.opensource": {} | undefined; "index.writing": {} | undefined; "index.latestProjects": {} | undefined; "index.latestArticles": {} | undefined; "index.allProjects": {} | undefined; "index.allArticles": {} | undefined; "index.latestSnippets": {} | undefined; "index.allSnippets": {} | undefined; "index.toc": {} | undefined; "contact.contenuVide": {} | undefined; }
declare module "astro-i18n" { declare module "astro-i18n" {
export * from "astro-i18n/" export * from "astro-i18n/"

View File

@ -1,12 +1,27 @@
# Astro Starter Kit: Minimal # nardu.in
remade with [astro](https://astro.build)
## Build Setup
```bash
# install dependencies
$ pnpm install
# serve with hot reload at localhost:3000
$ pnpm dev
# build for production as a full static website
$ pnpm build
# launch local server
$ pnpm preview
# deploy
rsync -avz -e 'ssh -p PORT' dist/ user@SERVER_IP:/var/www/FOLDER/dist
``` ```
npm create astro@latest -- --template minimal
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure ## 🚀 Project Structure

View File

@ -12,5 +12,8 @@ export default defineConfig({
experimental: { experimental: {
contentCollections: true, contentCollections: true,
}, },
markdown: {
syntaxHighlight: "prism",
},
integrations: [i18n(), image(), mdx()], integrations: [i18n(), image(), mdx()],
}); });

View File

@ -11,6 +11,7 @@ export default defineAstroI18nConfig({
routeTranslations: { routeTranslations: {
en: { en: {
"sci-hub-blocage": "sci-hub-unblock", "sci-hub-blocage": "sci-hub-unblock",
fragments: "snippets",
}, },
}, },
}); });

View File

@ -21,5 +21,8 @@
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"postcss": "^8.4.20" "postcss": "^8.4.20"
},
"engines": {
"node": "16.18.1"
} }
} }

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" aria-describedby="desc" aria-labelledby="title" viewBox="0 0 64 64">
<title>Lien</title>
<desc>Deux maillons d'une chaîne accrochés ensemble.</desc>
<path fill="none" stroke="#202020" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M32 14.4l8.8-8.8a12.4 12.4 0 0117.6 0 12.4 12.4 0 010 17.6L44.3 37.3a12.4 12.4 0 01-17.6 0 12.6 12.6 0 01-1.6-1.9" data-name="layer2"/>
<path fill="none" stroke="#202020" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" d="M32 49.6l-8.8 8.8a12.4 12.4 0 01-17.6 0 12.4 12.4 0 010-17.6l14.1-14.1a12.4 12.4 0 0117.6 0 12.5 12.5 0 011.6 1.9" data-name="layer1"/>
</svg>

After

Width:  |  Height:  |  Size: 751 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29 23"><g><g fill="none" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M28 11.5H1M17.5 1L28 11.5 17.5 22"/></g></g></svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 29 23"><g><g fill="none" stroke="#c94a4f" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M28 11.5H1M17.5 1L28 11.5 17.5 22"/></g></g></svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@ -1,12 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 36 36"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32">
<path fill="#000" d="M22.25 4h-8.5a1 1 0 0 0-.96.73l-5.54 19.4a.5.5 0 0 0 .62.62l5.05-1.44a2 2 0 0 0 1.38-1.4l3.22-11.66a.5.5 0 0 1 .96 0l3.22 11.67a2 2 0 0 0 1.38 1.39l5.05 1.44a.5.5 0 0 0 .62-.62l-5.54-19.4a1 1 0 0 0-.96-.73Z"/> <text x="2px" y="28px" fill="#000" font-size="30px" font-family="sans-serif">
<path fill="url(#gradient)" d="M18 28a7.63 7.63 0 0 1-5-2c-1.4 2.1-.35 4.35.6 5.55.14.17.41.07.47-.15.44-1.8 2.93-1.22 2.93.6 0 2.28.87 3.4 1.72 3.81.34.16.59-.2.49-.56-.31-1.05-.29-2.46 1.29-3.25 3-1.5 3.17-4.83 2.5-6-.67.67-2.6 2-5 2Z"/> N
<defs> </text>
<linearGradient id="gradient" x1="16" x2="16" y1="32" y2="24" gradientUnits="userSpaceOnUse">
<stop stop-color="#000"/>
<stop offset="1" stop-color="#000" stop-opacity="0"/>
</linearGradient>
</defs>
<style> <style>
@media (prefers-color-scheme:dark){:root{filter:invert(100%)}} @media (prefers-color-scheme:dark){:root{filter:invert(100%)}}
</style> </style>

Before

Width:  |  Height:  |  Size: 873 B

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

2
public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@ -6,9 +6,9 @@ const { src, alt, width, height, ...attrs } = Astro.props;
<Picture <Picture
src={src} src={src}
widths={[320, 640, 768, attrs.width]} widths={[320, 640, 768]}
aspectRatio={`${width}:${height}`} aspectRatio={`${width}:${height}`}
sizes={`(max-width: ${attrs.width}px) 100vw, ${attrs.width}px`} sizes={`(max-inline-size: ${width}px) 100vw, ${width}px`}
formats={["avif", "webp"]} formats={["avif", "webp"]}
alt={alt ? alt : ""} alt={alt ? alt : ""}
{...attrs} {...attrs}

View File

@ -1,32 +1,30 @@
--- ---
import { l } from "astro-i18n"; import ListTags from "./ListTags.astro";
const { item, routeName } = Astro.props; const { item, routeName } = Astro.props;
--- ---
<div class="card"> <div class="card">
<div> <div>
<h3> <h3>
<a class="card__link" href={`${routeName}/${item.slug}`} <a class="clean-link card__link" href={`${routeName}/${item.data.slug}`}
>{item.data.title}</a >{item.data.title}</a
> >
</h3> </h3>
<pre> <h4>{item.data.subtitle}</h4>
{item.slug} <ListTags list={item.data.tags} />
</pre>
<!-- <h4>{item.data.subtitle}</h4> -->
<!-- <tags-list list={item.tags}></tags-list> -->
</div> </div>
</div> </div>
<style scoped> <style scoped>
.card { .card {
padding: 2.4rem 1.6rem; padding: var(--space-s-m) var(--space-xs-s);
position: relative; position: relative;
display: block; display: block;
height: 100%; block-size: 100%;
overflow: hidden;
cursor: pointer; cursor: pointer;
box-shadow: var(--shadow-elevation-medium); box-shadow: var(--shadow-elevation-medium);
background-color: var(--white); background-color: white;
} }
.card:hover { .card:hover {
box-shadow: var(--shadow-elevation-high); box-shadow: var(--shadow-elevation-high);
@ -34,14 +32,31 @@ const { item, routeName } = Astro.props;
.card:focus-within { .card:focus-within {
box-shadow: var(--shadow-elevation-high); box-shadow: var(--shadow-elevation-high);
} }
.card:hover h3::after, .card::after {
.card:focus-within h3::after { content: "";
position: absolute;
inline-size: 30px;
block-size: 30px;
top: var(--space-m);
right: var(--space-s);
opacity: 0;
background-image: url("/assets/svg/arrow-right.svg");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
transform: translateX(1rem);
}
.card:hover::after,
.card:focus-within::after {
transform: translateX(0); transform: translateX(0);
opacity: 1; opacity: 1;
} }
.card:hover h3, .card:hover h3 a {
.card:focus-within h3 { text-decoration: underline;
color: var(--brique); }
.card:hover h3 a,
.card:focus-within h3 a {
color: var(--color-brique);
} }
.card::before { .card::before {
content: ""; content: "";
@ -49,11 +64,11 @@ const { item, routeName } = Astro.props;
top: 0; top: 0;
left: 0; left: 0;
bottom: 0; bottom: 0;
height: 100%; block-size: 100%;
width: 2px; inline-size: 2px;
transform: scaleY(0); transform: scaleY(0);
transform-origin: bottom; transform-origin: bottom;
background-color: var(--brique); background-color: var(--color-brique);
} }
.card:hover::before, .card:hover::before,
.card:focus-within::before { .card:focus-within::before {
@ -62,20 +77,18 @@ const { item, routeName } = Astro.props;
} }
h3 { h3 {
padding-right: 3rem; padding-inline-end: var(--space-s-m);
position: relative; font-size: var(--size-2);
font-size: clamp(2.4rem, 2.2222rem + 0.5556vw, 2.8rem);
font-weight: bold; font-weight: bold;
text-transform: none; text-transform: none;
} }
h3::after { .card__link {
content: url("~assets/svg/arrow-right.svg"); color: var(--color-blue);
}
.card__link::after {
content: "";
position: absolute; position: absolute;
width: 30px; inset: 0;
top: 0;
right: 0;
opacity: 0;
transform: translateX(1rem);
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
.card { .card {
@ -84,33 +97,33 @@ const { item, routeName } = Astro.props;
.card::before { .card::before {
transition: transform 0.2s ease-in-out; transition: transform 0.2s ease-in-out;
} }
h3 { .card::after {
transition: color ease 0.2s;
}
h3::after {
transition: opacity ease 0.2s, transform ease 0.2s; transition: opacity ease 0.2s, transform ease 0.2s;
} }
h3 a {
transition: color ease 0.2s;
}
} }
.card h4 { .card h4 {
margin-top: 0.8rem; margin-block-start: var(--space-2xs);
font-size: 2rem; font-size: var(--size-0);
font-weight: 500; font-weight: 500;
color: var(--darkBlue); color: var(--darkBlue);
} }
@media screen and (min-width: 768px) { /* @media screen and (min-width: 768px) {
h3 { h3 {
margin-top: 0; margin-block-start: 0;
margin-bottom: 0.8rem; margin-block-end: var(--space-2xs);
} }
.card { .card {
padding: 3.2rem 2.4rem; padding: var(--space-xs-s) var(--space-2xs-xs);
} }
} }
@media screen and (min-width: 1060px) { @media screen and (min-width: 1060px) {
.card { .card {
margin: 0; margin: 0;
} }
} } */
</style> </style>

View File

@ -1,5 +0,0 @@
---
const { title, url } = Astro.props;
---
<a href={url}>{title}</a>

View File

@ -1,4 +1,5 @@
--- ---
import MetaDate from "./MetaDate.astro";
import TOC from "./TOC.astro"; import TOC from "./TOC.astro";
const { content } = Astro.props; const { content } = Astro.props;
@ -7,13 +8,18 @@ const { Content, headings } = await content.render();
const toc = headings.map((heading) => { const toc = headings.map((heading) => {
return heading; return heading;
}); });
if (content.data.code) {
import "../styles/vendor/one-dark-pro.css";
}
--- ---
<div class="sidebar region"> <div class="sidebar region">
<TOC toc={toc} /> <TOC toc={toc} />
<article class="flow editorial"> <article class="flow editorial">
<h1>{content.data.title}</h1> <h1>{content.data.title}</h1>
<p class="h2">{content.data.subtitle}</p> <p class="h3">{content.data.subtitle}</p>
<MetaDate item={content.data} />
<div class="flow content"> <div class="flow content">
<Content /> <Content />
</div> </div>
@ -21,6 +27,15 @@ const toc = headings.map((heading) => {
</div> </div>
<style> <style>
.sidebar {
--gutter: var(--space-xs-m);
}
.sidebar > :global(:first-child) {
--sidebar-target-width: 16rem;
}
.sidebar > :global(:last-child) {
--sidebar-content-min-width: 60%;
}
h1 { h1 {
font-size: var(--size-4); font-size: var(--size-4);
} }
@ -29,4 +44,9 @@ const toc = headings.map((heading) => {
font-size: var(--size-3); font-size: var(--size-3);
color: var(--color-blue); color: var(--color-blue);
} }
.editorial :global(img),
.editorial :global(picture) {
margin: var(--space-s-m) 0;
}
</style> </style>

View File

@ -0,0 +1,38 @@
---
import { l, t } from "astro-i18n";
---
<footer class="footer" role="contentinfo">
<section class="flow">
<p>Nicolas Arduin</p>
<p>{t("tagline")}</p>
<ul class="flow" role="list">
<li>
<a href="mailto:contact@nardu.in" title={t("contact.email")}
>contact@nardu.in</a
>
</li>
<li>
<a href="tel:+33749464239" title={t("contact.tel")}>+337 49 46 42 39</a>
</li>
<li><a href={l("/plan-du-site")}>{t("sitemap")}</a></li>
</ul>
</section>
</footer>
<style>
.footer {
--flow-space: var(--space-xs-s);
inline-size: 100%;
margin: 0 auto;
margin-block-start: var(--space-m-l);
padding: var(--space-s-m);
text-align: center;
border-top: solid 2px var(--color-dark-blue);
}
.footer a {
font-weight: 500;
color: var(--color-blue);
}
</style>

View File

@ -37,8 +37,8 @@ import Navigation from "../components/Navigation.astro";
position: absolute; position: absolute;
top: 20px; top: 20px;
left: 20px; left: 20px;
height: 1px; block-size: 1px;
width: 1px; inline-size: 1px;
-webkit-clip: rect(0 0 0 0); -webkit-clip: rect(0 0 0 0);
clip: rect(0 0 0 0); clip: rect(0 0 0 0);
overflow: hidden; overflow: hidden;
@ -50,8 +50,8 @@ import Navigation from "../components/Navigation.astro";
-webkit-clip: auto; -webkit-clip: auto;
clip: auto; clip: auto;
overflow: visible; overflow: visible;
width: auto; inline-size: auto;
height: auto; block-size: auto;
} }
.logo { .logo {
font-size: var(--size-1); font-size: var(--size-1);

View File

@ -4,7 +4,7 @@ const { list, routeName } = Astro.props;
import CardEditorial from "./CardEditorial.astro"; import CardEditorial from "./CardEditorial.astro";
--- ---
<ul role="list"> <ul class="list" role="list">
{ {
list.map((item) => ( list.map((item) => (
<li class="list__item"> <li class="list__item">
@ -13,3 +13,14 @@ import CardEditorial from "./CardEditorial.astro";
)) ))
} }
</ul> </ul>
<style>
.list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(400px, 100%), 1fr));
gap: var(--space-m-l);
}
.list > * {
margin: 0;
}
</style>

View File

@ -0,0 +1,25 @@
---
const { list } = Astro.props;
---
<ul class="tags-list" role="list">
{
list.map((tag, index) => (
<li>
{tag}{index + 1 < list.length && <span>, </span>}
</li>
))
}
</ul>
<style>
.tags-list {
margin: var(--space-2xs) 0;
}
.tags-list li {
display: inline;
font-size: var(--size--1);
font-weight: 500;
color: var(--color-blue);
}
</style>

View File

@ -0,0 +1,44 @@
---
import { t, astroI18n } from "astro-i18n";
astroI18n.init(Astro);
const { item } = Astro.props;
function formatDate(date) {
const options = { year: "numeric", month: "long", day: "numeric" };
return new Date(date).toLocaleDateString(astroI18n.langCode, options);
}
// Needed because browser will transform the value to a readable english date in 'datetime' if not forced
// see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time#attr-datetime for valid formats
function rawDate(date) {
return new Date(date).toISOString();
}
---
<div class="meta">
<p class="meta__date">
{t("meta.publication")}&nbsp;:
<time datetime={rawDate(item.createdAt)}>{formatDate(item.createdAt)}</time>
</p>
{
formatDate(item.createdAt) != formatDate(item.updatedAt) &&
!!item.updatedAt && (
<p class="meta__date">
{t("meta.modification")}&nbsp;:
<time datetime={rawDate(item.createdAt)}>
{formatDate(item.updatedAt)}
</time>
</p>
)
}
</div>
<style>
.meta {
font-size: var(--size--1);
}
.meta__date {
margin: var(--space-3xs) 0;
}
</style>

View File

@ -16,7 +16,9 @@ import LanguageSwitcher from "./LangSwitcher.astro";
<span aria-hidden="true">&middot;</span> <span aria-hidden="true">&middot;</span>
</li> </li>
<li> <li>
<a href={l("/")} class="clean-link nice-link">{t("fragments.titre")}</a> <a href={l("/fragments")} class="clean-link nice-link"
>{t("fragments.titre")}</a
>
<span aria-hidden="true">&middot;</span> <span aria-hidden="true">&middot;</span>
</li> </li>
<li> <li>
@ -39,4 +41,7 @@ import LanguageSwitcher from "./LangSwitcher.astro";
gap: var(--space-2xs); gap: var(--space-2xs);
justify-content: center; justify-content: center;
} }
.main-nav a {
text-transform: capitalize;
}
</style> </style>

View File

@ -5,9 +5,9 @@ const { toc } = Astro.props;
<aside class="table-of-content"> <aside class="table-of-content">
<details open="true"> <details open="true">
<summary>{t("toc")}</summary> <summary>{t("index.toc")}</summary>
<nav role="navigation" aria-label={t("toc")}> <nav role="navigation" aria-label={t("index.toc")}>
<ul class="table-of-content__list" role="list"> <ol class="table-of-content__list" role="list">
{ {
// loop over the toc // loop over the toc
toc.map((heading) => toc.map((heading) =>
@ -18,19 +18,19 @@ const { toc } = Astro.props;
{heading.text} {heading.text}
</a> </a>
</li> </li>
) : // if h3, set as inner ul > li ) : // if h3, set as inner ol > li
heading.depth === 3 ? ( heading.depth === 3 ? (
<ul role="list"> <ol role="list">
<li> <li>
<a href={`#${heading.slug}`} class="toc-3"> <a href={`#${heading.slug}`} class="toc-3">
{heading.text} {heading.text}
</a> </a>
</li> </li>
</ul> </ol>
) : null ) : null
) )
} }
</ul> </ol>
</nav> </nav>
</details> </details>
</aside> </aside>
@ -48,8 +48,16 @@ const { toc } = Astro.props;
position: sticky; position: sticky;
top: var(--space-m); top: var(--space-m);
} }
summary {
padding: var(--space-2xs-xs);
font-size: var(--size-0);
font-weight: 500;
background-color: var(--color-soft-blue);
}
.table-of-content__list { .table-of-content__list {
padding-bottom: 2rem; padding: 0 var(--space-2xs);
padding-block-end: var(--space-2xs);
border: 2px solid var(--color-soft-blue);
} }
.table-of-content__list a { .table-of-content__list a {
position: relative; position: relative;
@ -64,7 +72,7 @@ const { toc } = Astro.props;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
transform: translate(-1rem, 0); transform: translate(-6px, 0);
color: var(--color-grey); color: var(--color-grey);
} }
.table-of-content__list a:visited::before { .table-of-content__list a:visited::before {
@ -80,7 +88,7 @@ const { toc } = Astro.props;
margin: 0.8rem 0 0.3rem; margin: 0.8rem 0 0.3rem;
} }
.toc-3 { .toc-3 {
margin-left: 1rem; margin-inline-start: 1rem;
margin-top: 0.4rem; margin-block-start: 0.4rem;
} }
</style> </style>

View File

@ -0,0 +1,42 @@
---
title: Nico v2.0
subtitle: 2022 update of many things.
lang: en
slug: "2022"
excerpt: Changes in my services, the website and myself.
tags: ["Freelance"]
createdAt: "2022-06-08T14:24:06.000Z"
---
After two years of full-time freelancing, I took a step back from my activity. I especially questioned my positioning and the services I was offering.
## Services
### No more print
Even though I am trained in branding, I rarely do it and do not practice it in my free time. The same goes for "classic" graphic design. Posters, flyers and other leaflets have disappeared from my quotes for a long time. It seems logical to me to no longer explicitly offer these services because I consider that I am no longer sufficiently competent or interested.
### Internet all the way
On the other hand, I spend my time **doing web stuff.** A lot of development, some design and a fait amout of server configuration. It is therefore obvious for me to offer **more specific web offerings,** in which I have specialised.
Thus, **accessibility** and **eco-design** become integrated expertises in my practice. They are no longer options on a quote but **my core business.**
## The website
I have reworked my website, especially the homepage and its content, to better reflect these decisions.
I have also made several behavioural changes:
- external links no longer open in a new window. I read <a href="https://css-tricks.com/use-target_blank/" rel="noopener noreferer">this article</a> about opening links in a new window and I haven't found a good reason to continue using this behaviour;
- I have removed the open graph and twitter card meta tags as I fully agree with <a href="https://twitter.com/HTeuMeuLeu/status/1370310316496728065" rel="noopener noreferer" hreflang="fr">this opinion (in french)</a> by [@HTeuMeuLeu](https://twitter.com/HTeuMeuLeu);
- <a href="https://twitter.com/HTeuMeuLeu/status/1370310312214339586" rel="noopener noreferer" hreflang="fr">same for the favicon</a>;
- I only trigger animations if the user has not asked the system to reduce the amount motion [(reference)](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion).
I am working on reducing the weight of my pages as much as possible, although I will certainly have to change tools (again) (hello [astro](https://astro.build/)&nbsp;👀).
## Le Nico
On a personal level, I have committed myself to a more responsible digital world by joining the <a href="https://www.good-it.org/" rel="noopener noreferer" hreflang="fr">Good-it! (in french)</a> collective and by actively participating in its development.
I have also started teaching in several schools. I mainly intervene on digital courses by teaching design and development. I take this opportunity to to make future generations aware of accessibility and eco-design.

View File

@ -0,0 +1,116 @@
---
title: After Effects Expressions
subtitle: Animation on steroïds.
lang: en
slug: "after-effects-expressions"
excerpt: Expressions in After Effects have always been blurry for me. I know they exist, I know they're powerful, I know it could save a lot of time and clean complex keyframe filled compositions but… They are hard to learn!
tags: ["Design"]
createdAt: "2019-04-24T09:00:00.000Z"
code: true
---
import AstroImage from "../../../components/AstroImage.astro";
export const basicExpression =
"https://assets.nardu.in/basic_expression_d81b12f1ac.jpeg";
export const shortcut = "https://assets.nardu.in/shortcut_39cc19d383.jpeg";
## An ever lasting battle
There is very little documentation and the existing one is hard to find or very old. Very often I just don't have the time to dive into expression learning while animating even though it could help the entire project on the long term.
So the last time I had to do a complex animation, **I took the damn time!**
## The begining of the end (of keyframes).
Everyone uses expressions whether they know it or not. Most of the time it's a rather transparent process for the animator. For example: when parenting a property to another one, After Effects creates an expression for us.
<AstroImage
src={basicExpression}
width="728"
height="80"
alt="Parenting the position of the form to a null creates an expression."
/>
Over the last updates, Adobe has made an effort to make expressions more accessible to everyone. For example, the expression box is now resembling a code editor thanks to code-coloring and auto-completion features. After Effects expression feel like JavaScript with custom functions.
Those custom functions can be called through a menu once you enabled the expressions on a property. It offers organized shortcut and proper syntax to all of AE native functions and a bunch of JavaScript standard ones.
<AstroImage
src={shortcut}
width="728"
height="322"
alt="Alt + Click the stopwatch to access the shortcuts."
/>
## So I need to learn javascript to do motion design now?
### Why bother?
I know that most animators don't have any kind of development background. However, if Adobe thought it best to let us use expressions, I think we ought to, at least, try.
> A lot of incredible professionals like [Mt Mograph](https://www.mtmograph.com/) and [Video Copilot](https://videocopilot.net/) use expressions.
Like every optimization process, building an expressions knowledge takes time. However, like all optimization process, you will benefit from it afterwards.
### Where to learn
I've gathered a list of my favourite learning grounds. Plenty more exist, **go get'em!**
- [Adobe official expression starter guide (FR)](https://helpx.adobe.com/fr/after-effects/using/expression-language-reference.html)
- [Adobe official expression basic (EN)](https://helpx.adobe.com/after-effects/using/expression-basics.html)
- [Adobe forums for expressions](https://community.adobe.com/t5/after-effects/bd-p/after-effects?page=1&sort=latest_replies&filter=all&topics=label-expressions)
- [Creative Cow expressions forums](https://creativecow.net/forums/forum/adobe-after-effects-expressions/)
- [Youtube channel about expressions](https://www.youtube.com/playlist?list=PLvr5U5ZSt6IzHyvSL9fo0M9NRPsTvra31)
- [All expressions explained](http://aescript.jecool.net/reference/)
- [All expressions explained again](http://expressions.aenhancers.com/index.html)
- [Nice examples of basic expressions](https://www.schoolofmotion.com/tutorials/amazing-expressions-in-after-effects)
## Real world example
### Number counter
Let's say you need to animate a rather simple counter from 0% to 100%.
- Start by creating a text layer. Define your typographic choices, colours, etc.
- On the effect menu add expression options > slider control
- Alt + Click the Source text property
<video width="728" loop controls preload="metadata">
<source src="https://assets.nardu.in/alt-counter.mp4" type="video/mp4" />
</video>
- Parent the source text to the slider control through the pickwhip.
<video width="462" loop controls preload="metadata">
<source src="https://assets.nardu.in/pickwhip.mp4" type="video/mp4" />
</video>
You should get something like this in the expressions panel:
```javascript
effect("Slider Control")("Slider")
```
- Set two keyframes on the slider from 0 to 100. The text should update accordingly.
- By default, After Effects does not round numbers. In the expression panel, wrap your expression with the `Math.round()` function.
<video width="728" loop controls preload="metadata">
<source src="https://assets.nardu.in/round.mp4" type="video/mp4" />
</video>
```javascript
Math.round(effect("Slider Control")("Slider"))
```
Great! We have a working counter. But we wanted a percentage counter. We could add a % glyph in another text layer but let's not.
Still in the expression panel, we're going to add a string containing the % glyph, using basic JavaScript concatenation: `+ '%'`
```javascript
Math.round(effect("Slider Control")("Slider")) + '%'
```
<video width="538" loop controls preload="metadata">
<source src="https://assets.nardu.in/percent.mp4" type="video/mp4" />
</video>
**And there we go!** A fully functioning and customizable counter using a slider controller and expressions.

View File

@ -0,0 +1,11 @@
---
title: Accessibility and sobriety
subtitle: Translation in progress, stay tuned ;)
lang: en
slug: "faq"
excerpt: Why, how et and especially what.
tags: ["Freelance"]
createdAt: "2022-06-22T15:34:45.000Z"
---
[Go back to available articles](/en/articles/)

View File

@ -0,0 +1,11 @@
---
title: Gratuiste
subtitle: Translation in progress, stay tuned ;)
lang: en
slug: "gratuiste"
excerpt: Translation in progress, stay tuned ;)
tags: ["Design", "Freelance"]
createdAt: "2017-05-27T07:47:36.000Z"
---
[Go back to available articles](/en/articles/)

View File

@ -6,9 +6,19 @@ slug: "sci-hub-unblock"
key: "scihub" key: "scihub"
excerpt: "In March 2019, the Paris Regional Court ruled in favour of the publishers of scientific articles Elsevier and Springer Nature by ordering internet service providers to block access to these two websites. Here is how to access them if they are blocked in your country anyway." excerpt: "In March 2019, the Paris Regional Court ruled in favour of the publishers of scientific articles Elsevier and Springer Nature by ordering internet service providers to block access to these two websites. Here is how to access them if they are blocked in your country anyway."
tags: ["Internet", "Science"] tags: ["Internet", "Science"]
pubDate: "2019-10-18T07:47:36.000Z" createdAt: "2019-03-31T07:47:36.000Z"
updatedAt: "2022-12-27T12:08:00.000Z"
--- ---
import AstroImage from "../../../components/AstroImage.astro";
export const macOs =
"https://assets.nardu.in/ef5a4b8e82a046e6a466c73c2fd9e99e.jpg";
export const windowsSettings = "https://assets.nardu.in/sci-hub-settings.jpg";
export const windowsNetwork = "https://assets.nardu.in/sci-hub-network.jpg";
export const windowsAdapter = "https://assets.nardu.in/sci-hub-adapter.jpg";
export const windowsAdapterSettings =
"https://assets.nardu.in/sci-hub-adapter-settings.jpg";
The current sci-hub address is: <a href="https://sci-hub.se" rel="noreferer noopener">sci-hub.se</a> The current sci-hub address is: <a href="https://sci-hub.se" rel="noreferer noopener">sci-hub.se</a>
## What is this about? ## What is this about?
@ -18,8 +28,8 @@ Not being a researcher myself, heres an [extremely well done explanatory vide
> “This is outrageous!”​ > “This is outrageous!”​
I noticed that my ISP was indeed blocking access. I have not yet been able to check for the other suppliers, but I imagine that they have all complied with the court decision. I noticed that my ISP was indeed blocking access. I have not yet been able to check for the other suppliers, but I imagine that they have all complied with the court decision.
The restriction in place is quite basic. This is a “DNS” block. A DNS is a server that is used to link a domain name: _www.sci-hub.tw_ to an IP address: _186.2.163.90_ The restriction in place is quite basic. This is a “DNS” block. A DNS is a server that is used to link a domain name: **www.sci-hub.tw** to an IP address: **186.2.163.90**
It is, in a way, the Internet phone book. Without the address, it is impossible to contact the domain. What ISPs have certainly done: block requests to the sci-hub domain name. As a result, no IP address is returned and the site is not accessible. It is, in a way, the Internet phone book. Without the address, it is impossible to contact the domain. What ISPs have certainly done: block requests to the [sci-hub.se.](http://sci-hub.se/) domain name. As a result, no IP address is returned and the site is not accessible.
## How to bypass this? ## How to bypass this?
@ -35,20 +45,18 @@ Go to:
1. System Preferences 1. System Preferences
1. Network 1. Network
1. (wi-fi or ethernet) 1. (wi-fi and/or ethernet)
1. Advanced 1. Advanced
1. DNS 1. DNS
From there, you can add DNS servers by clicking the + icon. Click ok and apply the new settings. You might need to restart your computer for the changes to work. From there, you can add DNS servers by clicking the + icon. Click ok and apply the new settings. You might need to restart your computer for the changes to work.
<nuxt-img <AstroImage
src="https://assets.nardu.in/ef5a4b8e82a046e6a466c73c2fd9e99e.jpg" src={macOs}
sizes="sm:100vw xl:728px" width="728"
width="728" height="1060"
height="1060" alt="MacOS network and DNS settings"
alt="MacOS network and DNS settings" />
loading="lazy">
</nuxt-img>
### Windows (10) ### Windows (10)
@ -64,53 +72,33 @@ Go to:
From there, you can add DNS servers. Click save. You might need to restart your computer for the changes to work. From there, you can add DNS servers. Click save. You might need to restart your computer for the changes to work.
<picture width="728" height="319"> <AstroImage
<nuxt-source src={windowsSettings}
src="https://assets.nardu.in/sci-hub-settings.webp" width="728"
sizes="sm:100vw xl:728px" > height="319"
</nuxt-source> alt="Windows system settings"
<nuxt-img src="https://assets.nardu.in/sci-hub-settings.jpg" />
sizes="sm:100vw xl:728px"
alt="Windows system settings"
loading="lazy" >
</nuxt-img>
</picture>
<picture width="728" height="803"> <AstroImage
<nuxt-source src={windowsNetwork}
src="https://assets.nardu.in/sci-hub-network.webp" width="728"
sizes="sm:100vw xl:728px" > height="803"
</nuxt-source> alt="Windows network settings"
<nuxt-img src="https://assets.nardu.in/sci-hub-network.jpg" />
sizes="sm:100vw xl:728px"
alt="Windows network settings"
loading="lazy" >
</nuxt-img>
</picture>
<picture width="728" height="327"> <AstroImage
<nuxt-source src={windowsAdapter}
src="https://assets.nardu.in/sci-hub-adapter.webp" width="728"
sizes="sm:100vw xl:728px" > height="327"
</nuxt-source> alt="Windows network connections settings"
<nuxt-img src="https://assets.nardu.in/sci-hub-adapter.jpg" />
sizes="sm:100vw xl:728px"
alt="Windows network connections settings"
loading="lazy" >
</nuxt-img>
</picture>
<picture width="728" height="434"> <AstroImage
<nuxt-source src={windowsAdapterSettings}
src="https://assets.nardu.in/sci-hub-adapter-settings.webp" width="728"
sizes="sm:100vw xl:728px" > height="434"
</nuxt-source> alt="Windows network adapter settings"
<nuxt-img src="https://assets.nardu.in/sci-hub-adapter-settings.jpg" />
sizes="sm:100vw xl:728px"
alt="Windows network adapter settings"
loading="lazy" >
</nuxt-img>
</picture>
## Which DNS? ## Which DNS?
@ -127,16 +115,16 @@ My favorite since it is a [french association](https://www.fdn.fr/)
- 2001:910:800::12 - 2001:910:800::12
- 2001:910:800::40 - 2001:910:800::40
### [Cloudflare DNS](https://1.1.1.1/) ### [Quad9](https://www.quad9.net)
For those who want the [“fastest DNS servers in the world”.](https://www.dnsperf.com/#!dns-resolvers) Quand9 is a non-profit foundation offering free public DNS servers. Their classic servers offer filtering of identified malicious domains. They do not collect data and are <abbr title="General Data Protection Regulation">GDPR</abbr>-compliant.
1. IPV4 1. IPV4
- 1.1.1.1 - 9.9.9.9
- 1.0.0.1 - 149.112.112.112
2. IPV6 2. IPV6
- 2606:4700:4700::1111 - 2620:fe::fe
- 2606:4700:4700::1001 - 2620:fe::9
## That's it. ## That's it.

View File

@ -0,0 +1,76 @@
---
title: The day I Jamd
subtitle: A story of unusual tools and fun gambles.
lang: en
slug: "the-day-I-jamd"
excerpt: Ooh, yeah! All right! Were jammin
tags: ["Dev", "Jamstack"]
createdAt: "2020-10-08T09:00:00.000Z"
updatedAt: "2022-12-27T15:40:06.000Z"
---
import AstroImage from "../../../components/AstroImage.astro";
export const wordpress = "https://assets.nardu.in/wordpress_8ee6f54b98.jpeg";
export const strapi11ty = "https://assets.nardu.in/static_2c0d9f1eb8.jpeg";
## The not so easy choice
When I was still using Wordpress for my personal website, I thought it would be nice to try [OpenLiteSpeed](https://openlitespeed.org) webserver. Being a designer, I always like to have a graphical interface and I heard OLS had very nice caching and overall performances.
## FIGHT!
A few years later, I learned about Jamstack and its incredible potential. Not being impulsive at all, I chose to entirely remake my website with tools like [11ty](https://www.11ty.dev/) and [Strapi](https://strapi.io/).
I remember Strapi still being in alpha/beta and colleagues telling me it was a risky gamble, especially since I was pretty new to the node.js world.
> Open source and french? Sign me up!
So I started recreating my website identically with 11ty and Strapi. Same hosting on a Digital Ocean droplet, same webserver, same content. I had done a fair amout of performance optimizations on my wordpress install so I was eager to compare the results with the new static version.
Boy did they exceed my expectations! With almost no optimization on the static side, I got the following results.
### wordpress
<AstroImage
src={wordpress}
width="728"
height="412"
alt="Performance score of 53/100 on Wordpress."
/>
Despite a lot of efforts I could not do better. Im no expert in caching, do not use CDN and relied on plugins to achieve a lot of stuff (php not being my strongest skill 😬).
### 11ty + strapi
<AstroImage
src={strapi11ty}
width="728"
height="412"
alt="Performance score of 97/100 on jamstack."
/>
Almost **zero** special configuration (I was too anxious to test) and I reached an awesome score. I know lighthouse scores are not everything but hey, **53 vs 97**… Ill take it!
## Now kiss 🥰
Both of these approaches have their benefits and drawbacks. As a freelance, my clients often prefer or ask for Wordpress because of its reputation. I always present a Jamstack alternative when possible but its still scary to most compared to Wordpress. I feel however that its going to be easier to use Jamstack with time.
## I did it again…
Here we are now, a quarantine and a few months later and this website is running with… [Nuxt.js](https://nuxtjs.org/). I know, Im sorry, I couldnt resist the latest [nuxt content](https://content.nuxtjs.org/) functionnality.
So I re-did everything again… and let me just say: **wow**, what an experience!
I **love** developing with Nuxt. Using nuxt content allowed me to focus on design and, well, content.
No more Apollo client and graphQL query, only markdown and a few promises.
Dont get me wrong, I really like working with Strapi and its graphQL capabilities but for the purpose of this website, nuxt content does it (extremely well).
Im still using Strapi as a way to upload, store and manage images, videos, gifs, etc. It might be too much for this use case but I like to know that I can still go back to it if I want to! Also, I spent quite a bit of time setting it up with OpenLiteSpeed and Im still too attached to this accomplishment to let it go. I wrote [an article on Strapi running with OpenLiteSpeed](/en/articles/strapi-openlitespeed) if youre into that kind of stuff.
## In the end
Wordpress, Jamstack, vanilla everything… Does it really matter? For me, the dev experience was far more enjoyable working with 11ty, nuxt.js and strapi than with Wordpress.
On the performance and accessibility side, it also seems to be the better/smarter choice. It might not be everytime! ¯\\\_(ツ)\_/¯
Cant wait to learn [astro](https://astro.build/) and start from scratch again!

View File

@ -0,0 +1,124 @@
---
title: Video compression
subtitle: Encode like you mean it.
lang: en
slug: "video-compression"
excerpt: How to gain precious weight when encoding videos.
tags: ["Design"]
createdAt: "2021-05-05T09:00:00.000Z"
updatedAt: "2022-06-08T14:24:06.000Z"
---
import AstroImage from "../../../components/AstroImage.astro";
export const premiereExport = "https://assets.nardu.in/wordpress_8ee6f54b98.jpeg";
export const handbrakeBase = "https://assets.nardu.in/video-handbrake-1.jpeg";
export const handbrakeVideo = "https://assets.nardu.in/video-handbrake-2.jpeg";
export const handbrakeAudio = "https://assets.nardu.in/video-handbrake-3.jpeg";
export const handbrakeWeb = "https://assets.nardu.in/video-handbrake-4.jpeg";
## Let's play.
Videos are everywhere. **EVERYWHERE!** But videos can and will be huge when 4k becomes an internet standard. Currently, 1920x1080 is king and we usually have no problem hosting or playing this resolution.
## Because we can doesn't mean we should.
Right now, the (good) trend of web performance is on the rise. Everyone wants a blazing fast loading website, which might not be possible if we send requests to 100mb (or even more) videos.
With a good connection, users will not see the difference. But if we go down that path, nor will they with a 300mb one. So the goal is to make every asset as small as possible. It's already the case with images. But it's simpler and faster than video encoding.
## Small size, best quality.
Let's say we exported a 1920x1080 video from Premiere Pro with these basic settings:
<AstroImage
src={premiereExport}
width="673"
height="800"
alt=""
/>
It's gorgeous, it's Full HD, it's 1:30 minute of excellent editing but it's 50mb… What a shame.
> We can already bring down the size from Premiere or Media Encoder by selecting CBR instead of VBR and using a low bitrate (like 2 or 3).
Let's now use [Handbrake!](https://handbrake.fr/) It's free, open source and multi platform. You can also read this great article from [Ueno](https://loremipsum.ueno.co/dear-ueno-how-do-you-compress-videos-6657ebd9dd28?gi=930afecec398) on video encoding.
**Please please PLEASE. Never export videos from within After Effects.**
## Handbrake
### Summary screen
While it's not as nice as Premiere Pro, it has way more exporting capabilities. Follow these steps to use good standard settings:
1. Open your source video
1. Select a preset corresponding to your future usage like Fast 1080p30
1. Check Web Optimized
1. Keep MPEG-4 as the format
<AstroImage
src={handbrakeBase}
width="728"
height="337"
alt=""
/>
### Video screen
1. Keep H.264 (x264) or use H.264 nvidia nvenc if you can (crazy fast encoding by nvidia)
1. Choose Constant Quality and try a value between 20 and 30 (higher = smaller size but lower quality)
1. Choose Peak Framerate. If you don't know the framerate, keep the default setting
1. Choose the type of video you are encoding (film, animation…)
<AstroImage
src={handbrakeVideo}
width="728"
height="337"
alt=""
/>
### Audio screen
If you don't have audio, be sure to set the audio channel to none.
If you have an audio channel, these settings are great and will not influence the size much:
1. Codec AAC
1. Samplerate 44.1
1. Bitrate 192 to 256 (your choice)
<AstroImage
src={handbrakeAudio}
width="728"
height="337"
alt=""
/>
### Export!
I used a RF of 25 for this example and no audio.
1. My Premiere Pro video was 50,6mb
1. My Handbrake video is 5,5mb
> What a save!
I ended up dividing the original size by 10. Which is cool.
I tried with a RF of 30. The video was still pretty good and only 3,3mb.
## Exporting for the web
Now that we know how to properly compress videos, let's go further. Say we have a website with a lot of videos on the same page. We still want to load the content as fast as possible. So our videos need to be as small as possible. We won't do better than previously seen using mp4/H.264. However, we can use **webm/VP9.**
Webm is an html video format and VP9 is its latest codec.
Using Handbrake and webm/VP9, we can achieve really great compression without losing too much quality (or none at all depending on the settings). I was able to divide by 4 the size of a video using these presets:
<AstroImage
src={handbrakeWeb}
width="728"
height="313"
alt=""
/>
The only down side is that it takes some time to encode. It will depend on the video length and your computing power.
I tried various settings but I read [here](https://trac.ffmpeg.org/wiki/Encode/VP9) that VP9 is supposed to be used with two-pass encoding. You then have to find the right bitrate (here 1000) for your situation.

View File

@ -0,0 +1,42 @@
---
title: Nico v2.0
subtitle: Mise à jour 2022 de plein de trucs.
lang: fr
slug: "2022"
excerpt: Évolution des services, du site et de moi-même.
tags: ["Freelance"]
createdAt: "2022-06-08T14:24:06.000Z"
---
Après deux ans de freelance à temps plein, jai pris du recul sur mon activité. Jai surtout questionné mon positionnement et les prestations que je proposais.
## Les services
### Jarrête le print
Même si je suis formé à lidentité visuelle, je nen fais que très rarement et ne pratique pas la discipline sur mon temps libre. Idem pour le graphisme «&nbsp;classique&nbsp;». Affiches, flyers et autres prospectus ont disparu de mes devis depuis bien longtemps. Il me semble ainsi logique de ne plus proposer explicitement ces services car je considère ne plus être suffisamment compétent ni intéressé.
### Jinternet à fond
À linverse, je passe mon temps à **faire du web.** Énormément de développement, un peu de design et pas mal de configuration serveur. Il est de fait évident pour moi de proposer **des offres web plus précises,** dans lesquelles je me suis spécialisé.
Ainsi, **laccessibilité numérique** et **léco-conception** deviennent des expertises intégrées à ma pratique. Ce ne sont plus des options sur un devis mais bien **mon cœur de métier.**
## Le site
Jai retravaillé mon site, notamment la page daccueil et son contenu, pour laccorder avec ces décisions.
Jai également fait plusieurs changements de comportements&nbsp;:
- les liens externes ne souvrent plus dans une nouvelle fenêtre. Je suis tombé sur <a href="https://css-tricks.com/use-target_blank/" rel="noopener noreferer" hreflang="en">cet article (en anglais)</a> traitant de louverture des liens dans une nouvelle fenêtre et je nai pas trouvé de bonne raison de continuer à utiliser ce comportement&nbsp;;
- jai supprimé les balises open graph et twitter card car je suis entièrement daccord avec [cette analyse](https://twitter.com/HTeuMeuLeu/status/1370310316496728065) de [@HTeuMeuLeu](https://twitter.com/HTeuMeuLeu)&nbsp;;
- [idem pour la favicon](https://twitter.com/HTeuMeuLeu/status/1370310312214339586)&nbsp;;
- je ne déclenche les animations que si l'utilisateur n'a pas demandé au système de minimiser la quantité d'animation ou de mouvement [(référence de l'option)](https://developer.mozilla.org/fr/docs/Web/CSS/@media/prefers-reduced-motion).
Je suis en train de travailler pour réduire au maximum le poids de mes pages, même si je vais certainement devoir (encore) changer doutil (coucou <a href="https://astro.build/" rel="noopener noreferer" hreflang="en" >astro</a> 👀).
## Le Nico
Dun point de vue personnel, je me suis engagé pour un numérique plus responsable en rejoignant le collectif [Good-it!](https://www.good-it.org/) et en participant activement à son développement.
Jai également commencé à donner des cours dans plusieurs écoles. Jinterviens essentiellement sur des cursus numériques en enseignant le design et le développement. Jen profite ainsi pour sensibiliser, dès la formation, les futures générations à laccessibilité et léco-conception.

View File

@ -0,0 +1,11 @@
---
title: After Effects Expressions
subtitle: En cours de traduction, revenez bientôt ;)
lang: fr
slug: "after-effects-expressions"
excerpt: En cours de traduction, revenez bientôt ;)
tags: ["Design"]
createdAt: "2019-04-24T09:00:00.000Z"
---
[Retour aux articles](/articles/)

View File

@ -0,0 +1,73 @@
---
title: Accessibilité, sobriété et F.A.Q.
subtitle: Explications sur ma vision et mon fonctionnement.
lang: fr
slug: "faq"
excerpt: Pourquoi, comment et surtout quèsaco.
tags: ["Freelance"]
createdAt: "2022-06-22T15:34:45.000Z"
---
## l'Accessibilité
### Quèsaco&nbsp;?
D'après [access42&nbsp;:](https://access42.net)
> Laccessibilité numérique est un droit fondamental. Cest la possibilité pour toutes et tous dutiliser les outils informatiques, quelle que soit leur façon dy accéder.
Ainsi, lorsque je <span lang="en">design</span> ou développe un site web, je fais le maximum pour que **n'importe quel visiteur** puisse l'utiliser. On pourrait croire que cela rajoute une charge de travail ou un délai allongé de réalisation mais **ce n'est pas le cas&nbsp;!**
Lorsqu'un site est pensé et conçu avec l'accessibilité en tête, il ne prend **pas plus de temps** qu'un projet qui ne le fait pas. En revanche, **corriger** un site existant qui n'a pas bénéficié de cette réflexion en amont **demande beaucoup plus d'efforts.**
### Pourquoi c'est important&nbsp;?
Après avoir été sensibilisé à l'accessibilité et aux [situations frustrantes voire bloquantes](https://www.france24.com/fr/info-en-continu/20220520-internet-parcours-d-obstacles-pour-les-aveugles) que rencontrent les personnes handicapées sur le web, j'ai décidé de tout faire pour me former et travailler en connaissance de cause. Il m'a semblé que proposer des sites utilisables par le plus grand nombre devait être la norme.
## l'Éco-conception
### Quèsaco&nbsp;?
D'après le collectif [<span lang="en">green it</span>&nbsp;:](https://www.greenit.fr)
> Léco-conception de service numérique consiste à améliorer lefficience des applications dès leur conception pour réduire les impacts environnementaux et économiques associés tout en améliorant significativement lexpérience utilisateur.
Il s'agit ici pour moi de créer des produits les plus sobres possibles. C'est-à-dire de développer ou d'utiliser une fonctionnalité uniquement si elle est indispensable.
Par exemple, mettre en place **un formulaire de contact** sur un site web implique plusieurs choses&nbsp;:
- développer un formulaire
- envoyer les données au serveur
- vérifier les données envoyées
- envoyer les données par mail
- confirmer l'envoi du mail (en fournissant une copie)
- gérer les erreurs à chaque étape
Un site statique seul ne peut pas faire tout ça. Il faut obligatoirement un serveur et un langage capable d'effectuer ces opérations (ou un prestataire qui s'en charge). C'est pourquoi je recommande une adresse email et un numéro de téléphone comme moyen de contact plutôt qu'un formulaire. Dans le cas d'un questionnaire ou autre situation particulière, un formulaire est tout à fait envisageable.
### Pourquoi c'est important&nbsp;?
L'éco-conception fait beaucoup de bien à la qualité globale d'un site et à l'expérience des visiteurs. En effet, étant réalisé uniquement avec ce dont il a besoin, il aura tendance à être plus léger, mieux optimisé, plus rapide, etc.
## F.A.Q.
### Cette démarche est-elle opportuniste&nbsp;?
**Non.** Il est vrai que l'accessibilité et, surtout, l'éco-conception, commencent à être «&nbsp;tendance&nbsp;». On pourrait donc croire que j'en profite pour faire du <span lang="en">business</span>. Je considère cependant que c'est ma responsabilité de créer des sites web accessibles et éco-conçus **par défaut.**
### Un site accessible est-il plus cher&nbsp;?
**Oui et non.** Oui car si on demande un audit, il faudra payer la société qui le réalise. De mon côté, je ne fais pas payer l'accessibilité plus cher sur mes factures étant donné que je travaille ainsi par défaut.
En revanche, j'augmente mes tarifs suite à l'obtention de certifications relatives à mon activité.
### Mon site sera-t-il 100&nbsp;% accessible&nbsp;?
**Non,** je ne suis pas en mesure de garantir ou de certifier la conformité totale d'un site au [référentiel général d'amélioration de l'accessibilité.](https://www.numerique.gouv.fr/publications/rgaa-accessibilite/) Seul un audit exécuté par une entreprise apte à le réaliser peut certifier la conformité d'un site. Cependant, je peux vous accompagner avant et après l'audit afin d'anticiper puis de corriger les critères d'accessibilité requis. [Access42](https://access42.net/) et [Tanaguru](https://www.tanaguru.com/) font parties de ces entreprises spécialisées.
### Pourquoi ne pas utiliser un outil automatique&nbsp;?
Car ces outils dits de “surcouche” (dont il ne faut pas prononcer le nom [sous peine de procès](https://www.lalutineduweb.fr/aide-frais-avocate-proces-contre-faciliti/)), ne sont pas des solutions micracles. Lisez [cet article](https://www.lalutineduweb.fr/surcouche-accessibilite-web-mensonges-boules-gommes/) d'une experte en accessibilité et/ou [ce test](https://blog.empreintedigitale.fr/2021/06/01/outils-de-surcouche-daccessibilite-que-valent-ils-vraiment/) de différents outils pour en savoir plus.
### Un site éco-conçu et accessible peut-il être beau&nbsp;?
**Oui, évidemment.** Je vous laisse juger par vous-même avec [cette liste](https://lowww.directory/) de sites éco-conçus. En ce qui concerne l'accessibilité, elle n'impacte que très peu le <span lang="en">design</span> global d'un site. Même si plusieurs critères visuels existent (contrastes, taille du texte, etc.), elle se concentre essentiellement sur le fonctionnement et l'utilisation d'un produit.

View File

@ -0,0 +1,72 @@
---
title: "Gratuiste"
subtitle: "Ou le travail gratuit."
lang: fr
slug: "gratuiste"
excerpt: "Jai cherché un moyen de mettre mes compétences au service dautrui et je pense avoir trouvé: je vais travailler gratuitement pour des associations."
tags: ["Graphisme", "Freelance"]
createdAt: "2017-05-27T07:47:36.000Z"
updatedAt: "2022-12-27T15:36:06.000Z"
---
## Gratuiste, quest-ce que cest ?
Dans le monde francophone du graphisme, le terme gratuiste est un terme péjoratif employé pour dénoncer certaines conditions de travail. Les plus répandues sont les concours daffiche/de logo, les clients qui demandent que vous commenciez à travailler sans vous payer et qui paieront si ça leur convient, les appels doffre non défrayés, etc.
De plus en plus, la communauté sélève contre ces pratiques et les [dénonce.](https://twitter.com/PayeTonAffiche)
> “ On nest pas des gratuistes ! ”
Comprendre : nous exerçons un métier à part entière qui requiert de nombreuses connaissances et beaucoup de travail, payez-nous. Rien dextravagant en somme. Regardez [cette vidéo](https://youtu.be/essNmNOrQto) qui transpose ces pratiques à des métiers “ standards ” et [celle-ci](https://youtu.be/DsstOs-K7gk) qui explique en détails le processus actuel.
## Tes débile du coup ?
Jaime à penser que non. Je naspire pas à devenir gratuiste dans ce sens là du terme. Jaimerais lui en donner un nouveau, plus positif. Dans les conditions précédentes, gratuiste est en effet une situation que personne (graphiste ou autre) naimerait expérimenter et encore moins promouvoir. À part peut-être des “ clients ” sans scrupules.
Étant encore assez nouveau sur le marché du travail, je nai pas la solution de faire des dons à des associations, quils soient financiers ou autre.
Néanmoins, jai envie daider à mon échelle et selon mes capacités.
## Le concept
Je compte proposer mes services et mes compétences gratuitement à des associations caritatives/ONG qui viennent en aide aux personnes dans le besoin ou aux animaux.
Ce type de prestation porte le nom de “ **pro bono publico****pour le bien public** ”.
Cette pratique existe essentiellement dans les milieux juridiques. Je pense cependant quelle nest pas incompatible avec le métier de graphiste.
> Graphiste pro bono, pourquoi pas&nbsp;?
## Quels genres de services ?
En toute logique, le type qui rend service à lassociation, qui peut faire une différence. À mes yeux, essentiellement des sites internet. Je pense quun site web aura bien plus dintérêt quun nouveau logo. Pourquoi pas des flyers/des petits visuels pour des pins/badges.
### En résumé :
- maquette de site web
- intégration front-end (en dur ou via wordpress) du site
- gestion de la mise en ligne du site
- newsletter (design et/ou intégration)
- flyers
- autres petites demandes
Vous pouvez avoir un aperçu de mon travail [sur ce même site.](/) Jai déjà eu loccasion de travailler pour&nbsp;:
- [Cygnal](/projets/cygnal)
- [OpenMole](https://openmole.org/)
- [Good-it](https://good-it.org)
## Dans quelles conditions ?
- lassociation doit être active
- elle doit porter des valeurs que je partage
- elle doit pouvoir me montrer ses actions
- elle ne doit pas avoir les moyens de se payer un graphiste
## Cest un peu trop beau pour être vrai ton truc…
Il faut garder à lesprit que je compte travailler bénévolement pendant mon temps libre. Je ne cherche pas une relation client habituelle puisquil ny aura pas de client à proprement parler. Cela me permettra déviter des délais intenables, des retours par milliers et toute obligation de finir le travail en cas de coup fourré.
Je préfère être franc là-dessus même si je ne me fais pas trop de soucis sur les mœurs du monde associatif. Aussi, faudra-t-il être un peu patient si jamais jai déjà une demande en cours.
Si vous êtes intéressés, [envoyez-moi un mail !](mailto:contac@nardu.in)
## Quest-ce que ty gagnes toi ?
Simplement la satisfaction davoir aidé, à mon échelle, des personnes en difficulté. Je ne fais pas ça pour me faire de la pub ou pour étayer mon portfolio. Cela me permettra en revanche de travailler sur dautres sujets et dans un cadre différent.

View File

@ -3,12 +3,21 @@ title: "Sci-hub bloqué, comment contourner"
subtitle: "La science du partage." subtitle: "La science du partage."
lang: fr lang: fr
slug: "sci-hub-blocage" slug: "sci-hub-blocage"
key: "scihub"
excerpt: "Le tribunal de grande instance de Paris a ordonné aux fournisseurs daccès à internet de bloquer laccès à sci-hub. Voici comment contourner les blocages mis en place." excerpt: "Le tribunal de grande instance de Paris a ordonné aux fournisseurs daccès à internet de bloquer laccès à sci-hub. Voici comment contourner les blocages mis en place."
tags: ["Internet", "Science"] tags: ["Internet", "Science"]
pubDate: "2019-03-31T07:47:36.000Z" createdAt: "2019-03-31T07:47:36.000Z"
updatedAt: "2022-12-27T12:08:00.000Z"
--- ---
import AstroImage from "../../../components/AstroImage.astro";
export const macOs =
"https://assets.nardu.in/ef5a4b8e82a046e6a466c73c2fd9e99e.jpg";
export const windowsSettings = "https://assets.nardu.in/sci-hub-settings.jpg";
export const windowsNetwork = "https://assets.nardu.in/sci-hub-network.jpg";
export const windowsAdapter = "https://assets.nardu.in/sci-hub-adapter.jpg";
export const windowsAdapterSettings =
"https://assets.nardu.in/sci-hub-adapter-settings.jpg";
L'adresse actuelle de sci-hub est&nbsp;: [sci-hub.se](https://sci-hub.se) L'adresse actuelle de sci-hub est&nbsp;: [sci-hub.se](https://sci-hub.se)
## Résumé de la situation ## Résumé de la situation
@ -22,7 +31,7 @@ Le blocage mis en place est assez basique. Il sagit dun blocage “ DNS
Un DNS est un serveur qui sert à faire le lien entre un nom de domaine&nbsp;: **sci-hub.tw** et une adresse IP : **186.2.163.90**. Un DNS est un serveur qui sert à faire le lien entre un nom de domaine&nbsp;: **sci-hub.tw** et une adresse IP : **186.2.163.90**.
Cest en quelque sorte lannuaire dinternet. Sans ladresse, impossible de contacter le domaine. Ce que les FAI ont certainement fait&nbsp;: bloquer les requêtes au nom de domaine [sci-hub.tw.](http://sci-hub.tw/) Ainsi, aucune adresse IP nest renvoyée et le site nest pas accessible. Cest en quelque sorte lannuaire dinternet. Sans ladresse, impossible de contacter le domaine. Ce que les FAI ont certainement fait&nbsp;: bloquer les requêtes au nom de domaine [sci-hub.se.](http://sci-hub.se/) Ainsi, aucune adresse IP nest renvoyée et le site nest pas accessible.
## Comment contourner ce blocage&nbsp;? ## Comment contourner ce blocage&nbsp;?
@ -32,28 +41,27 @@ Cest en quelque sorte lannuaire dinternet. Sans ladresse, impossible
Je recommande la deuxième solution car elle permet de se prémunir contre de futurs blocages/filtrages/censures de la part de votre FAI et ne nécessite pas de contrepartie financière. Je recommande la deuxième solution car elle permet de se prémunir contre de futurs blocages/filtrages/censures de la part de votre FAI et ne nécessite pas de contrepartie financière.
Je vais me focaliser sur le changement depuis un ordinateur car il sera effectif où que vous soyez. Je vais me focaliser sur le changement depuis un ordinateur car il sera effectif où que vous soyez.
### Sur MacOs&nbsp;: ### Sur MacOs
Ouvrez&nbsp;: Ouvrez&nbsp;:
1. Préférences système 1. Préférences système
1. Réseau 1. Réseau
1. Ethernet et/ou Wi-fi
1. Avancé 1. Avancé
1. DNS 1. DNS
1. Serveurs DNS 1. Serveurs DNS
De là, vous pouvez ajouter des serveurs DNS en cliquant sur l'icône +. Cliquez sur ok et appliquez les nouveaux paramètres. Vous devrez peut-être redémarrer votre ordinateur pour que les changements fonctionnent. De là, vous pouvez ajouter des serveurs DNS en cliquant sur l'icône +. Cliquez sur ok et appliquez les nouveaux paramètres. Vous devrez peut-être redémarrer votre ordinateur pour que les changements fonctionnent.
<nuxt-img <AstroImage
src="https://assets.nardu.in/ef5a4b8e82a046e6a466c73c2fd9e99e.jpg" src={macOs}
sizes="sm:100vw xl:728px" width="728"
width="728" height="1060"
height="1060" alt="MacOS réglages réseau et DNS"
alt="MacOS réglages réseau et DNS" />
loading="lazy">
</nuxt-img>
### Sur Windows (ici 10)&nbsp;: ### Sur Windows (ici 10)
Ouvrez&nbsp;: Ouvrez&nbsp;:
@ -65,53 +73,33 @@ Ouvrez&nbsp;:
1. Propriétés 1. Propriétés
1. Utiliser ladresse de serveur DNS suivante 1. Utiliser ladresse de serveur DNS suivante
<picture width="728" height="319"> <AstroImage
<nuxt-source src={windowsSettings}
src="https://assets.nardu.in/sci-hub-settings.webp" width="728"
sizes="sm:100vw xl:728px" > height="319"
</nuxt-source> alt="Réglages windows"
<nuxt-img src="https://assets.nardu.in/sci-hub-settings.jpg" />
sizes="sm:100vw xl:728px"
alt="Réglages windows"
loading="lazy" >
</nuxt-img>
</picture>
<picture width="728" height="803"> <AstroImage
<nuxt-source src={windowsNetwork}
src="https://assets.nardu.in/sci-hub-network.webp" width="728"
sizes="sm:100vw xl:728px" > height="803"
</nuxt-source> alt="Windows réglages réseaux"
<nuxt-img src="https://assets.nardu.in/sci-hub-network.jpg" />
sizes="sm:100vw xl:728px"
alt="Windows réglages réseaux"
loading="lazy" >
</nuxt-img>
</picture>
<picture width="728" height="327"> <AstroImage
<nuxt-source src={windowsAdapter}
src="https://assets.nardu.in/sci-hub-adapter.webp" width="728"
sizes="sm:100vw xl:728px" > height="327"
</nuxt-source> alt="Windows régalges connections réseaux"
<nuxt-img src="https://assets.nardu.in/sci-hub-adapter.jpg" />
sizes="sm:100vw xl:728px"
alt="Windows régalges connections réseaux"
loading="lazy" >
</nuxt-img>
</picture>
<picture width="728" height="434"> <AstroImage
<nuxt-source src={windowsAdapterSettings}
src="https://assets.nardu.in/sci-hub-adapter-settings.webp" width="728"
sizes="sm:100vw xl:728px" > height="434"
</nuxt-source> alt="Windows options adaptateur réseau"
<nuxt-img src="https://assets.nardu.in/sci-hub-adapter-settings.jpg" />
sizes="sm:100vw xl:728px"
alt="Windows options adaptateur réseau"
loading="lazy" >
</nuxt-img>
</picture>
## Quels serveurs DNS utiliser&nbsp;? ## Quels serveurs DNS utiliser&nbsp;?
@ -130,16 +118,16 @@ Mes favoris puisquil sagit dune [association française.](https://www.f
- 2001:910:800::12 - 2001:910:800::12
- 2001:910:800::40 - 2001:910:800::40
### [Cloudflare DNS](https://1.1.1.1/) ### [Quad9](https://www.quad9.net/fr)
Pour ceux qui veulent les serveurs DNS [“&nbsp;les plus rapides du monde&nbsp;”.](https://www.dnsperf.com/#!dns-resolvers) Quand9 est une fondation à but non lucratif proposant des serveurs DNS publics et gratuits. Leurs serveur classiques offrent un filtrage de domaines malveillants identifiés. Ils ne collectent pas de données et sont conforment au <abbr title="Règlement général sur la protection des données">RGPD</abbr>.
1. IPV4 1. IPV4
- 1.1.1.1 - 9.9.9.9
- 1.0.0.1 - 149.112.112.112
2. IPV6 2. IPV6
- 2606:4700:4700::1111 - 2620:fe::fe
- 2606:4700:4700::1001 - 2620:fe::9
## C'est tout. ## C'est tout.

View File

@ -0,0 +1,76 @@
---
title: The day I Jamd
subtitle: Des paris, des outils et du fun.
lang: fr
slug: "the-day-I-jamd"
excerpt: Ooh, yeah! All right! Were jammin
tags: ["Dev", "Jamstack"]
createdAt: "2020-10-08T07:47:36.000Z"
updatedAt: "2022-12-27T15:40:06.000Z"
---
import AstroImage from "../../../components/AstroImage.astro";
export const wordpress = "https://assets.nardu.in/wordpress_8ee6f54b98.jpeg";
export const strapi11ty = "https://assets.nardu.in/static_2c0d9f1eb8.jpeg";
## La solution de non facilité
Lorsque jutilisais encore Wordpress pour mon site web personnel, jai voulu essayer le serveur web <a href="https://openlitespeed.org" lang="en" hreflang="en" rel="noopener noreferer">OpenLiteSpeed (en anglais)</a>. En tant que designer, jai toujours aimé avoir une interface graphique et javais entendu dire quOLS jouissait de bonnes performances en général et de cache en particulier.
## Battez-vous!
Quelques années plus tard, je découvrais lunivers Jamstack et son incroyable potentiel. Nétant pas du tout impulsif, jai choisi de refaire entièrement mon site web avec des outils comme <a href="https://www.11ty.dev/" lang="en" hreflang="en" rel="noopener noreferer">11ty (en anglais)</a> et <a href="https://strapi.io/" hreflang="en">Strapi (en anglais)</a>.
Je me souviens que Strapi était encore en alpha/beta et que certains collègues me disaient que cétait un pari risqué, surtout que je découvrais à peine le monde de node.js.
> Open source et français&nbsp;? Je dis banco&nbsp;!
Jai donc commencé à recréer mon site web à lidentique avec 11ty et Strapi. Même hébergement chez Digital Ocean, même serveur web, même contenu. Javais fait pas mal doptimisations de performances sur mon installation wordpress, jétais donc impatient de comparer les résultats avec la nouvelle version statique.
Jen suis resté pantois&nbsp;! Quasiment sans optimisation du côté statique, jai obtenu les résultats ci-dessous.
### wordpress
<AstroImage
src={wordpress}
width="728"
height="412"
alt="Score de performance de 53/100 sur Wordpress."
/>
Malgré beaucoup defforts, je nai pas pu faire mieux. Je ne suis pas un expert en cache, je nutilise pas de CDN et je me suis appuyé sur des plugins pour réaliser beaucoup de choses (php nétant pas ma spécialité 😬).
### 11ty + strapi
<AstroImage
src={strapi11ty}
width="728"
height="412"
alt="Score de performance de 97/100 en Jamstack."
/>
Presque **zéro** configuration spéciale (jétais trop impatient de tester) et jai atteint un score impressionnant. Je sais que les scores lighthouse ne font pas tout mais **53 contre 97**… Ça me va&nbsp;!
## Embrassez-vous 🥰
Ces deux approches ont leurs avantages et leurs inconvénients. En tant que freelance, mes clients préfèrent ou demandent souvent Wordpress en raison de sa réputation. Je présente toujours une alternative Jamstack lorsque cest possible, mais cela reste effrayant la plupart du temps par rapport à Wordpress. Je pense cependant quil sera plus facile dutiliser la Jamstack avec le temps.
## Encore et encore…
Nous voici maintenant une quarantaine et quelques mois plus tard et ce site fonctionne avec… [Nuxt.js](https://fr.nuxtjs.org/). Je sais, je suis désolé, je nai pas pu résister à la dernière fonctionnalité [<span lang="en">nuxt content</span>](https://content.nuxtjs.org/fr) de Nuxt.
Du coup jai tout refait… et laissez-moi vous dire&nbsp;: **wow**, quelle expérience&nbsp;!
Jadore développer avec Nuxt. Lutilisation de <span lang="en">nuxt content</span> ma permis de me concentrer sur le design et, évidemment, sur le contenu.
Plus de client Apollo ni de requête graphQL, seulement du <span lang="en">markdown</span> et quelques promesses.
Attention, jaime beaucoup travailler avec Strapi et son interface graphQL mais pour les besoins de ce site, <span lang="en">nuxt content</span> fait le <span lang="en">job</span> (extrêmement bien).
Jutilise toujours Strapi pour téléverser, stocker et gérer les images, vidéos, gifs, etc. Cest peut-être trop pour ce que jen fait mais jaime savoir que je peux toujours y revenir si jai envie&nbsp;! De plus, jai passé pas mal de temps à le mettre en place avec <span lang="en">OpenLiteSpeed</span> et je suis encore trop attaché à cette réussite pour la laisser tomber.
## Et en fait à la fin
Wordpress, Jamstack, fait main… Est-ce vraiment important&nbsp;? Pour moi, lexpérience de développement a été bien plus agréable en travaillant avec 11ty, nuxt.js et strapi quavec Wordpress.
Du point de vue de la performance et de laccessibilité, il semble également que ce soit le choix le plus judicieux. Ce nest peut-être pas le cas à chaque fois&nbsp;! ¯\\\_(ツ)\_/¯
Jai hâte dapprendre <a href="https://astro.build/" rel="noopener noreferer" hreflang="en" >astro</a> et de tout recommencer à zéro&nbsp;!

View File

@ -0,0 +1,12 @@
---
title: Compression vidéo
subtitle: En cours de traduction, revenez bientôt ;)
lang: fr
slug: "video-compression"
excerpt: Pas encore traduit
tags: ["Design"]
createdAt: "2021-05-05T09:00:00.000Z"
updatedAt: "2022-06-08T14:24:06.000Z"
---
[Retour aux articles](/articles/)

View File

@ -8,14 +8,34 @@ const articles = defineCollection({
slug: z.string(), slug: z.string(),
tags: z.array(z.string()), // An array of strings tags: z.array(z.string()), // An array of strings
// Parse pubDate as a browser-standard `Date` object // Parse pubDate as a browser-standard `Date` object
pubDate: z createdAt: z.string().transform((str) => new Date(str)),
updatedAt: z
.string() .string()
.transform((str) => new Date(str)) .transform((str) => new Date(str))
.optional(), .optional(),
code: z.boolean().optional() || false,
},
});
const fragments = defineCollection({
schema: {
title: z.string(),
subtitle: z.string(),
lang: z.enum(["fr", "en"]),
slug: z.string(),
tags: z.array(z.string()), // An array of strings
// Parse pubDate as a browser-standard `Date` object
createdAt: z.string().transform((str) => new Date(str)),
updatedAt: z
.string()
.transform((str) => new Date(str))
.optional(),
code: z.boolean().optional() || false,
}, },
}); });
export const collections = { export const collections = {
// Don't forget 'quotes' for collection names containing dashes // Don't forget 'quotes' for collection names containing dashes
articles, articles,
fragments,
}; };

View File

@ -0,0 +1,111 @@
---
title: Strong TLS certificates with acme.sh
subtitle: 384-bit of https
lang: en
slug: "acme-sh-tls-cert"
createdAt: "2022-06-08T14:24:06.000Z"
excerpt: Real cert have curves.
tags: ["security"]
---
## Disclaimer
I'm, in absolutely no regards, a security expert. I just fancy shiny new things of the interwebs.
This is why I've switched my default TLS certificates to use elliptic curve cryptography (ECC) instead of RSA. Now I have a sweet 100/100 on [tls.imirhil.fr](https://tls.imirhil.fr/)
You can learn (far) more by reading [this topic](https://crypto.stackexchange.com/questions/1190/why-is-elliptic-curve-cryptography-not-widely-used-compared-to-rsa) and its linked resources.
## Requirements
### Installing acme.sh
For automation and ease of use purposes, I'm using [acme.sh](https://github.com/acmesh-official/acme.sh)
```bash
# for using standalone mode, you might have to install as sudo
curl https://get.acme.sh | sh -s email=mail@domain.tld
```
### Changing default authority
By default, acme.sh uses ZeroSSL to sign certificates. We need to change this to Let's Encrypt because according to acme.sh, they're the only ones offering ECC capabilities.
```bash
acme.sh --set-default-ca --server letsencrypt
```
## Using your DNS api
If available, the easiest way to issue a certificate is to use the DNS api of your DNS provider. acme.sh supports [a lot](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) of DNS providers.
### Define an api key
Follow the [docs](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) for your DNS provider, usually:
```bash
export PROVIDER_Key="YOUR_API_KEY"
```
### Issue the cert
```bash
acme.sh --issue -d domain.tld --dns dns_provider --keylength ec-384
```
## Using standalone mode
If you don't have access to the DNS provider, we can use the standalone mode to spin up a temporary web server that will handle all the verifications.
Port `80` must be free.
```bash
acme.sh --issue --standalone -d domain.tld --keylength ec-384
```
## Examples
### Multi domains standalone
```bash
acme.sh --issue --standalone -d domain.tld -d www.domain.tld -d subdomain.domain.tld --keylength ec-384
```
### Wildcard domain DNS
```bash
acme.sh --issue -d domain.tld -d '*.domain.tld' --dns dns_provider --keylength ec-384
```
## Next steps
The ECC certificate alone will not grant you a high/perfect score.
### TLS version
Limit TLS version to 1.2 and 1.3 (or just 1.3 as there is only a [5% compatibility gap](https://caniuse.com/?search=tls%201.) with 1.2).
### HSTS
Use the [strict transport security](https://scotthelme.co.uk/hsts-the-missing-link-in-tls/) header.
```
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
### Cipher suite
Use recent and strong ciphers. This is where my knowledge hits its limit… I'm having a really hard time understanding what to use and why.
I've based my initial choices of ciphers on [this list](https://tls.imirhil.fr/ciphers), cross referencing it with (older?) [browser compatibility](https://tls.imirhil.fr/suite).
I then asked [Aeris](https://twitter.com/aeris22), the creator of [tls.imirhil.fr](https://tls.imirhil.fr), about it and he advised me to use the following:
```
ECDHE+AES:ECDHE+CHACHA20
```
In order to achieve a perfect score, we can be a little more restrictive with:
```
ECDHE+AES256:ECDHE+CHACHA20
```

View File

@ -0,0 +1,63 @@
---
title: Filter an array against another array
subtitle: Array vs Array.
lang: en
slug: "array-vs-array"
createdAt: "2022-06-08T14:24:06.000Z"
excerpt: My peak javascript
tags: ["nuxt.js"]
---
## Context
For a project, I had to come up with a way to filter an array of objects based on another array of strings.
In other words, I needed to include every object which `user` property was found in the second array.
Here is an example:
```javascript
const skills = [
{
name: "Be awesome",
user: "Jean",
},
{
name: "Great jokes",
user: "Jacques",
},
{
name: "Heavy sleeper",
user: "Jean",
},
{
name: "Heavy sleeper",
user: "Beatriz",
},
];
const selectedUsers = ["Jean", "Beatriz"];
```
I thought it would be pretty easy but I guess I'm not familiar enough with javascript 😬
## My solution
After a bit of thinking, I came up with the following statement:
> I need to filter the skills based on which users are selected. So I need to loop over the `selectedUsers` array and filter the skills according to their `user` value.
After a bit more trials and errors, this is the code I ended up using in a `computed property`:
```javascript
// index.vue
const filteredSkills = selectedUsers.map((user) => {
return skills.filter((skill) => {
return skill.user === user;
});
});
```
I used `map()` in order to loop on the second array and used its string value to only include the corresponding skills with `filter()`.
I'm pretty sure there is a better way to do it though…

View File

@ -0,0 +1,189 @@
---
title: Buttons hover
subtitle: Simple, but nice.
lang: en
slug: "buttons"
excerpt: Easy to grab and use hover effects.
tags: ["CSS"]
code: true
createdAt: "2020-10-08T09:00:00.000Z"
---
## General rules
All the buttons use these styles as a “reset”:
> Don't forget to prefix if necessary!
```css
.btn {
margin: 20px 0;
padding: 12px 26px;
position: relative;
display: inline-block;
overflow: hidden;
font-size: 20rem; /* 20px */
line-height: 1.6;
text-align: center;
text-decoration: none;
font-weight: bold;
cursor: pointer;
border: none;
border-radius: 2px;
-moz-appearance: none;
-webkit-appearance: none;
color: white;
background-color: hotpink;
transition: background-color 0.3s ease;
}
```
## Add an icon
<button role="none" class="btn btn-icon">
<span>Icon</span>
</button>
```css
.btn-icon {
background-color: hotpink;
}
.btn-icon::before {
content: url("~assets/svg/arrow-right-white.svg");
position: absolute;
width: 20px;
top: 50%;
right: 0;
transform: translate(40px, -50%);
transition: transform ease 0.3s;
}
.btn-icon:hover,
.btn-icon:focus {
background-color: darkorchid;
}
.btn-icon:hover::before,
.btn-icon:focus::before {
transform: translate(-10px, -50%);
}
.btn-icon > span {
display: inline-block;
width: 100%;
height: 100%;
transition: transform 0.3s ease;
}
.btn-icon:hover > span,
.btn-icon:focus > span {
transform: translateX(-10px);
}
```
## Double shutter down
<button role="none" class="btn btn-rideau">
<span>Shutter</span>
</button>
```css
.btn-rideau {
border: 2px solid #10113a;
color: #10113a;
background-color: transparent;
transition: color 0.3s ease;
}
.btn-rideau:hover {
color: white;
}
.btn-rideau::before {
background: hotpink;
}
.btn-rideau::after {
background: darkorchid;
}
.btn-rideau::before,
.btn-rideau::after {
content: "";
position: absolute;
height: 100%;
width: 100%;
bottom: 100%;
left: 0;
z-index: -1;
transition: transform 0.3s;
transition-timing-function: ease;
transition-timing-function: cubic-bezier(0.75, 0, 0.125, 1);
}
.btn-rideau:hover::before,
.btn-rideau:hover::after,
.btn-rideau:focus::before,
.btn-rideau:focus::after {
transform: translateY(100%);
}
.btn-rideau:hover::after,
.btn-rideau:focus::after {
transition-delay: 0.175s;
}
```
## Animated gradient
<button role="none" class="btn btn-gradient">
<span>Gradient</span>
</button>
```css
.btn-gradient {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
background-position: 0% 50%;
animation: GradientReverse 0.5s ease 1 normal forwards;
}
.btn-gradient:hover,
.btn-gradient:focus {
animation: Gradient 0.5s ease 1 normal forwards;
}
@keyframes Gradient {
0% {
background-position: 0% 50%;
}
100% {
background-position: 100% 100%;
}
}
@keyframes GradientReverse {
0% {
background-position: 100% 100%;
}
100% {
background-position: 0% 50%;
}
}
```
## Non destructive scale
<button role="none" class="btn btn-scale">
<span>Scale</span>
</button>
```css
.btn-scale {
overflow: visible;
color: #10113a;
background-color: transparent;
}
.btn-scale::after {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
border: 2px solid #10113a;
border-radius: 2px;
transition: transform 0.3s ease;
}
.btn-scale:hover::after,
.btn-scale:focus::after {
transform: scale(1.1);
}
```

View File

@ -0,0 +1,11 @@
---
title: Full width image
subtitle: Translation in progress, stay tuned ;)
lang: en
slug: "image-full"
createdAt: "2020-09-15T09:00:00.000Z"
updatedAt: "2022-06-08T14:24:06.000Z"
tags: ["CSS"]
---
[Go back to available snippets](/en/snippets/)

View File

@ -0,0 +1,139 @@
---
title: Static website and GraphQL queries with Nuxt.js
subtitle: Graphql client is king.
lang: en
slug: "nuxt-graphql-static"
createdAt: "2022-06-08T14:24:06.000Z"
updatedAt: "2022-09-08T13:43:33.000Z"
excerpt: When the most used gql module doesn't work…
tags: ["nuxt.js"]
---
## The problem
I encountered a nasty bug while using static generation with Nuxt and [nuxt apollo](https://github.com/nuxt-community/apollo-module "Dépôt github du module nuxt apollo (new tab)") client.
I found [the issue](https://github.com/nuxt-community/apollo-module/issues/339 "Github issue on nuxt/apollo repository (new tab)") already reported on github.
It seems the module doesn't handle static generation correctly with `nuxt generate`.
I could find request to my local API url after the static generation. Moreover, it also seemed like `<nuxt-link>` navigation was broken.
## The solution 🙌
Fortunately, there is another Nuxt module that handles GraphQL requests!
[Nuxt graphql request to the rescue!](https://github.com/gomah/nuxt-graphql-request)
### The conf
```javascript
// nuxt.config.js
buildModules: [
'nuxt-graphql-request',
],
graphql: {
clients: {
default: {
endpoint: 'http://API_URL/graphql',
options: {
headers: {
authorization: 'Bearer API_TOKEN',
},
},
},
},
},
```
### The request
The best approach so far is to use `asyncData` in pages and `fetch` in components. Using `fetch` in pages does not work well at all with `nuxt generate`.
I also install the `graphql-tag` package (only in `devDependencies`) to be able to import directly `.gql` files.
Query example:
```graphql
# homepage.gql
query {
homepage {
title
subtitle
hero {
id
alt
}
}
}
```
#### Inside a page
```javascript
// index.vue
<script>
import homepageQuery from '~/graphql/queries/singles/homepage'
export default {
async asyncData({ $graphql }) {
const data = await $graphql.default.request(homepageQuery)
return { data }
},
}
</script>
```
#### Inside a component
It is safer to wait until `fetch` has received a response before displaying anything. You can use `$fetchState` to be sure ([documentation](https://nuxtjs.org/docs/2.x/components-glossary/pages-fetch "Documentation on the fetch hook (new tab)")).
```javascript
// Header.vue
<template>
<header v-if="!$fetchState.pending">
</header>
</template>
<script>
import headerQuery from '~/graphql/queries/singles/header'
export default {
data() {
return {
data: {},
}
},
async fetch() {
try {
const data = await this.$graphql.default.request(headerQuery)
this.data = data
} catch (error) {
console.error(JSON.stringify(error, undefined, 2))
}
},
}
</script>
```
### Options
To pass options to the request, for example for a multilingual version with [nuxt/i18n](https://i18n.nuxtjs.org/) and/or a url parameter in a dynamic page:
```javascript
// _slug.vue
<script>
import articleQuery from '~/graphql/queries/articles'
export default {
async asyncData({ $graphql, app, params }) {
const locale = app.i18n.localeProperties.iso
const data = await $graphql.default.request(articleQuery, {
code: locale,
slug: params.slug,
})
return { data }
},
}
</script>
```

View File

@ -0,0 +1,74 @@
---
title: The best cookies
subtitle: Consentless biscuits.
lang: en
slug: "super-cookies"
createdAt: "2022-06-08T14:24:06.000Z"
excerpt: It's a real recipe, not a joke about annoying files.
tags: ["food"]
---
## Vegetalian version
### Ingredients
- 250 grams of flour
- 70 grams of muscovado/vergeoise sugar (unrefined)
- 20 grams of brown sugar
- 1 pinch of salt
- 1 tablespoon of baking powder
- 3/4 of aquafaba from 400g of chickpea (chickpea water)
- 125 grams of coconut oil
- 2 teaspoons of maple syrup
- 1 chocolate bar (black) cut in “&nbsp;chunks&nbsp;
### Method
- if necessary, melt the coconut oil over low heat
- meanwhile, mix all the dry ingredients
- add all liquid ingredients (oil, aquafaba, maple syrup)
- mix well
- add the chocolate
- mix well (fig. 1)
### Baking
Form balls with the obtained mixture on a baking sheet.
Bake for 9 minutes at 210 degrees (Celsius) - fan setting (fig. 2).
## Non vegetalian version
### Ingredients
- 250 grams of flour
- 70 grams of muscovado/vergeoise sugar (unrefined)
- 20 grams of brown sugar
- 1 pinch of salt
- 1 table spoon of baking powder
- 1 egg
- 125 grams of melted butter
- 2 teaspoons of honey
- 1 chocolate bar (black) cut in “&nbsp;chunks&nbsp;
### Method
- melt butter over low heat
- meanwhile, mix all dry ingredients
- add all liquid ingredients (melted butter, egg, honey)
- mix well
- add the chocolate
- mix well (fig. 1)
### Baking
Form balls with the obtained mixture on a baking sheet.
Bake for 9 minutes at 210 degrees (Celsius) - fan setting (fig. 2).
## Notes
Chocolate can be replaced by anything like nuts, raisins, legos... (don't eat legos be reasonable).
## Figures
![Fig.1 - All ingredients mixed together to form a brown paste.](https://assets.nardu.in/cookies-fig-1.jpg)
![Fig.2 - Baked cookies are very soft.](https://assets.nardu.in/cookies-fig-2.jpg)

View File

@ -0,0 +1,68 @@
---
title: Toulouse yourself
subtitle: Only the bestest
lang: en
slug: "toulouse-fun"
excerpt: Gonna have to trust me on this ¯\_(ツ)_/¯
tags: ["lifestyle"]
createdAt: "2022-06-22T15:34:45.000Z"
---
Here is my list of great places to go to when in Toulouse. There are of course a lot of other great places to go to, these are just my all time favourite.
**It's always a good idea to make a reservation!**
## Restaurants
### French cuisine
- [Solides](https://www.solides.fr/) — semi-gastronomic
- [Les impulsifs](https://les-impulsifs-toulouse.eatbu.com/?lang=en) — semi-gastronomic
- [Sixta](https://sixta-toulouse.fr/) — vege/vegan/gluten friendly
- [Attila](https://attila.site-solocal.com/) — great **fish restaurant,** just above the Victor Hugo market
- [Chez Emile](https://www.restaurant-emile.com/) - best [cassoulet](https://en.wikipedia.org/wiki/Cassoulet) in town (or so I'm told)
### Korean
- [Le ptit Louis](https://leptitlouis.fr/) — best of its kind, **only for lunch**
- [Kongbap](https://kong-bap.com/) — street-food like
### Japanese
- [Iori](https://www.iori.fr/) — best ramen
- [Japoyaki](https://www.qwant.com/maps/place/osm:node:2447719414@Japoyaki#map=16.50/43.6061725/1.4473402) — best sushi/sashimis
## Drinks and snacks
### Bars
- [Le Bièrographe](https://www.qwant.com/maps/place/osm:node:1532531236@Le_Bi%C3%A8rographe#map=16.50/43.5986892/1.4428327) — all time favourite, **check out the typical toulouse basement**
- [A Taula](https://www.facebook.com/ataulatolosa/) — all time favourite in summer, **amazing terrace,** also: great tapas
- [The Hopscotch Pub](https://www.qwant.com/maps/place/osm:node:5592180378@The_Hopscotch#map=18.14/43.6006796/1.4431385) — good beers, good whiskies, good cocktials, good food
### Cafés & tea rooms
- [Bapz](https://www.bapz.fr/contact) — Excellent pastries / hot beverages
- [Ô thé divin](https://fr-fr.facebook.com/%C3%94-Th%C3%A9-Divin-245018828864405/) — nice lunch options
- [Les Rêveries dHercule](https://www.lesreveriesdhercule.com/) — Pottery Café, relaxing activity (ceramics need to be baked, there'll be a delay to get them back)
- Bonus: the best bakeries in [Carmes](https://www.qwant.com/maps/place/osm:node:450165912@Boulangerie_des_Carmes#map=16.37/43.6022638/1.4439872) and [Saint-Aubin](https://www.qwant.com/maps/place/osm:node:456844404@La_P%C3%A9trie#map=18.84/43.6045088/1.4544694) — croissants, baguettes **traditions**, chocolatines and more
### Wine shops
- [l'envie du sud](https://www.qwant.com/maps/place/osm:node:3861692629@LEnvie#map=19.57/43.5978194/1.4443930) — amazing selection of wines and other spirits, excellent advice from the staff
- [enoteca](https://www.qwant.com/maps/place/osm:node:3751077562@Enoteca_31#map=16.50/43.6045636/1.4531952) — smaller selection but more beers and great advice from the staff as well
## Other delights
### Ice creams
- [Forno gusto](https://www.qwant.com/maps/place/osm:node:2462248749@Caf%C3%A9t%C3%A9ria_Gelateria#map=20.00/43.6027626/1.4421450) — only in the summer
- [Moustache](https://www.qwant.com/maps/place/osm:node:2165543146@Glaces_Moustache#map=16.50/43.6036985/1.4350540) — lots of flavours
### Cheese shop
- [Xavier](https://xavier.fr/) — one of the **best of the country**, two shops in Toulouse, including one at Victor Hugo market
- [Massembea](https://www.qwant.com/maps/place/osm:node:6164095797@Massembea#map=16.50/43.6043930/1.4539490) — a serious contender in Toulouse (weird hours)
### Delicatessen
- [Café Bacquié](http://cafe-bacquie.com/) — stock up on fine foods, coffees, teas and other great quality products

View File

@ -0,0 +1,111 @@
---
title: Certificates TLS robustes avec acme.sh
subtitle: 384-bit de https
lang: fr
slug: "acme-sh-tls-cert"
createdAt: "2022-06-08T14:24:06.000Z"
excerpt: La sécurité avec des courbes.
tags: ["sécurité"]
---
## Attention
Je ne suis pas du tout un expert en sécurité. J'aime juste les trucs nouveaux et stylés des internets.
C'est pourquoi j'ai modifié mes certificats TLS par défaut pour utiliser la cryptographie à courbe elliptique (ECC) au lieu de RSA. J'ai maintenant un joli 100/100 sur [tls.imirhil.fr](https://tls.imirhil.fr/)
Vous pouvez en apprendre (beaucoup) plus [ici](https://crypto.stackexchange.com/questions/1190/why-is-elliptic-curve-cryptography-not-widely-used-compared-to-rsa) et sur les liens cités (en anglais).
## Prérequis
### Installer acme.sh
Pour des raisons de simplicité et d'automatisation, j'utilise [acme.sh](https://github.com/acmesh-official/acme.sh)
```bash
# pour utiliser le mode standalone, il peut être nécessaire d'installer en sudo
curl https://get.acme.sh | sh -s email=mail@domain.tld
```
### Changer l'authorité par défaut
Par défaut, acme.sh utilise ZeroSSL pour signer les certificats. Il faut changer ce paramètre pour Let's Encrypt car, d'après acme.sh, ils sont les seuls à proposer des certificats ECC.
```bash
acme.sh --set-default-ca --server letsencrypt
```
## Utiliser l'api DNS
Si vous en avez la possibilité, la façon la plus simple de générer un certificat est via l'api de votre fournisseur DNS. acme.sh supporte [énormément](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) de fournisseurs DNS.
### Definir la clé api
Suivez la [documentation](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) pour votre fournisseur DNS, généralement:
```bash
export PROVIDER_Key="YOUR_API_KEY"
```
### Émettre le certificat
```bash
acme.sh --issue -d domain.tld --dns dns_provider --keylength ec-384
```
## Utiliser le mode standalone
Si vous n'avez pas accès aux réglages DNS, le mode standalone permet de lancer un serveur web temporaire qui s'occupe de toutes les vérifications.
Le port `80` doit être disponible.
```bash
acme.sh --issue --standalone -d domain.tld --keylength ec-384
```
## Exemples
### Multi domaines standalone
```bash
acme.sh --issue --standalone -d domain.tld -d www.domain.tld -d subdomain.domain.tld --keylength ec-384
```
### Wildcard domaine DNS
```bash
acme.sh --issue -d domain.tld -d '*.domain.tld' --dns dns_provider --keylength ec-384
```
## Étapes supplémentaires
Le certificat ECC seul ne suffira pas à obtenir un score élevé/parfait.
### Version TLS
Limiter la version TLS à 1.2 et 1.3 (voire uniquement 1.3 vu [la différence de compatibilité de 5%](https://caniuse.com/?search=tls%201.) avec 1.2).
### HSTS
Utiliser le header [strict transport security](https://scotthelme.co.uk/hsts-the-missing-link-in-tls/).
```
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
### Suite cryptographique
Utiliser une suite cryptographique récente et robuste. C'est ici que mes connaissances deviennent limitées… J'ai encore un peu de mal à comprendre quelles suites fonctionnent bien et pourquoi.
J'avais basé ma première suite sur [cette liste](https://tls.imirhil.fr/ciphers), en la comparant avec celle de la compatibilité (d'anciens&nbsp;?) [navigateurs](https://tls.imirhil.fr/suite).
J'ai ensuite demandé à [Aeris](https://twitter.com/aeris22), le créateur de [tls.imirhil.fr](https://tls.imirhil.fr), ses conseils sur cette suite. Il m'a recommandé d'utiliser&nbsp;:
```
ECDHE+AES:ECDHE+CHACHA20
```
Afin d'atteindre un score de 100/100, il est possible de restreindre un peu plus la suite comme ceci&nbsp;:
```
ECDHE+AES256:ECDHE+CHACHA20
```

View File

@ -0,0 +1,11 @@
---
title: Filtrer un array avec un autre array
subtitle: En cours de traduction.
lang: fr
slug: "array-vs-array"
createdAt: "2022-06-08T14:24:06.000Z"
excerpt: En cours de traduction.
tags: ["nuxt.js"]
---
[Voir les fragments disponibles](/fragments/)

View File

@ -0,0 +1,189 @@
---
title: "Effets de survol de boutons"
subtitle: "Simples mais efficaces."
lang: fr
slug: "buttons"
excerpt: Quelques effets de survol faciles à récupérer et utiliser.
tags: ["CSS"]
code: true
createdAt: "2020-10-08T09:00:00.000Z"
---
## Styles généraux
Tous les boutons présents utilisent ces styles comme base en guise de «&nbsp;reset&nbsp;»&nbsp;:
> N'oubliez pas de préfixer si besoin&nbsp;!
```css
.btn {
margin: 20px 0;
padding: 12px 26px;
position: relative;
display: inline-block;
overflow: hidden;
font-size: 20rem; /* 20px */
line-height: 1.6;
text-align: center;
text-decoration: none;
font-weight: bold;
cursor: pointer;
border: none;
border-radius: 2px;
-moz-appearance: none;
-webkit-appearance: none;
color: white;
background-color: hotpink;
transition: background-color 0.3s ease;
}
```
## Ajout d'icône
<button role="none" class="btn btn-icon">
<span>Icône</span>
</button>
```css
.btn-icon {
background-color: hotpink;
}
.btn-icon::before {
content: url("~assets/svg/arrow-right-white.svg");
position: absolute;
width: 20px;
top: 50%;
right: 0;
transform: translate(40px, -50%);
transition: transform ease 0.3s;
}
.btn-icon:hover,
.btn-icon:focus {
background-color: darkorchid;
}
.btn-icon:hover::before,
.btn-icon:focus::before {
transform: translate(-10px, -50%);
}
.btn-icon > span {
display: inline-block;
width: 100%;
height: 100%;
transition: transform 0.3s ease;
}
.btn-icon:hover > span,
.btn-icon:focus > span {
transform: translateX(-10px);
}
```
## Double volet
<button role="none" class="btn btn-rideau">
<span>Volet</span>
</button>
```css
.btn-rideau {
border: 2px solid #10113a;
color: #10113a;
background-color: transparent;
transition: color 0.3s ease;
}
.btn-rideau:hover {
color: white;
}
.btn-rideau::before {
background: hotpink;
}
.btn-rideau::after {
background: darkorchid;
}
.btn-rideau::before,
.btn-rideau::after {
content: "";
position: absolute;
height: 100%;
width: 100%;
bottom: 100%;
left: 0;
z-index: -1;
transition: transform 0.3s;
transition-timing-function: ease;
transition-timing-function: cubic-bezier(0.75, 0, 0.125, 1);
}
.btn-rideau:hover::before,
.btn-rideau:hover::after,
.btn-rideau:focus::before,
.btn-rideau:focus::after {
transform: translateY(100%);
}
.btn-rideau:hover::after,
.btn-rideau:focus::after {
transition-delay: 0.175s;
}
```
## Dégradé animé
<button role="none" class="btn btn-gradient">
<span>Dégradé</span>
</button>
```css
.btn-gradient {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
background-position: 0% 50%;
animation: GradientReverse 0.5s ease 1 normal forwards;
}
.btn-gradient:hover,
.btn-gradient:focus {
animation: Gradient 0.5s ease 1 normal forwards;
}
@keyframes Gradient {
0% {
background-position: 0% 50%;
}
100% {
background-position: 100% 100%;
}
}
@keyframes GradientReverse {
0% {
background-position: 100% 100%;
}
100% {
background-position: 0% 50%;
}
}
```
## Échelle non desctructrice
<button role="none" class="btn btn-scale">
<span>Échelle</span>
</button>
```css
.btn-scale {
overflow: visible;
color: #10113a;
background-color: transparent;
}
.btn-scale::after {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
border: 2px solid #10113a;
border-radius: 2px;
transition: transform 0.3s ease;
}
.btn-scale:hover::after,
.btn-scale:focus::after {
transform: scale(1.1);
}
```

View File

@ -0,0 +1,122 @@
---
title: Image pleine largeur
subtitle: Casser le conteneur.
lang: fr
slug: "image-full"
createdAt: "2020-09-15T09:00:00.000Z"
updatedAt: "2022-06-08T14:24:06.000Z"
excerpt: Faire déborder une image de son conteneur sans tout casser.
tags: ["CSS"]
---
## Image inline
On est parfois obligé d'utiliser des images dans des balises `img` plutôt que dans un `background` en css. Comment faire alors pour que l'image sorte de son conteneur pour en faire une bannière&nbsp;? Exemple pratique à partir de ce même site.
[![](https://assets.nardu.in/image_bleed_container_9e3939b3ae.jpeg "Cliquer pour agrandir")](https://assets.nardu.in/image_bleed_container_9e3939b3ae.jpeg)
### Contexte
Considérons le html suivant&nbsp;:
```html
<section class="container">
<div class="hero">
<img class="hero__image" src="hero.img" />
</div>
</section>
```
Et le style suivant&nbsp;:
```css
.container {
padding: 0 14px;
margin-left: auto;
margin-right: auto;
max-width: 1040px;
}
img {
max-width: 100%;
height: auto;
}
```
[![](https://assets.nardu.in/image_bleed_original_d49f0d11bf.jpeg "Cliquer pour agrandir")](https://assets.nardu.in/image_bleed_original_d49f0d11bf.jpeg)
### Déborder du conteneur
Afin de faire prendre à l'image toute la largeur, on agit sur son conteneur&nbsp;:
```css
.hero {
margin-left: calc(50% - 50vw);
position: relative;
width: 100vw;
}
```
[![](https://assets.nardu.in/image_bleed_full_2a902f9539.jpeg "Cliquer pour agrandir")](https://assets.nardu.in/image_bleed_full_2a902f9539.jpeg)
### Faire une bannière
On peut alors réduire la hauteur du conteneur pour obtenir une bannière plutôt que l'image entière et faire correspondre la hauteur de l'image à la hauteur du conteneur&nbsp;:
```css
.hero {
height: 200px;
}
.hero__image {
height: 100%;
}
```
[![](https://assets.nardu.in/image_bleed_height_81b4ce969a.jpeg "Cliquer pour agrandir")](https://assets.nardu.in/image_bleed_height_81b4ce969a.jpeg)
Il faut ensuite forcer l'image à prendre toute la largeur du conteneur&nbsp;:
```css
.hero__image {
width: 100%;
}
```
[![](https://assets.nardu.in/image_bleed_deformed_479046d2cb.jpeg "Cliquer pour agrandir")](https://assets.nardu.in/image_bleed_deformed_479046d2cb.jpeg)
Pas top…
### J'ai cassé l'image…
**ENFIN** le code magique pour redonner son ratio à l'image sans la déformer&nbsp;:
```css
.hero__image {
object-fit: cover;
object-position: center; /* à positionner comme on veut */
}
```
[![](https://assets.nardu.in/image_bleed_6c164e82b3.jpeg "Cliquer pour agrandir")](https://assets.nardu.in/image_bleed_6c164e82b3.jpeg)
Cette technique s'apparente à l'utilisation d'une image de background mais en dur 😁
## TL;DR
Le code complet&nbsp;:
```css
.hero {
margin-left: calc(50% - 50vw);
position: relative;
width: 100vw;
height: 200px;
}
.hero__image {
width: 100%;
height: 100%;
-o-object-fit: cover;
object-fit: cover;
-o-object-position: center;
object-position: center;
}
```

View File

@ -0,0 +1,139 @@
---
title: Site statique et requêtes GraphQL avec Nuxt.js
subtitle: Le client graphql est roi.
lang: fr
slug: "nuxt-graphql-static"
createdAt: "2022-06-08T14:24:06.000Z"
updatedAt: "2022-09-08T13:43:33.000Z"
excerpt: Quand le module gql le plus utilisé ne fonctionne pas…
tags: ["nuxt.js"]
---
## Le problème
Je me suis heurté à un villain bug en utilisant Nuxt en mode génération statique complète et le client [nuxt apollo](https://github.com/nuxt-community/apollo-module "Dépôt github du module nuxt apollo (nouvel onglet)").
Après quelques recherches, voici [le bug constaté par quelqu'un d'autre](https://github.com/nuxt-community/apollo-module/issues/339 "Bug visible sur le dépôt github du module nuxt/apollo (nouvel onglet)") sur github.
Il semblerait que le module en l'état ne gère pas correctement la génération statique avec `nuxt generate`.
Je trouvais toujours des appels à mon API locale après la génération statique et la navigation depuis les `<nuxt-link>` ne fonctionnait pas.
## La solution 🙌
Heureusement, il existe un autre module Nuxt pour gérer les requêtes GraphQL&nbsp;!
[Nuxt graphql request à la rescousse&nbsp;!](https://github.com/gomah/nuxt-graphql-request "Dépôt github du module (nouvel onglet)")
### La conf
```javascript
// nuxt.config.js
buildModules: [
'nuxt-graphql-request',
],
graphql: {
clients: {
default: {
endpoint: 'http://API_URL/graphql',
options: {
headers: {
authorization: 'Bearer API_TOKEN',
},
},
},
},
},
```
### La requête
La meilleure méthode à ce jour est d'utiliser `asyncData` dans les pages et `fetch` dans les composants. Utiliser `fetch` dans les pages ne fonctionne pas bien du tout avec `nuxt generate`.
J'installe également le paquet `graphql-tag` (uniquement en `devDependencies`) afin de pouvoir importer directement des fichiers `.gql`
Exemple de fichier&nbsp;:
```graphql
# homepage.gql
query {
homepage {
title
subtitle
hero {
id
alt
}
}
}
```
#### Dans une page
```javascript
// index.vue
<script>
import homepageQuery from '~/graphql/queries/singles/homepage'
export default {
async asyncData({ $graphql }) {
const data = await $graphql.default.request(homepageQuery)
return { data }
},
}
</script>
```
#### Dans un composant
Il est plus prudent d'attendre que `fetch` ait reçu une réponse avant d'afficher quoi que ce soit. On peut utiliser `$fetchState` afin d'être tranquille ([documentation](https://fr.nuxtjs.org/docs/2.x/components-glossary/pages-fetch "Documentation sur la méthode fetch (nouvel onglet)")).
```javascript
// Header.vue
<template>
<header v-if="!$fetchState.pending">
</header>
</template>
<script>
import headerQuery from '~/graphql/queries/singles/header'
export default {
data() {
return {
data: {},
}
},
async fetch() {
try {
const data = await this.$graphql.default.request(headerQuery)
this.data = data
} catch (error) {
console.error(JSON.stringify(error, undefined, 2))
}
},
}
</script>
```
### Les options
Pour passer des options à la requête, par exemple pour une version multilingue avec [nuxt/i18n](https://i18n.nuxtjs.org/ "Documentation de nuxt i18n (nouvel onglet)") et/ou un paramètre d'url dans le cadre d'une page dynamique&nbsp;:
```javascript
// _slug.vue
<script>
import articleQuery from '~/graphql/queries/articles'
export default {
async asyncData({ $graphql, app, params }) {
const locale = app.i18n.localeProperties.iso
const data = await $graphql.default.request(articleQuery, {
code: locale,
slug: params.slug,
})
return { data }
},
}
</script>
```

View File

@ -0,0 +1,74 @@
---
title: Les meilleurs cookies
subtitle: Des gâteaux sans consentement.
lang: fr
slug: "super-cookies"
createdAt: "2022-06-08T14:24:06.000Z"
excerpt: C'est vraiment une recette hein, pas une blague sur les fichiers temporaires.
tags: ["cuisine"]
---
## Version végétalienne
### Ingrédients
- 250 grammes de farine
- 70 grammes de sucre muscovado/vergeoise (non raffiné)
- 20 grammes de cassonade
- 1 pincée de sel
- 1 cuillère à soupe de levure
- 3/4 de l'aquafaba de 400g de pois chiche (l'eau des pois chiche)
- 125 grammes d'huile de noix de coco
- 2 cuillères à café de sirop d'érable
- 1 plaquette de chocolat (noir) coupée en “&nbsp;chunks&nbsp;
### Méthode
- faire fondre si besoin l'huile de noix de coco à feu doux
- mélanger tous les ingrédients secs pendant ce temps
- ajouter tous les ingrédients liquides (huile, aquafaba, sirop d'érable)
- bien mélanger
- ajouter le chocolat
- bien mélanger (fig. 1)
### Cuisson
Réaliser des boules avec la mixture obtenue sur une plaque de cuisson.
Cuire 9 minutes à 210 degrés (celsius) - chaleur tournante (fig. 2).
## Version non végétalienne
### Ingrédients
- 250 grammes de farine
- 70 grammes de sucre muscovado/vergeoise (non raffiné)
- 20 grammes de cassonade
- 1 pincée de sel
- 1 cuillère à soupe de levure
- 1 œuf
- 125 grammes de beurre fondu
- 2 cuillères à café de miel
- 1 plaquette de chocolat (noir/blanc/les deux) coupée en “&nbsp;chunks&nbsp;
### Méthode
- faire fondre le beurre à feu doux
- mélanger tous les ingrédients secs pendant ce temps
- ajouter tous les ingrédients liquides (beurre fondu, œuf, miel)
- bien mélanger
- ajouter le chocolat
- bien mélanger (fig. 1)
### Cuisson
Réaliser des boules avec la mixture obtenue sur une plaque de cuisson.
Cuire 9 minutes à 210 degrés (celsius) - chaleur tournante (fig. 2).
## Notes
Le chocolat peut-être remplacé par n'importe quoi comme des noix, des raisins secs, des légos… (mangez pas des légos soyez sérieux).
## Figures
![Fig.1 - Tous les ingrédients mélangés forment une pâte marron.](https://assets.nardu.in/cookies-fig-1.jpg)
![Fig.2 - Les cookies cuits sont très moelleux.](https://assets.nardu.in/cookies-fig-2.jpg)

View File

@ -0,0 +1,11 @@
---
title: Toulouse yourself
subtitle: En cours de traduction.
lang: fr
slug: "toulouse-fun"
createdAt: "2022-06-22T15:34:45.000Z"
excerpt: En cours de traduction.
tags: ["lifestyle"]
---
[Voir les fragments disponibles](/fragments/)

View File

@ -43,20 +43,204 @@ declare module 'astro:content' {
const entryMap: { const entryMap: {
"articles": { "articles": {
"en/sci-hub-blocage.md": { "en/2022.md": {
id: "en/sci-hub-blocage.md", id: "en/2022.md",
slug: "en/2022",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"en/after-effects-expressions.mdx": {
id: "en/after-effects-expressions.mdx",
slug: "en/after-effects-expressions",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"en/faq.md": {
id: "en/faq.md",
slug: "en/faq",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"en/gratuiste.md": {
id: "en/gratuiste.md",
slug: "en/gratuiste",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"en/sci-hub-blocage.mdx": {
id: "en/sci-hub-blocage.mdx",
slug: "en/sci-hub-blocage", slug: "en/sci-hub-blocage",
body: string, body: string,
collection: "articles", collection: "articles",
data: InferEntrySchema<"articles"> data: InferEntrySchema<"articles">
}, },
"fr/sci-hub-blocage.md": { "en/the-day-I-jamd.mdx": {
id: "fr/sci-hub-blocage.md", id: "en/the-day-I-jamd.mdx",
slug: "en/the-day-I-jamd",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"en/video-compression.md": {
id: "en/video-compression.md",
slug: "en/video-compression",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"fr/2022.md": {
id: "fr/2022.md",
slug: "fr/2022",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"fr/after-effects-expressions.md": {
id: "fr/after-effects-expressions.md",
slug: "fr/after-effects-expressions",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"fr/faq.md": {
id: "fr/faq.md",
slug: "fr/faq",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"fr/gratuiste.md": {
id: "fr/gratuiste.md",
slug: "fr/gratuiste",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"fr/sci-hub-blocage.mdx": {
id: "fr/sci-hub-blocage.mdx",
slug: "fr/sci-hub-blocage", slug: "fr/sci-hub-blocage",
body: string, body: string,
collection: "articles", collection: "articles",
data: InferEntrySchema<"articles"> data: InferEntrySchema<"articles">
}, },
"fr/the-day-I-jamd.mdx": {
id: "fr/the-day-I-jamd.mdx",
slug: "fr/the-day-I-jamd",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
"fr/video-compression.md": {
id: "fr/video-compression.md",
slug: "fr/video-compression",
body: string,
collection: "articles",
data: InferEntrySchema<"articles">
},
},
"fragments": {
"en/acme-sh-tls-cert.md": {
id: "en/acme-sh-tls-cert.md",
slug: "en/acme-sh-tls-cert",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"en/array-vs-array.md": {
id: "en/array-vs-array.md",
slug: "en/array-vs-array",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"en/buttons.md": {
id: "en/buttons.md",
slug: "en/buttons",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"en/image-full.md": {
id: "en/image-full.md",
slug: "en/image-full",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"en/nuxt-graphql-static.md": {
id: "en/nuxt-graphql-static.md",
slug: "en/nuxt-graphql-static",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"en/super-cookies.md": {
id: "en/super-cookies.md",
slug: "en/super-cookies",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"en/toulouse-fun.md": {
id: "en/toulouse-fun.md",
slug: "en/toulouse-fun",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"fr/acme-sh-tls-cert.md": {
id: "fr/acme-sh-tls-cert.md",
slug: "fr/acme-sh-tls-cert",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"fr/array-vs-array.md": {
id: "fr/array-vs-array.md",
slug: "fr/array-vs-array",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"fr/buttons.md": {
id: "fr/buttons.md",
slug: "fr/buttons",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"fr/image-full.md": {
id: "fr/image-full.md",
slug: "fr/image-full",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"fr/nuxt-graphql-static.md": {
id: "fr/nuxt-graphql-static.md",
slug: "fr/nuxt-graphql-static",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"fr/super-cookies.md": {
id: "fr/super-cookies.md",
slug: "fr/super-cookies",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
"fr/toulouse-fun.md": {
id: "fr/toulouse-fun.md",
slug: "fr/toulouse-fun",
body: string,
collection: "fragments",
data: InferEntrySchema<"fragments">
},
}, },
}; };

View File

@ -2,6 +2,8 @@
type: section type: section
id: offre id: offre
lang: en lang: en
slug:
createdAt:
image: /assets/images/home/offres.svg image: /assets/images/home/offres.svg
order: 1 order: 1
quickTitle: My offers quickTitle: My offers

View File

@ -2,6 +2,8 @@
type: section type: section
id: methodology id: methodology
lang: en lang: en
slug:
createdAt:
image: /assets/images/home/methodo.svg image: /assets/images/home/methodo.svg
order: 2 order: 2
quickTitle: My methodology quickTitle: My methodology

View File

@ -2,6 +2,8 @@
type: section type: section
id: about id: about
lang: en lang: en
slug:
createdAt:
image: /assets/images/home/about.svg image: /assets/images/home/about.svg
order: 3 order: 3
quickTitle: Me quickTitle: Me

View File

@ -2,6 +2,8 @@
type: section type: section
id: offre id: offre
lang: fr lang: fr
slug:
createdAt:
image: /assets/images/home/offres.svg image: /assets/images/home/offres.svg
order: 1 order: 1
quickTitle: Mes offres en freelance quickTitle: Mes offres en freelance

View File

@ -2,6 +2,8 @@
type: section type: section
id: methodology id: methodology
lang: fr lang: fr
slug:
createdAt:
image: /assets/images/home/methodo.svg image: /assets/images/home/methodo.svg
order: 2 order: 2
quickTitle: Ma méthodologie quickTitle: Ma méthodologie

View File

@ -2,6 +2,8 @@
type: section type: section
id: about id: about
lang: fr lang: fr
slug:
createdAt:
image: /assets/images/home/about.svg image: /assets/images/home/about.svg
order: 3 order: 3
quickTitle: Moi quickTitle: Moi

View File

@ -2,6 +2,8 @@
title: "Access blocked Sci-hub" title: "Access blocked Sci-hub"
subtitle: "The science of sharing." subtitle: "The science of sharing."
lang: en lang: en
slug:
createdAt:
excerpt: "In March 2019, the Paris Regional Court ruled in favour of the publishers of scientific articles Elsevier and Springer Nature by ordering internet service providers to block access to these two websites. Here is how to access them if they are blocked in your country anyway." excerpt: "In March 2019, the Paris Regional Court ruled in favour of the publishers of scientific articles Elsevier and Springer Nature by ordering internet service providers to block access to these two websites. Here is how to access them if they are blocked in your country anyway."
tags: ["Internet", "Science"] tags: ["Internet", "Science"]
pubDate: "2019-10-18T07:47:36.000Z" pubDate: "2019-10-18T07:47:36.000Z"

View File

@ -2,6 +2,8 @@
title: "Sci-hub bloqué, comment contourner" title: "Sci-hub bloqué, comment contourner"
subtitle: "La science du partage." subtitle: "La science du partage."
lang: fr lang: fr
slug:
createdAt:
excerpt: "Le tribunal de grande instance de Paris a ordonné aux fournisseurs daccès à internet de bloquer laccès à sci-hub. Voici comment contourner les blocages mis en place." excerpt: "Le tribunal de grande instance de Paris a ordonné aux fournisseurs daccès à internet de bloquer laccès à sci-hub. Voici comment contourner les blocages mis en place."
tags: ["Internet", "Science"] tags: ["Internet", "Science"]
pubDate: "2019-03-31T07:47:36.000Z" pubDate: "2019-03-31T07:47:36.000Z"

View File

@ -30,11 +30,11 @@
"credit": "Image by" "credit": "Image by"
}, },
"fragments": { "fragments": {
"titre": "Snippets", "titre": "snippets",
"tagline": "School with Nicool." "tagline": "School with Nicool."
}, },
"projet": { "projet": {
"titre": "Projects", "titre": "projects",
"tagline": "Freelance work", "tagline": "Freelance work",
"cta": "Visit website", "cta": "Visit website",
"lienTitle": "Website of", "lienTitle": "Website of",

View File

@ -5,6 +5,7 @@ astroI18n.init(Astro);
import "../styles/style.css"; import "../styles/style.css";
import Header from "../components/Header.astro"; import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
const { pageTitle, titleColor } = Astro.props; const { pageTitle, titleColor } = Astro.props;
--- ---
@ -14,7 +15,6 @@ const { pageTitle, titleColor } = Astro.props;
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>{pageTitle} - Nicolas Arduin</title> <title>{pageTitle} - Nicolas Arduin</title>
</head> </head>
<body> <body>
@ -24,6 +24,7 @@ const { pageTitle, titleColor } = Astro.props;
<slot /> <slot />
</main> </main>
</div> </div>
<Footer />
</body> </body>
<style define:vars={{ titleColor }}> <style define:vars={{ titleColor }}>
h1 { h1 {

View File

@ -1,12 +1,9 @@
--- ---
import { l, t, astroI18n } from "astro-i18n"; import { l, t, astroI18n } from "astro-i18n";
import { log } from "astro/dist/core/logger/core";
import { literal } from "astro/zod";
astroI18n.init(Astro); astroI18n.init(Astro);
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import EditorialContent from "../../components/EditorialContent.astro"; import EditorialContent from "../../components/EditorialContent.astro";
import TOC from "../../components/TOC.astro";
import BaseLayout from "../../layouts/BaseLayout.astro"; import BaseLayout from "../../layouts/BaseLayout.astro";
@ -15,7 +12,6 @@ export async function getStaticPaths() {
return data.lang === astroI18n.langCode; return data.lang === astroI18n.langCode;
}); });
return articles.map((article) => ({ return articles.map((article) => ({
// temp 'split' workaround for i18n
params: { slug: article.data.slug }, params: { slug: article.data.slug },
props: { article }, props: { article },
})); }));

View File

@ -6,27 +6,39 @@ astroI18n.init(Astro);
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import BaseLayout from "../../layouts/BaseLayout.astro"; import BaseLayout from "../../layouts/BaseLayout.astro";
import ContentPost from "../../components/ContentPost.astro"; import ListCards from "../../components/ListCards.astro";
const localizedPost = await getCollection("articles", ({ data }) => { const localizedPost = await getCollection("articles", ({ data }) => {
return data.lang === astroI18n.langCode; return data.lang === astroI18n.langCode;
}); });
// sort articles by descending publication date
const sortedArticles = localizedPost.sort(
(a, b) => b.data.createdAt - a.data.createdAt
);
const pageTitle = t("index.articles.pageName"); const pageTitle = t("index.articles.pageName");
--- ---
<BaseLayout pageTitle={pageTitle}> <BaseLayout pageTitle={pageTitle}>
<h2>{t("index.articles.pageName")}</h2> <section class="region flow">
<p>{t("index.articles.subtitle")}</p> <h1>{t("index.articles.pageName")}</h1>
<ul> <h2>{t("index.articles.subtitle")}</h2>
{ <ListCards list={sortedArticles} routeName={t("article.titre")} />
localizedPost.map((post) => ( </section>
<li>
<a href={l("/articles/[slug]", { slug: post.data.slug })}>
{post.data.title}
</a>
</li>
))
}
</ul>
</BaseLayout> </BaseLayout>
<style>
.flow h2 + :global(*) {
margin-block-start: var(--space-m-l);
}
h1 {
text-transform: capitalize;
}
h1,
h2 {
padding-inline-start: var(--space-xs-s);
}
h2 {
color: var(--color-brique);
}
</style>

View File

@ -0,0 +1,9 @@
---
import Page from "../../fragments/[slug].astro"
export { getStaticPaths } from "../../fragments/[slug].astro"
const { props } = Astro
---
<Page {...props} />

View File

@ -0,0 +1,7 @@
---
import Page from "../../fragments/index.astro"
const { props } = Astro
---
<Page {...props} />

View File

@ -0,0 +1,25 @@
---
import { l, t, astroI18n } from "astro-i18n";
astroI18n.init(Astro);
import { getCollection } from "astro:content";
import EditorialContent from "../../components/EditorialContent.astro";
import BaseLayout from "../../layouts/BaseLayout.astro";
export async function getStaticPaths() {
const snippets = await getCollection("fragments", ({ data }) => {
return data.lang === astroI18n.langCode;
});
return snippets.map((snippet) => ({
params: { slug: snippet.data.slug },
props: { snippet },
}));
}
const { snippet } = Astro.props;
---
<BaseLayout pageTitle={snippet.data.title}>
<EditorialContent content={snippet} />
</BaseLayout>

View File

@ -0,0 +1,4 @@
{
"pageName": "Snippets",
"subtitle": "School with Nicool."
}

View File

@ -0,0 +1,4 @@
{
"pageName": "Fragments",
"subtitle": "Les tutos de Nico mdr."
}

View File

@ -0,0 +1,44 @@
---
import { l, t, astroI18n } from "astro-i18n";
astroI18n.init(Astro);
// New astro content collections
import { getCollection } from "astro:content";
import BaseLayout from "../../layouts/BaseLayout.astro";
import ListCards from "../../components/ListCards.astro";
const localizedPost = await getCollection("fragments", ({ data }) => {
return data.lang === astroI18n.langCode;
});
// sort snippets by descending publication date
const sortedPosts = localizedPost.sort(
(a, b) => b.data.createdAt - a.data.createdAt
);
const pageTitle = t("index.fragments.pageName");
---
<BaseLayout pageTitle={pageTitle}>
<section class="region flow">
<h1>{t("index.fragments.pageName")}</h1>
<h2>{t("index.fragments.subtitle")}</h2>
<ListCards list={sortedPosts} routeName={t("fragments.titre")} />
</section>
</BaseLayout>
<style>
.flow h2 + :global(*) {
margin-block-start: var(--space-m-l);
}
h1 {
text-transform: capitalize;
}
h1,
h2 {
padding-inline-start: var(--space-xs-s);
}
h2 {
color: var(--color-brique);
}
</style>

View File

@ -7,5 +7,9 @@
"writing": "Oh and I write <a class='u-nice-links' href='/en/articles/'>articles!</a> Articles about design and the web in general.", "writing": "Oh and I write <a class='u-nice-links' href='/en/articles/'>articles!</a> Articles about design and the web in general.",
"latestProjects": "Latest projects", "latestProjects": "Latest projects",
"latestArticles": "Latest articles", "latestArticles": "Latest articles",
"scihub": "schi-hub-unlock" "allProjects": "All projects",
"allArticles": "All articles",
"latestSnippets": "Latest snippets",
"allSnippets": "All snippets",
"toc": "table of content"
} }

View File

@ -7,5 +7,9 @@
"writing": "Ah et jécris <a href='/articles/'>des articles</a> aussi&nbsp;! Des articles sur le graphisme et linformatique.", "writing": "Ah et jécris <a href='/articles/'>des articles</a> aussi&nbsp;! Des articles sur le graphisme et linformatique.",
"latestProjects": "Derniers projets", "latestProjects": "Derniers projets",
"latestArticles": "Derniers articles", "latestArticles": "Derniers articles",
"scihub": "schi-hub-deblocage" "allProjects": "Tous les projets",
"allArticles": "Tous les articles",
"latestSnippets": "Derniers fragments",
"allSnippets": "Tous les fragments",
"toc": "table des matières"
} }

View File

@ -17,23 +17,24 @@ const localizedSections = allSections.filter((section) => {
return section.frontmatter.lang === astroI18n.langCode; return section.frontmatter.lang === astroI18n.langCode;
}); });
// get all articles // New astro content collections
const allArticles = await Astro.glob("../data/articles/**/*.{md,mdx}"); import { getCollection } from "astro:content";
// only keep the right locale version // Only return posts with correct lang in the frontmatter
const localizedArticles = allArticles.filter((article) => { const localizedArticles = await getCollection("articles", ({ data }) => {
return article.frontmatter.lang === astroI18n.langCode; return data.lang === astroI18n.langCode;
}); });
// sort articles by descending publication date // sort articles by descending publication date
const sortedArticles = localizedArticles.sort( const sortedArticles = localizedArticles.sort(
(a, b) => b.frontmatter.pubDate - a.frontmatter.pubDate (a, b) => b.data.createdAt - a.data.createdAt
); );
// Only return snippets with correct lang in the frontmatter
// New astro content collections const localizedSnippets = await getCollection("fragments", ({ data }) => {
import { getCollection } from "astro:content";
// Only return posts with `draft: true` in the frontmatter
const newLocalizedArticles = await getCollection("articles", ({ data }) => {
return data.lang === astroI18n.langCode; return data.lang === astroI18n.langCode;
}); });
// sort articles by descending publication date
const sortedSnippets = localizedSnippets.sort(
(a, b) => b.data.createdAt - a.data.createdAt
);
--- ---
<BaseLayout pageTitle={pageTitle}> <BaseLayout pageTitle={pageTitle}>
@ -74,10 +75,16 @@ const newLocalizedArticles = await getCollection("articles", ({ data }) => {
)) ))
} }
<section class="latest"> <section class="region latest">
<div class="latest__work"> <div class="flow latest__articles">
<h2>{t("index.latestArticles")}</h2> <h2>{t("index.latestArticles")}</h2>
<ListCards list={newLocalizedArticles} routeName={t("article.titre")} /> <ListCards list={sortedArticles} routeName={t("article.titre")} />
<p><a href={l("/articles")}>{t("index.allArticles")}</a></p>
</div>
<div class="flow latest__snippets">
<h2>{t("index.latestSnippets")}</h2>
<ListCards list={sortedSnippets} routeName={t("fragments.titre")} />
<p><a href={l("/fragments")}>{t("index.allSnippets")}</a></p>
</div> </div>
</section> </section>
</BaseLayout> </BaseLayout>
@ -92,9 +99,9 @@ const newLocalizedArticles = await getCollection("articles", ({ data }) => {
position: absolute; position: absolute;
top: -30%; top: -30%;
left: -10%; left: -10%;
max-width: 100vw; max-inline-size: 100vw;
width: 320px; inline-size: 320px;
height: 320px; block-size: 320px;
z-index: -1; z-index: -1;
border-radius: 50%; border-radius: 50%;
background-color: var(--color-light-blue); background-color: var(--color-light-blue);
@ -121,7 +128,7 @@ const newLocalizedArticles = await getCollection("articles", ({ data }) => {
color: var(--color-dark-blue); color: var(--color-dark-blue);
} }
.quick-access { .quick-access {
margin-top: var(--space-xl-2xl); margin-block-start: var(--space-xl-2xl);
container-type: inline-size; container-type: inline-size;
container-name: intro; container-name: intro;
} }
@ -171,7 +178,7 @@ const newLocalizedArticles = await getCollection("articles", ({ data }) => {
.section__content { .section__content {
order: 2; order: 2;
flex-basis: 0; flex-basis: 0;
min-width: 40ch; min-inline-size: 40ch;
} }
} }
</style> </style>

View File

@ -1,6 +1,5 @@
--- ---
import BaseLayout from "../../layouts/BaseLayout.astro"; import BaseLayout from "../../layouts/BaseLayout.astro";
import ContentPost from "../../components/ContentPost.astro";
export async function getStaticPaths({}) { export async function getStaticPaths({}) {
const allPosts = await Astro.glob("../../data/**/*.mdx"); const allPosts = await Astro.glob("../../data/**/*.mdx");
@ -26,12 +25,6 @@ const { posts } = Astro.props;
<BaseLayout pageTitle={tag}> <BaseLayout pageTitle={tag}>
<p>Posts tagged with {tag}</p> <p>Posts tagged with {tag}</p>
<ul> <ul>
{ {posts.map((post) => <li>{post.frontmatter.title}</li>)}
posts.map((post) => (
<li>
<ContentPost url={post.url} title={post.frontmatter.title} />
</li>
))
}
</ul> </ul>
</BaseLayout> </BaseLayout>

View File

@ -24,7 +24,7 @@ EXCEPTIONS
.sidebar > :last-child { .sidebar > :last-child {
flex-basis: 0; flex-basis: 0;
flex-grow: 999; flex-grow: 999;
min-width: var(--sidebar-content-min-width, 50%); min-inline-size: var(--sidebar-content-min-width, 50%);
} }
/* /*
@ -50,5 +50,5 @@ A flipped version where the sidebar is on the right
.sidebar-reverse > :first-child { .sidebar-reverse > :first-child {
flex-basis: 0; flex-basis: 0;
flex-grow: 999; flex-grow: 999;
min-width: var(--sidebar-content-min-width, 50%); min-inline-size: var(--sidebar-content-min-width, 50%);
} }

View File

@ -27,7 +27,7 @@ main {
font-family: var(--font-secondary); font-family: var(--font-secondary);
} }
:where(h1) { :where(h1) {
max-width: 20ch; max-inline-size: 20ch;
font-size: var(--size-6); font-size: var(--size-6);
font-weight: bold; font-weight: bold;
color: var(--color-dark-blue); color: var(--color-dark-blue);
@ -41,7 +41,7 @@ h2,
h3, h3,
.h3 { .h3 {
max-width: initial; max-inline-size: initial;
font-size: var(--size-2); font-size: var(--size-2);
font-weight: bold; font-weight: bold;
letter-spacing: 0.05rem; letter-spacing: 0.05rem;
@ -96,11 +96,11 @@ ol:not([role="list"]) > li + li {
.sr-only { .sr-only {
clip: rect(0 0 0 0); clip: rect(0 0 0 0);
clip-path: inset(50%); clip-path: inset(50%);
height: 1px; block-size: 1px;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
white-space: nowrap; white-space: nowrap;
width: 1px; inline-size: 1px;
} }
.clean-button { .clean-button {
@ -181,8 +181,8 @@ button[disabled] {
position: absolute; position: absolute;
left: 0; left: 0;
bottom: -2px; bottom: -2px;
width: 100%; inline-size: 100%;
height: 2px; block-size: 2px;
text-decoration: none; text-decoration: none;
transform: scaleX(0); transform: scaleX(0);
opacity: 1; opacity: 1;
@ -219,3 +219,35 @@ blockquote cite {
blockquote code { blockquote code {
font-weight: bold; font-weight: bold;
} }
/* code highlight */
.content :not(pre) > code {
padding: var(--space-3xs) var(--space-2xs);
position: relative;
display: inline-block;
font-size: var(--size--1);
font-family: var(--font-code);
}
.content :not(pre) > code:before {
content: "";
position: absolute;
inset: 0px -1px;
background: var(--color-light-grey);
border-radius: 4px;
z-index: -1;
}
.content code[class*="language-"],
.content pre[class*="language-"] {
white-space: pre-wrap;
}
/* .astro-code .filename {
margin: 0.5rem 0;
display: block;
font-family: var(--font-code);
font-size: 1.4rem;
text-align: right;
} */

View File

@ -1,16 +1,16 @@
.waves { .waves {
background: transparent; background: transparent;
height: 4px; block-size: 4px;
position: relative; position: relative;
} }
.waves::before { .waves::before {
content: ''; content: "";
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
right: 0; right: 0;
background-repeat: repeat; background-repeat: repeat;
height: 10px; block-size: 10px;
background-size: 20px 20px; background-size: 20px 20px;
background-image: radial-gradient( background-image: radial-gradient(
circle at 10px -5px, circle at 10px -5px,
@ -19,13 +19,13 @@
); );
} }
.waves::after { .waves::after {
content: ''; content: "";
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
right: 0; right: 0;
background-repeat: repeat; background-repeat: repeat;
height: 15px; block-size: 15px;
background-size: 40px 20px; background-size: 40px 20px;
background-image: radial-gradient( background-image: radial-gradient(
circle at 10px 15px, circle at 10px 15px,
@ -41,30 +41,30 @@
} }
.waves--large { .waves--large {
position: absolute; position: absolute;
height: 30px; block-size: 30px;
width: 100%; inline-size: 100%;
background: var(--waves-color, var(--color-light)); background: var(--waves-color, var(--color-light));
bottom: 0; bottom: 0;
} }
.waves--large::before, .waves--large::before,
.waves--large::after { .waves--large::after {
content: ''; content: "";
display: block; display: block;
position: absolute; position: absolute;
border-radius: 100% 50%; border-radius: 100% 50%;
} }
.waves--large::before { .waves--large::before {
width: 55%; inline-size: 55%;
height: 100%; block-size: 100%;
background-color: var(--waves-color, var(--color-light)); background-color: var(--waves-color, var(--color-light));
left: -1.5%; left: -1.5%;
top: 40%; top: 40%;
} }
.waves--large::after { .waves--large::after {
width: 55%; inline-size: 55%;
height: 109%; block-size: 109%;
background-color: var(--waves-bg-color, var(--color-white)); background-color: var(--waves-bg-color, var(--color-white));
right: -1.5%; right: -1.5%;
top: 60%; top: 60%;

267
src/styles/vendor/one-dark-pro.css vendored Normal file
View File

@ -0,0 +1,267 @@
code[class*="language-"],
pre[class*="language-"] {
color: #abb2bf;
background: none;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
font-size: var(--size--1);
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: var(--space-2xs-xs) var(--space-xs-s);
margin: var(--space-2xs-xs) 0;
overflow: auto;
border-radius: 10px;
}
/* Inline code */
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #282c34;
}
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
background: rgba(148, 170, 209, 0.2);
}
/* General styling */
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata,
.token.punctuation {
color: #abb2bf;
}
.token.namespace {
opacity: 0.7;
}
.token.keyword,
.token.regex,
.token.important {
color: #c678dd;
}
.token.property,
.token.attr-name,
.token.constant,
.token.symbol,
.token.deleted {
color: #e06c75;
}
.token.unit {
color: #2f02d1;
}
.token.boolean,
.token.number,
.token.selector,
.token.tag,
.token.char,
.token.builtin,
.token.inserted,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #ce004b;
}
.token.operator,
.token.pseudo-element,
.token.pseudo-class,
.token.attr-value,
.token.function,
.token.class-name {
color: #abb2bf;
}
.token.atrule {
color: #c678dd;
}
.token.string {
color: #10113a;
}
/* Specific styling */
.language-html .token.doctype-tag {
color: #e06c75;
}
.language-html .token.name {
color: #d19a66;
}
.language-html .token.punctuation {
color: #abb2bf;
}
.language-html .token.tag .token.tag {
color: #e06c75;
}
.language-html .token.tag .token.attr-name {
color: #d19a66;
}
.language-html .token.tag .token.attr-value {
color: #98c379;
}
.language-html .token.tag .token.attr-value .token.punctuation {
color: #98c379;
}
.language-html .token.tag .token.attr-value .token.punctuation.attr-equals {
color: #abb2bf;
}
.language-html .token.comment {
color: #7f848e;
font-style: italic;
}
code[class*="language-css"],
pre[class*="language-css"] {
color: #d19a66;
}
.language-css .token.operator,
.language-css .token.punctuation,
.language-css .token.property,
.language-css .token.operator,
.language-css .token.combinator {
color: #abb2bf;
}
.language-css .token.attr-name,
.language-css .token.color,
.language-css .token.number,
.language-css .token.class {
color: #d19a66;
}
.language-css .token.attr-value,
.language-css .token.string {
color: #98c379;
}
.language-css .token.selector,
.language-css .token.unit {
color: #e06c75;
}
.language-css .token.property.prefix,
.language-css .token.pseudo-element,
.language-css .token.pseudo-class {
color: #56b6c2;
}
.language-css .token.atrule {
color: #abb2bf;
}
.language-css .token.atrule .token.rule {
color: #c678dd;
}
.language-css .token.comment {
color: #7f848e;
font-style: italic;
}
.language-css .token.keyword {
color: #56b6c2;
}
code[class*="language-javascript"],
pre[class*="language-javascript"] {
color: #e06c75;
}
.language-javascript {
color: #e06c75;
}
.language-javascript .token.punctuation {
color: #abb2bf;
}
.language-javascript .token.comment {
color: #7f848e;
font-style: italic;
}
.language-javascript .token.keyword.this,
.language-javascript .token.dom.variable,
.language-javascript .token.class-name {
color: #e5c07b;
}
.language-javascript .token.null,
.language-javascript .token.boolean,
.language-javascript .token.number {
color: #d19a66;
}
.language-javascript .token.property-access,
.language-javascript .token.imports,
.language-javascript .token.parameter {
color: #e06c75;
}
.language-javascript .token.parameter {
font-style: italic;
}
.language-javascript .token.keyword {
color: #c678dd;
}
.language-javascript .token.function,
.language-javascript .token.property-access.function.method {
color: #61afef;
}
.language-javascript .token.regex-source,
.language-javascript .token.operator {
color: #56b6c2;
}
.language-javascript .token.regex-delimiter,
.language-javascript .token.string {
color: #98c379;
}
.language-json .token.punctuation,
.language-json .token.operator {
color: #abb2bf;
}
.language-json .token.string {
color: #98c379;
}
.language-json .token.boolean,
.language-json .token.number,
.language-json .token.null.keyword {
color: #d19a66;
}
.language-json .token.property {
color: #e06c75;
}
.language-bash .token.string,
.language-shell .token.string {
color: #98c379;
}
.language-bash .token.shebang.important,
.language-bash .token.comment,
.language-shell .token.shebang.important,
.language-shell .token.comment {
color: #7f848e;
font-style: italic;
}
.language-bash .token.builtin.class-name,
.language-bash .token.entity,
.language-shell .token.builtin.class-name,
.language-shell .token.entity {
color: #56b6c2;
}
.language-bash .token.keyword,
.language-shell .token.keyword {
color: #c678dd;
}
.language-bash .token.variable,
.language-shell .token.variable {
color: #e06c75;
}