npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

awesome-risk-radar-kpi

v0.1.0

Published

Enterprise-grade interactive Risk Radar KPI component for React — spider/radar chart with drilldown, anomaly detection, trend analysis, multi-filter, and export.

Readme

awesome-risk-radar-kpi

Enterprise-grade interactive Risk Radar KPI component for React. A spider/radar chart that visualises multi-dimensional risk exposure with drilldown panels, anomaly detection, trend arrows, multi-filter controls, comparison mode, and one-click export to PNG, SVG, JSON, or CSV — all styles injected at runtime so no CSS import is needed.


Features

  • Spider / radar chart — SVG-based 8-axis risk polygon with configurable grid levels and axis labels
  • 3 risk score modelsmatrix (probability × impact bands), weighted (configurable weights), exposure (probability × impact × exposure)
  • Trend detection — per-node trend arrows (▲/▼/●) from trendData time series using a 2% threshold
  • Anomaly detection — statistical outlier detection (2σ above mean) highlights sudden risk spikes with a ! badge and "Sudden spike" tag
  • Drilldown panel — click any node to open a detailed panel with owner, probability, impact, last updated, and drivers
  • Hover tooltip — floating panel with full risk detail on mouse-over
  • Multi-filter controls — filter by Department, Region, Risk Owner, and Time Period simultaneously
  • Top Ranked Risks sidebar — automatically sorted top-5 by risk score with colour-coded values
  • Risk Highlights sidebar — shows all risks with trend labels and anomaly tags
  • Comparison mode — overlay a second dataset on the radar for period-over-period or scenario comparison
  • 4 built-in themeslight, dark, purple, corporate; all colours driven by a theme object
  • Export — one-click PNG, SVG, JSON, and CSV export of the current filtered dataset
  • High contrast modehighContrast prop applies CSS contrast filter
  • Self-contained — CSS Module replaced with runtime <style> injection; no CSS import required
  • ESM + CJS dual build — works in Vite, Next.js, CRA, and any modern bundler
  • Zero runtime dependencies beyond React

Installation

npm install awesome-risk-radar-kpi
# or
yarn add awesome-risk-radar-kpi
# or
pnpm add awesome-risk-radar-kpi

Peer dependencies — React ≥ 17 and ReactDOM ≥ 17 must already be installed.


Quick Start

No CSS import needed — styles are injected automatically on first render.

import AwesomeRiskRadarKpi from "awesome-risk-radar-kpi";

var data = [
  {
    id: "RISK001",
    category: "Supply Chain",
    probability: 0.7,
    impact: 85,
    trendData: [65, 70, 72, 74, 78],
    owner: "Operations",
    department: "Logistics",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-10",
    drivers: ["Supplier delay"],
  },
  {
    id: "RISK002",
    category: "Cybersecurity",
    probability: 0.9,
    impact: 95,
    trendData: [85, 88, 90, 91, 92],
    owner: "Security",
    department: "IT",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-14",
    drivers: ["Ransomware"],
  },
  {
    id: "RISK003",
    category: "Financial Risk",
    probability: 0.8,
    impact: 88,
    trendData: [70, 74, 76, 80, 82],
    owner: "Finance",
    department: "Treasury",
    region: "North America",
    period: "Q1 2026",
    lastUpdated: "2026-02-12",
  },
  {
    id: "RISK004",
    category: "Market Risk",
    probability: 0.75,
    impact: 76,
    trendData: [68, 70, 71, 72, 73],
    owner: "Strategy",
    department: "Market Intelligence",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-11",
  },
];

export default function App() {
  return (
    <div style={{ padding: 32 }}>
      <AwesomeRiskRadarKpi data={data} />
    </div>
  );
}

Props

