pub const CHART_CSS: &str = "/* forge-charts \u{2014} default stylesheet.\n *\n * Consumers inject this once at the app root via\n * <Stylesheet text=forge_charts::CHART_CSS />\n *\n * Theme via CSS variables on `:root` or any parent of `.charts-root`.\n * Per-series colors come from `--charts-series-<color_class>` /\n * `--charts-series-<color_class>-soft` (the soft variant fades into\n * the gradient stop). The defaults below give a clean ApexCharts-\n * inspired blue/teal palette; override per-app as needed.\n */\n\n:root {\n --charts-fg: rgb(17 24 39);\n --charts-fg-muted: rgb(107 114 128);\n --charts-fg-faint: rgb(156 163 175);\n --charts-grid-color: rgba(17, 24, 39, 0.06);\n --charts-bg: transparent;\n\n /* Default series palette. Add more --charts-series-<name>* pairs\n * in the consumer app to extend. */\n --charts-series-opened: rgb(43, 127, 255);\n --charts-series-opened-soft: rgba(142, 197, 255, 0.55);\n --charts-series-closed: rgb(34, 197, 94);\n --charts-series-closed-soft: rgba(134, 239, 172, 0.55);\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --charts-fg: rgb(229 231 235);\n --charts-fg-muted: rgb(156 163 175);\n --charts-fg-faint: rgb(107 114 128);\n --charts-grid-color: rgba(229, 231, 235, 0.08);\n }\n}\n\n/* ---------- outer container ---------- */\n\n.charts-root {\n display: flex;\n flex-direction: column;\n width: 100%;\n /* X-axis strip geometry \u{2014} ONE source of truth. The tick-label row\n * (`--charts-x-axis-label`) plus the gap above it\n * (`--charts-x-axis-gap`) make up the strip footprint\n * (`--charts-x-axis-height`). The Y-tick scale spacer and the\n * crosshair/dots overlays all offset by that footprint so the plot\'s\n * 0%..100% lines up with the SVG baseline. Previously these were four\n * separate `26px`/`22px`/`4px` literals with \"keep in sync\" comments;\n * deriving them here means a consumer can retune the axis in one place\n * and nothing can drift. */\n --charts-x-axis-gap: 4px;\n --charts-x-axis-label: 22px;\n --charts-x-axis-height: calc(var(--charts-x-axis-label) + var(--charts-x-axis-gap));\n /* Set by the AreaChart component via inline style:\n * --charts-height: 320px;\n * Falls back to 320px if not provided.\n *\n * Use a *fixed* height (not min-height) so the legend + plot live\n * inside a bounded box. With min-height the SVG\'s intrinsic\n * viewBox aspect ratio can drive the column taller than its track\n * and visually bleed into the legend above it. */\n height: var(--charts-height, 320px);\n color: var(--charts-fg);\n background: var(--charts-bg);\n font-family: inherit;\n font-size: 12px;\n position: relative;\n}\n\n.charts-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 1;\n color: var(--charts-fg-faint);\n font-style: italic;\n}\n\n/* ---------- legend ---------- */\n\n.charts-legend {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n /* Wider horizontal gap so adjacent legend items feel like distinct\n * controls instead of one run-on row; row-gap keeps wrapped lines\n * from sticking together when the chart is narrow. */\n column-gap: 28px;\n row-gap: 8px;\n padding: 6px 8px 14px 8px;\n color: var(--charts-fg-muted);\n}\n.charts-legend-item {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n/* Hidden series (toggled off via a name click): dim the whole item and\n * strike the label so it\'s obviously excluded from the plot. */\n.charts-legend-item.is-hidden {\n opacity: 0.4;\n}\n.charts-legend-item.is-hidden .charts-legend-name {\n text-decoration: line-through;\n}\n/* The swatch (dot + hidden color input) is the color-picker affordance;\n * the name beside it is the show/hide toggle. Keeping them separate is\n * what lets a name click mean \"hide\" instead of \"open picker\". */\n.charts-legend-swatch {\n position: relative;\n display: inline-flex;\n align-items: center;\n cursor: pointer;\n}\n.charts-legend-name {\n cursor: pointer;\n}\n.charts-legend-dot {\n display: inline-block;\n width: 10px;\n height: 10px;\n border-radius: 999px;\n background: currentColor;\n}\n.charts-legend-name {\n color: var(--charts-fg);\n font-size: 12.5px;\n}\n/* The swatch is a <label> wrapping a hidden color input on top of the\n * dot. Clicking the dot opens the native picker; the dot stays the\n * visible affordance. `appearance: none` plus sizing the input to match\n * the dot keeps the picker invisible on Chromium + Firefox + Safari. */\n.charts-legend-item {\n user-select: none;\n}\n.charts-legend-swatch:hover .charts-legend-dot {\n transform: scale(1.15);\n transition: transform 120ms ease-out;\n}\n.charts-legend-color-input {\n position: absolute;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n width: 10px;\n height: 10px;\n padding: 0;\n margin: 0;\n border: none;\n outline: none;\n background: transparent;\n opacity: 0;\n cursor: pointer;\n -webkit-appearance: none;\n appearance: none;\n}\n.charts-legend-color-input::-webkit-color-swatch-wrapper { padding: 0; }\n.charts-legend-color-input::-webkit-color-swatch { border: none; }\n\n/* ---------- plot area ---------- */\n\n.charts-plot {\n /* Y-axis gutter is a CSS var so consumers with wide tick labels\n * (durations like \"33m 20s\", byte counts) can widen it without\n * forking the stylesheet. Default fits ~5 digits. */\n display: grid;\n grid-template-columns: var(--charts-y-gutter, 52px) 1fr;\n flex: 1;\n min-height: 0;\n position: relative;\n}\n\n.charts-y-axis {\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n/* The scale holds the tick labels and is sized to the SVG (flex:1).\n * The ::after spacer matches the x-axis strip footprint\n * (`--charts-x-axis-height`) so the scale\'s 0%..100% lines up with the\n * plot\'s top edge and baseline. */\n.charts-y-axis-scale {\n position: relative;\n flex: 1 1 0;\n min-height: 0;\n}\n.charts-y-axis::after {\n content: \"\";\n display: block;\n flex: 0 0 var(--charts-x-axis-height);\n}\n.charts-y-tick {\n position: absolute;\n right: 8px;\n transform: translateY(-50%);\n color: var(--charts-fg-muted);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.charts-plot-area {\n position: relative;\n display: flex;\n flex-direction: column;\n /* min-* both zero \u{2014} without these the flex algorithm refuses to\n * shrink the SVG below its intrinsic 800x280 box and we end up\n * overflowing the parent row in either axis. */\n min-width: 0;\n min-height: 0;\n}\n.charts-svg {\n /* Explicit basis of 0 + min-height: 0 lets the flex algorithm size\n * the SVG strictly to its track. Without `min-height: 0` an SVG\n * with a viewBox falls back to its intrinsic aspect-ratio height\n * and bleeds out of the plot area. `overflow: visible` lets hover\n * dots near the bottom render as full circles instead of being\n * clipped against the X axis \u{2014} the sizing rule above is what\n * actually keeps the bleed honest, not clipping. */\n flex: 1 1 0;\n min-height: 0;\n width: 100%;\n display: block;\n overflow: visible;\n}\n.charts-x-axis {\n position: relative;\n height: var(--charts-x-axis-label);\n margin-top: var(--charts-x-axis-gap);\n}\n.charts-x-tick {\n position: absolute;\n transform: translateX(-50%);\n color: var(--charts-fg-muted);\n font-size: 11px;\n white-space: nowrap;\n}\n\n/* ---------- gridlines ---------- */\n\n.charts-grid-line {\n stroke: var(--charts-grid-color);\n stroke-width: 1;\n vector-effect: non-scaling-stroke;\n}\n\n/* ---------- series + animations ---------- */\n\n/*\n * Animation strategy: SVG `d` attributes can\'t be CSS-interpolated,\n * so we don\'t try to morph paths on data change. Instead each\n * series group `scaleY` animates from 0\u{2192}1 against the chart\'s\n * bottom, giving the \"rise from baseline\" reveal that reads as a\n * polished intro (and replays on the next data change because the\n * group\'s key signature, derived from the data, shifts under it).\n *\n * Honor `prefers-reduced-motion`: skip the transform animation,\n * keep the opacity fade so the chart still appears smoothly.\n */\n@keyframes charts-series-rise {\n from {\n transform: scaleY(0);\n opacity: 0;\n }\n to {\n transform: scaleY(1);\n opacity: 1;\n }\n}\n@keyframes charts-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.charts-series-paths {\n transform-origin: 0 100%;\n animation: charts-series-rise 650ms cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n/* Stagger the second series slightly so they feel layered rather\n * than rising in lockstep. Generalize to per-series if we ever ship\n * more than ~3 series. */\n.charts-series-paths:nth-child(2) { animation-delay: 80ms; }\n.charts-series-paths:nth-child(3) { animation-delay: 160ms; }\n\n.charts-y-axis,\n.charts-x-axis,\n.charts-grid {\n animation: charts-fade-in 500ms ease-out both;\n animation-delay: 100ms;\n}\n.charts-legend {\n animation: charts-fade-in 400ms ease-out both;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .charts-series-paths,\n .charts-y-axis,\n .charts-x-axis,\n .charts-grid,\n .charts-legend {\n animation: none;\n }\n}\n\n.charts-area {\n /* fill via the <linearGradient id=\"charts-grad-<color>\"> ref */\n stroke: none;\n opacity: 1;\n /* Phase B will toggle opacity on hover; transition primes the\n * change to be smooth. */\n transition: opacity 180ms ease-out;\n}\n.charts-line {\n stroke-width: 2;\n vector-effect: non-scaling-stroke;\n fill: none;\n transition: stroke-width 180ms ease-out;\n}\n\n/* ---------- crosshair + hover dots (Phase B) ---------- */\n\n/* Crosshairs live in this HTML overlay (not as SVG lines) so WebKit\'s\n * SVG stacking quirk can\'t paint a spike\'s filled area over them. Bounds\n * match the dots overlay (and the SVG) so `left:%` maps 1:1. Sits below\n * the dots overlay in document order \u{2192} line under dot, above everything\n * else. */\n.charts-crosshair-overlay {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n /* Offset above the x-axis strip so percent positions map to the plot. */\n bottom: var(--charts-x-axis-height);\n pointer-events: none;\n}\n.charts-crosshair {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 0;\n border-left: 1px dashed var(--charts-fg-faint);\n opacity: 0.85;\n}\n/* Pinned crosshair (set by a click): heavier + more opaque so the\n * parked reference reads as distinct from the live cursor. */\n.charts-crosshair-pinned {\n border-left-width: 1.5px;\n border-left-color: var(--charts-fg-muted);\n opacity: 0.95;\n}\n/* Hover dots live as HTML divs in this overlay rather than as SVG\n * circles. The SVG above uses `preserveAspectRatio=\"none\"` to stretch\n * paths across whatever container width it gets \u{2014} that stretching\n * turned circles into ovals. Rendering dots in HTML/pixel space keeps\n * them round regardless of the chart\'s aspect ratio. The overlay sits\n * over the plot region only (offset above the x-axis) so percent\n * positions map 1:1 to the SVG viewBox underneath. */\n.charts-dots-overlay {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n /* Offset above the x-axis strip so percent positions map to the plot. */\n bottom: var(--charts-x-axis-height);\n pointer-events: none;\n}\n.charts-dot {\n position: absolute;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n transform: translate(-50%, -50%);\n pointer-events: none;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22);\n}\n.charts-dot.charts-series-opened {\n background: var(--charts-series-opened);\n}\n.charts-dot.charts-series-closed {\n background: var(--charts-series-closed);\n}\n\n/* ---------- tooltip card (Phase B) ----------\n * Translucent, rounded, soft shadow. Content is consumer-provided via\n * the `tooltip` prop closure; the crate provides the chrome only. */\n.charts-tooltip {\n position: absolute;\n pointer-events: none;\n z-index: 5;\n min-width: 200px;\n padding: 10px 12px;\n background: rgba(20, 22, 28, 0.92);\n color: rgb(229 231 235);\n border-radius: 10px;\n box-shadow: 0 8px 22px rgba(0, 0, 0, 0.22);\n font-size: 12px;\n line-height: 1.5;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n animation: charts-fade-in 140ms ease-out both;\n}\n@media (prefers-color-scheme: light) {\n .charts-tooltip {\n background: rgba(255, 255, 255, 0.96);\n color: rgb(17 24 39);\n box-shadow: 0 8px 22px rgba(15, 23, 42, 0.16);\n }\n}\n/* Pinned tooltip (parked by a click, shown on every synced chart at the\n * pinned instant). Thin accent ring distinguishes it from the live one,\n * and `min-width` is relaxed since several can be on screen at once. */\n.charts-tooltip-pinned {\n border: 1px solid var(--charts-fg-faint);\n min-width: 0;\n animation: none;\n}\n\n/* Default tooltip row styling (consumer markup uses these classes\n * via the bundled `tooltip_for` example or their own card). */\n.charts-tooltip-card {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.charts-tooltip-date {\n font-weight: 600;\n margin-bottom: 4px;\n color: inherit;\n}\n.charts-tooltip-row {\n display: grid;\n grid-template-columns: auto 1fr auto;\n align-items: center;\n gap: 6px;\n}\n.charts-tooltip-row.sub {\n padding-left: 16px;\n opacity: 0.75;\n font-size: 11px;\n}\n.charts-tooltip-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: currentColor;\n}\n.charts-tooltip-dot.charts-series-opened {\n color: var(--charts-series-opened);\n}\n.charts-tooltip-dot.charts-series-closed {\n color: var(--charts-series-closed);\n}\n.charts-tooltip-label {\n color: inherit;\n}\n.charts-tooltip-value {\n font-variant-numeric: tabular-nums;\n font-weight: 500;\n}\n\n/* Per-series color hooks. Add a CSS rule per series in the consumer\n * app for any color_class beyond the bundled defaults. */\n.charts-line.charts-series-opened {\n stroke: var(--charts-series-opened);\n}\n.charts-line.charts-series-closed {\n stroke: var(--charts-series-closed);\n}\n.charts-legend-item:has(.charts-series-opened) .charts-legend-dot {\n color: var(--charts-series-opened);\n}\n.charts-legend-item:has(.charts-series-closed) .charts-legend-dot {\n color: var(--charts-series-closed);\n}\n\n/* Gradient stops \u{2014} referenced by <stop> inside <linearGradient>. */\n.charts-gradient.charts-series-opened .charts-gradient-top {\n stop-color: var(--charts-series-opened-soft);\n stop-opacity: 0.85;\n}\n.charts-gradient.charts-series-opened .charts-gradient-bottom {\n stop-color: var(--charts-series-opened-soft);\n stop-opacity: 0.05;\n}\n.charts-gradient.charts-series-closed .charts-gradient-top {\n stop-color: var(--charts-series-closed-soft);\n stop-opacity: 0.85;\n}\n.charts-gradient.charts-series-closed .charts-gradient-bottom {\n stop-color: var(--charts-series-closed-soft);\n stop-opacity: 0.05;\n}\n\n/* ---------- Phase C: drag-to-zoom band + reset pill ---------- */\n\n/* Semi-transparent rectangle drawn while the user drags a selection\n * across the plot. Drawn in SVG viewBox units so it stretches with\n * the chart; `pointer-events: none` keeps the drag itself live on the\n * underlying plot area instead of being intercepted by the band. */\n.charts-zoom-band {\n fill: rgba(66, 98, 255, 0.12);\n stroke: rgba(66, 98, 255, 0.55);\n stroke-width: 1;\n stroke-dasharray: 3 2;\n pointer-events: none;\n vector-effect: non-scaling-stroke;\n}\n\n@media (prefers-color-scheme: dark) {\n .charts-zoom-band {\n fill: rgba(120, 150, 255, 0.18);\n stroke: rgba(120, 150, 255, 0.7);\n }\n}\n\n/* Reset-zoom pill \u{2014} absolute-positioned top-right of the plot area.\n * Only mounted when a zoom is active. Mirrors the tooltip card\'s\n * translucent visual language. */\n.charts-zoom-reset {\n position: absolute;\n top: 8px;\n right: 8px;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 10px;\n font: inherit;\n font-size: 11px;\n font-weight: 500;\n color: var(--charts-fg);\n background: rgba(255, 255, 255, 0.88);\n border: 1px solid rgba(17, 24, 39, 0.12);\n border-radius: 999px;\n box-shadow: 0 1px 2px rgba(17, 24, 39, 0.06);\n cursor: pointer;\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n transition: background-color 120ms ease, box-shadow 120ms ease;\n}\n.charts-zoom-reset:hover {\n background: rgba(255, 255, 255, 1);\n box-shadow: 0 2px 6px rgba(17, 24, 39, 0.1);\n}\n.charts-zoom-reset:focus-visible {\n outline: 2px solid var(--charts-series-opened);\n outline-offset: 2px;\n}\n\n@media (prefers-color-scheme: dark) {\n .charts-zoom-reset {\n background: rgba(31, 41, 55, 0.85);\n border-color: rgba(229, 231, 235, 0.18);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);\n }\n .charts-zoom-reset:hover {\n background: rgba(31, 41, 55, 1);\n }\n}\n";Expand description
Default stylesheet bundled with the crate. Inject once at the app
root via <Stylesheet text=CHART_CSS /> (Leptos meta).