cleanup + better code blocks + redirects

This commit is contained in:
nicolas arduin 2025-01-01 22:35:54 +01:00
parent 703d1d7f08
commit fa61a28160
Signed by: nicolas
SSH Key Fingerprint: SHA256:ELi8eDeNLl5PTn64G+o2Kx5+XVDfHF5um2tZigfwWkM
34 changed files with 284 additions and 888 deletions

View File

@ -2,13 +2,13 @@
export default new Map([
["src/content/articles/en/after-effects-expressions.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Fen%2Fafter-effects-expressions.mdx&astroContentModuleFlag=true")],
["src/content/articles/en/sci-hub-blocage.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Fen%2Fsci-hub-blocage.mdx&astroContentModuleFlag=true")],
["src/content/fragments/en/image-full.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Fen%2Fimage-full.mdx&astroContentModuleFlag=true")],
["src/content/articles/en/the-day-I-jamd.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Fen%2Fthe-day-I-jamd.mdx&astroContentModuleFlag=true")],
["src/content/articles/en/video-compression.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Fen%2Fvideo-compression.mdx&astroContentModuleFlag=true")],
["src/content/fragments/en/super-cookies.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Fen%2Fsuper-cookies.mdx&astroContentModuleFlag=true")],
["src/content/articles/fr/sci-hub-blocage.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Ffr%2Fsci-hub-blocage.mdx&astroContentModuleFlag=true")],
["src/content/fragments/fr/buttons.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Ffr%2Fbuttons.mdx&astroContentModuleFlag=true")],
["src/content/fragments/fr/image-full.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Ffr%2Fimage-full.mdx&astroContentModuleFlag=true")],
["src/content/fragments/fr/super-cookies.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Ffr%2Fsuper-cookies.mdx&astroContentModuleFlag=true")],
["src/content/articles/fr/sci-hub-blocage.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Ffr%2Fsci-hub-blocage.mdx&astroContentModuleFlag=true")],
["src/content/fragments/en/image-full.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Fen%2Fimage-full.mdx&astroContentModuleFlag=true")],
["src/content/fragments/en/super-cookies.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Ffragments%2Fen%2Fsuper-cookies.mdx&astroContentModuleFlag=true")],
["src/content/articles/fr/the-day-I-jamd.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Farticles%2Ffr%2Fthe-day-I-jamd.mdx&astroContentModuleFlag=true")]]);

206
.astro/content.d.ts vendored
View File