| Prop | Type | Default | Description | | ----------------- | ---------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------- | | data | RiskItem[] | — | Static array of risk objects. Takes precedence over dataUrl and apiEndpoint. | | dataUrl | string | '/data/riskRadarData.json' | Path to a local JSON file. Used when data is not provided. | | apiEndpoint | string | — | REST API URL. Used when neither data nor dataUrl resolves. | | size | number | 520 | SVG canvas size in px (width and height of the radar chart). | | thresholdRules | { low: number, moderate: number } | { low: 40, moderate: 70 } | Risk colour thresholds. Scores below low → green, below moderate → amber, else → red. | | riskScoreModel | 'matrix' \| 'weighted' \| 'exposure' | 'matrix' | Scoring model used when riskScore is not pre-calculated on the item. | | matrixConfig | { likelihoodThresholds: number[], impactThresholds: number[] } | [20,40,60,80] each | Band thresholds for the matrix model. | | weights | { probability: number, impact: number, exposure: number } | 0.45 / 0.45 / 0.1 | Weights for the weighted model. | | trendAdjustment | { multiplier: number, mode: 'additive' \| 'multiplicative' } | — | Optional trend-based score adjustment applied after model scoring. | | showTrend | boolean | true | Show trend arrows (▲/▼/●) on nodes and the trend legend. | | showLabels | boolean | true | Show axis labels on the radar chart. | | animation | boolean | true | Fade-in animation on mount. | | compareMode | string | — | Label for comparison mode (e.g. 'Q4 2025'). Enables overlay of compareData. | | compareData | RiskItem[] | — | Second dataset to overlay on the radar in grey. | | theme | 'light' \| 'dark' \| 'purple' \| 'corporate' | 'light' | Built-in colour theme. | | colorScheme | Partial<ThemeObject> | — | Override individual theme tokens. Merged on top of the resolved theme. | | showFilters | boolean | true | Show the Department / Region / Owner / Period filter dropdowns. | | highContrast | boolean | false | Apply CSS contrast(1.1) filter for accessibility. | | onDrilldown | (category: string) => void | — | Fired when a risk node is clicked. Receives the category name. |

RiskItem object

| Field | Type | Required | Description | | ------------- | ---------- | -------- | -------------------------------------------------------------------------- | | id | string | ✅ | Unique identifier. Used as React key. | | category | string | ✅ | Must match one of the 8 radar axes (see below). | | probability | number | ✅ | 0–1 (fraction) or 0–100 (percentage). Auto-normalised. | | impact | number | ✅ | 0–1 or 0–100. Auto-normalised. | | riskScore | number | ➖ | Pre-calculated 0–100 score. If supplied, skips model calculation. | | exposure | number | ➖ | 0–1 or 0–100. Used by exposure and weighted models. | | threshold | number | ➖ | Per-item colour threshold override (0–100). | | trendData | number[] | ➖ | Historical score series (oldest → newest) for trend and anomaly detection. | | owner | string | ➖ | Risk owner name shown in tooltip and drilldown. | | department | string | ➖ | Department used as filter option. | | region | string | ➖ | Region used as filter option. | | period | string | ➖ | Time period used as filter option. | | lastUpdated | string | ➖ | Date string shown in tooltip and drilldown. | | drivers | string[] | ➖ | List of risk drivers shown in the drilldown panel. |

Radar axes (fixed)

The 8 axes are: Supply Chain, Financial Risk, Operational Risk, Cybersecurity, Regulatory Compliance, Market Risk, Customer Risk, Technology Risk. Each RiskItem.category must exactly match one of these to appear on the chart.


Examples

Example 1 — Full 8-axis dataset with dark theme

Uses all 8 risk categories from the sample data, dark theme, and an onDrilldown callback.

import AwesomeRiskRadarKpi from "awesome-risk-radar-kpi";

