| name | metodologia-info-icon-page |
| description | Add or maintain the trilingual ℹ info-icon modal system on a single metodologia.info page. Use when the user asks to add info-icons, info modals, glossary tooltips, or "explain-the-concept" affordances to a page, or to migrate a page's inline modal engine to the shared js/info-icons.js module. Drives one page at a time: inventory → trilingual glossary → dialog markup + shared engine → wire cards → verify (vitest + DOM-assert) → PR. |
| allowed-tools | Read, Edit, Write, Grep, Glob, Bash |
metodologia-info-icon-page
Build / maintain the ℹ info-icon modal system for one metodologia.info page.
ES/EN/PT trilingual. Reuse-first: the shared engine, CSS and CI contract already
ship — never reinvent them.
When to use
- "Add info-icons / info modals / glossary to
<page>."
- "Make
<page>'s modals trilingual."
- "Migrate
<page> to the shared info-icons module."
- Editing existing modal text on a page that already has the system.
Foundations already in main (do NOT recreate)
| Asset | Path | Role |
|---|
| Shared engine | js/info-icons.js | initInfoIcons({ getEntry }) → { openInfo, wireCard, wireButton, setInfoAria, relabel, wired }. Auto-wires [data-info], langchange relabel (window+document), backdrop close. |
| CSS affordance | estilos/neoswiss-v5.css §12b "INFO AFFORDANCE" | .x-card--info, .has-info-badge, .info-icon, .info-host, .accel-wrap. Hover/focus reveal, gold pill, AA, reduced-motion. Edit only here to change affordance globally. |
| CI guard | tests/unit/info-icons-contract.test.js | Asserts every entry in every js/*-glossary.js (incl. LEVELS/DIAG/PROCESO) has es/en/pt with non-empty term/title/body. |
| Doc | docs/INFO_ICONS_SYSTEM.md | Architecture, PR→page map, deploy. Read first. |
Hard rules (brand — non-negotiable)
- Trilingual ES/EN/PT, all three complete (CI fails otherwise).
- No prices → "Cotización tras Discovery". No "gratis" / "sin compromiso".
- Disclaimers on cases/testimonials: "resultado de cliente, no garantía".
- Fonts Poppins (headings) / Montserrat (body); light theme default.
- Read before write. Branch from
github/main, never from a stale worktree.
- Evidence tags in your notes:
[CÓDIGO] [CONFIG] [DOC] [INFERENCIA] [SUPUESTO].
- Commit/PR only the page's files. Production is NOT git — deploy is the owner's (rsync + hcdn purge).
Recipe (one page)
0. Ground
git fetch github main && git switch -C feature/info-icons-<page> github/main
Read docs/INFO_ICONS_SYSTEM.md, the page's index.html, and (if present)
js/<page>-glossary.js. Confirm whether the page already has #infoDialog.
1. Inventory
Grep the page for concept-bearing cards/terms worth an explainer:
grep -n 'x-card\|axis\|principle\|accel-tile\|ref\|stats__cell\|tl-item' <page>/index.html
List each trigger → a stable glossary key (prefix by page, e.g. emp_, cas_).
Exclude legal/ (must stay 100% visible) and placeholder pages (insights/).
2. Trilingual glossary
Create/extend js/<page>-glossary.js:
export const GLOSSARY = {
<page>_<concept>: {
es: { term, title, body , cta?, ctaHref? },
en: { term, title, body, cta?, ctaHref? },
pt: { term, title, body, cta?, ctaHref? },
},
};
export function getEntry(key) {
const e = GLOSSARY[key]; if (!e) return null;
const lang = (document.documentElement.lang || 'es').slice(0, 2);
return e[lang] || e.es;
}
Body content pattern: what it is · why it matters to the reader · next step.
Special shape still in repo: diag-glossary.js uses DIAG/PROCESO+url+getDiagUIText
— keep its shape, just complete es/en/pt. (The module also fills an optional
#infoDialogType badge slot from entry.type if present.)
3. Dialog markup + shared engine
If the page has no #infoDialog, copy the block from an existing page (e.g.
casos/index.html): <dialog class="info-dialog" id="infoDialog"> with
#infoDialogTerm/#infoDialogTitle/#infoDialogBody/#infoDialogCTA + hidden
#dialog-title. Then in the page <script type="module">:
import { getEntry } from '../js/<page>-glossary.js?v=1';
import { initInfoIcons } from '../js/info-icons.js?v=1';
const { wireCard, wireButton } = initInfoIcons({ getEntry });
Migration = delete the ~80-line inline engine (openInfo/setInfoAria/wireCard/
langchange/backdrop) and replace with the two lines above; keep the page's
byIndex(...) / wireCard(...) calls (now destructured from the module).
4. Wire triggers
- Cards (div/article):
wireCard(el, 'key') → role=button, tabindex, aria-haspopup, affordance.
- Standalone term / link corner:
<button class="info-icon" data-info="key">ℹ</button>
(auto-wired) or wireButton(btn, 'key') — stopPropagation so wrapping <a> doesn't navigate.
- Never nest a
<button> inside an <a> → use .accel-wrap (button as sibling).
- Inline term inside a
data-i18n-html node (re-rendered on hydrate/langchange):
inject via a MutationObserver so the ℹ survives re-render.
- Add
hreflang en + pt <link>s if missing.
5. Verify
npx vitest run tests/unit
npx serve . -l 4999
Then DOM-assert (Playwright browser_evaluate or Chrome MCP):
- expected count of
.x-card--info / .has-info-badge;
- each section opens the correct
title; CTA shows only when present;
- link/accelerator ℹ opens modal without navigating;
- Esc + backdrop close, focus returns (native
<dialog>);
- toggle ES↔EN↔PT swaps body +
aria-label;
- 0 console errors (404 of
js/i18n/dictionaries/<page>.json is pre-existing).
6. Ship
git add <page>/index.html js/<page>-glossary.js
git commit
gh pr create --base main --repo JaviMontano/mao-site
Update docs/INFO_ICONS_SYSTEM.md §1 PR→page map. Deploy (rsync + hcdn purge)
is the owner's step — flag it, don't run it.
Definition of done