Exclusivobeta

As Of <mv-as-of>

Control de punto en el tiempo (“as of”) para apps de datos que pueden mostrar el pasado, para que las cifras históricas nunca se confundan con las actuales. Tres filas permanecen siempre visibles: Viewing (el momento elegido, o los datos en vivo, con un campo de fecha o de fecha y hora, pasos de ±1 hora/día/semana/mes/trimestre/año que respetan el fin de mes y un menú Jump to con atajos como “End of last quarter” y destinos recientes), Now (un reloj en vivo con la zona horaria) y Left from (el momento en el que estabas antes del último salto, a un clic para volver, así que comparar dos fechas es un simple ir y venir). Mientras ves el pasado, un banner tranquilo (“You’re viewing data as of Jun 30, 2026. Return to now”) y unos marcos opcionales alrededor de tus regiones de datos lo dicen con palabras, y cada control [data-as-of-live-only] de su ámbito (botones de editar, ajustar, guardar) queda inerte hasta que vuelves. Cada salto pasa por un mv-travel cancelable cuyo waitUntil(promise) muestra un estado pendiente mientras vuelves a cargar los datos, y se revierte con un anuncio si la carga falla; los pasos rápidos se agrupan en un solo viaje, el futuro se limita al presente en vivo y cualquier momento anterior a min a los datos más antiguos disponibles, el momento puede vivir en un parámetro de URL (con soporte de atrás/adelante) y los destinos recientes pueden persistir en localStorage.

CategoríaNavegación
TipoWeb Component (<mv-as-of>)
Estadobeta
KitNavegación para usuarios avanzados
También instalabutton, input, select
Keywordsexclusive, culture, point-in-time, as-of, time-travel, history, snapshot, audit, versioning, dashboard, report, financial, period-end, read-only, url-state, temporal

When to use

  • A dashboard or report can be recomputed as of a past date and viewers must never confuse it with current figures
  • An audit or history view shows a configuration, permissions or inventory exactly as it stood at a given moment
  • Financial statements are reviewed at period ends (month, quarter, year) with quick back-and-forth between two dates
  • Editing must be blocked while someone is looking at a historical snapshot, and resume as soon as they return to now

Avoid when

  • The user picks a date as form input (due date, booking, birthday), not a moment to view the whole app at → use Date Picker instead
  • The job is to display one moment or deadline clearly across time zones → use Dateline instead
  • The need is to undo or compare edits of a single form or document rather than view data at a past moment → use Rewind instead

Instalación

node scripts/add.mjs as-of --out ./src/marvelous