var riskData = [
  {
    id: "RISK001",
    category: "Supply Chain",
    probability: 0.7,
    impact: 85,
    exposure: 0.9,
    threshold: 60,
    trendData: [65, 70, 72, 74, 78],
    owner: "Operations",
    department: "Logistics",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-10",
    drivers: ["Supplier delay", "Shipping cost spike", "Port congestion"],
  },
  {
    id: "RISK002",
    category: "Financial Risk",
    probability: 0.8,
    impact: 88,
    exposure: 0.85,
    threshold: 65,
    trendData: [70, 74, 76, 80, 82],
    owner: "Finance",
    department: "Treasury",
    region: "North America",
    period: "Q1 2026",
    lastUpdated: "2026-02-12",
    drivers: ["Interest rate volatility", "Liquidity tightening"],
  },
  {
    id: "RISK003",
    category: "Operational Risk",
    probability: 0.6,
    impact: 70,
    exposure: 0.7,
    threshold: 55,
    trendData: [60, 61, 63, 64, 64],
    owner: "Operations",
    department: "Manufacturing",
    region: "Europe",
    period: "Q1 2026",
    lastUpdated: "2026-02-08",
    drivers: ["Workforce shortage", "Equipment downtime"],
  },
  {
    id: "RISK004",
    category: "Cybersecurity",
    probability: 0.9,
    impact: 95,
    exposure: 0.95,
    threshold: 70,
    trendData: [85, 88, 90, 91, 92],
    owner: "Security",
    department: "IT",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-14",
    drivers: ["Ransomware attempts", "Third-party exposure"],
  },
  {
    id: "RISK005",
    category: "Regulatory Compliance",
    probability: 0.55,
    impact: 60,
    exposure: 0.6,
    threshold: 60,
    trendData: [62, 60, 59, 58, 58],
    owner: "Legal",
    department: "Compliance",
    region: "APAC",
    period: "Q1 2026",
    lastUpdated: "2026-02-05",
    drivers: ["New reporting mandates", "Audit backlog"],
  },
  {
    id: "RISK006",
    category: "Market Risk",
    probability: 0.75,
    impact: 76,
    exposure: 0.8,
    threshold: 62,
    trendData: [68, 70, 71, 72, 73],
    owner: "Strategy",
    department: "Market Intelligence",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-11",
    drivers: ["Competitor pricing", "Demand volatility"],
  },
  {
    id: "RISK007",
    category: "Customer Risk",
    probability: 0.6,
    impact: 65,
    exposure: 0.7,
    threshold: 55,
    trendData: [58, 59, 60, 61, 61],
    owner: "Customer Success",
    department: "Sales",
    region: "Latin America",
    period: "Q1 2026",
    lastUpdated: "2026-02-09",
    drivers: ["Churn risk", "Service backlog"],
  },
  {
    id: "RISK008",
    category: "Technology Risk",
    probability: 0.65,
    impact: 72,
    exposure: 0.75,
    threshold: 58,
    trendData: [62, 64, 66, 68, 69],
    owner: "Technology",
    department: "Engineering",
    region: "North America",
    period: "Q1 2026",
    lastUpdated: "2026-02-13",
    drivers: ["Legacy system aging", "Scalability constraints"],
  },
];

export default function RiskDashboard() {
  return (
    <div style={{ padding: 32, background: "#111827", minHeight: "100vh" }}>
      <AwesomeRiskRadarKpi
        data={riskData}
        theme="dark"
        size={540}
        riskScoreModel="matrix"
        thresholdRules={{ low: 40, moderate: 70 }}
        showTrend={true}
        showLabels={true}
        animation={true}
        onDrilldown={function (category) {
          console.log("Drilldown on:", category);
        }}
      />
    </div>
  );
}

Example 2 — Corporate theme with weighted model, filters hidden, onDrilldown routing

Uses the weighted scoring model, disables the filter bar, and routes drilldown clicks to a custom panel.

import { useState } from "react";
import AwesomeRiskRadarKpi from "awesome-risk-radar-kpi";

