Exclusifbêta

Field Guide <mv-field-guide>

Grille de collection numérotée où chaque entrée a trois niveaux de connaissance, pour que ce qui manque soit aussi visible que ce qui est acquis : unknown (un emplacement numéroté vide au contour pointillé, avec un indice optionnel), seen (l’illustration aplatie en silhouette monochrome, le nom, les détails verrouillés et un indice « comment l’obtenir ») et owned (carte en couleurs avec résumé, description, caractéristiques et appel à l’action). Les trous dans la numérotation deviennent des emplacements sans nom : l’utilisateur voit les manques de la série. La montée de niveau s’anime sur place : la silhouette émerge d’un flou à la découverte, puis l’illustration complète se remplit depuis le bas à l’acquisition, avec une pulsation unique (un simple fondu enchaîné en mouvement réduit). Un en-tête de synthèse affiche « 12 sur 30 utilisées » avec une barre segmentée (owned / seen / unknown, distingués par la forme autant que par la couleur) et des pastilles de filtre avec compteurs ; un panneau de détail sticky suit l’entrée focalisée. La grille est une vraie listbox avec navigation 2D aux flèches par rangée visuelle, Home/End, PageUp/PageDown et recherche par saisie sur les noms ; mark(id, state) la pilote et émet les événements annulables mv-discover et mv-own, la progression peut être conservée dans localStorage (la connaissance ne recule jamais au rechargement), et les libellés d’état s’adaptent au domaine (Utilisé, Obtenu, Terminé…).

CatégorieAffichage de données
TypeWeb Component (<mv-field-guide>)
Statutbêta
Installe aussibutton
Keywordsexclusive, culture, collection, discovery, feature-discovery, achievements, badges, gamification, progress, onboarding, curriculum, unlock, locked, silhouette, listbox, grid, keyboard, persist

When to use

  • A SaaS app wants to show which features a team has used, which it has only heard about and which remain to find
  • Achievements, badges or collectibles where locked items should tease their shape and tell users how to earn them
  • A course or learning path where lessons are locked, unlocked or completed and progress must read at a glance
  • A developer portal tracking which API endpoints or SDKs someone has tried, with a how-to for each untried one

Avoid when

  • The items must be done in a fixed order, one after another, like a checkout or a setup wizard → use Stepper instead
  • Only a single completion percentage matters and individual items don't need to be browsed → use Progress instead
  • The list is a marketing overview of features for prospects, not a record of one user's progress → use Features instead

Installation

node scripts/add.mjs field-guide --out ./src/marvelous

