/* Shared Help center + tooltip module for the Work Package Suite. Included by the home page, the suite, and the embedded creator. It injects: - tooltip styles for the .help-tip (ⓘ) component and [data-tip] hovers - a searchable, multi-topic Help center modal opened via window.openHelp() - a floating "?" launcher on any page that doesn't already have a Help button API (unchanged + extended): openHelp() open the help center openHelp('topicId') open and jump to a topic (e.g. openHelp('constraints')) closeHelp() close it Add a Help button anywhere with onclick="openHelp()". */ (function (global) { 'use strict'; // ── styles ──────────────────────────────────────────────────────────────── var css = ` .help-tip{ display:inline-flex; align-items:center; justify-content:center; width:15px; height:15px; margin-left:5px; border-radius:50%; background:#525252; color:#fff; font-size:10px; font-weight:700; font-family:ui-sans-serif,system-ui,sans-serif; cursor:help; vertical-align:middle; position:relative; } .help-tip::after{ content:attr(data-tip); position:absolute; bottom:130%; left:50%; transform:translateX(-50%); background:#161616; color:#fff; padding:7px 10px; border-radius:0; font-size:12px; font-weight:400; line-height:1.4; white-space:normal; width:max-content; max-width:260px; text-align:left; z-index:9999; opacity:0; pointer-events:none; transition:opacity .12s; box-shadow:0 4px 14px rgba(20,30,50,.22); } .help-tip::before{ content:''; position:absolute; bottom:130%; left:50%; transform:translate(-50%,95%); border:5px solid transparent; border-top-color:#161616; opacity:0; transition:opacity .12s; z-index:9999; } .help-tip:hover::after, .help-tip:hover::before, .help-tip:focus::after, .help-tip:focus::before{ opacity:1; } .ui-help-overlay{ position:fixed; inset:0; background:rgba(20,30,50,.5); display:none; align-items:center; justify-content:center; z-index:10000; padding:4vh 16px; } .ui-help-overlay.open{ display:flex; } .ui-help-modal{ background:#fff; color:#161616; max-width:980px; width:100%; height:88vh; max-height:880px; border-radius:0; box-shadow:0 12px 40px rgba(20,30,50,.3); display:flex; flex-direction:column; overflow:hidden; font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif; } .ui-help-head{ display:flex; align-items:center; gap:14px; padding:13px 18px; border-bottom:1px solid #e0e0e0; flex:none; } .ui-help-head .ui-help-title{ font-size:15px; font-weight:700; white-space:nowrap; } .ui-help-search{ flex:1; position:relative; max-width:420px; } .ui-help-search input{ width:100%; padding:8px 12px; border:1px solid #8d8d8d; border-radius:0; font-size:13px; outline:none; background:#f7f8fa; } .ui-help-search input:focus{ border-color:#0f62fe; background:#fff; box-shadow:0 0 0 2px rgba(37,99,214,.15); } .ui-help-head .ui-help-x{ margin-left:auto; background:none; border:none; font-size:20px; cursor:pointer; color:#525252; line-height:1; } .ui-help-wrap{ display:flex; flex:1; min-height:0; } .ui-help-nav{ width:230px; flex:none; border-right:1px solid #e0e0e0; overflow:auto; padding:10px 8px; background:#fafbfc; } .ui-help-nav a{ display:block; padding:7px 10px; border-radius:0; color:#27313f; text-decoration:none; font-size:13px; cursor:pointer; margin-bottom:1px; } .ui-help-nav a:hover{ background:#eef1f6; } .ui-help-nav a.active{ background:#edf5ff; color:#0353e9; font-weight:600; } .ui-help-nav a.nohit{ display:none; } .ui-help-content{ flex:1; overflow:auto; padding:22px 28px; scroll-behavior:smooth; } .ui-help-sec{ margin-bottom:30px; } .ui-help-sec.hide{ display:none; } .ui-help-sec h3{ font-size:18px; margin:0 0 10px; color:#161616; scroll-margin-top:10px; } .ui-help-sec h4{ margin:18px 0 6px; font-size:12px; text-transform:uppercase; letter-spacing:.04em; color:#0f62fe; } .ui-help-content p{ font-size:13.5px; line-height:1.62; margin:0 0 9px; color:#27313f; } .ui-help-content ol, .ui-help-content ul{ margin:0 0 10px; padding-left:20px; font-size:13.5px; line-height:1.6; } .ui-help-content li{ margin-bottom:5px; } .ui-help-content code{ background:#eef1f6; padding:1px 5px; border-radius:4px; font-size:12px; } .ui-help-content table{ border-collapse:collapse; width:100%; font-size:12.5px; margin:6px 0 12px; } .ui-help-content th, .ui-help-content td{ border:1px solid #e0e0e0; padding:6px 9px; text-align:left; vertical-align:top; } .ui-help-content th{ background:#f4f6f9; font-weight:600; } .ui-help-pill{ display:inline-block; padding:1px 8px; border-radius:11px; font-size:11px; font-weight:600; } .pill-draft{ background:#eef1f6; color:#525252; } .pill-sched{ background:#edf5ff; color:#0353e9; } .pill-prog{ background:#fef3e0; color:#b45309; } .pill-issued{ background:#e4f6ec; color:#15924f; } .pill-qc{ background:#f3e8ff; color:#7c3aed; } .pill-closed{ background:#e2e8f0; color:#334155; } .pill-hold{ background:#fde8e8; color:#c0392b; } .ui-help-callout{ background:#f4f8ff; border-left:3px solid #0f62fe; padding:10px 14px; border-radius:0; font-size:13px; line-height:1.55; margin:10px 0; } .ui-help-noresult{ display:none; color:#525252; font-size:14px; padding:10px 2px; } .ui-help-content mark{ background:#fff1a8; color:inherit; border-radius:2px; padding:0 1px; } .ui-help-fab{ position:fixed; bottom:12px; left:12px; z-index:9998; width:38px; height:38px; border-radius:50%; border:none; background:#0f62fe; color:#fff; font-size:18px; font-weight:700; cursor:pointer; box-shadow:0 2px 10px rgba(20,30,50,.28); } .ui-help-fab:hover{ background:#0353e9; } @media (max-width:760px){ .ui-help-modal{ height:92vh; } .ui-help-wrap{ flex-direction:column; } .ui-help-nav{ width:auto; display:flex; flex-wrap:wrap; gap:4px; border-right:none; border-bottom:1px solid #e0e0e0; } .ui-help-nav a{ margin:0; font-size:12px; padding:5px 9px; } .ui-help-head{ flex-wrap:wrap; } }`; var style = document.createElement('style'); style.textContent = css; (document.head || document.documentElement).appendChild(style); // ── content ───────────────────────────────────────────────────────────────── // Each topic: { id, title, body(HTML) }. Order here is the nav order. var TOPICS = [ { id: 'overview', title: 'Getting started', body: `

Getting started

The Work Package Suite turns a project's standard procedure into release-ready Installation Work Packages (IWPs). You work in three stages, always in the same order:

  1. Pick or create a Project on the home page. Each project keeps its own SOP and its own Work Packages, so you can run many jobs at once.
  2. SOP Configuration — set the project baseline in 10 steps (team, sign-offs, WP types, governance & sizing, quality, platforms, sequence, constraints, sources). Every Work Package inherits these defaults. The Creator stays locked until the SOP is marked complete.
  3. Work Package Creation — author individual IWPs against the SOP, clear their constraints, and issue them to the field.
  4. Dashboard — track status, hours, due dates, and what's gating each package across the project.

Moving around

From the home page, open SOP Configuration, the Work Package Creator, or the Dashboard. Inside the suite, switch any time using the top tabs: ⚙️ SOP Configuration, 📋 Work Package Creation, and 📊 Dashboard. The active project and SOP follow you across all of them.

Quick start

  1. Open “SOP Configuration” and complete the 10 steps for your project (~15 minutes).
  2. Finish the SOP — its home-page card turns green and unlocks the Work Package Creator.
  3. Open “Work Package Creation” to author packages with your SOP defaults pre-populated.
  4. Update from the field using the Field View, and leave feedback on any page with the Feedback button.
New here? On the home page choose the Sample Project, then click ⭐ Load sample in the suite to see a fully filled-out SOP and an example Work Package.
` }, { id: 'projects', title: 'Projects', body: `

Projects

A project is the top-level container — every SOP and Work Package belongs to one. Create or select projects on the home page.

Project fields

The active project

The active project is the one you're currently working in. All SOP and Work Package data is scoped (namespaced) to it, so switching projects loads that project's own configuration and packages — nothing leaks between jobs. Use the change link next to the active project name to switch.

Projects are stored centrally via the API and mirrored to your browser, so the suite still works offline; it re-syncs when the connection returns.
` }, { id: 'sop', title: 'SOP Configuration', body: `

SOP Configuration (10 steps)

The SOP is the project baseline. Walk the 10 steps with ← Back / Next →, or jump using the step indicators. The final step is ✓ SOP complete — saving it unlocks the Work Package Creator and turns the home-page card green.

  1. Project Basics — name, number, client, division/sector, site. Inherited by every WP.
  2. Project Team Leadership — PM, APM, CM, QM, plus any additional members (+ Add Team Member).
  3. Required Sign-Off Roles — Superintendent and Foreman are always required; add optional roles (HSE, Quality Rep, Planner, etc.) with + Add Role.
  4. Work Package Types — enable the install types this project uses (Conduit Install, Wire Pull, Terminations, …). Enabled types populate the WP type picker.
  5. Governance & WP Numbering — the WP number format (e.g. WP##-[Sector]-[TYPE]), issuance strategy, the project's disciplines, the discipline strategy, and WP sizing (see Sizing and Disciplines).
  6. Quality & Inspection Strategy — QC requirement, photo/documentation standard, and hold/witness points.
  7. Tracking & Commissioning Platforms — e.g. CxAlloy, Procore, ACC.
  8. Construction Sequence — the install flow; reorder by dragging (⠿), edit labels, add ◆ QC Hold gates or custom steps. These feed the WP "predecessor" picker.
  9. Release Gate Constraints — choose which standard AWP constraints apply and add custom ones (see Constraints).
  10. Engineering Sources & References — labelled links (Design Drawings, Specs, …) that appear as quick-access buttons in the WP Creator's Drawings & Attachments.
Fields a WP inherits from the SOP show a "from SOP" tag and are locked. You can override a locked field with 🔒 Edit, which requires a logged reason.
` }, { id: 'wps', title: 'Work Packages', body: `

Creating Work Packages

In the Creator, start a package with + New (blank, auto-numbered) or ⧉ Duplicate (copies a saved package and increments the number). The WP Number is built automatically from the SOP number format plus your scope fields, the WP type, and a counter — it's read-only.

Key fields

Saving

Save Draft stores the package; ⚡ Save & View saves and renders the print-ready output. Drafts auto-save to your browser as you type, so nothing is lost if you close the tab.

` }, { id: 'statuses', title: 'Statuses', body: `

Work Package statuses

StatusMeaning
DraftWork in progress; not yet released.
ScheduledPlanned and scheduled; upcoming.
IssuedReleased to the field. Requires all constraints Cleared or N/A.
In ProgressActively being worked.
QCIn quality check / inspection.
ClosedCompleted.
Issue (Hold)A constraint reopened after release — work is paused until it's resolved.
A package cannot move to Issued while any constraint is Open. If a constraint reopens after a package is Issued, its status automatically drops to Issue (Hold) and the suite makes you log what happened.

On a multi-discipline package, each discipline carries its own status and the overall status rolls up to the least-advanced discipline — so a package is never "Closed" while one trade still lags.

` }, { id: 'constraints', title: 'Constraints & release', body: `

Constraints & release readiness

Constraints are the readiness checklist that gates a package's release to the field. They follow Advanced Work Packaging (AWP Vol II §2.3.2). The standard set:

  1. Safety & Permitting
  2. Quality Control / Inspection
  3. IFC Drawings & Specs
  4. Schedule
  5. Materials (on site, bagged & tagged)
  6. Prefabrication
  7. Work Access & Laydown
  8. Craft Availability
  9. Construction Equipment & Tools
  10. Scaffolding / Access Equipment

Pick which apply (and add custom ones) in SOP Step 9. Each constraint on a package has one of three states:

StateEffect
OpenNot yet cleared — blocks release.
ClearedRequirement met — counts toward release-ready.
N/ANot applicable to this package — counts as cleared.

The release gate

` }, { id: 'disciplines', title: 'Disciplines & Split', body: `

Disciplines & Split by Discipline

Disciplines are trades (Mechanical, Electrical, Tech, …) set in SOP Step 5. The discipline strategy controls how packages handle them:

Split by Discipline

When a package covers 2+ disciplines, the ⎘ Split by Discipline button breaks it into one numbered instance per discipline — WP01A, WP01B, WP01C (or _MECH/_ELEC suffixes, set in the SOP). The original is kept as a master / roll-up; each instance:

Tag material rows to a discipline before splitting. Untagged rows stay on the master only and won't be routed to any instance. Masters are excluded from dashboard counts so hours aren't double-counted.
` }, { id: 'sizing', title: 'Sizing', body: `

Work Package sizing

In SOP Step 5 you set a typical WP size band, which sets a split threshold (max labor hours):

Size bandSplit threshold
Small — 1–2 days (≈8–24 hrs)24 hrs
Standard — 3–5 days (≈40–80 hrs)80 hrs
Large — 1–2 weeks (≈80–160 hrs)160 hrs
Custom…you set it

In the Creator, the Est. Hrs. field is checked live against the threshold. Within range you see the target band; over it you get an amber warning — "⚠ … exceeds the …-hr split threshold — consider breaking this package down" — and a nudge to split by discipline where that applies. It's a guide, not a hard block: you can proceed if it's intentional.

` }, { id: 'dashboard', title: 'Dashboard', body: `

Dashboard & metrics

The dashboard aggregates every (non-master) package in the active project. Open it from the home page, the suite's 📊 Dashboard tab, or the Creator header.

Metric cards (click to filter)

Breakdowns & gates

The table

Shows WP #, subject, type, discipline, status, Gates (clear, n open, or master), due date (red if overdue), and hours. Row actions: issue (when release-ready), view, and edit. Filter with the search box and the status / discipline dropdowns.

Split masters are labelled and excluded from the counts; you issue their instances one at a time as each becomes release-ready.
` }, { id: 'data', title: 'Samples, sharing & comments', body: `

Samples, import / export & comments

Load sample

⭐ Load sample is context-aware: on the SOP tab it loads a complete sample SOP; on the WP tab it loads an example Work Package. Great for learning the tool or demoing.

Import / Export

Comments & feedback

Leave feedback from the home page, per-step comments in the SOP tool (💬 Step Comments), or package comments in the Creator's 💬 Comments drawer. Comments are saved and can be exported/imported as .json so reviewers can share them — and, when the API is reachable, they're collected centrally too.

Usage logs

📊 Usage Logs / ▤ Usage Data shows session and event counts and can export the full log. A dev-mode toggle pauses tracking during demos.

` }, { id: 'shortcuts', title: 'Tips & shortcuts', body: `

Tips & keyboard shortcuts

` }, { id: 'glossary', title: 'Glossary', body: `

Glossary

TermMeaning
IWPInstallation Work Package — the field-level package this tool produces.
AWPAdvanced Work Packaging — the methodology behind the constraint set and release gate.
SOPStandard Operating Procedure — the project baseline every WP inherits.
ConstraintA readiness item (Open / Cleared / N/A) that gates release.
Release-readyAll constraints Cleared or N/A — the package can be Issued.
IssuedReleased to the field.
Issue (Hold)A released package paused because a constraint reopened.
DisciplineA trade (Mechanical, Electrical, Tech, …).
Split / Master / InstanceBreaking a multi-discipline package (master/roll-up) into single-discipline instances (WP01A/B/C).
ScopeThe sequenced steps the crew performs.
SequenceSOP-defined construction phases; a WP can name a predecessor step.
Bagged & taggedMaterials on site, kitted, and labelled — part of the Materials constraint.
MIMOMaterial In / Material Out — kitting and staging logistics.
AssetA controls.dev record (equipment/system) a package is built around.
Hold / Witness pointHold = work stops until inspection sign-off; Witness = inspection offered but work may proceed.
Active projectThe currently selected project; all data is scoped to it.
` }, { id: 'faq', title: 'FAQ', body: `

Frequently asked questions

The Work Package Creator is locked — why?

The SOP for the active project isn't complete yet. Finish SOP Configuration and click ✓ SOP complete on the last step; the Creator unlocks and the home card turns green.

Why can't I set a package to Issued?

At least one constraint is still Open. Clear or mark N/A every constraint — the release banner turns green — and the suite will offer to issue it.

My package's materials didn't all carry over when I split it.

Only material rows tagged to a discipline are routed to that instance. Untagged rows stay on the master. Tag them before splitting.

Why don't split masters show in the dashboard totals?

Masters are roll-ups; counting them would double-count their hours and packages. The individual instances are counted instead.

Will I lose my work if I close the browser?

No — drafts auto-save locally per project and reload next time. Use Export (JSON) for a backup or to share with a teammate.

Does each project keep its own data?

Yes. SOP and Work Packages are scoped to the active project; switching projects loads that project's own set.

How do I report a problem or suggestion?

Use the feedback / comments features (home page, SOP Step Comments, or the Creator's Comments drawer).

` } ]; // ── build ───────────────────────────────────────────────────────────────── function buildModal() { if (document.getElementById('ui-help-overlay')) return; var overlay = document.createElement('div'); overlay.className = 'ui-help-overlay'; overlay.id = 'ui-help-overlay'; var nav = TOPICS.map(function (t) { return '' + t.title + ''; }).join(''); var sections = TOPICS.map(function (t) { return '
' + t.body + '
'; }).join(''); overlay.innerHTML = ''; overlay.addEventListener('click', function (e) { if (e.target === overlay) closeHelp(); }); document.body.appendChild(overlay); // nav clicks + in-content cross-links jump to a section overlay.addEventListener('click', function (e) { var t = e.target.closest('[data-help-target],[data-help-jump]'); if (!t) return; e.preventDefault(); jumpTo(t.getAttribute('data-help-target') || t.getAttribute('data-help-jump')); }); // search var q = overlay.querySelector('#ui-help-q'); q.addEventListener('input', function () { runSearch(q.value); }); // highlight nav as you scroll var content = overlay.querySelector('#ui-help-content'); content.addEventListener('scroll', syncActiveNav, { passive: true }); } function jumpTo(id) { var sec = document.getElementById('ui-help-sec-' + id); if (!sec) return; // Clear any active search filter so the target is visible. var q = document.getElementById('ui-help-q'); if (q && q.value) { q.value = ''; runSearch(''); } sec.scrollIntoView({ block: 'start' }); setActiveNav(id); } function setActiveNav(id) { var nav = document.getElementById('ui-help-nav'); if (!nav) return; nav.querySelectorAll('a').forEach(function (a) { a.classList.toggle('active', a.getAttribute('data-help-target') === id); }); } function syncActiveNav() { var content = document.getElementById('ui-help-content'); if (!content) return; var top = content.scrollTop, best = null, bestDist = Infinity; TOPICS.forEach(function (t) { var sec = document.getElementById('ui-help-sec-' + t.id); if (!sec || sec.classList.contains('hide')) return; var d = Math.abs(sec.offsetTop - top); if (sec.offsetTop - top <= 40 && d < bestDist) { bestDist = d; best = t.id; } }); if (best) setActiveNav(best); } // ── search: filter sections + highlight matches ─────────────────────────── function clearMarks(root) { root.querySelectorAll('mark').forEach(function (m) { var txt = document.createTextNode(m.textContent); m.parentNode.replaceChild(txt, m); }); root.normalize(); } function markMatches(el, query) { var lower = query.toLowerCase(); var walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { if (!node.nodeValue.trim()) return NodeFilter.FILTER_REJECT; var p = node.parentNode.nodeName; if (p === 'MARK' || p === 'STYLE' || p === 'SCRIPT') return NodeFilter.FILTER_REJECT; return node.nodeValue.toLowerCase().indexOf(lower) >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } }); var nodes = [], n; while ((n = walker.nextNode())) nodes.push(n); nodes.forEach(function (node) { var val = node.nodeValue, low = val.toLowerCase(), frag = document.createDocumentFragment(), i = 0, idx; while ((idx = low.indexOf(lower, i)) >= 0) { if (idx > i) frag.appendChild(document.createTextNode(val.slice(i, idx))); var mk = document.createElement('mark'); mk.textContent = val.slice(idx, idx + query.length); frag.appendChild(mk); i = idx + query.length; } if (i < val.length) frag.appendChild(document.createTextNode(val.slice(i))); node.parentNode.replaceChild(frag, node); }); } function runSearch(query) { var content = document.getElementById('ui-help-content'); var nav = document.getElementById('ui-help-nav'); var noresult = document.getElementById('ui-help-noresult'); if (!content) return; query = (query || '').trim(); var hits = 0; TOPICS.forEach(function (t) { var sec = document.getElementById('ui-help-sec-' + t.id); var navItem = nav.querySelector('[data-help-target="' + t.id + '"]'); clearMarks(sec); var match = !query || sec.textContent.toLowerCase().indexOf(query.toLowerCase()) >= 0; sec.classList.toggle('hide', !match); if (navItem) navItem.classList.toggle('nohit', !!query && !match); if (match) { hits++; if (query) markMatches(sec, query); } }); noresult.style.display = (query && hits === 0) ? 'block' : 'none'; if (query) { content.scrollTop = 0; } else { syncActiveNav(); } } // ── public API ────────────────────────────────────────────────────────────── global.openHelp = function (topicId) { buildModal(); document.getElementById('ui-help-overlay').classList.add('open'); var q = document.getElementById('ui-help-q'); if (topicId && typeof topicId === 'string') jumpTo(topicId); else { setActiveNav(TOPICS[0].id); if (q) setTimeout(function () { q.focus(); }, 30); } }; global.closeHelp = function () { var o = document.getElementById('ui-help-overlay'); if (o) o.classList.remove('open'); }; document.addEventListener('keydown', function (e) { if (e.key === 'Escape') global.closeHelp(); }); // ── floating launcher on pages without their own Help button ──────────────── function maybeAddFab() { var inIframe = (function () { try { return window.top !== window.self; } catch (e) { return true; } })(); if (inIframe || global.WP_HELP_NO_FAB) return; // suite shows the parent's button if (document.querySelector('[onclick*="openHelp"]')) return; // page already has a Help trigger if (document.getElementById('ui-help-fab')) return; var b = document.createElement('button'); b.id = 'ui-help-fab'; b.className = 'ui-help-fab'; b.type = 'button'; b.title = 'Help'; b.setAttribute('aria-label', 'Open help'); b.textContent = '?'; b.addEventListener('click', function () { global.openHelp(); }); document.body.appendChild(b); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', maybeAddFab); else maybeAddFab(); })(window);