var riskData = [
  {
    id: "RISK001",
    category: "Supply Chain",
    probability: 0.7,
    impact: 85,
    exposure: 0.9,
    trendData: [65, 70, 72, 74, 78],
    owner: "Operations",
    department: "Logistics",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-10",
    drivers: ["Supplier delay", "Shipping cost spike", "Port congestion"],
  },
  {
    id: "RISK002",
    category: "Financial Risk",
    probability: 0.8,
    impact: 88,
    exposure: 0.85,
    trendData: [70, 74, 76, 80, 82],
    owner: "Finance",
    department: "Treasury",
    region: "North America",
    period: "Q1 2026",
    lastUpdated: "2026-02-12",
    drivers: ["Interest rate volatility", "Liquidity tightening"],
  },
  {
    id: "RISK003",
    category: "Operational Risk",
    probability: 0.6,
    impact: 70,
    exposure: 0.7,
    trendData: [60, 61, 63, 64, 64],
    owner: "Operations",
    department: "Manufacturing",
    region: "Europe",
    period: "Q1 2026",
    lastUpdated: "2026-02-08",
    drivers: ["Workforce shortage", "Equipment downtime"],
  },
  {
    id: "RISK004",
    category: "Cybersecurity",
    probability: 0.9,
    impact: 95,
    exposure: 0.95,
    trendData: [85, 88, 90, 91, 92],
    owner: "Security",
    department: "IT",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-14",
    drivers: ["Ransomware attempts", "Third-party exposure"],
  },
  {
    id: "RISK005",
    category: "Regulatory Compliance",
    probability: 0.55,
    impact: 60,
    exposure: 0.6,
    trendData: [62, 60, 59, 58, 58],
    owner: "Legal",
    department: "Compliance",
    region: "APAC",
    period: "Q1 2026",
    lastUpdated: "2026-02-05",
    drivers: ["New reporting mandates", "Audit backlog"],
  },
  {
    id: "RISK006",
    category: "Market Risk",
    probability: 0.75,
    impact: 76,
    exposure: 0.8,
    trendData: [68, 70, 71, 72, 73],
    owner: "Strategy",
    department: "Market Intelligence",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-11",
    drivers: ["Competitor pricing", "Demand volatility"],
  },
  {
    id: "RISK007",
    category: "Customer Risk",
    probability: 0.6,
    impact: 65,
    exposure: 0.7,
    trendData: [58, 59, 60, 61, 61],
    owner: "Customer Success",
    department: "Sales",
    region: "Latin America",
    period: "Q1 2026",
    lastUpdated: "2026-02-09",
    drivers: ["Churn risk", "Service backlog"],
  },
  {
    id: "RISK008",
    category: "Technology Risk",
    probability: 0.65,
    impact: 72,
    exposure: 0.75,
    trendData: [62, 64, 66, 68, 69],
    owner: "Technology",
    department: "Engineering",
    region: "North America",
    period: "Q1 2026",
    lastUpdated: "2026-02-13",
    drivers: ["Legacy system aging", "Scalability constraints"],
  },
];

var drawerStyle = {
  position: "fixed",
  top: 0,
  right: 0,
  width: 320,
  height: "100vh",
  background: "#ffffff",
  borderLeft: "1px solid #e5e7eb",
  padding: 28,
  zIndex: 200,
  fontFamily: "sans-serif",
  overflowY: "auto",
};