Agent IA avec le serveur MCP Marvelous UI : install_components({ slugs: ["field-guide"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Fichiers copiés (dépendances comprises) : tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/focus.js, components/field-guide/field-guide.js, components/field-guide/field-guide.css, components/button/button.css.

Utilisation

Balisage de référence : partez de celui-ci et personnalisez-le avec les attributs, data-* et les variables CSS :

<div id="fgd-demo" style="width:min(100%,68rem);margin-inline:auto">
  <style>
    #fgd-demo { display:grid; gap:1.25rem; align-content:start }
    #fgd-demo .fgd-card { border:1px solid var(--mv-border); border-radius:var(--mv-radius-xl); background:var(--mv-surface); box-shadow:var(--mv-shadow-sm); overflow:hidden }
    #fgd-demo .fgd-top { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; padding:1rem 1.25rem; border-bottom:1px solid var(--mv-border) }
    #fgd-demo .fgd-top h3 { display:flex; align-items:center; gap:.5rem; margin:0; font-size:1rem; letter-spacing:-.01em }
    #fgd-demo .fgd-top p { margin:.125rem 0 0; color:var(--mv-fgd-muted); font-size:.8125rem }
    #fgd-demo .fgd-inner { padding:1.25rem }
    #fgd-demo .fgd-controls { display:flex; align-items:center; justify-content:space-between; gap:.75rem 1rem; flex-wrap:wrap; padding:.875rem 1.25rem; border-top:1px solid var(--mv-border); background:var(--mv-bg-subtle) }
    #fgd-demo .fgd-actions { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap }
    #fgd-demo .fgd-k { color:var(--mv-fgd-muted); font-size:.75rem; font-weight:600 }
    #fgd-demo .fgd-log { flex:1 1 16rem; min-height:1.25rem; margin:0; color:var(--mv-fgd-subtle); font:.75rem/1.4 var(--mv-font-mono); text-align:end }
    #fgd-demo .fgd-choice { font-size:.8125rem }
  </style>

  <!-- Icon sprite: symbols inherit stroke styling from the component -->
  <svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
    <symbol id="fgd-i-dash" viewBox="0 0 24 24"><rect x="3.5" y="3.5" width="7" height="8" rx="1.5"/><rect x="13.5" y="3.5" width="7" height="5" rx="1.5"/><rect x="13.5" y="11.5" width="7" height="9" rx="1.5"/><rect x="3.5" y="14.5" width="7" height="6" rx="1.5"/></symbol>
    <symbol id="fgd-i-pulse" viewBox="0 0 24 24"><path d="M3 12h4l2.5-6.5 5 13L17 12h4"/></symbol>
    <symbol id="fgd-i-funnel" viewBox="0 0 24 24"><path d="M3.5 4.5h17l-6.5 8v6l-4 2v-8z"/></symbol>
    <symbol id="fgd-i-retention" viewBox="0 0 24 24"><path d="M4 4v16h16"/><path d="M7.5 8.5c2 5 5.5 7.5 11 8"/></symbol>
    <symbol id="fgd-i-cohort" viewBox="0 0 24 24"><circle cx="9" cy="8.5" r="3.5"/><path d="M2.5 20a6.5 6.5 0 0 1 13 0"/><path d="M16 5a3.5 3.5 0 0 1 0 7"/><path d="M18.5 14.5A6.5 6.5 0 0 1 21.5 20"/></symbol>
    <symbol id="fgd-i-replay" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2.5"/><path d="M10 9.25v5.5L14.75 12z"/></symbol>
    <symbol id="fgd-i-bell" viewBox="0 0 24 24"><path d="M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 2h-15z"/><path d="M10 21h4"/></symbol>
    <symbol id="fgd-i-calendar" viewBox="0 0 24 24"><rect x="3.5" y="5" width="17" height="15.5" rx="2.5"/><path d="M3.5 10h17M8 3v4M16 3v4"/></symbol>
    <symbol id="fgd-i-flame" viewBox="0 0 24 24"><path d="M12 3c.8 3.2 5 5.4 5 10a5 5 0 0 1-10 0c0-2.2 1-3.6 2.2-4.6.2 1.8 1 2.8 2.3 3.1C11 8.8 10.8 6 12 3z"/></symbol>
    <symbol id="fgd-i-flag" viewBox="0 0 24 24"><path d="M5.5 21V4"/><path d="M5.5 4.5h12l-2.5 4 2.5 4h-12"/></symbol>
    <symbol id="fgd-i-beaker" viewBox="0 0 24 24"><path d="M9 3.5h6M10 3.5v6l-5.2 8.6A2 2 0 0 0 6.5 21h11a2 2 0 0 0 1.7-2.9L14 9.5v-6"/><path d="M7.5 15h9"/></symbol>
    <symbol id="fgd-i-db" viewBox="0 0 24 24"><ellipse cx="12" cy="5.5" rx="7" ry="2.5"/><path d="M5 5.5v13c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5v-13"/><path d="M5 12c0 1.4 3.1 2.5 7 2.5s7-1.1 7-2.5"/></symbol>
    <symbol id="fgd-i-link" viewBox="0 0 24 24"><path d="M9.5 17H7a5 5 0 0 1 0-10h2.5"/><path d="M14.5 7H17a5 5 0 0 1 0 10h-2.5"/><path d="M8 12h8"/></symbol>
    <symbol id="fgd-i-sync" viewBox="0 0 24 24"><path d="M19.5 10.5A7.5 7.5 0 0 0 6 6.8L4.5 8.5"/><path d="M4.5 4.5v4h4"/><path d="M4.5 13.5A7.5 7.5 0 0 0 18 17.2l1.5-1.7"/><path d="M19.5 19.5v-4h-4"/></symbol>
    <symbol id="fgd-i-note" viewBox="0 0 24 24"><path d="M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v9a1.5 1.5 0 0 1-1.5 1.5H9l-5 4z"/></symbol>
    <symbol id="fgd-i-share" viewBox="0 0 24 24"><circle cx="17.5" cy="5.5" r="2.5"/><circle cx="6.5" cy="12" r="2.5"/><circle cx="17.5" cy="18.5" r="2.5"/><path d="m8.7 10.8 6.6-4M8.7 13.2l6.6 4"/></symbol>
    <symbol id="fgd-i-shield" viewBox="0 0 24 24"><path d="M12 3 5 6v5.5c0 4.3 3 7.9 7 9.5 4-1.6 7-5.2 7-9.5V6z"/></symbol>
    <symbol id="fgd-i-key" viewBox="0 0 24 24"><circle cx="8" cy="15.5" r="4"/><path d="m11 12.5 8.5-8.5M16.5 7l2.5 2.5"/></symbol>
    <symbol id="fgd-i-file" viewBox="0 0 24 24"><path d="M14 3.5H7A1.5 1.5 0 0 0 5.5 5v14A1.5 1.5 0 0 0 7 20.5h10a1.5 1.5 0 0 0 1.5-1.5V8z"/><path d="M14 3.5V8h4.5M9 13h6M9 16.5h4"/></symbol>
    <symbol id="fgd-i-code" viewBox="0 0 24 24"><path d="m8 8-4 4 4 4M16 8l4 4-4 4M13.5 5l-3 14"/></symbol>
    <symbol id="fgd-i-phone" viewBox="0 0 24 24"><rect x="7" y="2.5" width="10" height="19" rx="2.5"/><path d="M11 18.5h2"/></symbol>
    <symbol id="fgd-i-spark" viewBox="0 0 24 24"><path d="m11 3 1.9 5.1L18 10l-5.1 1.9L11 17l-1.9-5.1L4 10l5.1-1.9z"/><path d="m18.5 15 .8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8z"/></symbol>
  </svg>

  <!-- Main: feature discovery in a product analytics app -->
  <section class="fgd-card" aria-label="Feature discovery">
    <div class="fgd-top">
      <div>
        <h3>Your team in Lumen <span class="mv-badge" data-variant="info" data-shape="pill">Northwind Labs</span></h3>
        <p>What your workspace uses, what it has seen and what is still waiting to be found.</p>
      </div>
      <label class="mv-choice fgd-choice" data-control="end">
        <input type="checkbox" role="switch" class="mv-switch" id="fgd-panel" checked>
        <span class="mv-choice-text"><span class="mv-choice-title">Details panel</span></span>
      </label>
    </div>
    <div class="fgd-inner">
      <mv-field-guide id="fgd-main" label="Lumen features" owned-label="Used" total="24" selected="webhooks">
        <div data-entry="dashboards" data-number="1" data-state="owned" data-name="Dashboards" data-icon="#fgd-i-dash" data-summary="42 dashboards · used today" data-description="Pin charts, tables and notes to shared boards that refresh every five minutes." data-href="#dashboards"></div>
        <div data-entry="events" data-number="2" data-state="owned" data-name="Event tracking" data-icon="#fgd-i-pulse" data-summary="3.2M events this month" data-href="#events"></div>
        <div data-entry="funnels" data-number="3" data-state="owned" data-name="Funnels" data-icon="#fgd-i-funnel" data-summary="Used 2 days ago" data-href="#funnels"></div>
        <div data-entry="retention" data-number="4" data-state="owned" data-name="Retention" data-icon="#fgd-i-retention" data-summary="Used last week" data-href="#retention"></div>
        <div data-entry="cohorts" data-number="5" data-state="seen" data-name="Cohorts" data-icon="#fgd-i-cohort" data-hint="Save a segment from any chart to turn it into a cohort." data-href="#cohorts"></div>
        <div data-entry="session-replay" data-number="6" data-state="seen" data-name="Session replay" data-icon="#fgd-i-replay" data-hint="Install the web SDK v4 and turn on recording." data-href="#replay" data-cta="Set up recording" data-summary="First replay watched today" data-description="Watch real sessions with clicks, scrolls and rage-clicks highlighted, linked to every funnel step."></div>
        <div data-entry="alerts" data-number="7" data-state="owned" data-name="Alerts" data-icon="#fgd-i-bell" data-summary="6 alerts · last fired Sep 21" data-href="#alerts"></div>
        <div data-entry="reports" data-number="8" data-state="owned" data-name="Scheduled reports" data-icon="#fgd-i-calendar" data-summary="Weekly to 14 people" data-href="#reports"></div>
        <div data-entry="heatmaps" data-number="9" data-state="unknown" data-name="Heatmaps" data-icon="#fgd-i-flame" data-hint="Hidden in the page menu of any chart." data-href="#heatmaps" data-cta="Open a heatmap"></div>
        <div data-entry="flags" data-number="10" data-state="seen" data-name="Feature flags" data-icon="#fgd-i-flag" data-hint="Create a flag from Settings, then Experiments." data-href="#flags"></div>
        <div data-entry="ab-tests" data-number="12" data-state="unknown" data-name="A/B tests" data-icon="#fgd-i-beaker" data-hint="Unlocks after your first feature flag."></div>
        <div data-entry="sql" data-number="13" data-state="owned" data-name="SQL editor" data-icon="#fgd-i-db" data-summary="118 saved queries" data-href="#sql">
          <dl data-details class="mv-field-guide-facts">
            <div><dt>Queries this month</dt><dd>1,284</dd></div>
            <div><dt>Median run time</dt><dd>1.8 s</dd></div>
          </dl>
        </div>
        <div data-entry="webhooks" data-number="14" data-state="seen" data-name="Webhooks" data-icon="#fgd-i-link" data-hint="Add an endpoint in Settings, then Developers, and send a test event." data-href="#webhooks" data-cta="Set up webhooks" data-summary="1 endpoint · 200 OK" data-description="Stream events to your own servers in real time, signed and retried for 72 hours."></div>
        <div data-entry="warehouse" data-number="15" data-state="unknown" data-name="Warehouse sync" data-icon="#fgd-i-sync" data-hint="Available on the Scale plan."></div>
        <div data-entry="annotations" data-number="16" data-state="owned" data-name="Annotations" data-icon="#fgd-i-note" data-summary="Used yesterday" data-href="#annotations"></div>
        <div data-entry="shared-links" data-number="17" data-state="owned" data-name="Shared links" data-icon="#fgd-i-share" data-summary="9 public links" data-href="#links"></div>
        <div data-entry="roles" data-number="18" data-state="seen" data-name="Roles &amp; permissions" data-icon="#fgd-i-shield" data-hint="Invite a teammate as Viewer to try custom roles." data-href="#roles"></div>
        <div data-entry="sso" data-number="19" data-state="unknown" data-name="Single sign-on" data-icon="#fgd-i-key" data-hint="Available on the Enterprise plan."></div>
        <div data-entry="audit-log" data-number="20" data-state="unknown" data-name="Audit log" data-icon="#fgd-i-file"></div>
        <div data-entry="api-keys" data-number="21" data-state="owned" data-name="API keys" data-icon="#fgd-i-code" data-summary="3 active keys" data-href="#keys"></div>
        <div data-entry="mobile-sdk" data-number="23" data-state="seen" data-name="Mobile SDK" data-icon="#fgd-i-phone" data-hint="Add the iOS or Android package and send one event." data-href="#mobile"></div>
        <div data-entry="ai-insights" data-number="24" data-state="unknown" data-name="AI insights" data-icon="#fgd-i-spark" data-hint="Rolling out this fall."></div>
      </mv-field-guide>
    </div>
    <div class="fgd-controls">
      <span class="fgd-actions">
        <span class="fgd-k">Simulate:</span>
        <button type="button" class="mv-button" data-variant="outline" data-size="sm" id="fgd-discover">Open a heatmap</button>
        <button type="button" class="mv-button" data-variant="outline" data-size="sm" id="fgd-webhook">Send a test webhook</button>
        <button type="button" class="mv-button" data-variant="outline" data-size="sm" id="fgd-replay">Watch a replay</button>
        <button type="button" class="mv-button" data-variant="ghost" data-size="sm" id="fgd-reset">Reset</button>
      </span>
      <p class="fgd-log" id="fgd-log" aria-live="polite">Arrow keys move through the grid · type a name to jump</p>
    </div>
  </section>

  <!-- Compact: a learning path without the side panel -->
  <section class="fgd-card" aria-label="Learning path">
    <div class="fgd-top">
      <div>
        <h3>SQL for analysts</h3>
        <p>Lessons unlock as you go; locked ones say what opens them.</p>
      </div>
    </div>
    <div class="fgd-inner">
      <mv-field-guide id="fgd-path" label="Learning path" owned-label="Completed" seen-label="Unlocked" unknown-label="Locked" density="compact" detail="none" filters="false">
        <div data-entry="select" data-state="owned" data-name="SELECT basics" data-icon="#fgd-i-db" data-summary="12 min"></div>
        <div data-entry="where" data-state="owned" data-name="Filtering rows" data-icon="#fgd-i-funnel" data-summary="15 min"></div>
        <div data-entry="order" data-state="owned" data-name="Sorting" data-icon="#fgd-i-retention" data-summary="8 min"></div>
        <div data-entry="group" data-state="owned" data-name="Aggregates" data-icon="#fgd-i-dash" data-summary="20 min"></div>
        <div data-entry="joins" data-state="seen" data-name="Joins" data-icon="#fgd-i-link" data-hint="Next up · 25 min"></div>
        <div data-entry="subqueries" data-state="seen" data-name="Subqueries" data-icon="#fgd-i-code" data-hint="18 min"></div>
        <div data-entry="dates" data-state="unknown" data-name="Dates &amp; time" data-icon="#fgd-i-calendar" data-hint="After Joins"></div>
        <div data-entry="windows" data-state="unknown" data-name="Window functions" data-icon="#fgd-i-pulse" data-hint="After Subqueries"></div>
        <div data-entry="ctes" data-state="unknown" data-name="CTEs" data-icon="#fgd-i-file"></div>
        <div data-entry="perf" data-state="unknown" data-name="Query performance" data-icon="#fgd-i-flame"></div>
        <div data-entry="capstone" data-state="unknown" data-name="Capstone project" data-icon="#fgd-i-spark" data-hint="Final lesson"></div>
      </mv-field-guide>
    </div>
  </section>

  <script type="module">
    const guide = document.getElementById("fgd-main");
    await customElements.whenDefined("mv-field-guide");
    const $ = (id) => document.getElementById(id);
    const log = $("fgd-log");
    const initial = guide.state;
    const say = (text) => { log.textContent = text; };

    const sync = () => {
      const s = guide.state;
      $("fgd-discover").disabled = s.heatmaps !== "unknown";
      $("fgd-webhook").disabled = s.webhooks === "owned";
      $("fgd-replay").disabled = s["session-replay"] === "owned";
    };
    guide.addEventListener("mv-discover", (e) => say(`mv-discover · ${e.detail.name}`));
    guide.addEventListener("mv-own", (e) => say(`mv-own · ${e.detail.name} (from ${e.detail.from})`));
    guide.addEventListener("mv-select", (e) => say(`mv-select · entry ${e.detail.number} (${e.detail.state})`));
    guide.addEventListener("mv-change", sync);
    guide.addEventListener("click", (e) => { if (e.target.closest("a[href^='#']")) e.preventDefault(); });

    $("fgd-discover").addEventListener("click", () => { guide.select("heatmaps"); guide.mark("heatmaps", "seen", { source: "demo" }); });
    $("fgd-webhook").addEventListener("click", () => { guide.select("webhooks"); guide.mark("webhooks", "owned", { source: "demo" }); });
    $("fgd-replay").addEventListener("click", () => { guide.select("session-replay"); guide.mark("session-replay", "owned", { source: "demo" }); });
    $("fgd-reset").addEventListener("click", () => { guide.state = initial; guide.filter = "all"; guide.select("webhooks"); sync(); say("Progress reset."); });
    $("fgd-panel").addEventListener("change", (e) => { guide.detail = e.target.checked ? "side" : "none"; });
    sync();
  </script>
</div>

Référence culturelle

Pokémon Rouge et Vert, Game Freak / Nintendo (1996, jeu). L’encyclopédie du jeu répertorie chaque créature par numéro, dans trois états : inconnue (juste un numéro), vue (un aperçu et un nom, sans données) et capturée (la fiche complète) ; ce sont les trous de la liste numérotée qui poussent les joueurs à la compléter. Dans l’interface, n’importe quelle collection (fonctionnalités utilisées, badges obtenus, leçons terminées) devient une grille numérotée d’emplacements inconnus, de silhouettes accompagnées d’un indice pour les obtenir et de cartes complètes, où les manques restent visibles et où chaque montée de niveau se révèle sur place.

API

Attributes

NameTypeDefaultDescription
labelstringCollectionName of the collection: shown above the score and used as the accessible name of the grid and the progress bar.
owned-label / seen-label / unknown-labelstringOwned / Seen / MissingDomain words for the three states (e.g. Used / Seen / Missing for features, Completed / Unlocked / Locked for lessons, Earned for badges). Used in the filters, the score (“12 of 30 used”), the detail panel and every accessible name (“seen, not yet used”). Past participles read best.
filterall | owned | seen | unknownallCurrent filter (reflected; the filter pills set it). An entry that changes state under a filter stays visible until the filter changes, so nothing jumps away mid-interaction.
selectedstring (entry id)Active entry shown in the detail panel (reflected as the user moves). Defaults to the first entry.
totalnumberSize of the series. Missing numbers up to it (and any gap between numbered entries) are rendered as nameless unknown slots, so secret entries never reach the DOM.
detailside | below | nonesideDetail panel beside the grid (stacks below it when the element is narrower than 40rem), always below, or hidden.
densitycompactSmaller tiles and artwork, for long series or narrow columns.
silhouetteshape | blurshapeHow seen images are hidden: shape uses the image alpha as a one-tone mask (icons, transparent PNG/SVG); blur shows a grayscale, blurred, low-contrast version (photos). SVG icons are always flattened into a solid shape.
filters"true" | "false"true"false" hides the filter pills (score and bar stay).
persiststringlocalStorage key: progress is saved on every change and restored on load. Stored states only raise what the markup says (knowledge never goes backward); reset() clears it.
data-entry (on children)string (id)Declares an entry in markup. Other fields: data-number, data-state (unknown | seen | owned), data-name, data-hint, data-summary, data-description, data-href, data-cta (link label while not owned; owned entries link as “Open {name}”), data-icon (#sprite-id or image URL), data-image. A child <svg> or <img> is the artwork; a child [data-details] is rich owned-only content moved into the panel.

Properties

NameTypeDescription
entriesArray<{ id, number?, name, state?, hint?, summary?, description?, href?, cta?, icon?: Element | "#sprite-id" | url, image?, details?: Element | Array<{ label, value }> }>Entries as data (replaces markup). Unnumbered entries take the lowest free numbers; an entry without a name can only be unknown. Can be set before the element is defined.
stateRecord<id, "unknown" | "seen" | "owned">Knowledge map. Setting it (e.g. from your server) applies silently: no events, no animation.
counts{ owned, seen, unknown, known, total }Read-only counts; known = owned + seen.
stringsPartial<Record<string, string>>Overrides for every visible text and announcement (keys: label, all, owned, seen, unknown, number, score, percent, progress, filters, details, nameOwned, nameSeen, nameUnknown, unknownTitle, unknownText, locked, howTo, open ({name}), learn, empty, announceDiscover, announceOwn, announceLost, announceFilter). English defaults.

Methods

NameDescription
mark(id, state, { animate?, announce?, source? })Moves an entry (id, or number) to a state. Going up emits the cancelable mv-discover or mv-own first; going down is silent except for mv-change. Returns true when applied, false if vetoed, unknown id or a nameless slot.
discover(id) / own(id)Shorthands for mark(id, "seen") and mark(id, "owned").
select(id, { focus? })Makes an entry active in the panel (clears a filter that hides it).
reset()Clears persisted progress and returns every entry to its initial state, silently.

Events

NameDescription
mv-discoverCancelable, before an entry goes from unknown to seen. detail: { id, number, name, from, to, source, entry }. preventDefault() keeps it hidden (e.g. until your server confirms).
mv-ownCancelable, before an entry becomes owned (from seen, or straight from unknown with from: "unknown"). Same detail as mv-discover.
mv-changeAfter any state change, up or down. detail: { id, from, to, source, counts }. Sync your backend here.
mv-selectThe user moved to another entry (click, arrows, typeahead, Tab). detail: { id, number, state, entry }.
mv-activateEnter, Space, double click or the panel's call to action on an entry. detail: { id, number, state, source: "keyboard" | "pointer" | "link", entry }.

Content structure

NameDescription
[data-entry] childrenEntries declared in markup (read once, then replaced by the rendered grid). Their [data-details] child is moved, not cloned, so its listeners survive.

CSS classes

NameDescription
mv-field-guide-headSummary: -title, -scoreline (-score with -count, -pct), -bar (role=progressbar, .mv-field-guide-seg[data-state]), -filters (.mv-field-guide-filter[aria-pressed] with -swatch, -filter-label, -filter-count).
mv-field-guide-grid / mv-field-guide-entryThe listbox and its options; data-state="unknown | seen | owned", data-flash="discover | own" for a moment after leveling up. Parts: -num, -mark, -art, -name, -note.
mv-field-guide-artArtwork box with data-state and three stacked layers: -ghost (big number for unknown), -sil (silhouette), -full (full artwork, revealed by clip-path).
mv-field-guide-panelDetail panel (data-state): -panel-head, -panel-num (“Entry 14”), -panel-name, -status, -panel-body (-lead, -text, -facts, -locked, -howto, -cta).

CSS variables

NameDefaultDescription
--mv-field-guide-ownedvar(--mv-accent)Owned color: bar segment, swatch, check mark, selection ring.
--mv-field-guide-owned-bg / --mv-field-guide-owned-inkaccent tint / var(--mv-accent-fg)Background and icon color of owned artwork.
--mv-field-guide-seenaccent mixed with --mv-bg-emphasisSeen color (hatched bar segment, half swatch, discovery pulse).
--mv-field-guide-silhouettemix of --mv-fg and --mv-bg-mutedTone of silhouettes.
--mv-field-guide-min8.5rem (compact 6.75rem)Minimum tile width (auto-fill columns).
--mv-field-guide-art-size3.25rem (compact 2.5rem)Artwork size in tiles.
--mv-field-guide-panel-width18remWidth of the side panel.
--mv-field-guide-gapvar(--mv-space-2)Gap between tiles.

Accessibility

The grid is a real single-select listbox (aria-labelledby the collection title) whose options use a roving tabindex: Tab enters on the active entry, arrows move left/right and up/down by visual row (columns are measured, RTL mirrors left and right), Home/End jump to the ends, PageUp/PageDown move three rows, typing letters jumps to a known entry by name, Enter/Space/double click emit mv-activate; selection follows focus and drives the detail panel. Each option's accessible name carries its name and state in words (“Webhooks, seen, not yet used”; its place in the series comes from the listbox position), while unknown entries are named by their number only (“Entry 21, not discovered yet”) and never expose their name, and its hint or summary is linked with aria-describedby. State is never conveyed by the silhouette or color alone: tiles differ by border style (dashed slot), mark icon (check / eye) and text, swatches and bar segments differ by shape (full, half, hollow; hatched segment), and the panel shows a status pill with icon and label. The progress bar is a role=progressbar with an aria-valuetext of all three counts; filter pills are toggle buttons (aria-pressed) with their count in their name. Level-ups, filter changes and downgrades are announced once in a polite live region (“Discovered Heatmaps. 17 of 24 discovered.”, “Webhooks is now used. 11 of 24 used.”); focus is never moved by state changes, and an entry that changes state under a filter stays in place until the filter changes. Reduced motion (OS or data-motion="reduce"): the blur-and-fill reveal becomes a 200 ms crossfade and the pulses are dropped. Forced colors: selection uses Highlight, silhouettes GrayText, and the bar and swatches keep system-colored shapes.

Cette page a été traduite par IA. Signaler un problème de traduction