# Theming and light/dark

# Basics

    library(shiny)
    library(shinyglass)

    ui <- fluidPage(
      theme = glass_theme(), # or preset = "dark" / "auto"
      titlePanel("Liquid Glass"),
      plotOutput("p")
    )

`glass_theme()` returns a bslib theme. Pass it as `theme =` to `fluidPage()`, `navbarPage()`, `bslib::page_sidebar()`, and other page helpers that accept a bslib theme.

# Presets

`preset` | Behavior
--- | ---
`"light"` | Light glass pack (default)
`"dark"` | Dark glass pack
`"auto"` | Follows `prefers-color-scheme`; updates when the OS theme changes

Light and dark surface tokens ship as dual CSS custom-property packs on `[data-glass-preset]`. Switching preset updates `document.documentElement.dataset.glassPreset` — no Sass recompile and no full page reload.

# Runtime updates from the server

    ui <- fluidPage(
      theme = glass_theme(preset = "auto"),
      glass_theme_toggle(selected = "auto"),
      selectInput("accent", "Accent", c(
        Blue = "#007AFF", Purple = "#AF52DE", Orange = "#FF9500"
      ))
    )

    server <- function(input, output, session) {
      observe_glass_theme_toggle(input, session)
      observeEvent(input$accent, {
        update_glass_theme(session, primary = input$accent)
      }, ignoreInit = TRUE)
    }

`update_glass_theme()` accepts:

Argument | Effect
--- | ---
`preset` | `"light"` / `"dark"` / `"auto"`
`tint` | Content-aware ambient color from plots/images
`primary` | Live accent via CSS variables (`--bs-primary`, …)

`primary` updates Bootstrap accent CSS variables so buttons, checked controls, and other accent surfaces follow without a reload. A few Sass-baked one-offs may still need a full reload to pick up a new color.

# Theme toggle helper

    ui <- fluidPage(
      theme = glass_theme(preset = "auto"),
      glass_theme_toggle(),           # Light / Dark / Auto buttons
      # ...
    )

    server <- function(input, output, session) {
      observe_glass_theme_toggle(input, session)
    }

Buttons call `window.shinyglass.setPreset()` immediately and also fire Shiny inputs (`glass_toggle_light`, …) so the server can stay in sync.

# Behavior knobs

    glass_theme(
      preset = "auto",
      primary = "#AF52DE",
      blur = 36,
      saturation = 200,
      radius = "1.5rem",
      material = "regular", # or "clear" over media-rich content
      intensity = 0.45,     # 0 Ultra Clear → 1 Tinted (iOS 27)
      tint = TRUE,      # sample plot/image colors into glass surfaces
      specular = TRUE,  # pointer specular highlight
      nav_morph = TRUE  # compact navbar while scrolling down
    )

`material = "regular"` is the adaptive Tahoe-style fill (default). Use `"clear"` when chrome sits over rich media and labels stay bold.

Use [glass_intensity_slider()] for a live Ultra Clear → Tinted control (same idea as iOS 27 Settings → Appearance → Liquid Glass):

    glass_intensity_slider("glass_intensity", value = 0.45)

All three JS behaviors default to `TRUE` (same as 0.1.x). They respect `prefers-reduced-motion: reduce` where relevant. Advanced escape hatch: `window.__shinyglassDisableTint = true` still disables tint.

# CSS variables (migration from 0.1.x)

Breaking in 0.2.0: light/dark surfaces are dual CSS packs, not a Bootswatch `darkly` rebuild. Prefer CSS custom properties over Sass `$glass-*` tokens when customizing:

    :root {
      /* shared knobs also set by glass_theme(blur=..., radius=...) */
      --glass-blur: 36px;
      --glass-radius: 1.25rem;
      --bs-primary: #007AFF;       /* also updated by update_glass_theme(primary=) */
      --glass-primary: #007AFF;
    }

    :root[data-glass-preset="dark"] {
      /* override a dark-pack value if needed */
      --glass-bg: rgba(255, 255, 255, 0.1);
    }

Apps that overrode Sass-only `$glass-*` tokens should target `--glass-*` or `[data-glass-preset="dark"]` instead.

# Client helpers

    window.shinyglass.setPreset("dark"); // or "light" / "auto"
    window.shinyglass.getPreset();       // resolved "light" | "dark"
    window.shinyglass.getMode();         // requested mode incl. "auto"
    window.shinyglass.setTint(false);
    window.shinyglass.setPrimary("#AF52DE");
    window.shinyglass.getPrimary();

# Teal

    options(teal.bs_theme = glass_theme(preset = "auto"))
    # then teal::init(...) as usual

# Reduced motion

When the user prefers reduced motion (`prefers-reduced-motion: reduce`):

- Navbar morph transforms are disabled (JS + CSS)
- Pointer specular tracking is skipped
- Content tint sampling is skipped
- Decorative transitions on cards/inputs/nav are removed

This is automatic — no app code required.

# Active-on-accent contrast

Bootstrap’s `color-contrast()` often picks black ink for system blue `#007AFF`. `glass_theme()` forces light ink on primary fills so checked checkboxes, radios, switches, and active pagination stay readable.