export default function CorporateRiskDashboard() {
  var [selected, setSelected] = useState(null);

  return (
    <div style={{ padding: 32, background: "#f3f4f6", minHeight: "100vh" }}>
      <AwesomeRiskRadarKpi
        data={riskData}
        theme="corporate"
        size={500}
        riskScoreModel="weighted"
        weights={{ probability: 0.5, impact: 0.4, exposure: 0.1 }}
        thresholdRules={{ low: 35, moderate: 65 }}
        showFilters={false}
        showTrend={true}
        animation={true}
        onDrilldown={function (category) {
          var item = riskData.find(function (r) {
            return r.category === category;
          });
          setSelected(item || null);
        }}
      />

      {selected && (
        <div style={drawerStyle}>
          <button
            onClick={function () {
              setSelected(null);
            }}
            style={{
              float: "right",
              background: "none",
              border: "1px solid #d1d5db",
              borderRadius: 4,
              padding: "4px 10px",
              cursor: "pointer",
            }}
          >
            ✕
          </button>
          <h2 style={{ fontSize: 18, fontWeight: 700, marginBottom: 4 }}>
            {selected.category}
          </h2>
          <p style={{ fontSize: 12, color: "#6b7280", marginBottom: 16 }}>
            Owner: {selected.owner}
          </p>
          <hr style={{ marginBottom: 16 }} />
          <p style={{ fontSize: 13 }}>
            Probability: {(selected.probability * 100).toFixed(0)}%
          </p>
          <p style={{ fontSize: 13 }}>Impact: {selected.impact}</p>
          <p style={{ fontSize: 13 }}>Last Updated: {selected.lastUpdated}</p>
          {selected.drivers && (
            <>
              <p style={{ fontSize: 13, fontWeight: 600, marginTop: 12 }}>
                Drivers:
              </p>
              <ul style={{ fontSize: 13, paddingLeft: 18 }}>
                {selected.drivers.map(function (d) {
                  return <li key={d}>{d}</li>;
                })}
              </ul>
            </>
          )}
        </div>
      )}
    </div>
  );
}

Example 3 — Comparison mode (Q1 vs Q4 prior year) with purple theme and REST fallback

Overlays a prior-period dataset on the radar for visual comparison, with a live API endpoint and a static fallback.

import AwesomeRiskRadarKpi from "awesome-risk-radar-kpi";

// Current period — Q1 2026
var currentData = [
  {
    id: "RISK001",
    category: "Supply Chain",
    probability: 0.7,
    impact: 85,
    trendData: [65, 70, 72, 74, 78],
    owner: "Operations",
    department: "Logistics",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-10",
    drivers: ["Supplier delay", "Port congestion"],
  },
  {
    id: "RISK002",
    category: "Financial Risk",
    probability: 0.8,
    impact: 88,
    trendData: [70, 74, 76, 80, 82],
    owner: "Finance",
    department: "Treasury",
    region: "North America",
    period: "Q1 2026",
    lastUpdated: "2026-02-12",
    drivers: ["Interest rate volatility"],
  },
  {
    id: "RISK003",
    category: "Operational Risk",
    probability: 0.6,
    impact: 70,
    trendData: [60, 61, 63, 64, 64],
    owner: "Operations",
    department: "Manufacturing",
    region: "Europe",
    period: "Q1 2026",
    lastUpdated: "2026-02-08",
    drivers: ["Workforce shortage"],
  },
  {
    id: "RISK004",
    category: "Cybersecurity",
    probability: 0.9,
    impact: 95,
    trendData: [85, 88, 90, 91, 92],
    owner: "Security",
    department: "IT",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-14",
    drivers: ["Ransomware attempts"],
  },
  {
    id: "RISK005",
    category: "Regulatory Compliance",
    probability: 0.55,
    impact: 60,
    trendData: [62, 60, 59, 58, 58],
    owner: "Legal",
    department: "Compliance",
    region: "APAC",
    period: "Q1 2026",
    lastUpdated: "2026-02-05",
    drivers: ["New reporting mandates"],
  },
  {
    id: "RISK006",
    category: "Market Risk",
    probability: 0.75,
    impact: 76,
    trendData: [68, 70, 71, 72, 73],
    owner: "Strategy",
    department: "Market Intelligence",
    region: "Global",
    period: "Q1 2026",
    lastUpdated: "2026-02-11",
    drivers: ["Competitor pricing"],
  },
  {
    id: "RISK007",
    category: "Customer Risk",
    probability: 0.6,
    impact: 65,
    trendData: [58, 59, 60, 61, 61],
    owner: "Customer Success",
    department: "Sales",
    region: "Latin America",
    period: "Q1 2026",
    lastUpdated: "2026-02-09",
    drivers: ["Churn risk"],
  },
  {
    id: "RISK008",
    category: "Technology Risk",
    probability: 0.65,
    impact: 72,
    trendData: [62, 64, 66, 68, 69],
    owner: "Technology",
    department: "Engineering",
    region: "North America",
    period: "Q1 2026",
    lastUpdated: "2026-02-13",
    drivers: ["Legacy system aging"],
  },
];

