all repos — slides @ f6031f9008b3caf28cad5d76b329ed186914b853

Reveal-md slides I made for various occasions

Add slides for Crea React
Tim Izzo tim@octree.ch
Sun, 11 Jun 2023 16:50:08 +0200
commit

f6031f9008b3caf28cad5d76b329ed186914b853

parent

7e9909e8a98adbc39f2237464836d02a09309f56

A crea/react/GraphQL.md

@@ -0,0 +1,308 @@

+--- +title: GraphQL +theme: ./_themes/5ika.css +highlightTheme: github +verticalSeparator: <!--v--> +revealOptions: + transition: 'fade' +--- + + +![GraphQL](https://graphql.org/img/brand/logos/logo-wordmark.svg) + +--- + +**Nouveau format de slides !** + +- Moins de contenu sur les slides (prenez les notes qui vous semblent nécessaires) +- Slides en HTML +- Plus d'intéractivité, plus de pratique + +Ces slides sont disponibles sur https://5ika.ch/slides/GraphQL.md. + +--- + +## Qu'est-ce que GraphQL ? + +- C'est un *language de requête* (QL) pour requêter des données organisées *en graphe*. +- Il permet de décrire de manière précise les données requises par une application cliente et d'obtenir ces données avec une seule requête. +- C'est une alternative aux API REST avec une approche plus structurée et typée. + +--- + +## Avantages de GraphQL + +- **Demande de données précise**: Les clients peuvent demander exactement les données dont ils ont besoin, ce qui évite le sur-fetching ou le under-fetching de données. +- **Introspection**: GraphQL fournit un système de type pour découvrir et explorer le schéma de l'API. +- **Réduction des aller-retours**: Les clients peuvent obtenir toutes les données nécessaires en une seule requête, ce qui réduit les allers-retours entre le client et le serveur. + +--- + +## Fonctionnement + +On ne peut pas expliquer plus clairement que le site officiel + +=> https://graphql.org/ + +--- + +Utiliser GraphQL dans un projet nécessite plusieurs étapes: + +1. Définir un schéma GraphQL +2. Développer le backend +3. Faire des requêtes depuis le frontend + +--- + +## Schéma GraphQL + +- Le schéma GraphQL est un contrat entre le client et le serveur. +- Il définit les types de données disponibles et les opérations pouvant être effectuées sur ces types. + +```graphql +type User { + id: ID! + name: String! + email: String! +} + +type Query { + getUser(id: ID!): User + getUsers: [User]! +} + +type Mutation { + removeUser(id: ID!): Boolean +} +``` + +--- + +## Queries & Mutations + +- Une **Query** permet de récupérer des données (read-only) +- Une **Mutation** permet d'effectuer un changement sur les données (création, modification, suppression,...) +- Une **Subscription** permet à un front de suivre des changements côté backend [pas abordé] + +--- + +## Backend + +Le backend doit faire correspondre un **resolver** à chaque élément +de `Query` et `Mutation`. + +Par exemple: + +```typescript +const resolvers = { + getUser: (id: string) => { + const users = knex('users') + .select(['id', 'name','email']) + .where({id}); + return users?.[0]; + } +} +``` + +--- + +Faire correspondre un schéma GraphQL à des resolvers demande un peu de code +qui est toujours le même. + +En général, on préfère utiliser un framework tout prêt pour éviter +de ré-inventer la roue quand on doit développer un backend GraphQL. + +Le framework le + utilisé est [Apollo](https://www.apollographql.com/). + +--- + +## Apollo + +Apollo est un ensemble d'outils pour: + +- Créer un serveur GraphQL: Apollo Server +- Créer un client GraphQL: Apollo Client +- Gérer ses schémas à grande échelle: GraphOS + +--- + +## Travail pratique + +Ensemble, nous allons mettre en place un serveur GraphQL permettant de gérer un blog. + +Pour ce TP, nous allons développer ensemble sur les mêmes fichiers de manière +synchronisée avec un [gitpod](https://www.gitpod.io/1). + +➡️ [Accéder au projet](https://gitpod.5ika.ch/?tkn=vvBXK79hp45yZofuTae2&folder=/home/vsc/projects/graphql-project) + +--- + +**Première étape**: Définir le schémas. + +Nous devons définir: + +- Le type `Post` définissant un article de blog +- Les *queries* et *mutations* possibles sur notre graphe. Ici, on veut CRUD des articles. + +--- + +**Deuxième étape**: Installer Apollo Server + +=> https://www.apollographql.com/docs/apollo-server/getting-started + +--- + +**Troisième étape**: Développer les resolvers. + +> Il est nécessaire de se répartir le travail pour éviter +> de se marcher sur les pieds. + +--- + +**Quatrième étape**: Tester avec Apollo Sandbox + +Une interface graphique est automatiquement disponible sur le port +utilisé par Apollo lorsqu'on est en mode dev. + +--- + +## Requête depuis un client + +Une fois que le backend est prêt, on peut faire des requêtes +depuis un client. + +On va utiliser pour cela [Apollo Client](https://www.apollographql.com/docs/react/) +qui permet une utilisation directement dans React. + + +--- + +## Configuration du client + +1. Installation des modules + ```bash + npm install @apollo/client graphql + ``` + +2. Création du client + ```typescript + import { ApolloClient, InMemoryCache } from '@apollo/client'; + + const client = new ApolloClient({ + uri: 'http://localhost:4000', + cache: new InMemoryCache(), + }); + ``` + +3. Mise à disposition du client pour React + ```typescript + <ApolloProvider client={client}> + <App /> + </ApolloProvider>, + ``` + +--- + +## Utilisation + +```typescript +import { gql } from '@apollo/client'; + +const POSTS_QUERY = gql` + query getPosts { + getPosts { + id + name + } + } +` + +const Compo = () => { + const { loading, error, data } = useQuery(POSTS_QUERY); + + return ... +} + +``` +--- + +## Avec des arguments + +```typescript +import { gql } from '@apollo/client'; + +const POST_QUERY = gql` + query getPost($postId: ID!) { + getPost(id: $postId) { + id + name + } + } +` + +const Compo = () => { + const { loading, error, data } = useQuery(POST_QUERY, { + variables: {postId: 3} + }); + + return ... +} +``` + +--- + +## Mutation + +```typescript +const CREATE_POST_MUTATION = gql` + mutation createPost($post: PostInput!) { + createPost(data: $post) { + id + name + } + } +`; + +const [createPost, { data, loading, error }] = useMutation(CREATE_POST_MUTATION); +``` +--- + +## Travail pratique + +Créer un simple frontend avec ViteJS qui va lister les articles de blog +disponibles et en créer un nouveau au clique sur un bouton (avec du +contenu par défaut). + +--- + +On peut faire encore + pratique en générant +automatiquement des hooks prêt-à-l'emploi pour chaque +requête que l'on souhaite faire. + +Par exemple: + +```typescript +const {data, loading, error} = usePostsQuery(); +const {data, loading, error} = usePostQuery({variables: {postId: 5}}); +``` + +--- + +## GraphQL codegen + +Avec [codegen](https://the-guild.dev/graphql/codegen), on peut écrire l'ensemble +de notre queries et mutations dans des fichiers *.gql* et lancer un petit +script qui nous génère des hooks pratiques. + +--- + +## Travail pratique + +Mettre en place [codegen](https://the-guild.dev/graphql/codegen) dans notre projet +commun pour mieux organiser nos appels GraphQL. + +--- + +# Exploration d'un graphe existant + +=> https://rickandmortyapi.com/graphql
A crea/react/NextJS.md

@@ -0,0 +1,322 @@

+--- +title: Rendering & NextJS +theme: ./_themes/5ika.css +highlightTheme: github +verticalSeparator: <!--v--> +revealOptions: + transition: 'fade' +--- + +# Rendering & NextJS + +--- + +De base, React (et alt.) fonctionne avec le principe du **Client-Side Rendering** (CSR): + +Le serveur envoi un fichier HTML simpliste et un bundle Javascript. Le navigateur génère ensuite toute l’app React côté client. + +--- + +![CSR](./img/client-side-rendering.png) + +--- + +**Avantages** +- Toute l’app tient dans quelques fichiers HTML+CSS+JS statiques qui peuvent être servis par tout serveur Web car la logique est côté frontend. +- Facilité de faire des webapps qui fonctionnent en mode offline + +**Inconvénients** + +- Difficile voir impossible pour les indexeurs (Google, DuckDuckGo,...) et les réseaux sociaux de lire les méta-données et le contenu car il faut générer toute l’app React pour avoir le contenu. +- Sur des systèmes lents, on a un effet de page blanche le temps que le navigateur lise et interprète le JS. + +--- + +# Frameworks + +- React "Vanilla" +- [ViteJS](https://vitejs.dev/) + +--- + +Le **Server-side Rendering** (SSR) consiste à utiliser un serveur pour interpréter le React et générer du HTML côté backend. + +Ainsi, on peut envoyer directement du contenu et des méta-données au client, dès la première requête. + +Le navigateur peut donc rapidement afficher le visuel du site (HTML+CSS) puis ajouter l'interactivité en interprétant le JS dans un second temps. + +--- + +![SSR](./img/server-side-rendering.png) + +--- + +**Avantages** +- Meilleures performances car le travail est fait par le backend essentiellement +- Meilleure SEO + +**Inconvénients** + +- Nécessaire de faire tourner un serveur pour générer les pages (plus complexe qu'un serveur Web) + +--- + +# Frameworks + +- [NextJS](https://nextjs.org/) +- [Remix](https://remix.run/) + +--- + +Le **Static-Site Generation** (SSG) est une méthode qui croise les 2 précédentes: + +On pré-génère les pages HTML et le code JS minimal, non pas à chaque requête d’un client, mais dans une étape de build. + +--- + +![SSG](img/static-site-generation.png) + +--- + +**Avantages** +- On peut utiliser un serveur Web simple (pas de “serveur frontend”) car on a uniquement des fichiers HTML+CSS+JS +- On renvoi des pages HTML lisibles par les indexeurs et les navigateurs (SEO) + +**Inconvénients** +- Ce n’est pas pratique quand on a du contenu qui bouge souvent car il faut regénérer l’ensemble à chaque modification ou faire des trucs pas propres +- C’est impossible à utiliser pour des pages nécessitant une authentification ou des données dynamiques + +--- + +# Frameworks + +- [NextJS](https://nextjs.org/) +- [Gatsby](https://www.gatsbyjs.com/) +- [Astro](https://astro.build/) + +--- + +**Démo**: SSR et SSG avec NextJS + +> Cette manière de faire est en train d'être remplacée mais beaucoup de projets fonctionnent encore comme cela. + +--- + +# Server Components + +Depuis cette année, la nouvelle manière de faire du React est d'utiliser +des *Server Components* (SC). + +C'est une manière de faire du SSR ou du SSG de manière propre et officielle. + +--- + +## Comment ça fonctionne ? + +On distingue deux types de composants: + +- Les *Client Components*, que l'on connaît déjà et qui sont executés au niveau du navigateur Web +- Les *Server Components*, qui sont executés côté serveur (NodeJS) + +--- + +## Pourquoi les Server Components ? + +Comme le code des SC est exécuté côté serveur, ils ont d'autres caractéristiques: + +- Possible de faire des appels à une base de données, utiliser des secrets sans que le client ait accès +- Certains modules externes sont utilisés uniquement côté serveur et n'alourdissent pas le code envoyé au client +- **Pas possible d'utiliser les hooks** car ils sont faits pour intéragir avec l'UI +- On peut utiliser async / await directement dans notre composant + +--- + +<center> + +![](img/SC%20vs%20CC.png) + +</center> + +--- + +![](https://nextjs.org/_next/image?url=%2Fdocs%2Fdark%2Fthinking-in-server-components.png&w=1920&q=75&dpl=dpl_D3xw4qGQxUxVjVbKQoy1MVX3DPRZ) + +--- + + +En utilisant correctement les *Server* Components et les *Client* Components dans notre app, +on augmente grandement les performances: + +- Les SC génèrent du code HTML / CSS, un peu comme on le fait avec PHP +- Seul le code JS utile (correspondant aux CC) est envoyé au navigateur + +--- + +# NextJS +The React Framework for the Web + +--- + +NextJS est le framework React le plus complet et le plus utilisé aujourd'hui. + +Il permet de faire du SSG, SSR, ISR et s'est orienté récemment +vers l'utilisation des Server Components avec un remaniement complet. + +--- + +## Créer un projet NextJS + +```bash +npx create-next-app@latest +``` + +``` +What is your project named? my-app +Would you like to use TypeScript with this project? No +Would you like to use ESLint with this project? Yes +Would you like to use Tailwind CSS with this project? Yes +Would you like to use `src/` directory with this project? Yes +Use App Router (recommended)? Yes +Would you like to customize the default import alias? Yes +``` + +--- + +NextJS a un système de routage puissant: + +- Chaque fichier `page.jsx` dans le dossier `app/` correspond à une page +- On peut organiser ces pages en dossiers pour définir une arborescence +- Au niveau de chaque dossier, on peut définir des fichiers `layout.jsx` qui + encadrent les pages contenu dans le même dossier et ses sous-dossiers. + +![](https://nextjs.org/_next/image?url=%2Fdocs%2Fdark%2Froute-segments-to-path-segments.png&w=1920&q=75&dpl=dpl_D3xw4qGQxUxVjVbKQoy1MVX3DPRZ) + +--- + +![](https://nextjs.org/_next/image?url=%2Fdocs%2Fdark%2Fnested-file-conventions-component-hierarchy.png&w=1920&q=75&dpl=dpl_D3xw4qGQxUxVjVbKQoy1MVX3DPRZ) + +--- + +# Server Components & Client Components + +Par défaut, tous les composants sont des Server Components. + +Quand on veut faire un Client Component, on ajoute `'use client'` +à la toute première ligne du fichier. +Ainsi, le composant et tous ses descendants seront rendus +sur le client plutôt que le serveur. + +--- + +## Server Component + +```javascript +import Item from "./Item"; + +export default async function Home() { + const response = await fetch("https://rickandmortyapi.com/api/character"); + const { results = [] } = await response.json(); + + console.log(results); // Uniquement dans le terminal + + return ( + <main> + {results?.map(character => ( + <Item character={character} key={character.id} /> + ))} + </main> + ); +} +``` + +--- + +## Client Component + +```javascript +"use client"; + +import { useState } from "react"; + +function Item({ character }) { + const [clicked, setClicked] = useState(false); + const style = clicked ? "p-2 text-sky-400" : "p-2 cursor-pointer"; + + console.log({clicked}); // Dans la console du nav + + return ( + <div className={style} onClick={() => setClicked(true)}> + {character.name} + </div> + ); +} + +export default Item; +``` + +--- + +# Routes dynamiques + +Il est possible de gérer des routes avec une ou plusieurs parties +correspondants à un paramètre. + +Par exemple, faire en sorte que `/species/Human` et `/species/Alien` +correspondent à la même page mais avec une props `kind` qui vaut +`Human` ou `Alien`. + +Pour cela, on nomme les dossiers avec des `[]` pour définir les paramètres. +Par exemple, la page qui permettra de gérer cela aura le chemin +`src/app/species/[kind]/page.jsx`. + +--- + +## Paramètre de route dynamique + +```javascript +export default function Page({ params }) { + const response = await fetch( + `https://rickandmortyapi.com/api/character/?species=${params.kind}`); + const { results = [] } = await response.json(); + + return <div> + <h1>Liste des {params.kind}</h1> + ... + </div> +} +``` + +--- + +# Variables d'environnement + +Toutes les variables définies dans un fichier `.env` ou `.env.local` +sont chargées par NextJS. + +- Les *Server* Components peuvent récupérer toutes les variables d'env. +- Les *Client* Components peuvent uniquement récupérer les variables qui commencent par `NEXT_PUBLIC_` + +Cela permet de gérer proprement les secrets. + +--- + +```javascript +const {DATABASE_PASSWORD} = process.env; +const NEXT_PUBLIC_API_KEY = process.env.NEXT_PUBLIC_API_KEY; +``` + +--- + +# Travail pratique + +En équipe sur le Gitpod, développé une app simpliste permettant de: + +- Afficher la météo pour Genève quand on arrive sur la page +- Entrer le nom d'une ville ou une adresse pour laquelle afficher la météo + +Pour cela, plusieurs tâches sont nécessaires: +- Développer une fonction pour récupérer la météo depuis https://open-meteo.com/ +- Développer une fonction pour récupérer la latitude/longitude d'une ville ou d'une adresse (OpenStreetMap) +- Développer le composant qui affiche la météo d'une ville +- Générer le composant qui affiche le formulaire pour entrer du texte +- Probablement +
A crea/react/Typescript.md

@@ -0,0 +1,234 @@

+--- +title: Typescript +theme: ./_themes/5ika.css +highlightTheme: github +verticalSeparator: <!--v--> +revealOptions: + transition: 'fade' +--- + + +# TypeScript + +--- + +## Qu'est-ce que TypeScript ? + +- TypeScript est un langage de programmation Open Source développé par Microsoft +- C'est une sorte de surcouche à Javascript qui rend le langage **typé**. +- TypeScript est conçu pour améliorer le développement d'applications JavaScript à grande échelle en ajoutant des outils de vérification des types, d'autocomplétion et de refactorisation. + +--- + +## Avantages de TypeScript + +- **Vérification des types :** TypeScript permet de détecter les erreurs de type pendant la phase de développement, dans l'IDE, ce qui facilite le débogage et rend le code plus robuste. +- **Intellisense améliorée :** Grâce aux annotations de type, TypeScript offre une meilleure prise en charge de l'autocomplétion et de l'analyse statique du code dans les éditeurs de code. +- **Meilleure maintenabilité :** Les fonctionnalités avancées de TypeScript, telles que les interfaces, les classes et les modules, favorisent une meilleure organisation et une meilleure maintenabilité du code. + +--- + +## Installation de TypeScript + +Pour installer TypeScript, vous pouvez utiliser npm : + +```bash +npm install -g typescript +``` + +On peut ainsi utiliser la commande `tsc` qui peut convertir le Typescript en Javascript. +Même si généralement, on utilise des outils qui s'occupent de cette conversion automatiquement. + +--- + +## Exemple de code + +```typescript +function add(a: number, b: number): number { + return a + b; +} + +const result = add(2, 3); +``` + +> L'extension de fichier utilisée est `.ts` plutôt que `.js`. +> `.tsx` plutôt que `.jsx`. + +--- + +## Types de données Typescript + +TypeScript comprend plusieurs types de données prédéfinis, tels que number, string, boolean, object, etc. + +Voici quelques exemples : + +```typescript +let age: number = 25; +let name: string = "John"; +let isStudent: boolean = true; +let person: object = { name: "John", age: 25 }; +let weekdays: string[] = ["lundi", "jeudi", "samedi"]; +``` + +Il est également possible de définir des types personnalisés à l'aide des interfaces et des types. + +--- + +## Interfaces en TypeScript + +Les interfaces sont utilisées pour définir des contrats de structure de données en TypeScript. + +Voici un exemple d'utilisation d'interfaces : + +```typescript +interface Person { + name: string; + age: number; + greet: () => void; +} + +const person: Person = { + name: "John", + age: 25, + greet: () => { + console.log(`Hello, my name is ${person.name}`); + }, +}; + +person.greet(); +``` + +--- + +## Composant React + +Typescript nous permet notamment de typer les `props` des composants: + +```typescript +interface Props { + posts: Post[]; + getPost: (id: string) => void; +} + +const PostsLists = (props: Props) => { + const {posts, getPost} = props; + + return ... +} +``` + +--- + +## Hooks + +De même, on peut typer les states pour assurer un code consistant: + +```typescript +const Compo = () => { + const [count, setCount] = useState<number>(0); + const [name, setName] = useState<string>(''); + const [profile, setProfile] = useState<{name: string, age: number}>(); + + // Cela affiche des erreurs: + setCount({value:2}); + setName(13); + setProfile({firstname: 'Tim'}) +} +``` + +--- + +## Inférence + +Typescript est capable de faire de l'*inférence*. +C'est à dire qu'il est capable parfois de deviner un type de variable sans qu'on ait besoin +de lui indiquer. Par exemple avec les hooks: + +```typescript +const Compo = () => { + const [count, setCount] = useState(0); // number + const [name, setName] = useState('Tim'); // string + const [profile, setProfile] = useState({age: 45, city: 'GE'}); // {age: number, city: string} + + const arr = [1, 'yop', null]; // (number | string | null) +} +``` + +--- + +## Union + +Parfois, une variable peut avoir plusieurs types potentiels. +On peut utiliser une *union* pour la définir: + +```typescript +let id: string | number = 10; + +id = '13'; // OK +id = 13: // OK +id = [11, 12]; // Pas OK +``` + +Pour déterminer dans notre code le type actuel d'une variable: + +```typescript +if(typeof id === 'string') console.log(`ID est un string`); +else if(typeof id === 'number') console.log('ID est un nombre'); +``` + +--- + +Les interfaces peuvent avoir des propriétés facultatives qui ont le droit +d'être `undefined`. + +```typescript +interface MyCompoProps { + className?: string; + isFocused?: boolean; + style: object; +} +``` + +--- + +## Any + +Le type `any` est une triche qui permet de bypasser le système de type. +Une variable de type `any` peut stocker tout type de données sans +lever d'erreur. + +Son utilisation n'est pas recommandée et sert à des cas très particuliers. + +--- + +De plus en plus de libs sont écrites en Typescript, comme React. +Cela permet notamment d'avoir de l'auto-complétion quand on développe +et d'avoir une mini-doc toujours à disposition. + +Pour avoir les interfaces et types d'une lib externe, il est nécessaire +de les télécharger séparemment. Par convention, ce sont des modules +qui commencent par `@types/`. + +```bash +npm install --dev @types/react @types/react-dom +``` + +--- + +## Dans la pratique + +Au début, Typescript est plutôt contraignant. +Il lève des erreurs partout et en tant que néophyte, on veut typer absolument +tout. + +Ce n'est pas nécessaire de vouloir faire parfait dès le début. +Installer Typescript en faisant du Javascript standard (dans des fichiers `.ts`) +est déjà une bonne chose qui améliore le code. + +--- + +**Hands-on**: Convertir un projet NextJS fait en classe en Typescript + +Doc: https://nextjs.org/docs/app/building-your-application/configuring/typescript + +---
A crea/react/_themes/5ika.css

@@ -0,0 +1,384 @@

+/** + * A simple theme for reveal.js presentations, similar + * to the default theme. The accent color is darkblue. + * Based on https://github.com/hakimel/reveal.js/blob/master/css/theme/source/simple.scss + * + * This theme is Copyright (C) 2012 Owen Versteeg, https://github.com/StereotypicalApps. It is MIT licensed. + * reveal.js is Copyright (C) 2011-2012 Hakim El Hattab, http://hakim.se + */ +@import url(https://fonts.googleapis.com/css?family=News+Cycle:400,700); +@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic); +section.has-dark-background, +section.has-dark-background h1, +section.has-dark-background h2, +section.has-dark-background h3, +section.has-dark-background h4, +section.has-dark-background h5, +section.has-dark-background h6 { + color: #fff; +} + +/********************************************* + * GLOBAL STYLES + *********************************************/ +:root { + --r-background-color: #fff; + --r-main-font: Lato, sans-serif; + --r-main-font-size: 40px; + --r-main-color: #000; + --r-block-margin: 20px; + --r-heading-margin: 0 0 20px 0; + --r-heading-font: News Cycle, Impact, sans-serif; + --r-heading-color: #000; + --r-heading-line-height: 1.2; + --r-heading-letter-spacing: normal; + --r-heading-text-transform: none; + --r-heading-text-shadow: none; + --r-heading-font-weight: normal; + --r-heading1-text-shadow: none; + --r-heading1-size: 1.5em; + --r-heading2-size: 1.4em; + --r-heading3-size: 1.3em; + --r-heading4-size: 1em; + --r-text-size: 0.8em; + --r-list-size: 0.7em; + --r-code-font: monospace; + --r-link-color: #00008b; + --r-link-color-dark: #00003f; + --r-link-color-hover: #0000f1; + --r-selection-background-color: rgba(0, 0, 0, 0.99); + --r-selection-color: #fff; +} + +.reveal-viewport { + background: #fff; + background-color: var(--r-background-color); +} + +.reveal { + font-family: var(--r-main-font); + font-size: var(--r-main-font-size); + font-weight: normal; + color: var(--r-main-color); +} + +.reveal ::selection { + color: var(--r-selection-color); + background: var(--r-selection-background-color); + text-shadow: none; +} + +.reveal ::-moz-selection { + color: var(--r-selection-color); + background: var(--r-selection-background-color); + text-shadow: none; +} + +.reveal .slides { + text-align: left; +} + +.reveal .slides section, +.reveal .slides section > section { + line-height: 1.3; + font-weight: inherit; +} + +/********************************************* + * HEADERS + *********************************************/ +.reveal h1, +.reveal h2, +.reveal h3, +.reveal h4, +.reveal h5, +.reveal h6 { + margin: var(--r-heading-margin); + color: var(--r-heading-color); + font-family: var(--r-heading-font); + font-weight: var(--r-heading-font-weight); + line-height: var(--r-heading-line-height); + letter-spacing: var(--r-heading-letter-spacing); + text-transform: var(--r-heading-text-transform); + text-shadow: var(--r-heading-text-shadow); + word-wrap: break-word; +} + +.reveal h1 { + font-size: var(--r-heading1-size); +} + +.reveal h2 { + font-size: var(--r-heading2-size); +} + +.reveal h3 { + font-size: var(--r-heading3-size); +} + +.reveal h4 { + font-size: var(--r-heading4-size); +} + +.reveal h1 { + text-shadow: var(--r-heading1-text-shadow); +} + +/********************************************* + * OTHER + *********************************************/ +.reveal p { + margin: var(--r-block-margin) 0; + line-height: 1.3; + font-size: var(--r-text-size); +} + +/* Remove trailing margins after titles */ +.reveal h1:last-child, +.reveal h2:last-child, +.reveal h3:last-child, +.reveal h4:last-child, +.reveal h5:last-child, +.reveal h6:last-child { + margin-bottom: 0; +} + +/* Ensure certain elements are never larger than the slide itself */ +.reveal img, +.reveal video, +.reveal iframe { + max-width: 95%; + max-height: 95%; +} + +.reveal strong, +.reveal b { + font-weight: bold; +} + +.reveal em { + font-style: italic; +} + +.reveal ol, +.reveal dl, +.reveal ul { + display: inline-block; + text-align: left; + margin: 0 0 0 1em; + font-size: var(--r-list-size); +} + +.reveal ol { + list-style-type: decimal; +} + +.reveal ul { + list-style-type: disc; +} + +.reveal ul ul { + list-style-type: square; +} + +.reveal ul ul ul { + list-style-type: circle; +} + +.reveal ul ul, +.reveal ul ol, +.reveal ol ol, +.reveal ol ul { + display: block; + margin-left: 40px; +} + +.reveal dt { + font-weight: bold; +} + +.reveal dd { + margin-left: 40px; +} + +.reveal blockquote { + display: block; + position: relative; + margin: var(--r-block-margin) 0; + padding: 5px; + padding-left: 16px; + font-style: italic; + background: rgba(0, 0, 0, 0.05); + font-size: 0.7em; + border-left: 8px solid var(--r-main-color); + border-radius: 4px; +} + +.reveal blockquote p:first-child, +.reveal blockquote p:last-child { + display: inline-block; +} + +.reveal q { + font-style: italic; +} + +.reveal pre { + display: block; + position: relative; + width: 90%; + margin: var(--r-block-margin) auto; + text-align: left; + font-size: 0.55em; + font-family: var(--r-code-font); + line-height: 1.2em; + word-wrap: break-word; + box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.15); +} + +.reveal code { + font-family: var(--r-code-font); + text-transform: none; + tab-size: 2; + background: rgba(0, 0, 0, 0.05); + padding-left: 4px; + padding-right: 4px; +} + +.reveal pre code { + display: block; + padding: 5px; + overflow: auto; + max-height: 400px; + word-wrap: normal; +} + +.reveal .code-wrapper { + white-space: normal; +} + +.reveal .code-wrapper code { + white-space: pre; +} + +.reveal table { + margin: auto; + border-collapse: collapse; + border-spacing: 0; +} + +.reveal table th { + font-weight: bold; +} + +.reveal table th, +.reveal table td { + text-align: left; + padding: 0.2em 0.5em 0.2em 0.5em; + border-bottom: 1px solid; +} + +.reveal table th[align="center"], +.reveal table td[align="center"] { + text-align: center; +} + +.reveal table th[align="right"], +.reveal table td[align="right"] { + text-align: right; +} + +.reveal table tbody tr:last-child th, +.reveal table tbody tr:last-child td { + border-bottom: none; +} + +.reveal sup { + vertical-align: super; + font-size: smaller; +} + +.reveal sub { + vertical-align: sub; + font-size: smaller; +} + +.reveal small { + display: inline-block; + font-size: 0.6em; + line-height: 1.2em; + vertical-align: top; +} + +.reveal small * { + vertical-align: top; +} + +.reveal img { + margin: var(--r-block-margin) 0; +} + +.reveal p:has(> img) { + text-align: center; +} + +/********************************************* + * LINKS + *********************************************/ +.reveal a { + color: var(--r-link-color); + text-decoration: none; + transition: color 0.15s ease; +} + +.reveal a:hover { + color: var(--r-link-color-hover); + text-shadow: none; + border: none; +} + +.reveal .roll span:after { + color: #fff; + background: var(--r-link-color-dark); +} + +/********************************************* + * Frame helper + *********************************************/ +.reveal .r-frame { + border: 4px solid var(--r-main-color); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); +} + +.reveal a .r-frame { + transition: all 0.15s linear; +} + +.reveal a:hover .r-frame { + border-color: var(--r-link-color); + box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); +} + +/********************************************* + * NAVIGATION CONTROLS + *********************************************/ +.reveal .controls { + color: var(--r-link-color); +} + +/********************************************* + * PROGRESS BAR + *********************************************/ +.reveal .progress { + background: rgba(0, 0, 0, 0.2); + color: var(--r-link-color); +} + +/********************************************* + * PRINT BACKGROUND + *********************************************/ +@media print { + .backgrounds { + background-color: var(--r-background-color); + } +}