Agente de IA con el servidor MCP de Marvelous UI: install_components({ slugs: ["as-of"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Archivos copiados (dependencias incluidas): tokens/tokens.css, core/base.css, core/dismiss.js, core/dom.js, core/element.js, core/focus.js, core/observe.js, core/position.js, components/as-of/as-of.js, components/as-of/as-of.css, components/button/button.css, components/input/input.css, components/select/select.css.

Uso

Inicio rápido, el marcado mínimo que funciona:

<mv-as-of frame="#report" min="2024-01-01"></mv-as-of>
<section id="report">…your figures…</section>

Marcado de referencia: parte de él y personalízalo con atributos, data-* y variables CSS:

<div id="ao-demo" style="width:min(100%,66rem);margin-inline:auto">
  <style>
    #ao-demo { display:grid; grid-template-columns:minmax(0,1fr); gap:2.5rem; align-content:start }
    #ao-demo .ao-main { display:grid; grid-template-columns:minmax(0,1fr) 24rem; gap:1.5rem; align-items:start }
    #ao-demo .ao-report { display:grid; gap:1rem; padding:1.25rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-xs) }
    #ao-demo .ao-top { display:flex; align-items:flex-start; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap }
    #ao-demo .ao-top h3 { margin:0; font-size:1.0625rem; letter-spacing:-.01em }
    #ao-demo .ao-top p { margin:.125rem 0 0; color:var(--mv-fg-muted); font-size:.8125rem }
    #ao-demo .ao-actions { display:flex; gap:.5rem; flex-wrap:wrap }
    #ao-demo .ao-kpis { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:.75rem; margin:0 }
    #ao-demo .ao-kpi { display:grid; gap:.25rem; padding:.75rem .875rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-lg); background:var(--mv-bg-subtle) }
    #ao-demo .ao-kpi dt { color:var(--mv-fg-muted); font-size:.75rem }
    #ao-demo .ao-kpi dd { margin:0; font-size:1.25rem; font-weight:650; letter-spacing:-.02em; font-variant-numeric:tabular-nums }
    #ao-demo .ao-kpi small { display:block; color:var(--mv-fg-muted); font-size:.6875rem; font-weight:500; letter-spacing:0 }
    #ao-demo table { width:100%; border-collapse:collapse; font-size:.8125rem }
    #ao-demo caption { text-align:start; padding:0 0 .5rem; color:var(--mv-fg-muted); font-size:.75rem; font-weight:500 }
    #ao-demo th, #ao-demo td { padding:.5rem .625rem; border-bottom:1px solid var(--mv-border); text-align:start; white-space:nowrap }
    #ao-demo th { color:var(--mv-fg-muted); font-size:.6875rem; font-weight:600; letter-spacing:.04em; text-transform:uppercase }
    #ao-demo td.num, #ao-demo th.num { text-align:end; font-variant-numeric:tabular-nums }
    #ao-demo tr:last-child td { border-bottom:0 }
    #ao-demo .ao-side { display:grid; gap:.875rem }
    #ao-demo .ao-panel { display:grid; gap:.625rem; padding:.875rem 1rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-lg); background:var(--mv-bg-subtle); font-size:.8125rem }
    #ao-demo .ao-log { min-height:1.25rem; margin:0; color:var(--mv-fg-subtle); font:.75rem/1.45 var(--mv-font-mono); overflow-wrap:anywhere }
    #ao-demo .ao-h { margin:0 0 .75rem; font-size:.8125rem; font-weight:600; color:var(--mv-fg-muted) }
    #ao-demo .ao-flags { padding:1rem 1.25rem .75rem; border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); margin-top:1.25rem }
    #ao-demo .ao-flag-name { font-family:var(--mv-font-mono); font-size:.75rem }
    @media (max-width:62rem) { #ao-demo .ao-main { grid-template-columns:minmax(0,1fr) } }
    @media (max-width:40rem) { #ao-demo .ao-kpis { grid-template-columns:repeat(2,minmax(0,1fr)) } #ao-demo .ao-hide-sm { display:none } #ao-demo td:first-child { white-space:normal } #ao-demo .ao-report, #ao-demo .ao-flags { padding-inline:.875rem } #ao-demo th, #ao-demo td { padding-inline:.375rem } }
  </style>

  <!-- 1. Inventory report with the stacked control beside it -->
  <div class="ao-main" id="ao-app">
    <section class="ao-report" id="ao-report" aria-labelledby="ao-report-title">
      <div class="ao-top">
        <div>
          <h3 id="ao-report-title">Inventory &amp; revenue</h3>
          <p>Northwind Supply · all warehouses</p>
        </div>
        <div class="ao-actions">
          <button type="button" class="mv-button" data-variant="outline" data-size="sm" data-as-of-live-only>Adjust stock</button>
          <button type="button" class="mv-button" data-size="sm" data-as-of-live-only>Edit forecast</button>
        </div>
      </div>
      <dl class="ao-kpis" id="ao-kpis"></dl>
      <table>
        <caption id="ao-caption">Top SKUs by value on hand</caption>
        <thead><tr><th scope="col">SKU</th><th scope="col" class="ao-hide-sm">Warehouse</th><th scope="col" class="num">On hand</th><th scope="col" class="num">Value</th><th scope="col">Status</th></tr></thead>
        <tbody id="ao-rows"></tbody>
      </table>
    </section>

    <div class="ao-side">
      <mv-as-of id="ao-main" param="asof" frame="#ao-report" scope="#ao-app" min="2024-01-01" storage-key="ao-demo-recent"></mv-as-of>
      <div class="ao-panel">
        <label class="mv-choice" data-control="end">
          <input type="checkbox" role="switch" class="mv-switch" id="ao-fail">
          <span class="mv-choice-text"><span class="mv-choice-title">Make the next fetch fail</span><span class="mv-choice-description">The jump is rolled back and announced</span></span>
        </label>
        <p class="ao-log" id="ao-log" aria-live="polite">Loading…</p>
      </div>
    </div>
  </div>

  <!-- 2. Bar layout, minute precision: a production config audited at a past moment -->
  <div>
    <p class="ao-h">Feature flags · production (bar layout, minute precision)</p>
    <div id="ao-flags-app">
      <mv-as-of id="ao-bar" layout="bar" precision="minute" steps="hour, day, week" unit="hour" presets="7d, 30d, last-month" frame="#ao-flags" scope="#ao-flags-app" label="Config point in time"></mv-as-of>
      <div class="ao-flags" id="ao-flags">
        <table>
          <caption id="ao-flag-caption">Flags</caption>
          <thead><tr><th scope="col">Flag</th><th scope="col">State</th><th scope="col" class="num">Rollout</th><th scope="col" class="ao-hide-sm">Changed by</th><th scope="col"><span class="mv-sr-only">Actions</span></th></tr></thead>
          <tbody id="ao-flag-rows"></tbody>
        </table>
      </div>
    </div>
  </div>

  <script type="module">
    const main = document.getElementById("ao-main");
    const bar = document.getElementById("ao-bar");
    const log = document.getElementById("ao-log");
    const fail = document.getElementById("ao-fail");
    const usd = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 });
    const int = new Intl.NumberFormat("en-US");
    const short = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric" });

    // Deterministic "database": the same moment always returns the same figures.
    const seed = (str) => { let h = 2166136261; for (const c of str) { h ^= c.charCodeAt(0); h = Math.imul(h, 16777619); } return () => ((h = Math.imul(h ^ (h >>> 15), 2246822507) ^ Math.imul(h ^ (h >>> 13), 3266489909)) >>> 0) / 4294967296; };
    const cell = (text, cls) => { const td = document.createElement("td"); if (cls) td.className = cls; td.append(text); return td; };
    const badge = (text, variant) => { const b = document.createElement("span"); b.className = "mv-badge"; b.dataset.variant = variant; b.dataset.size = "sm"; b.textContent = text; return b; };

    const SKUS = [
      ["Ceramic pour-over set", "Rotterdam"], ["Linen apron, charcoal", "Reno"], ["Cast-iron skillet 26 cm", "Rotterdam"],
      ["Oak cutting board", "Monterrey"], ["Stoneware mug, set of 4", "Reno"],
    ];
    function renderReport(date) {
      const key = date ? date.toISOString().slice(0, 10) : "live";
      const r = seed(key);
      const f = date ? 0.82 + r() * 0.16 : 1;
      const kpis = [
        ["Revenue, trailing 30 days", usd.format(Math.round(412_800 * f)), date ? `as of ${short.format(date)}` : "live"],
        ["Units on hand", int.format(Math.round(18_420 * (0.9 + r() * 0.2))), "5 warehouses"],
        ["Open orders", int.format(Math.round(236 * (0.7 + r() * 0.5))), "awaiting shipment"],
        ["Gross margin", `${(41.2 + (r() - 0.5) * 6).toFixed(1)}%`, "trailing 30 days"],
      ];
      document.getElementById("ao-kpis").replaceChildren(...kpis.map(([k, v, s]) => {
        const div = document.createElement("div"); div.className = "ao-kpi";
        const dt = document.createElement("dt"); dt.textContent = k;
        const dd = document.createElement("dd"); dd.textContent = v;
        const sm = document.createElement("small"); sm.textContent = s; dd.append(sm);
        div.append(dt, dd); return div;
      }));
      document.getElementById("ao-caption").textContent = date ? `Top SKUs by value on hand, as of ${short.format(date)}` : "Top SKUs by value on hand, live";
      document.getElementById("ao-rows").replaceChildren(...SKUS.map(([name, wh], i) => {
        const qty = Math.round((1400 - i * 210) * (0.25 + r() * 0.9));
        const value = qty * (18 + (5 - i) * 7);
        const status = qty < 320 ? ["Reorder", "danger"] : qty < 620 ? ["Low", "warning"] : ["In stock", "success"];
        const tr = document.createElement("tr");
        tr.append(cell(name), cell(wh, "ao-hide-sm"), cell(int.format(qty), "num"), cell(usd.format(value), "num"), cell(badge(...status)));
        return tr;
      }));
    }

    const FLAGS = ["checkout.one-page", "search.semantic-ranking", "billing.annual-discount", "mobile.dark-launch"];
    const PEOPLE = ["Aiko Tanaka", "Lucas Moreau", "Priya Raman", "Mateo Silva"];
    function renderFlags(date) {
      const key = date ? `${date.toISOString().slice(0, 13)}` : "live";
      const r = seed(key);
      document.getElementById("ao-flag-caption").textContent = date ? `Flags as of ${date.toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" })}` : "Flags, live";
      document.getElementById("ao-flag-rows").replaceChildren(...FLAGS.map((flag, i) => {
        const on = (i + Math.floor(r() * 3)) % 3 !== 0;
        const rollout = on ? [5, 25, 50, 100][Math.floor(r() * 4)] : 0;
        const tr = document.createElement("tr");
        const name = document.createElement("span"); name.className = "ao-flag-name"; name.textContent = flag;
        const edit = document.createElement("button");
        edit.type = "button"; edit.className = "mv-button"; edit.dataset.variant = "ghost"; edit.dataset.size = "sm";
        edit.dataset.asOfLiveOnly = ""; edit.textContent = "Edit"; edit.setAttribute("aria-label", `Edit ${flag}`);
        tr.append(cell(name), cell(badge(on ? "On" : "Off", on ? "success" : "secondary")), cell(`${rollout}%`, "num"), cell(PEOPLE[(i + Math.floor(r() * 4)) % 4], "ao-hide-sm"), cell(edit, "num"));
        return tr;
      }));
    }

    // A fake API: 450 ms round trip, optionally failing once.
    const fetchAsOf = (reason) => new Promise((resolve, reject) => {
      if (reason === "api") return resolve();
      setTimeout(() => {
        if (fail.checked) { fail.checked = false; reject(new Error("504 Gateway Timeout")); } else resolve();
      }, 450);
    });
    const label = (d) => (d ? short.format(d) : "live");

    main.addEventListener("mv-travel", (e) => {
      const { from, to, reason } = e.detail;
      log.textContent = `GET /reports/inventory?asof=${e.detail.toValue ?? "now"} · ${reason}`;
      e.detail.waitUntil(fetchAsOf(reason).then(() => renderReport(to)));
    });
    main.addEventListener("mv-arrive", (e) => { log.textContent = `mv-arrive · ${label(e.detail.from)} → ${label(e.detail.to)}`; });
    main.addEventListener("mv-travel-error", (e) => { log.textContent = `Fetch failed (${e.detail.error.message}) · rolled back to ${label(e.detail.from)}`; renderReport(e.detail.from); });

    bar.addEventListener("mv-travel", (e) => { e.detail.waitUntil(fetchAsOf(e.detail.reason).then(() => renderFlags(e.detail.to))); });

    // Start in the past so all three rows are filled: live → end of last month → end of last quarter.
    const now = new Date();
    const lastQuarter = new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 0);
    let first = new Date(now.getFullYear(), now.getMonth(), 0);
    if (+first === +lastQuarter) first = new Date(now.getFullYear(), now.getMonth() - 1, 0);
    renderReport(main.date);
    renderFlags(null);
    if (main.live) {
      main.travel(first).then(() => main.travel(lastQuarter));
    }
    const twoDaysAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2, 16, 45);
    bar.travel(twoDaysAgo);
  </script>
</div>

Referencia cultural

Regreso al futuro, Robert Zemeckis (1985, película). El tablero de la máquina del tiempo muestra siempre tres indicadores, hora de destino, hora actual y hora de la última salida, para que el viajero nunca pierda la noción de cuándo está respecto al punto de partida. En la interfaz, una app de datos muestra esas mismas tres filas (el momento consultado, el presente en vivo y el momento que dejaste antes del último salto, a un clic de distancia), con un banner tranquilo, datos enmarcados y ediciones bloqueadas siempre que estás en el pasado.

API

Attributes

NameTipoDefaultDescription
valuestring ("2026-06-30" | "2026-06-30T17:30" | ISO with offset)The viewed moment as a local wall time; absent = live. Reflected after every travel. Setting it from outside applies silently (no mv-travel, Left from unchanged): call travel() to go through the event flow. Future moments resolve to live, earlier ones than min are clamped to min.
precisionday | minutedayday: a date field, and a value means the end of that day (close of business); picking today means live. minute: a date-time field and values to the minute; anything within the last minute means live.
mindate or date-timeEarliest moment with data (retention start, company founding). Stepping and typing clamp to it and the clamp is announced; the back step is disabled there.
stepscomma list of hour | day | week | month | quarter | yearday, week, monthUnits offered in the step-size select (hidden when only one). Month, quarter and year steps keep end of month sticky: Mar 31 → Apr 30 → May 31.
unitone of stepsday (or the first step)Initially selected step unit.
presetscomma list of yesterday | last-week | last-month | last-quarter | last-year | 7d | 30d | 90d | 1yyesterday, last-week, last-month, last-quarter, last-yearBuilt-in presets of the Jump to menu, resolved against the current date when the menu opens (weeks follow the locale's first day). The presets property also accepts custom presets.
paramstringURL search parameter that holds the moment (e.g. asof). Read on first connection (a deep link wins over value), written after every travel, removed when live; popstate travels with reason "history".
param-modereplace | pushreplacereplace keeps one history entry; push adds an entry per travel so Back/Forward walk through visited moments.
frameCSS selectorData regions to mark while in the past: data-as-of-past (tinted outline + an “As of …” tag drawn with ::before), aria-busy during a pending travel and aria-describedby pointing to the banner text. They get position: relative (zero specificity, your styles win).
scopeCSS selectordocumentRoot in which [data-as-of-live-only] elements are governed, including ones added later. Set it when a page has several instances.
layoutstack | barstackstack: three stacked rows (sidebar, panel). bar: three columns for the top of a dashboard (from 46rem of container width, stacked below), banner under the bar.
localeBCP 47en-USDate, time, relative-time formatting and first day of the week.
labelstringPoint in timeAccessible name of the three-row group.
history-sizenumber5How many recent destinations the Jump to menu keeps (0 disables them).
storage-keystringlocalStorage key to persist recent destinations across visits (failures are ignored).
settlenumber (ms)400Quiet time after the last step click before a single travel is emitted. Typed dates settle after 900 ms, or at once on Enter or blur; Escape discards a typed draft.
data-statelive | pastSet by the component. data-pending is present while waitUntil promises are pending, data-drafting while steps settle.
data-as-of-live-onlyboolean (on your elements)Elements in scope with this attribute become inert (plus data-as-of-locked for styling, dimmed by default) while viewing the past; their previous inert state is restored when you return or when the component is removed.

Properties

NameTipoDescription
dateDate | nullThe viewed moment (end of day with precision="day"), null when live (read-only).
livebooleanTrue while viewing live data (read-only).
departed{ value, date, live, at } | nullWhere you were before the last jump (the Left from row); at is when you left (read-only).
recentstring[]Recent destinations as normalized values, newest first. Settable (e.g. from your server).
pendingbooleanTrue while the last travel waits for its waitUntil promises (read-only).
presetsstring | Array<{ label, date: Date | string | (now: Date) => Date }>A string mirrors the attribute; an array replaces the built-in presets (e.g. fiscal periods, release dates, “Before the migration”).
stringsPartial<Record<string, string>>Overrides for every visible text and announcement (viewing, now, departed, live, past, liveTitle, liveSub, endOfDay, bannerText ({date}), bannerNote, bannerLocked, returnToNow, frameLabel ({date}), jump, presets, recent, goBack, goBackTo ({date}), stepBack / stepForward ({unit}), unitDay…, presetLastQuarter…, announceTravel ({date}), announceLive, announceMin, announceError ({date}, {current})…). English defaults.

Methods

NameDescription
travel(target, { reason? })Travel to a Date, a value string, or null / "now" for live. Emits mv-travel (cancelable), applies the moment, waits for waitUntil promises, then emits mv-arrive. Returns Promise<boolean> (false if cancelled, unchanged, superseded or failed).
returnToNow()Travel to live data (reason "return").
goBack()Travel to the Left from moment (reason "back"); calling it twice toggles between two moments.
step(n = -1, unit?)Step n units from the viewed moment (live counts as now); consecutive steps settle into one travel (reason "step").

Events

NameDescription
mv-travelCancelable, before a jump. detail: { from, to (Date | null = live), fromValue, toValue, live, reason: "step" | "input" | "preset" | "recent" | "back" | "return" | "history" | "api", waitUntil(promise) }. Refetch here and pass the promise to waitUntil: frames get aria-busy and a progress line runs; a rejection rolls the moment, Left from and recent back. preventDefault() refuses the jump.
mv-arriveThe jump is complete (after waitUntil promises resolved). detail: { from, to, fromValue, toValue, live, reason }. The arrival is announced politely.
mv-travel-errorA waitUntil promise rejected and the jump was rolled back. detail: { from, to, fromValue, toValue, reason, error }.

CSS classes

NameDescription
mv-as-of-bannerPast-mode region: -banner-icon, -banner-text (-banner-lead with <time class="mv-as-of-banner-time">, -banner-note) and .mv-as-of-return.
mv-as-of-consoleThe labeled group of three .mv-as-of-row[data-row="viewing | now | departed"], each with -head (.mv-as-of-label), -moment (<time class="mv-as-of-time"> + -sub) and -action. The viewing row gets data-past.
mv-as-of-controlsViewing row controls: -stepper (.mv-as-of-step[data-dir] + .mv-as-of-input), .mv-as-of-unit, .mv-as-of-jump; .mv-as-of-state is the Live / Past badge (data-tone), .mv-as-of-progress the pending line.
mv-as-of-menuJump to menu (Popover API top layer): -menu-group, -menu-heading, .mv-as-of-option (aria-current="date" on the viewed moment) with -option-check, -option-label, -option-sub.

CSS variables

NameDefaultDescription
--mv-as-of-tintvar(--mv-warning)Past-mode color: banner, Viewing row edge, Past badge, frame outline and tag (also read by frames outside the component).
--mv-as-of-livevar(--mv-success)Live color: Live badge and the breathing Now marker.
--mv-as-of-label-width6.75remWidth of the row label column in the stack layout.

Accessibility

The three rows are a labeled group (label, “Point in time” by default) of three nested groups named by their visible labels (Viewing, Now, Left from), each exposing its moment in a real <time datetime> element, so a screen reader reads “Viewing, group, Tue, Jun 30, 2026, End of day · 3 months ago”. Every control is a native element reachable with Tab: back / forward step buttons named “Back 1 month” / “Forward 1 month” (aria-disabled rather than disabled at the limits, so focus is never lost), a labeled date or date-time field (Enter commits, Escape discards the draft), a labeled step-size select, the Jump to button (aria-expanded, aria-controls) and “Go back”, whose accessible name includes the destination (“Go back to Monday, August 31, 2026”). The Jump to menu opens with focus on the current moment, moves with arrow keys, Home and End (roving tabindex), marks the viewed moment with aria-current="date" and a check icon, and closes on Escape or outside click, returning focus to its button. Each arrival is announced once in a polite live region (“Now viewing data as of Tuesday, June 30, 2026.” / “Back to live data.”), as are clamps to the earliest data and failed fetches with their rollback; the Now clock updates silently. The past is never signaled by color alone: the banner is a named region (“Historical view”) with words, a <time> and a Return to now button, the badge says Live or Past with a distinct icon, and framed regions get an “As of …” tag plus aria-describedby pointing to the banner text and aria-busy while data reloads. Live-only controls become inert (out of the tab order and the accessibility tree) and the banner says editing is paused; if a control that had focus disappears, focus moves to the date field. Reduced motion (OS or data-motion="reduce"): the Now marker stops breathing and the loading line becomes static. Forced colors: borders use CanvasText and the past edge, frames and progress use Highlight.

Esta página se tradujo con IA. Informar de un problema de traducción