// Prior period — Q4 2025 (lower scores for comparison)
var priorData = [
  { id: "P001", category: "Supply Chain", riskScore: 55 },
  { id: "P002", category: "Financial Risk", riskScore: 61 },
  { id: "P003", category: "Operational Risk", riskScore: 48 },
  { id: "P004", category: "Cybersecurity", riskScore: 72 },
  { id: "P005", category: "Regulatory Compliance", riskScore: 44 },
  { id: "P006", category: "Market Risk", riskScore: 58 },
  { id: "P007", category: "Customer Risk", riskScore: 42 },
  { id: "P008", category: "Technology Risk", riskScore: 50 },
];

export default function ComparisonDashboard() {
  return (
    <div style={{ padding: 32, background: "#f6f0ff", minHeight: "100vh" }}>
      <AwesomeRiskRadarKpi
        data={currentData}
        theme="purple"
        size={520}
        riskScoreModel="matrix"
        compareMode="Q4 2025"
        compareData={priorData}
        showTrend={true}
        showLabels={true}
        highContrast={false}
        animation={true}
        onDrilldown={function (category) {
          console.log("Drilldown:", category);
        }}
      />
      <p
        style={{
          textAlign: "center",
          marginTop: 12,
          fontSize: 13,
          color: "#6f5c8f",
        }}
      >
        Solid polygon = Q1 2026 · Outline polygon = Q4 2025 (comparison)
      </p>
    </div>
  );
}

Themes

Pass the theme prop with one of the built-in names, or pass colorScheme to override individual tokens:

// Built-in themes
<AwesomeRiskRadarKpi theme="dark" ... />
<AwesomeRiskRadarKpi theme="light" ... />
<AwesomeRiskRadarKpi theme="purple" ... />
<AwesomeRiskRadarKpi theme="corporate" ... />

// Custom token overrides (merged on top of light theme)
<AwesomeRiskRadarKpi
  colorScheme={{
    accent: '#e11d48',
    low: '#059669',
    moderate: '#d97706',
    high: '#dc2626',
  }}
/>

Theme token reference

| Token | Description | | ------------ | -------------------------------------------------------- | | background | Outer container background | | panel | Card / tooltip / drilldown background | | text | Primary text colour | | mutedText | Secondary / muted text | | grid | Radar grid polygon stroke | | axis | Axis line stroke | | low | Colour for low-risk nodes (score < thresholdRules.low) | | moderate | Colour for moderate-risk nodes | | high | Colour for high-risk nodes | | accent | Primary radar polygon fill / stroke | | compare | Comparison dataset polygon fill / stroke |


Risk Score Models

matrix (default)

Bands probability and impact into N levels (default 5 each using [20,40,60,80] thresholds), multiplies the band indices, and normalises to 0–100.

<AwesomeRiskRadarKpi
  riskScoreModel="matrix"
  matrixConfig={{
    likelihoodThresholds: [25, 50, 75],
    impactThresholds: [25, 50, 75],
  }}
/>

weighted

Weighted average of probability, impact, and exposure.

<AwesomeRiskRadarKpi
  riskScoreModel="weighted"
  weights={{ probability: 0.5, impact: 0.4, exposure: 0.1 }}
/>

exposure

score = (probability × impact × exposure) / 10000

<AwesomeRiskRadarKpi riskScoreModel="exposure" />

📄 License

MIT