├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── slides.cljs └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | node_modules/ 3 | .lsp/ 4 | .clj-kondo/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Chris McCormick 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Minimal presentation slides for ClojureScript. Made with [Scittle](https://github.com/babashka/scittle/). 2 | 3 | [features](#features) | [quickstart](#quickstart) | [slide format](#slide-format) | [navigation](#navigation) | [dev](#dev) | [about](#about) 4 | 5 | # features 6 | 7 | - Write your slides in Reagent Hiccup. 8 | - Static web app you can upload to any web host. 9 | - Tiny hackable codebase. 10 | - No setup or config, just clone the repo to start. 11 | 12 | Here's a [YouTube video about ClojureScript Tiny Slides](https://www.youtube.com/watch?v=gBt_tBoy1kE). 13 | 14 | ![Screencast of ClojureScript Tiny Slides](https://mccormick.cx/gfx/blogref/joplin/a69bca99b7a1401fbf8ba6a4157fd9ec.gif) 15 | 16 | # quickstart 17 | 18 | - Clone this repository 19 | - Edit [`slides.cljs`](./slides.cljs) and add your slides. 20 | - Open [`index.html`](./index.html) in your browser. 21 | 22 | To get a live-reloading dev experience you can [Start a `josh` server](#dev). 23 | 24 | # slide format 25 | 26 | Each slide is a `:section` tag like this: 27 | 28 | ```clojure 29 | (defn slides [state] 30 | [:<> 31 | [:section 32 | [:h1 "Hello"] 33 | [:h2 "Your first slide."]] 34 | 35 | [:section 36 | [:h1 "Slide Two"] 37 | [:img {:src "https://w.wiki/CAvg"}] 38 | [:h3 "It's the moon."]] 39 | 40 | ; ... 41 | ``` 42 | 43 | # navigation 44 | 45 | Slide navigation keys: 46 | 47 | - Next: RightArrow, DownArrow, PageDown, Spacebar, Enter 48 | - Prev: LeftArrow, UpArrow, PageUp 49 | - First: Home, Escape, Q 50 | - Last: End 51 | 52 | Or tap/click the right/left side of the screen to go foward/backward. 53 | 54 | # dev 55 | 56 | Start a live-reloading dev server: 57 | 58 | ``` 59 | echo {} > package.json 60 | npm i cljs-josh 61 | npx josh 62 | ``` 63 | 64 | (Or just `josh` if you have done `npm i -g cljs-josh` to install it globally). 65 | 66 | # about 67 | 68 | Built at Barcamp London 2024 for [this talk](https://chr15m.github.io/barcamp-whats-the-point-of-lisp/). 69 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tiny Slides 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /slides.cljs: -------------------------------------------------------------------------------- 1 | (ns slides 2 | (:require 3 | [reagent.core :as r] 4 | [reagent.dom :as rdom])) 5 | 6 | (defn slides [] 7 | [:<> 8 | ; your slides start here 9 | ; each slide is a :section 10 | ; you can add whatever hiccup you like 11 | 12 | [:section 13 | [:h1 "Hello"] 14 | [:h2 "Your first slide."] 15 | [:footer 16 | [:small 17 | [:a {:href "https://github.com/chr15m/scittle-tiny-slides" 18 | :target "_BLANK"} 19 | "Made with Scittle Tiny Slides"]]]] 20 | 21 | [:section 22 | [:h1 "Slide Two"] 23 | [:img {:src "https://w.wiki/CAvg"}] 24 | [:h3 "It's the moon."]] 25 | 26 | [:section 27 | [:h1 "Slide Three"] 28 | [:h2 29 | [:p [:code "Thank you for watching."]]]]]) 30 | 31 | ; *** implementation details *** ; 32 | 33 | (defonce state (r/atom nil)) ; re-initialized below 34 | 35 | (defn get-slide-count [] 36 | (aget 37 | (js/document.querySelectorAll "section") 38 | "length")) 39 | 40 | (defn move-slide! [state ev dir-fn] 41 | (.preventDefault ev) 42 | (swap! state update :slide dir-fn)) 43 | 44 | (defn clickable? [ev] 45 | (let [tag-name (.toLowerCase (aget ev "target" "tagName"))] 46 | (contains? #{"button" "label" "select" 47 | "textarea" "input" "a" 48 | "details" "summary"} 49 | tag-name))) 50 | 51 | (defn keydown 52 | [ev] 53 | (when (not (clickable? ev)) 54 | (let [k (aget ev "key")] 55 | (cond 56 | (contains? #{"ArrowLeft" "ArrowUp" "PageUp"} k) 57 | (move-slide! state ev dec) 58 | (contains? #{"ArrowRight" "ArrowDown" "PageDown" "Enter" " "} k) 59 | (move-slide! state ev inc) 60 | (contains? #{"Escape" "Home" "h"} k) 61 | (swap! state assoc :slide 0) 62 | (contains? #{"End"} k) 63 | (swap! state assoc :slide (dec (get-slide-count))))))) 64 | 65 | (defn component:show-slide [state] 66 | [:style (str "section:nth-child(" 67 | (inc (mod (:slide @state) (get-slide-count))) 68 | ") { display: block; }")]) 69 | 70 | (defn component:touch-ui [state] 71 | [:div#touch-ui 72 | {:style {:opacity 73 | (if (:touch-ui @state) 0 1)}} 74 | [:div {:on-click #(move-slide! state % dec)} "⟪"] 75 | [:div {:on-click #(move-slide! state % inc)} "⟫"]]) 76 | 77 | (defn component:slide-viewer [state] 78 | [:<> 79 | [:main {:on-click 80 | #(when (not (clickable? %)) 81 | (swap! state update :touch-ui not))} 82 | [slides]] 83 | [component:show-slide state] 84 | [component:touch-ui state]]) 85 | 86 | (rdom/render 87 | [component:slide-viewer state] 88 | (.getElementById js/document "app")) 89 | 90 | (defonce setup 91 | (do 92 | (aset js/window "onkeydown" #(keydown %)) 93 | ; trigger a second render so we get the sections count 94 | (swap! state assoc :slide 0 :touch-ui true))) 95 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 2em; 3 | } 4 | 5 | html, body, #app, main { 6 | width: 100%; 7 | height: 100%; 8 | padding: 0; 9 | margin: 0; 10 | } 11 | 12 | main { 13 | margin: 0 3em; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | text-align: center; 19 | width: calc(100% - 6em); 20 | } 21 | 22 | @media (width <= 1024px) { 23 | body { 24 | font-size: 1.5em; 25 | } 26 | } 27 | 28 | @media (width <= 600px) { 29 | body { 30 | font-size: 1em; 31 | } 32 | } 33 | 34 | img { 35 | max-height: 70vh; 36 | } 37 | 38 | ul li { 39 | text-align: left; 40 | line-height: 1.5em; 41 | } 42 | 43 | section { 44 | display: none; 45 | } 46 | 47 | footer { 48 | position: fixed; 49 | bottom: 0; 50 | left: 0; 51 | padding: 1em 3em; 52 | width: 100%; 53 | } 54 | 55 | #touch-ui > div { 56 | cursor: pointer; 57 | font-size: 1.5em; 58 | position: absolute; 59 | height: 100%; 60 | width: 2em; 61 | top: 0; 62 | display: flex; 63 | justify-content: center; 64 | align-items: center; 65 | user-select: none; 66 | background-color: rgb(150 150 150 / 10%); 67 | } 68 | 69 | #touch-ui > div:last-child { 70 | right: 0; 71 | } 72 | 73 | #touch-ui > div:first-child { 74 | left: 0; 75 | } 76 | --------------------------------------------------------------------------------