added content
This commit is contained in:
parent
bf2221b9a9
commit
9b91b02d90
79 changed files with 3053 additions and 284 deletions
111
src/content/fragments/en/acme-sh-tls-cert.md
Normal file
111
src/content/fragments/en/acme-sh-tls-cert.md
Normal 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
|
||||
```
|
63
src/content/fragments/en/array-vs-array.md
Normal file
63
src/content/fragments/en/array-vs-array.md
Normal 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…
|
189
src/content/fragments/en/buttons.md
Normal file
189
src/content/fragments/en/buttons.md
Normal 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);
|
||||
}
|
||||
```
|
11
src/content/fragments/en/image-full.md
Normal file
11
src/content/fragments/en/image-full.md
Normal 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/)
|
139
src/content/fragments/en/nuxt-graphql-static.md
Normal file
139
src/content/fragments/en/nuxt-graphql-static.md
Normal 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>
|
||||
```
|
74
src/content/fragments/en/super-cookies.md
Normal file
74
src/content/fragments/en/super-cookies.md
Normal 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 “ chunks ”
|
||||
|
||||
### 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 “ chunks ”
|
||||
|
||||
### 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
|
||||
|
||||

|
||||

|
68
src/content/fragments/en/toulouse-fun.md
Normal file
68
src/content/fragments/en/toulouse-fun.md
Normal 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 d’Hercule](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
|
111
src/content/fragments/fr/acme-sh-tls-cert.md
Normal file
111
src/content/fragments/fr/acme-sh-tls-cert.md
Normal 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 ?) [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 :
|
||||
|
||||
```
|
||||
ECDHE+AES:ECDHE+CHACHA20
|
||||
```
|
||||
|
||||
Afin d'atteindre un score de 100/100, il est possible de restreindre un peu plus la suite comme ceci :
|
||||
|
||||
```
|
||||
ECDHE+AES256:ECDHE+CHACHA20
|
||||
```
|
11
src/content/fragments/fr/array-vs-array.md
Normal file
11
src/content/fragments/fr/array-vs-array.md
Normal 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/)
|
189
src/content/fragments/fr/buttons.md
Normal file
189
src/content/fragments/fr/buttons.md
Normal 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 « reset » :
|
||||
|
||||
> N'oubliez pas de préfixer si besoin !
|
||||
|
||||
```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);
|
||||
}
|
||||
```
|
122
src/content/fragments/fr/image-full.md
Normal file
122
src/content/fragments/fr/image-full.md
Normal 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 ? Exemple pratique à partir de ce même site.
|
||||
|
||||
[](https://assets.nardu.in/image_bleed_container_9e3939b3ae.jpeg)
|
||||
|
||||
### Contexte
|
||||
|
||||
Considérons le html suivant :
|
||||
|
||||
```html
|
||||
<section class="container">
|
||||
<div class="hero">
|
||||
<img class="hero__image" src="hero.img" />
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
Et le style suivant :
|
||||
|
||||
```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)
|
||||
|
||||
### Déborder du conteneur
|
||||
|
||||
Afin de faire prendre à l'image toute la largeur, on agit sur son conteneur :
|
||||
|
||||
```css
|
||||
.hero {
|
||||
margin-left: calc(50% - 50vw);
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
}
|
||||
```
|
||||
|
||||
[](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 :
|
||||
|
||||
```css
|
||||
.hero {
|
||||
height: 200px;
|
||||
}
|
||||
.hero__image {
|
||||
height: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
[](https://assets.nardu.in/image_bleed_height_81b4ce969a.jpeg)
|
||||
|
||||
Il faut ensuite forcer l'image à prendre toute la largeur du conteneur :
|
||||
|
||||
```css
|
||||
.hero__image {
|
||||
width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
[](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 :
|
||||
|
||||
```css
|
||||
.hero__image {
|
||||
object-fit: cover;
|
||||
object-position: center; /* à positionner comme on veut */
|
||||
}
|
||||
```
|
||||
|
||||
[](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 :
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
139
src/content/fragments/fr/nuxt-graphql-static.md
Normal file
139
src/content/fragments/fr/nuxt-graphql-static.md
Normal 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 !
|
||||
|
||||
[Nuxt graphql request à la rescousse !](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 :
|
||||
|
||||
```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 :
|
||||
|
||||
```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>
|
||||
```
|
74
src/content/fragments/fr/super-cookies.md
Normal file
74
src/content/fragments/fr/super-cookies.md
Normal 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 “ chunks ”
|
||||
|
||||
### 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 “ chunks ”
|
||||
|
||||
### 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
|
||||
|
||||

|
||||

|
11
src/content/fragments/fr/toulouse-fun.md
Normal file
11
src/content/fragments/fr/toulouse-fun.md
Normal 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/)
|
Loading…
Add table
Add a link
Reference in a new issue