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

@ -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/)