--- title: "Get started" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Get started} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") site_url <- "https://insightsurge.github.io/mascot/" library(mascot) # nolint: undesirable_function_linter. ``` ## In a minute `mascot` puts a small character, drawn in code, into your Shiny app or Quarto page. The character shows a mood with a status line beside it and wiggles into its new face when the mood changes. You control it from R with plain arguments; no AI service is involved. The package is built for good-practice (`GxP`) work: a mascot sits beside your ordinary field errors and results, and does not replace them. What you get: - **A character with moods:** four built-in characters with five shared moods (`"idle"`, `"thinking"`, `"happy"`, `"worried"` and `"celebrating"`). - **Status text beside it:** a visible text line next to the drawing so the face never speaks alone. - **Motion:** a short wiggle on mood changes, plus nods and waves that respect reduced-motion settings. - **Light and dark mode:** follows your `bslib` colour toggle or Quarto's theme mode. - **Your colours:** built-in palettes, custom light and dark colours, or brand colours from `bslib::bs_theme()` (which parts it colours is experimental). - **Offline by design:** ships every script and style in the package, and shows a worried face if Shiny disconnects. - **A studio to explore:** [`mascot_studio()`](`r paste0(site_url, "articles/guide-mascot_studio.html")`) (experimental) lets you try every function live and copy the R code. Install the released version of `mascot` from CRAN: ```{r install, eval=FALSE} install.packages("mascot") ``` Or install the development version from GitHub with `pak`: ```{r install-dev, eval=FALSE} # install.packages("pak") pak::pak("insightsurge/mascot") ``` ## Try it without writing code The fastest way to see what `mascot` can do is the studio, a small app that runs on your computer and needs no internet. It is experimental, so its looks may change, and it uses the optional `bslib` and `knitr` packages. - [`mascot_studio()`](`r paste0(site_url, "articles/guide-mascot_studio.html")`) opens the studio. Pick a character, pick a function from the list, change its arguments and watch the mascot react. The R code to copy sits under the picture and always matches what you see. - Each function has a short guide page in the studio, with the same text as the website's guide pages (**Guides** menu, **Studio guide: every function**). - `mascot_studio(view = "theme")` (or `mascot_example("theme")`) opens **Theme a mascot**. Choose one character, then try a ready-made palette, your own colours for light and dark mode, or the colours of your app's `bslib` theme. Its **Check** tab shows every mood in light and dark and warns about low contrast, and the code to copy is the shortest `mascot_theme()` call that gives the same look. - `mascot_studio(view = "examples")` lists the bundled example apps. You can also open the theme page from the studio's **More** menu. - To start on one function or character, name them: `mascot_studio(fn = "mascot_theme", character = "sela")`. ```{r studio-start, eval=FALSE} mascot_studio() mascot_studio(view = "theme") ``` ## Your first app Suppose you have a batch review page where Tessa, the default character, waits in the corner until someone clicks **Finish review**, then smiles and confirms that every row passed. Put this code in an `app.R` file and run it from an interactive R session: ```{r first-app, eval=FALSE} library(shiny) library(mascot) ui <- fluidPage( h1("Batch review"), actionButton("finish", "Finish review"), mascot("helper", status = "Ready to review") ) server <- function(input, output, session) { observeEvent(input$finish, { update_mascot( "helper", mood = "happy", status = "Review complete", bubble = mascot_bubble("All rows checked.", duration = 5) ) }) } shinyApp(ui, server) ``` How each line works: - `library(shiny)` and `library(mascot)` load Shiny and the package. - [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) adds Tessa in the bottom-right corner with the id `"helper"` and `"Ready to review"` beside her. - `observeEvent(input$finish, { ... })` waits for your user to click **Finish review**. - [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`) updates `"helper"` in place using the same id. - `mood = "happy"` and `status = "Review complete"` change Tessa's face and status text together. - [`mascot_bubble()`](`r paste0(site_url, "articles/guide-mascot_bubble.html")`) shows a speech bubble above Tessa that closes after five seconds (`duration = 5`). ## Open an example You can try the bundled Shiny apps before writing your own code. - [`mascot_examples()`](`r paste0(site_url, "articles/guide-mascot_examples.html")`) lists the seven bundled example apps in teaching order. - [`mascot_example()`](`r paste0(site_url, "articles/guide-mascot_example.html")`) runs an example by name, or returns the path to its `app.R` with `run = FALSE`. - [`mascot_studio()`](`r paste0(site_url, "articles/guide-mascot_studio.html")`) (experimental) opens an app where you can click through every function and copy the code. ```{r open-example-list} mascot_examples() ``` ```{r open-example-run, eval=FALSE} mascot_example("form-check") mascot_studio() ``` ## The big ideas - **One id connects UI and server:** pass the same id string to [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) in the UI and [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`) in the server, naming all other arguments. - **Mood plus status text:** the text beside the character carries the meaning for your users and screen readers, so the face never stands alone. - **`NULL` leaves a setting unchanged:** in `update_mascot()`, `NULL` keeps the current value on the page, while calling a helper such as `mascot_bubble()` with no arguments resets it. - **Bad runtime values warn without crashing:** invalid values in `update_mascot()` warn and leave the mascot untouched instead of stopping the app. - **Helpers are optional shortcuts:** pass a plain value like `size = 180`, or use [`mascot_size()`](`r paste0(site_url, "articles/guide-mascot_size.html")`) when you want extra options. ## Moods and status When a form has missing entries or a report takes 20 seconds to build, update the mood and status line together so people using your app can see what is happening. A short animation: Knurl is idle, then thinking while a report runs through three steps (reading the data, fitting the model, writing the report), and celebrating when the report is ready. His status text and the matching update_mascot() call change with each step. - Every character has five shared moods (`"idle"`, `"thinking"`, `"happy"`, `"worried"` and `"celebrating"`), listed by [`mascot_moods()`](`r paste0(site_url, "articles/guide-mascot_moods.html")`). - Set a mood by name or with [`mascot_mood()`](`r paste0(site_url, "articles/guide-mascot_mood.html")`) in [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) and [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`). - Leave `status = NULL` in `mascot()` to use the mood's default status line, or pass your own text with [`mascot_status()`](`r paste0(site_url, "articles/guide-mascot_status.html")`). - For a slow task, show the `"thinking"` mood first, do the work, then update the mood when it finishes (`"long-task"` in [`mascot_example()`](`r paste0(site_url, "articles/guide-mascot_example.html")`) shows how). - Choose a character with [`mascot_character()`](`r paste0(site_url, "articles/guide-mascot_character.html")`), list them with [`mascot_characters()`](`r paste0(site_url, "articles/guide-mascot_characters.html")`), and see their families and attachment points with [`mascot_families()`](`r paste0(site_url, "articles/guide-mascot_families.html")`) and [`mascot_anchors()`](`r paste0(site_url, "articles/guide-mascot_anchors.html")`). - Tessa and Sela are stable, while Knurl and Neer are experimental and may change (with a warning one release before any removal). ```{r moods-and-characters} mascot_characters() mascot_moods("tessa") ``` ```{r mood-status-update, eval=FALSE} update_mascot("helper", mood = "worried", status = "2 fields need attention") update_mascot("helper", mood = "happy", status = mascot_status()) ``` You can read and check a character file with [`mascot_read_character()`](`r paste0(site_url, "articles/guide-mascot_read_character.html")`) or [`mascot_check_character()`](`r paste0(site_url, "articles/guide-mascot_check_character.html")`) (both experimental) and pass the result to `mascot_character()`, `mascot()`, `update_mascot()` or [`mascot_preview()`](`r paste0(site_url, "articles/guide-mascot_preview.html")`). Saving and registering characters and palettes are planned for a later release. See [Write your own character](`r paste0(site_url, "articles/write-your-own-character.html")`) for details. ## Message bubbles While the status line shows the current state, a speech bubble announces a specific event, such as three skipped rows in an uploaded file. - Pass a string or [`mascot_bubble()`](`r paste0(site_url, "articles/guide-mascot_bubble.html")`) to `bubble` in [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) or [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`). - A bubble stays for five seconds (`duration = 5`) and shows a close button by default. - Use `duration = Inf` for a message your user must act on so it stays until closed or cleared with `mascot_bubble()`. - The timer pauses on hover, on keyboard focus, or while the mascot is minimised. - Bubbles stay open across mood changes and character swaps. - Plain text is shown as written (any HTML in it is not run), or you can pass trusted markup with `htmltools::HTML()` or self-contained `htmltools` tags. ```{r bubbles-helpers, results="hide"} mascot_bubble("Three rows skipped.") mascot_bubble("Check the dates in column B.", duration = Inf) mascot_bubble() ``` ```{r bubbles-update, eval=FALSE} update_mascot( "helper", mood = "worried", status = "Import needs attention", bubble = mascot_bubble("Check the dates in column B.", duration = Inf) ) ``` ## One-off gestures When someone saves a filter or switches tabs, a one-off gesture plays a quick nod or wave without changing the character's mood. - Send a gesture from the server with `gesture` in [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`), using a name like `"nod"` or [`mascot_gesture()`](`r paste0(site_url, "articles/guide-mascot_gesture.html")`). - Every character has `"nod"` to acknowledge an action and `"wave"` to greet, listed by [`mascot_gestures()`](`r paste0(site_url, "articles/guide-mascot_gestures.html")`). - If you change the mood and send a gesture in the same call, the new mood appears first. - A character swap already includes a greeting wave, so any extra gesture in that update is skipped. - Reduced-motion settings or [`mascot_motion_settings()`](`r paste0(site_url, "articles/guide-mascot_motion_settings.html")`) can skip the movement while keeping the rest of the update. ```{r gestures-listing} mascot_gestures("tessa") ``` ```{r gesture-update, eval=FALSE} update_mascot("helper", gesture = mascot_gesture("nod")) ``` ## Size and placement A mascot can float in a window corner as an app-wide helper or sit inline inside a card right next to a form. - By default, [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) draws at `160` pixels in the `"bottom-right"` corner with a minimise button (`minimise = TRUE`). - Set `size` to a pixel number (at least `24`), a percentage like `"20%"`, or [`mascot_size()`](`r paste0(site_url, "articles/guide-mascot_size.html")`). - On phone screens, `small_screen = "auto"` shrinks floating mascots to at most `96` pixels while leaving inline mascots unchanged. - Set `placement` to `"inline"`, a corner name like `"top-left"`, or [`mascot_placement()`](`r paste0(site_url, "articles/guide-mascot_placement.html")`). - Minimising a floating mascot turns it into a round restore button and sets `input$_minimised` in Shiny. - Set `drag = "snap"` for corner snapping or `drag = "free"` (experimental) for free dragging. In Shiny, each drop updates `input$_position`, in either mode. ```{r size-placement-helpers, results="hide"} mascot_size(180, small_screen = 72) mascot_placement("bottom-right", minimise = TRUE, drag = "snap") ``` ```{r size-placement-app, eval=FALSE} mascot("card_helper", size = 120, placement = "inline") mascot("corner_helper", placement = mascot_placement("bottom-right", drag = "snap")) update_mascot("corner_helper", placement = mascot_placement("top-left")) ``` ## Theme and light/dark mode When your app has its own brand palette or a dark mode switch, you can match the character's colours through `theme`. - Leave `theme = NULL` in [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) to keep the character's own colours (or pass `"original"` in [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`) to restore them). - Pick a built-in palette from [`mascot_palettes()`](`r paste0(site_url, "articles/guide-mascot_palettes.html")`), such as `theme = "coral_navy"` for Tessa. - Use [`mascot_colour_roles()`](`r paste0(site_url, "articles/guide-mascot_colour_roles.html")`) to see a character's parts and which leader roles they follow when you set only a few colours. - Pass `light` and `dark` lists in [`mascot_theme()`](`r paste0(site_url, "articles/guide-mascot_theme.html")`) for separate light and dark colours, or `css` for browser CSS variables (experimental). - Pass a `bslib::bs_theme()` to fill the character's primary and secondary brand roles (experimental role mapping). - By default, `mode = "auto"` follows `bslib::input_dark_mode()` or Quarto's dark mode, and stays light when the page gives no signal. - To try colours before you choose, open **Theme a mascot** with `mascot_studio(view = "theme")`. ```{r palettes-and-roles} mascot_palettes("tessa") ``` ```{r theme-examples, eval=FALSE} pink_theme <- mascot_theme( light = list(body = "#B53D78", rim = "#A94A82"), dark = list(body = "#F2A6CF", rim = "#C5B0EB") ) mascot("helper", theme = pink_theme) update_mascot("helper", theme = "coral_navy") ``` ## Motion On a busy review dashboard where extra movement would distract people, you can quieten or stop animation while keeping every mood and message working. - Pass `motion = FALSE` to [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) or [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`) to stop all movement at once. - Use [`mascot_motion_settings()`](`r paste0(site_url, "articles/guide-mascot_motion_settings.html")`) to switch `idle`, `mood_change`, `gestures`, `swap`, `minimising`, `restoring` and `messages` on or off individually. - Any switch left as `NULL` follows `others` (`TRUE` by default), so `others = FALSE` with `gestures = TRUE` allows only gestures. - A user's browser or system reduced-motion preference always wins over `TRUE`. - After the mascot appears or changes (a new mood, a gesture or a swap) it breathes and blinks for up to five seconds, then rests; and [`mascot_gesture()`](`r paste0(site_url, "articles/guide-mascot_gesture.html")`) triggers one-off movements when allowed. ```{r motion-helpers, results="hide"} mascot_motion_settings(idle = FALSE) mascot_motion_settings(others = FALSE, gestures = TRUE) ``` ```{r motion-app, eval=FALSE} mascot("still_helper", motion = FALSE) update_mascot("still_helper", motion = mascot_motion_settings(idle = FALSE)) ``` ## Clicks and menus A mascot is a plain image by default so keyboard users never land on a dead control, but `click` turns it into a button or a menu. - Leave `click = NULL` in [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) when clicking the character does nothing. - Pass a label like `click = "Open help"` or [`mascot_action()`](`r paste0(site_url, "articles/guide-mascot_action.html")`) for a single action that counts clicks in `input$_click`. - Pass [`mascot_popup_menu()`](`r paste0(site_url, "articles/guide-mascot_popup_menu.html")`) with a named character vector to open a menu that sends the chosen item's name to `input$_menu`. - Opening or closing a menu never changes `input$_click`, and dragging never counts as a click. - Keyboard users can Tab to the button, press Enter or Space, use arrow keys in the menu, and press Escape to close it. - Set `disabled = TRUE` to pause clicks during a slow task, or pass an empty helper to [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`) to remove the click. ```{r clicks-and-menus, eval=FALSE} mascot( "helper", click = mascot_popup_menu(c(finish = "Finish review", reset = "Reset helper")) ) observeEvent(input$helper_menu, { if (identical(input$helper_menu, "finish")) { update_mascot( "helper", mood = "happy", status = "Review complete", bubble = mascot_bubble("All checks finished.", duration = Inf), gesture = mascot_gesture("nod") ) } }) ``` ## Shiny modules When you split a larger Shiny app into modules, including `golem` and `rhino` apps, each module can own and update its own mascot. - In the module UI, call [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) with a namespaced id such as `mascot(ns("helper"))`. - Pass the plain local id `"helper"` to [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`) inside `shiny::moduleServer()`. - Read `input$helper_click`, `input$helper_menu`, `input$helper_position` and `input$helper_minimised` directly from that module's `input`. - For one shared corner mascot across modules, return a reactive from each module and update the parent mascot from the top-level server. - Run [`mascot_example()`](`r paste0(site_url, "articles/guide-mascot_example.html")`) with `"modules"`, `"golem"` or `"rhino"` to see full working apps. ```{r shiny-module, eval=FALSE} review_ui <- function(id) { ns <- shiny::NS(id) shiny::tagList( shiny::actionButton(ns("finish"), "Finish review"), mascot(ns("helper"), placement = "inline") ) } review_server <- function(id) { shiny::moduleServer(id, function(input, output, session) { shiny::observeEvent(input$finish, { update_mascot("helper", mood = "happy", status = "Review complete") }) }) } ``` ## Quarto pages You can also place [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) in a rendered Quarto document with no Shiny server to show a fixed mood and status line. - The character draws when the page loads, and stays static because there is no R session behind the page. - Use `placement = "inline"` (or [`mascot_placement()`](`r paste0(site_url, "articles/guide-mascot_placement.html")`)) to sit in the page flow, and give each mascot a unique `id`. - On a static page that never shows a bubble, hide the reserved bubble space with `.mascot-bubble-region { display: none; }` in CSS. - The mascot follows Quarto's light and dark theme toggle and works in self-contained pages (`embed-resources: true`). - Quarto support is lightly tested so far, and a Quarto `shortcode` is planned for 0.2.0. Put this in an R chunk of your `.qmd` file: ```r #| echo: false library(mascot) mascot("helper", mood = "happy", status = "Rendered with Quarto", placement = "inline") ``` ## Check a look before using it Before wiring a character or colour theme into your app, you can inspect every mood and size on one offline page or try controls live in the studio. - [`mascot_preview()`](`r paste0(site_url, "articles/guide-mascot_preview.html")`) (experimental) builds a static offline page showing every mood in light and dark mode across your chosen size and 160, 96 and 24 pixels. - Previews always draw mascots inline with `motion = FALSE` by default so you can compare faces side by side. - Assign the result of `mascot_preview()` to inspect `attr(page, "contrast")` for any custom colours below 3:1 contrast, and print `page` to view it. - [`mascot_studio()`](`r paste0(site_url, "articles/guide-mascot_studio.html")`) (experimental) opens an offline app where you can test every function, check themes with `view = "theme"`, and copy R code. - Use [`mascot_examples()`](`r paste0(site_url, "articles/guide-mascot_examples.html")`) and [`mascot_example()`](`r paste0(site_url, "articles/guide-mascot_example.html")`) to browse or open the seven bundled example apps. ```{r preview-and-studio, eval=FALSE} page <- mascot_preview( theme = mascot_theme( light = list(body = "#B53D78", rim = "#A94A82"), dark = list(body = "#F2A6CF", rim = "#C5B0EB") ) ) attr(page, "contrast") mascot_studio(view = "theme") ``` ## Leave unchanged or reset When you call [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`) from the server, you can leave settings alone, replace them, or reset them to their defaults. - Leaving an argument as `NULL` in `update_mascot()` keeps its current value, whereas `NULL` in [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`) means none or the default. - Passing an empty helper resets that setting to its starting state, as shown in the table below. - Any non-`NULL` argument replaces that whole setting at once (for example, `size = 200` also resets `small_screen` to `"auto"`). - If any value in `update_mascot()` is invalid, R warns and ignores the whole call so the mascot never updates halfway. - Updates sent before a `shiny::renderUI()` mascot appears on screen are queued in order and applied as soon as it mounts. | Setting | Reset sent to `update_mascot()` | Result | |---|---|---| | Character | `character = `[`mascot_character()`](`r paste0(site_url, "articles/guide-mascot_character.html")`) | Switches back to `"tessa"` | | Mood | `mood = `[`mascot_mood()`](`r paste0(site_url, "articles/guide-mascot_mood.html")`) | Returns to `"idle"` | | Size | `size = `[`mascot_size()`](`r paste0(site_url, "articles/guide-mascot_size.html")`) | Resets to `160` pixels (`small_screen = "auto"`) | | Custom status | `status = `[`mascot_status()`](`r paste0(site_url, "articles/guide-mascot_status.html")`) | Follows the mood again | | Bubble | `bubble = `[`mascot_bubble()`](`r paste0(site_url, "articles/guide-mascot_bubble.html")`) | Removes the speech bubble | | Theme | `theme = `[`mascot_theme()`](`r paste0(site_url, "articles/guide-mascot_theme.html")`) (or `"original"`) | Restores the character's own colours | | Click | `click = `[`mascot_action()`](`r paste0(site_url, "articles/guide-mascot_action.html")`) (or [`mascot_popup_menu()`](`r paste0(site_url, "articles/guide-mascot_popup_menu.html")`)) | Makes the mascot a picture again | | Placement | `placement = `[`mascot_placement()`](`r paste0(site_url, "articles/guide-mascot_placement.html")`) | Floats in `"bottom-right"` (`"inline"` for inline) | | Motion | `motion = `[`mascot_motion_settings()`](`r paste0(site_url, "articles/guide-mascot_motion_settings.html")`) | Allows movement again | ```{r reset-examples, eval=FALSE} # Change only the mood; status, bubble and theme stay as they were: update_mascot("helper", mood = "thinking") # Reset custom status, clear the bubble, and restore original colours: update_mascot( "helper", status = mascot_status(), bubble = mascot_bubble(), theme = mascot_theme() ) ``` ## Where next - **Studio guide pages:** open **Guides** > **Studio guide: every function** on the website (or the **Guide** tab inside [`mascot_studio()`](`r paste0(site_url, "articles/guide-mascot_studio.html")`)) for visual guides to [`mascot()`](`r paste0(site_url, "articles/guide-mascot.html")`), [`update_mascot()`](`r paste0(site_url, "articles/guide-update_mascot.html")`), [`mascot_theme()`](`r paste0(site_url, "articles/guide-mascot_theme.html")`) and every other export. - **Package reference:** check the **Reference** menu (or `?mascot`, `?"mascot-package"` and `?mascot_styling` in R) for all argument details, condition classes and CSS variables. - **Bundled example apps:** use [`mascot_examples()`](`r paste0(site_url, "articles/guide-mascot_examples.html")`) and [`mascot_example()`](`r paste0(site_url, "articles/guide-mascot_example.html")`) to copy working apps for form checks, slow tasks, modules, `golem` and `rhino`. - **Custom characters (experimental):** read [Write your own character](`r paste0(site_url, "articles/write-your-own-character.html")`) to see how [`mascot_read_character()`](`r paste0(site_url, "articles/guide-mascot_read_character.html")`) and [`mascot_check_character()`](`r paste0(site_url, "articles/guide-mascot_check_character.html")`) read and check a character file.