lib/money/input/visualizer/assets.ex

defmodule Money.Input.Visualizer.Assets do
  @moduledoc false

  # Static assets for the visualizer. Inline CSS keeps the
  # visualizer dependency-free at the asset layer.
  #
  # The palette is borrowed from `image_playground`'s `--ip-*`
  # tokens so the two apps feel like one product. We add a light
  # variant alongside the dark one — the host page picks one via
  # `data-theme="light"` / `data-theme="dark"` on `<html>`. With
  # no attribute set, `prefers-color-scheme` decides at the
  # browser level.

  @css """
  :root {
    /* Defaults are the light theme; dark is overridden by
       [data-theme=dark] and by prefers-color-scheme when the
       user hasn't set an explicit preference. */
    --mi-bg:           #f7f8fa;
    --mi-surface:      #ffffff;
    --mi-surface-2:    #f1f3f6;
    --mi-border:       #e1e4ea;
    --mi-text:         #15181d;
    --mi-text-dim:     #4b5563;
    --mi-text-faint:   #6b7280;
    --mi-accent:       #2563eb;
    --mi-accent-soft:  rgba(37, 99, 235, 0.12);
    --mi-accent-strong:#1d4ed8;
    --mi-accent-fg:    #ffffff;
    --mi-error:        #b91c1c;
    --mi-error-bg:     #fef2f2;
    --mi-pill-bg:      #e5e7eb;
    --mi-pill-fg:      #374151;

    --mi-radius:       0.5rem;
    --mi-radius-sm:    0.375rem;
    --mi-radius-pill:  9999px;
    --mi-shadow-sm:    0 1px 2px rgba(15, 23, 42, 0.06);
    --mi-shadow-md:    0 4px 14px rgba(15, 23, 42, 0.08);
    --mi-mono:         ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  }

  [data-theme="dark"] {
    --mi-bg:           #0b0d10;
    --mi-surface:      #15181d;
    --mi-surface-2:    #1d2127;
    --mi-border:       #2a2f37;
    --mi-text:         #e5e7eb;
    --mi-text-dim:     #9ca3af;
    --mi-text-faint:   #6b7280;
    --mi-accent:       #60a5fa;
    --mi-accent-soft:  rgba(96, 165, 250, 0.16);
    --mi-accent-strong:#3b82f6;
    --mi-accent-fg:    #0b0d10;
    --mi-error:        #fca5a5;
    --mi-error-bg:     rgba(252, 165, 165, 0.12);
    --mi-pill-bg:      #2a2f37;
    --mi-pill-fg:      #e5e7eb;
    --mi-shadow-sm:    0 1px 2px rgba(0, 0, 0, 0.4);
    --mi-shadow-md:    0 4px 14px rgba(0, 0, 0, 0.35);
  }

  /* Track system preference when the user hasn't set explicit
     light/dark via the toggle. The :not([data-theme]) selector
     means "only fall through to system if no toggle choice
     persisted". */
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme]) {
      --mi-bg:           #0b0d10;
      --mi-surface:      #15181d;
      --mi-surface-2:    #1d2127;
      --mi-border:       #2a2f37;
      --mi-text:         #e5e7eb;
      --mi-text-dim:     #9ca3af;
      --mi-text-faint:   #6b7280;
      --mi-accent:       #60a5fa;
      --mi-accent-soft:  rgba(96, 165, 250, 0.16);
      --mi-accent-strong:#3b82f6;
      --mi-accent-fg:    #0b0d10;
      --mi-error:        #fca5a5;
      --mi-error-bg:     rgba(252, 165, 165, 0.12);
      --mi-pill-bg:      #2a2f37;
      --mi-pill-fg:      #e5e7eb;
      --mi-shadow-sm:    0 1px 2px rgba(0, 0, 0, 0.4);
      --mi-shadow-md:    0 4px 14px rgba(0, 0, 0, 0.35);
    }
  }

  * { box-sizing: border-box; }

  html, body {
    margin: 0;
    background: var(--mi-bg);
    color: var(--mi-text);
    font: 14px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI",
                   Roboto, "Helvetica Neue", Arial, sans-serif;
    transition: background 120ms ease, color 120ms ease;
  }

  /* ── Header ────────────────────────────────────────────── */

  .mi-header {
    background: var(--mi-surface);
    border-bottom: 1px solid var(--mi-border);
    padding: 1.25rem 2rem 0;
  }
  .mi-header-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1.5rem;
    margin-bottom: 1rem;
  }
  .mi-brand {
    text-decoration: none;
    color: inherit;
    display: inline-flex;
    align-items: center;
    gap: 0.85rem;
  }
  .mi-brand-text { display: flex; flex-direction: column; }
  .mi-logo {
    width: 40px;
    height: 40px;
    border-radius: 8px;
    flex: 0 0 auto;
    box-shadow: var(--mi-shadow-sm);
  }
  .mi-brand h1 {
    font-size: 1.25rem;
    margin: 0 0 0.15rem;
    font-weight: 700;
    letter-spacing: -0.01em;
  }
  .mi-brand p { color: var(--mi-text-dim); margin: 0; font-size: 0.85rem; }

  .mi-tabs { display: flex; gap: 0.25rem; }
  .mi-tabs a {
    text-decoration: none;
    padding: 0.55rem 1rem;
    color: var(--mi-text-dim);
    border-bottom: 2px solid transparent;
    font-weight: 500;
    font-size: 0.9rem;
    transition: color 120ms ease, border-color 120ms ease;
  }
  .mi-tabs a.active {
    color: var(--mi-accent);
    border-bottom-color: var(--mi-accent);
  }
  .mi-tabs a:hover { color: var(--mi-text); }

  /* ── Theme toggle (segmented control, 3 states) ────────── */

  .mi-theme-toggle {
    position: relative;
    display: inline-flex;
    align-items: center;
    background: var(--mi-surface-2);
    border: 1px solid var(--mi-border);
    border-radius: var(--mi-radius-pill);
    padding: 2px;
    gap: 0;
  }
  .mi-theme-toggle button {
    position: relative;
    z-index: 1;
    border: 0;
    background: transparent;
    color: var(--mi-text-dim);
    font: inherit;
    cursor: pointer;
    padding: 0.3rem 0.6rem;
    border-radius: var(--mi-radius-pill);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 1.75rem;
    transition: color 120ms ease;
  }
  .mi-theme-toggle button:hover { color: var(--mi-text); }
  .mi-theme-toggle button[aria-pressed="true"] {
    color: var(--mi-text);
  }
  .mi-theme-toggle-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: calc((100% - 4px) / 3);
    height: calc(100% - 4px);
    background: var(--mi-surface);
    border-radius: var(--mi-radius-pill);
    box-shadow: var(--mi-shadow-sm);
    transition: transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
    pointer-events: none;
  }
  .mi-theme-toggle[data-active="system"] .mi-theme-toggle-thumb { transform: translateX(0%); }
  .mi-theme-toggle[data-active="light"]  .mi-theme-toggle-thumb { transform: translateX(100%); }
  .mi-theme-toggle[data-active="dark"]   .mi-theme-toggle-thumb { transform: translateX(200%); }
  @media (prefers-reduced-motion: reduce) {
    .mi-theme-toggle-thumb { transition: none; }
    html, body { transition: none; }
  }
  .mi-theme-toggle svg {
    width: 14px;
    height: 14px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
  }

  /* ── Main content ──────────────────────────────────────── */

  .mi-main {
    max-width: 60rem;
    margin: 0 auto;
    padding: 2rem;
  }

  .mi-error {
    background: var(--mi-error-bg);
    color: var(--mi-error);
    border: 1px solid var(--mi-error);
    padding: 0.75rem 1rem;
    border-radius: var(--mi-radius);
    margin-bottom: 1rem;
  }

  /* Cards (panels) — borrowed from image_playground's .ip-card */
  .mi-card {
    background: var(--mi-surface);
    border: 1px solid var(--mi-border);
    border-radius: 12px;
    padding: 1.5rem;
    margin-bottom: 1.25rem;
    box-shadow: var(--mi-shadow-sm);
  }
  .mi-card h2 {
    font-size: 1rem;
    margin: 0 0 0.25rem;
    font-weight: 600;
    letter-spacing: -0.005em;
  }
  .mi-card p.mi-desc {
    color: var(--mi-text-dim);
    margin: 0 0 1.25rem;
    font-size: 0.9rem;
    max-width: 60ch;
  }

  /* ── Forms ─────────────────────────────────────────────── */

  form.mi-form {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem 1.25rem;
    margin-bottom: 1rem;
  }
  form.mi-form .mi-field-wide { grid-column: 1 / -1; }
  form.mi-form .mi-actions {
    grid-column: 1 / -1;
    display: flex;
    gap: 0.5rem;
    align-items: center;
    margin-top: 0.25rem;
  }

  .mi-field { display: flex; flex-direction: column; gap: 0.25rem; }
  .mi-field label {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    font-size: 0.7rem;
    color: var(--mi-text-dim);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }
  /* Don't use `font: inherit` here — the outer <label> sets
     a tiny-uppercase style that would propagate into <select>,
     and several browsers (Firefox especially) ship a serif
     default for <option>. Spell out every property the label
     might have touched so the control reads correctly. */
  .mi-field input[type="text"],
  .mi-field input[type="search"],
  .mi-field select,
  .mi-field textarea,
  .mi-field select option {
    font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
                 "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    font-size: 0.9rem;
    font-weight: 400;
    font-style: normal;
    text-transform: none;
    letter-spacing: normal;
  }

  .mi-field input[type="text"],
  .mi-field input[type="search"],
  .mi-field select,
  .mi-field textarea {
    padding: 0.45rem 0.7rem;
    border: 1px solid var(--mi-border);
    border-radius: var(--mi-radius-sm);
    background: var(--mi-surface-2);
    color: var(--mi-text);
    line-height: 1.4;
  }

  /* On most browsers the <option> popup is rendered by the OS
     and only respects a handful of CSS properties. Set what we
     can — colour + background so it tracks the theme — and
     accept that the OS may override the rest. */
  .mi-field select option {
    background: var(--mi-surface);
    color: var(--mi-text);
  }
  .mi-field input:focus, .mi-field select:focus {
    outline: none;
    border-color: var(--mi-accent);
    box-shadow: 0 0 0 3px var(--mi-accent-soft);
  }
  .mi-checkbox {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.9rem;
    color: var(--mi-text);
    text-transform: none;
    letter-spacing: 0;
    font-weight: normal;
  }
  .mi-checkbox input[type="checkbox"] { accent-color: var(--mi-accent); }
  .mi-hint {
    color: var(--mi-text-faint);
    font-size: 0.78rem;
    text-transform: none;
    letter-spacing: 0;
    font-weight: normal;
  }

  button.mi-btn {
    font: inherit;
    background: var(--mi-accent);
    color: var(--mi-accent-fg);
    border: none;
    padding: 0.55rem 1.25rem;
    border-radius: var(--mi-radius-sm);
    cursor: pointer;
    font-weight: 600;
    font-size: 0.9rem;
    transition: background 120ms ease;
  }
  button.mi-btn:hover { background: var(--mi-accent-strong); }
  button.mi-btn:focus-visible {
    outline: 2px solid var(--mi-accent);
    outline-offset: 2px;
  }

  /* ── Result lists / tables ──────────────────────────────── */

  .mi-result {
    display: grid;
    grid-template-columns: 12rem 1fr;
    gap: 0.45rem 1rem;
    background: var(--mi-surface-2);
    padding: 1rem 1.25rem;
    border-radius: var(--mi-radius);
    font-size: 0.9rem;
  }
  .mi-result dt {
    color: var(--mi-text-dim);
    font-weight: 500;
    font-size: 0.78rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding-top: 0.2rem;
  }
  .mi-result dd { margin: 0; font-family: var(--mi-mono); }
  .mi-result dd.mi-bad { color: var(--mi-error); }

  table.mi-table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 0.5rem;
    font-size: 0.9rem;
  }
  table.mi-table th, table.mi-table td {
    padding: 0.5rem 0.75rem;
    text-align: left;
    border-bottom: 1px solid var(--mi-border);
    vertical-align: top;
  }
  table.mi-table th {
    background: var(--mi-surface-2);
    font-weight: 600;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--mi-text-dim);
  }
  table.mi-table td.mi-mono { font-family: var(--mi-mono); }
  table.mi-table td.mi-bad  { color: var(--mi-error); }

  .mi-code {
    background: var(--mi-surface-2);
    color: var(--mi-text);
    padding: 1rem 1.25rem;
    border-radius: var(--mi-radius);
    margin: 0;
    font-family: var(--mi-mono);
    font-size: 0.82rem;
    line-height: 1.55;
    overflow-x: auto;
  }

  /* Copy-to-clipboard icon button — sits in the top-right of a
     code panel. The wrapping `.mi-code-wrap` provides the
     positioning context so the button anchors to the <pre>
     itself (not the surrounding card). */
  .mi-code-wrap { position: relative; }

  .mi-copy-btn {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    width: 1.75rem;
    height: 1.75rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--mi-border);
    background: var(--mi-surface);
    color: var(--mi-text-dim);
    border-radius: var(--mi-radius-sm);
    cursor: pointer;
    padding: 0;
    transition: color 120ms ease, background 120ms ease, border-color 120ms ease;
  }
  .mi-copy-btn:hover { color: var(--mi-text); background: var(--mi-surface-2); }
  .mi-copy-btn svg {
    width: 14px;
    height: 14px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .mi-copy-btn .mi-copy-icon-check { display: none; }
  .mi-copy-btn[data-copied="true"] {
    color: var(--mi-accent);
    border-color: var(--mi-accent);
  }
  .mi-copy-btn[data-copied="true"] .mi-copy-icon-clipboard { display: none; }
  .mi-copy-btn[data-copied="true"] .mi-copy-icon-check { display: inline; }

  code, .mi-code-inline {
    font-family: var(--mi-mono);
    background: var(--mi-surface-2);
    padding: 0.1rem 0.35rem;
    border-radius: 0.25rem;
    font-size: 0.85em;
  }

  .mi-pill {
    display: inline-block;
    background: var(--mi-pill-bg);
    color: var(--mi-pill-fg);
    padding: 0.1rem 0.55rem;
    border-radius: var(--mi-radius-pill);
    font-size: 0.7rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-left: 0.4rem;
  }

  /* ── Theming the embedded components ────────────────────── */
  /* The components ship their own CSS file via @import — these
     overrides bind the components to the visualizer's theme so
     they switch with the page. */

  .money-input-wrapper {
    background: var(--mi-surface-2);
    border-color: var(--mi-border);
  }
  .money-input-wrapper:focus-within {
    border-color: var(--mi-accent);
    outline-color: var(--mi-accent);
    box-shadow: 0 0 0 3px var(--mi-accent-soft);
  }
  .money-input-field { color: var(--mi-text); background: transparent; }
  .money-input-symbol {
    background: var(--mi-surface);
    color: var(--mi-text);
    border-color: var(--mi-border);
  }
  .currency-picker-trigger {
    background: var(--mi-surface);
    color: var(--mi-text);
  }
  .currency-picker-trigger:hover { background: var(--mi-surface-2); }
  .currency-picker-overlay {
    background: var(--mi-surface);
    border-color: var(--mi-border);
    color: var(--mi-text);
    box-shadow: var(--mi-shadow-md);
  }
  .currency-picker-search-row { border-color: var(--mi-border); }
  .currency-picker-search {
    background: var(--mi-surface-2);
    color: var(--mi-text);
    border-color: var(--mi-border);
  }
  .currency-picker-search:focus {
    outline: none;
    border-color: var(--mi-accent);
    box-shadow: 0 0 0 3px var(--mi-accent-soft);
  }
  .currency-picker-section { color: var(--mi-text-dim); }
  .currency-picker-row:hover { background: var(--mi-accent-soft); }
  .currency-picker-row[aria-selected="true"] {
    background: var(--mi-accent);
    color: var(--mi-accent-fg);
  }
  .currency-picker-row-symbol { color: var(--mi-text-faint); }
  .currency-picker-empty { color: var(--mi-text-dim); }

  /* ── Footer ────────────────────────────────────────────── */

  .mi-footer {
    margin-top: 3rem;
    color: var(--mi-text-faint);
    font-size: 0.82rem;
    text-align: center;
  }
  """

  @doc "Returns the visualizer's CSS as a binary."
  @spec css() :: String.t()
  def css, do: @css

  @external_resource Path.join(:code.priv_dir(:ex_money_input), "static/money_input.css")
  @external_resource Path.join(:code.priv_dir(:ex_money_input), "static/money_input.js")
  @external_resource Path.join(:code.priv_dir(:ex_money_input), "static/money.png")

  @money_input_css File.read!(
                     Path.join(:code.priv_dir(:ex_money_input), "static/money_input.css")
                   )
  @money_input_js File.read!(Path.join(:code.priv_dir(:ex_money_input), "static/money_input.js"))
  @money_logo_png File.read!(Path.join(:code.priv_dir(:ex_money_input), "static/money.png"))

  @doc "Returns the component CSS shipped in priv/static."
  @spec money_input_css() :: String.t()
  def money_input_css, do: @money_input_css

  @doc "Returns the JS hooks shipped in priv/static."
  @spec money_input_js() :: String.t()
  def money_input_js, do: @money_input_js

  @doc """
  Returns the binary PNG bytes of the Money library's logo.

  Compiled into the BEAM at build time so the visualizer and
  any wrapper app (e.g. `money_input_playground`) can serve it
  without a separate static-files plug.
  """
  def logo_png, do: @money_logo_png
end