@ -1,206 +0,0 @@
declare module 'astro:content' {
interface Render {
'.mdx': Promise<{
Content: import('astro').MarkdownInstance<{}>['Content'];
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
components: import('astro').MDXInstance<{}>['components'];
}>;
}
}
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
}
declare module 'astro:content' {
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof AnyEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
export type ContentCollectionKey = keyof ContentEntryMap;
export type DataCollectionKey = keyof DataEntryMap;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
ContentEntryMap[C]
>['slug'];
/** @deprecated Use `getEntry` instead. */
export function getEntryBySlug<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
// Note that this has to accept a regular string too, for SSR
entrySlug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
/** @deprecated Use `getEntry` instead. */
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
collection: C,
entryId: E,
): Promise<CollectionEntry<C>>;
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof AnyEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(entry: {
collection: C;
slug: E;
}): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(entry: {
collection: C;
id: E;
}): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof ContentEntryMap,
E extends ValidContentEntrySlug<C> | (string & {}),
>(
collection: C,
slug: E,
): E extends ValidContentEntrySlug<C>
? Promise<CollectionEntry<C>>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof ContentEntryMap>(
entries: {
collection: C;
slug: ValidContentEntrySlug<C>;
}[],
): Promise<CollectionEntry<C>[]>;
export function getEntries<C extends keyof DataEntryMap>(
entries: {
collection: C;
id: keyof DataEntryMap[C];
}[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof AnyEntryMap>(
entry: AnyEntryMap[C][string],
): Promise<RenderResult>;
export function reference<C extends keyof AnyEntryMap>(
collection: C,
): import('astro/zod').ZodEffects<
import('astro/zod').ZodString,
C extends keyof ContentEntryMap
? {
collection: C;
slug: ValidContentEntrySlug<C>;
}
: {
collection: C;
id: keyof DataEntryMap[C];
}
>;
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
export function reference<C extends string>(
collection: C,
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ContentEntryMap = {
};
type DataEntryMap = {
"HPsections": Record<string, {
id: string;
body?: string;
collection: "HPsections";
data: InferEntrySchema<"HPsections">;
rendered?: RenderedContent;
filePath?: string;
}>;
"articles": Record<string, {
id: string;
body?: string;
collection: "articles";
data: InferEntrySchema<"articles">;
rendered?: RenderedContent;
filePath?: string;
}>;
"fragments": Record<string, {
id: string;
body?: string;
collection: "fragments";
data: InferEntrySchema<"fragments">;
rendered?: RenderedContent;
filePath?: string;
}>;
"references": Record<string, {
id: string;
body?: string;
collection: "references";
data: InferEntrySchema<"references">;
rendered?: RenderedContent;
filePath?: string;
}>;
"veille": Record<string, {
id: string;
body?: string;
collection: "veille";
data: InferEntrySchema<"veille">;
rendered?: RenderedContent;
filePath?: string;
}>;
};
type AnyEntryMap = ContentEntryMap & DataEntryMap;
export type ContentConfig = typeof import("../src/content.config.js");
}

File diff suppressed because one or more lines are too long

View File

@ -1,30 +1,58 @@
import { defineConfig } from 'astro/config'
import { transformerMetaHighlight } from '@shikijs/transformers'
// https://astro.build/config
import mdx from '@astrojs/mdx'
import sitemap from '@astrojs/sitemap'
// https://astro.build/config
import { pluginLineNumbers } from '@expressive-code/plugin-line-numbers'
import expressiveCode from 'astro-expressive-code'
export default defineConfig({
site: 'https://www.nardu.in',
build: {
format: 'directory'
},
i18n: {
locales: ['fr', 'en'],
defaultLocale: 'fr'
redirects: {
'/en/': {
status: 308,
destination: '/articles/en-2025'
},
'/articles/en/': {
status: 308,
destination: '/articles/#en-articles'
},
'/articles/en/[...id]': {
status: 308,
destination: '/articles/en-[...id]'
},
'/snippets/en/': {
status: 308,
destination: '/fragments/#en-fragments'
},
'/snippets/en/[...id]': {
status: 308,
destination: '/fragments/en-[...id]'
}
},
image: {
domains: ['assets.nardu.in'],
remotePatterns: [{ protocol: 'https' }]
},
integrations: [mdx(), sitemap()],
markdown: {
shikiConfig: {
integrations: [
expressiveCode({
theme: 'one-dark-pro',
transformers: [transformerMetaHighlight()]
}
}
plugins: [pluginLineNumbers()],
defaultProps: {
// Disable line numbers by default
showLineNumbers: false,
// But enable line numbers for certain languages
overridesByLang: {
'css,html,js,ts,vue': {
showLineNumbers: true
}
}
}
}),
mdx(),
sitemap()
]
})

BIN
bun.lockb

Binary file not shown.

View File

@ -14,7 +14,9 @@
"@astrojs/rss": "4.0.10",
"@astrojs/sitemap": "3.2.1",
"@astrojs/ts-plugin": "^1.10.4",
"@expressive-code/plugin-line-numbers": "^0.38.3",
"astro": "5.1.1",
"astro-expressive-code": "^0.38.3",
"sharp": "^0.33.5"
},
"devDependencies": {

View File

@ -0,0 +1,13 @@
---
title: Nico v3.0
subtitle: Update 2025.
lang: en
slug: en-2023
excerpt: So long i18n
tags: ['Freelance']
type: articles
---
## This website no longer has an english version.
I have maintained an english version of my website for some years. But the

View File

@ -13,7 +13,7 @@ updatedAt: '2022-12-27T12:08:00.000Z'
import AstroImage from '../../../components/AstroImage.astro'
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://www.sci-hub.st/" rel="noreferer noopener">sci-hub.st</a>
## What is this about?

View File

@ -12,7 +12,7 @@ updatedAt: '2022-12-27T12:08:00.000Z'
import AstroImage from '../../../components/AstroImage.astro'
L'adresse actuelle de sci-hub est&nbsp;: [sci-hub.se](https://sci-hub.se)
L'adresse actuelle de sci-hub est&nbsp;: <a href="https://www.sci-hub.st/" rel="noreferer noopener">sci-hub.st</a>
## Résumé de la situation

View File

@ -89,7 +89,7 @@ Limit TLS version to 1.2 and 1.3 (or just 1.3 as there is only a [5% compatibili
Use the [strict transport security](https://scotthelme.co.uk/hsts-the-missing-link-in-tls/) header.
```
```http
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
@ -101,12 +101,12 @@ I've based my initial choices of ciphers on [this list](https://tls.imirhil.fr/c
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:
```
```cypher
ECDHE+AES:ECDHE+CHACHA20
```
In order to achieve a perfect score, we can be a little more restrictive with:
```
```cypher
ECDHE+AES256:ECDHE+CHACHA20
```

View File

@ -70,16 +70,16 @@ query {
#### Inside a page
```javascript
```vue
// index.vue
<script>
import homepageQuery from '~/graphql/queries/singles/homepage'
export default {
async asyncData({ $graphql }) {
const data = await $graphql.default.request(homepageQuery)
return { data }
},
async asyncData({ $graphql }) {
const data = await $graphql.default.request(homepageQuery)
return { data }
}
}
</script>
```
@ -88,31 +88,29 @@ export default {
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
```vue
// Header.vue
<template>
<header v-if="!$fetchState.pending">
</header>
<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))
}
},
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>
```
@ -121,20 +119,20 @@ export default {
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
```vue
// _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 }
},
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

@ -89,7 +89,7 @@ Limiter la version TLS à 1.2 et 1.3 (voire uniquement 1.3 vu [la différence de
Utiliser le header [strict transport security](https://scotthelme.co.uk/hsts-the-missing-link-in-tls/).
```
```http
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
@ -101,12 +101,12 @@ J'avais basé ma première suite sur [cette liste](https://tls.imirhil.fr/cipher
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;:
```
```cypher
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;:
```
```cypher
ECDHE+AES256:ECDHE+CHACHA20
```

View File

@ -27,6 +27,7 @@ On est parfois obligé d'utiliser des images dans des balises `img` plutôt que
Considérons le html suivant&nbsp;:
```html
<!-- index.html -->
<section class="container">
<div class="hero">
<img class="hero__image" src="hero.img" />
@ -37,15 +38,15 @@ Considérons le html suivant&nbsp;:
Et le style suivant&nbsp;:
```css
/* style.css */
.container {
padding: 0 14px;
margin-left: auto;
margin-right: auto;
max-width: 1040px;
margin-inline: auto;
max-inline-size: 1040px;
}
img {
max-width: 100%;
height: auto;
max-inline-size: 100%;
block-size: auto;
}
```
@ -60,10 +61,11 @@ img {
Afin de faire prendre à l'image toute la largeur, on agit sur son conteneur&nbsp;:
```css
/* style.css */
.hero {
margin-left: calc(50% - 50vw);
margin-inline-start: calc(50% - 50vw);
position: relative;
width: 100vw;
inline-size: 100vw;
}
```
@ -77,12 +79,15 @@ Afin de faire prendre à l'image toute la largeur, on agit sur son conteneur&nbs
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
```css title='style.css' ins={5, 7-9}
.hero {
height: 200px;
margin-inline-start: calc(50% - 50vw);
position: relative;
inline-size: 100vw;
block-size: 200px;
}
.hero__image {
height: 100%;
block-size: 100%;
}
```
@ -94,9 +99,10 @@ On peut alors réduire la hauteur du conteneur pour obtenir une bannière plutô
Il faut ensuite forcer l'image à prendre toute la largeur du conteneur&nbsp;:
```css
```css title='style.css' ins={3}
.hero__image {
width: 100%;
block-size: 100%;
inline-size: 100%;
}
```
@ -112,8 +118,10 @@ Pas top…
**ENFIN** le code magique pour redonner son ratio à l'image sans la déformer&nbsp;:
```css
```css title='style.css' ins={4,5}
.hero__image {
block-size: 100%;
inline-size: 100%;
object-fit: cover;
object-position: center; /* à positionner comme on veut */
}
@ -125,22 +133,23 @@ Pas top…
height='568'
/>
Cette technique s'apparente à l'utilisation d'une image de background mais en dur 😁
Cette technique s'apparente à l'utilisation d'une image de background.
## TL;DR
Le code complet&nbsp;:
```css
/* style.css */
.hero {
margin-left: calc(50% - 50vw);
margin-inline-start: calc(50% - 50vw);
position: relative;
width: 100vw;
height: 200px;
inline-size: 100vw;
block-size: 200px;
}
.hero__image {
width: 100%;
height: 100%;
inline-size: 100%;
block-size: 100%;
-o-object-fit: cover;
object-fit: cover;
-o-object-position: center;

View File

@ -70,16 +70,16 @@ query {
#### Dans une page
```javascript
```vue
// index.vue
<script>
import homepageQuery from '~/graphql/queries/singles/homepage'
export default {
async asyncData({ $graphql }) {
const data = await $graphql.default.request(homepageQuery)
return { data }
},
async asyncData({ $graphql }) {
const data = await $graphql.default.request(homepageQuery)
return { data }
}
}
</script>
```
@ -88,31 +88,29 @@ export default {
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
```vue
// Header.vue
<template>
<header v-if="!$fetchState.pending">
</header>
<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))
}
},
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>
```
@ -121,20 +119,20 @@ export default {
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
```vue
// _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 }
},
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

@ -45,7 +45,7 @@ J'ai personnellement choisi de faire différemment. J'ai décidé d'utiliser un
### Le code
```css {1,4-6}
```css {5,16} title="style.css"
a {
position: relative;
text-decoration: none;

View File

@ -1,11 +1,13 @@
---
import { getCollection } from 'astro:content'
import type { ArticleEntry } from 'src/content.config'
import EditorialContent from '../../components/EditorialContent.astro'
import BaseLayout from '../../layouts/BaseLayout.astro'
import { getCollection } from 'astro:content'
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
const articles = await getCollection('articles')
const articles: ArticleEntry[] = await getCollection('articles')
return articles.map((article) => ({
params: { id: article.data.slug },
props: { article }

View File

@ -34,7 +34,7 @@ const pageTitle = 'Articles'
<ListCards list={frArticles} routeName='articles' />
</section>
<section class='flow' lang='en'>
<h3>In english</h3>
<h3 id='en-articles'>In english</h3>
<ListCards list={enArticles} routeName='articles' />
</section>
</section>

View File

@ -1,12 +1,13 @@
---
import { getCollection } from 'astro:content'
import EditorialContent from '../../components/EditorialContent.astro'
import type { FragmentEntry } from 'src/content.config'
import EditorialContent from '../../components/EditorialContent.astro'
import BaseLayout from '../../layouts/BaseLayout.astro'
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
const fragments = await getCollection('fragments')
const fragments: FragmentEntry[] = await getCollection('fragments')
return fragments.map((fragment) => ({
params: { id: fragment.data.slug },
props: { fragment }

View File

@ -34,7 +34,7 @@ const pageTitle = 'Fragments'
<ListCards list={frFragments} routeName='fragments' />
</section>
<section class='flow' lang='en'>
<h3>In english</h3>
<h3 id='en-fragments'>In english</h3>
<ListCards list={enFragments} routeName='fragments' />
</section>
</section>

View File

@ -140,7 +140,6 @@ const allReferences = await getCollection('references')
}
.intro__subtitle {
margin: var(--space-s-m) 0;
font-family: 'wotfard';
font-weight: 500;
text-align: center;
color: var(--color-dark-blue);

View File

@ -30,7 +30,7 @@ EXCEPTIONS
/*
A flipped version where the sidebar is on the right
*/
.sidebar[data-direction="rtl"] {
.sidebar[data-direction='rtl'] {
flex-direction: row-reverse;
}

View File

@ -1,129 +1,131 @@
.btn {
padding: 12px 26px;
position: relative;
display: inline-block;
overflow: hidden;
cursor: pointer;
color: white;
border: none;
background-color: transparent;
transition: background-color 0.3s ease;
padding: 12px 26px;
position: relative;
display: inline-block;
overflow: hidden;
cursor: pointer;
color: white;
border: none;
background-color: transparent;
transition: background-color 0.3s ease;
}
.btn-icon {
background-color: hotpink;
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;
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;
background-color: darkorchid;
}
.btn-icon:hover::before,
.btn-icon:focus::before {
transform: translate(-10px, -50%);
transform: translate(-10px, -50%);
}
.btn-icon > span {
display: inline-block;
width: 100%;
height: 100%;
transition: transform 0.3s ease;
display: inline-block;
width: 100%;
height: 100%;
transition: transform 0.3s ease;
}
.btn-icon:hover > span,
.btn-icon:focus > span {
transform: translateX(-10px);
transform: translateX(-10px);
}
.btn-rideau {
border: 2px solid var(--dark);
color: var(--dark);
background-color: transparent;
transition: color 0.3s ease, border-color 0.3s ease;
border: 2px solid var(--dark);
color: var(--dark);
background-color: transparent;
transition:
color 0.3s ease,
border-color 0.3s ease;
}
.btn-rideau:hover {
color: white;
border-color: darkorchid;
color: white;
border-color: darkorchid;
}
.btn-rideau::before {
background: hotpink;
background: hotpink;
}
.btn-rideau::after {
background: darkorchid;
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);
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%);
transform: translateY(100%);
}
.btn-rideau:hover::after,
.btn-rideau:focus::after {
transition-delay: 0.175s;
transition-delay: 0.175s;
}
.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;
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;
animation: Gradient 0.5s ease 1 normal forwards;
}
@keyframes Gradient {
0% {
background-position: 0% 50%;
}
100% {
background-position: 100% 100%;
}
0% {
background-position: 0% 50%;
}
100% {
background-position: 100% 100%;
}
}
@keyframes GradientReverse {
0% {
background-position: 100% 100%;
}
100% {
background-position: 0% 50%;
}
0% {
background-position: 100% 100%;
}
100% {
background-position: 0% 50%;
}
}
.btn-scale {
overflow: visible;
color: #10113a;
background-color: transparent;
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;
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);
transform: scale(1.1);
}

View File

@ -1,35 +0,0 @@
@font-face {
font-family: "wotfard";
src: url("../../fonts/wotfard/wotfard-medium-webfont.woff2") format("woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "wotfard";
src: url("../../fonts/wotfard/wotfard-semibold-webfont.woff2") format("woff2");
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "wotfard";
src: url("../../fonts/wotfard/wotfard-regular-webfont.woff2") format("woff2");
font-weight: normal;
font-style: normal;
font-display: swap;
}
/*
* reduces Cumulative Layout Shift
* https://www.24joursdeweb.fr/2021/performance-web-lintegrateur-ce-heros/
*/
@font-face {
font-family: "ArialReplace";
src: local("Arial");
font-weight: 400;
font-style: normal;
font-display: swap;
size-adjust: 96%;
letter-spacing: 1px;
}

View File

@ -14,7 +14,7 @@
body {
font-family: var(--font-primary);
font-size: var(--size-0);
line-height: 1.5;
line-height: 1.4;
color: var(--color-dark);
background-color: var(--color-light-white);
accent-color: var(--color-brique);
@ -204,46 +204,8 @@ blockquote code {
/* code highlight */
.astro-code,
code {
font-size: var(--size--1);
font-family: var(--font-code);
}
.astro-code {
position: relative;
border-radius: var(--radius-small);
padding-block: var(--space-xs);
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
code {
padding-inline: var(--space-s);
white-space: pre-wrap;
display: block;
inline-size: fit-content;
min-inline-size: 100%;
}
}
.line {
&.highlighted::before {
content: '';
position: absolute;
display: inline-block;
inline-size: 100%;
block-size: 1lh;
inset-inline-start: 0;
background-color: hsla(0, 0%, 80%, 0.1);
}
background-color: var(--color-light-grey);
}

View File

@ -1,7 +1,7 @@
/* RESET */
:root {
--font-tnum: "tnum" on;
--font-tnum: 'tnum' on;
}
* {
@ -29,7 +29,7 @@
}
@supports (font-variant-numeric: tabular-nums) {
html {
--font-tnum: "____";
--font-tnum: '____';
font-variant-numeric: tabular-nums;
}
}
@ -87,7 +87,7 @@ body {
/* Remove built-in form typography styles */
:where(input, button, textarea, select),
:where(input[type="file"])::-webkit-file-upload-button {
:where(input[type='file'])::-webkit-file-upload-button {
color: inherit;
font: inherit;
font-size: inherit;
@ -113,7 +113,7 @@ body {
}
/* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
:where(ul, ol)[role="list"] {
:where(ul, ol)[role='list'] {
list-style: none;
}
@ -124,24 +124,24 @@ a:not([class]) {
/* Make it clear that interactive elements are interactive */
:where(
a[href],
area,
button,
input,
label[for],
select,
summary,
textarea,
[tabindex]:not([tabindex*="-"])
) {
a[href],
area,
button,
input,
label[for],
select,
summary,
textarea,
[tabindex]:not([tabindex*='-'])
) {
cursor: pointer;
touch-action: manipulation;
}
:where(input[type="file"]) {
:where(input[type='file']) {
cursor: auto;
}
:where(input[type="file"])::-webkit-file-upload-button,
:where(input[type="file"])::file-selector-button {
:where(input[type='file'])::-webkit-file-upload-button,
:where(input[type='file'])::file-selector-button {
cursor: pointer;
}
@ -160,14 +160,14 @@ a:not([class]) {
/* Make sure users can't select button text */
:where(
button,
button[type],
input[type="button"],
input[type="submit"],
input[type="reset"]
),
:where(input[type="file"])::-webkit-file-upload-button,
:where(input[type="file"])::file-selector-button {
button,
button[type],
input[type='button'],
input[type='submit'],
input[type='reset']
),
:where(input[type='file'])::-webkit-file-upload-button,
:where(input[type='file'])::file-selector-button {
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
user-select: none;
@ -178,9 +178,9 @@ a:not([class]) {
:where(
button,
button[type],
input[type="button"],
input[type="submit"],
input[type="reset"]
input[type='button'],
input[type='submit'],
input[type='reset']
)[disabled] {
cursor: not-allowed;
}

View File

@ -51,10 +51,10 @@
--space-l-3xl: clamp(2.25rem, calc(-0.08rem + 11.67vw), 7.5rem);
/* fonts */
--font-primary: "wotfard", "ArialReplace", sans-serif;
--font-secondary: "recoleta", Palatino, serif;
--font-code: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
--font-tnum: "tnum" on;
--font-primary: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--font-code: monospace;
--font-tnum: 'tnum' on;
/* colors */
--color-dark: hsl(239, 57%, 15%);

View File

@ -1,18 +1,10 @@
/* @import "open-props/style"; */
/* @import "open-props/normalize"; */
@import './global/reset.css';
@import './global/variables.css';
@import './global/global-styles.css';
@import "./global/reset.css";
@import "./global/fonts.css";
@import "./global/variables.css";
@import "./global/global-styles.css";
@import './compositions/grid.css';
@import './compositions/sidebar.css';
@import "./compositions/grid.css";
@import "./compositions/sidebar.css";
@import "./utilities/flow.css";
@import "./utilities/region.css";
@import "./utilities/wrapper.css";
/* @import-glob './blocks/*.css'; */
/* @import-glob './compositions/*.css'; */
/* @import-glob './utilities/*.css'; */
@import './utilities/flow.css';
@import './utilities/region.css';
@import './utilities/wrapper.css';

View File

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

View File

@ -32,6 +32,7 @@
grid-column: full;
}
/* set full width color to full grid */
/* prettier-ignore */
.wrapper.full-width-color {
/* https://codepen.io/t_afif/pen/oNEaqQX */
border-image: conic-gradient(var(--color-full-width, var(--color-light)) 0 0)

View File

@ -1,271 +0,0 @@
code[class*="language-"],
pre[class*="language-"],
:not(pre) > code {
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-"],
:not(pre) > code {
padding: 0.2em;
white-space: normal;
color: var(--color-dark);
border-radius: 0.3em;
background-color: var(--color-light-grey);
}
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;
}

View File

@ -1,28 +0,0 @@
import { defaultLang, ui } from '../i18n/ui'
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split('/')
if (lang in ui) return lang as keyof typeof ui
return defaultLang
}
type NestedKeyOf<T> = {
[K in keyof T]: T[K] extends object
? `${K & string}.${NestedKeyOf<T[K]> & string}`
: K & string
}[keyof T]
export function useTranslations(lang: keyof typeof ui) {
return function t(key: NestedKeyOf<(typeof ui)[typeof defaultLang]>) {
const keys = key.split('.')
let value = ui[lang]
let fallback = ui[defaultLang]
for (const k of keys) {
value = value?.[k]
fallback = fallback?.[k]
}
return value || fallback
}
}