Skip to content

Commit a1ffe1e

Browse files
committed
feature: add info tooltips (ⓘ) to show project instructions
1 parent 2b98cd8 commit a1ffe1e

4 files changed

Lines changed: 616 additions & 5 deletions

File tree

web-app/css/styles.css

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2883,7 +2883,7 @@ body.sidebar-active .hero-controls {
28832883
}
28842884
}
28852885

2886-
..modal-close {
2886+
.modal-close {
28872887
position: absolute;
28882888
top: 1rem;
28892889
right: 1rem;
@@ -5313,3 +5313,143 @@ html[data-theme="dark"] body {
53135313
max-width: 100% !important;
53145314
overflow-x: hidden !important;
53155315
}
5316+
5317+
/* Modal Header*/
5318+
.modal-header {
5319+
display: flex;
5320+
justify-content: flex-end;
5321+
padding: 16px 20px 0;
5322+
}
5323+
5324+
/* Info Button */
5325+
.modal-info {
5326+
width: 36px;
5327+
height: 36px;
5328+
border-radius: 50%;
5329+
background: var(--accent-soft);
5330+
border: 1px solid var(--border-accent);
5331+
color: var(--accent);
5332+
font-size: 1.2rem;
5333+
cursor: pointer;
5334+
transition: all 0.2s ease;
5335+
display: flex;
5336+
align-items: center;
5337+
justify-content: center;
5338+
}
5339+
5340+
.modal-info:hover {
5341+
background: var(--accent);
5342+
color: white;
5343+
transform: scale(1.05);
5344+
}
5345+
5346+
/* Close Button */
5347+
.modal-close {
5348+
width: 36px;
5349+
height: 36px;
5350+
border-radius: 50%;
5351+
background: var(--bg-glass);
5352+
border: 1px solid var(--border);
5353+
color: var(--text-secondary);
5354+
font-size: 1.2rem;
5355+
cursor: pointer;
5356+
display: flex;
5357+
align-items: center;
5358+
justify-content: center;
5359+
}
5360+
5361+
.modal-close:hover {
5362+
background: rgba(239, 68, 68, 0.15);
5363+
color: var(--danger);
5364+
border-color: rgba(239, 68, 68, 0.3);
5365+
}
5366+
5367+
/* Info Modal Popup */
5368+
.info-modal-overlay {
5369+
position: fixed;
5370+
top: 0;
5371+
left: 0;
5372+
width: 100%;
5373+
height: 100%;
5374+
background: rgba(0, 0, 0, 0.7);
5375+
backdrop-filter: blur(8px);
5376+
z-index: 2002;
5377+
display: flex;
5378+
align-items: center;
5379+
justify-content: center;
5380+
visibility: hidden;
5381+
opacity: 0;
5382+
transition: all 0.2s ease;
5383+
}
5384+
5385+
.info-modal-overlay.active {
5386+
visibility: visible;
5387+
opacity: 1;
5388+
}
5389+
5390+
.info-modal-content {
5391+
background: var(--surface);
5392+
border: 1px solid var(--border);
5393+
border-radius: 24px;
5394+
padding: 32px;
5395+
max-width: 400px;
5396+
width: 90%;
5397+
text-align: center;
5398+
transform: scale(0.9);
5399+
transition: transform 0.2s ease;
5400+
}
5401+
5402+
.info-modal-overlay.active .info-modal-content {
5403+
transform: scale(1);
5404+
}
5405+
5406+
.info-modal-close {
5407+
float: right;
5408+
background: none;
5409+
border: none;
5410+
font-size: 24px;
5411+
cursor: pointer;
5412+
color: var(--text-secondary);
5413+
}
5414+
5415+
.info-modal-content h3 {
5416+
margin-top: 0;
5417+
margin-bottom: 20px;
5418+
}
5419+
5420+
.info-modal-content ul {
5421+
text-align: left;
5422+
margin: 20px 0;
5423+
padding-left: 20px;
5424+
}
5425+
5426+
.info-modal-content li {
5427+
margin: 10px 0;
5428+
color: var(--text-secondary);
5429+
line-height: 1.5;
5430+
}
5431+
5432+
.info-modal-btn {
5433+
background: var(--accent);
5434+
color: white;
5435+
border: none;
5436+
padding: 10px 24px;
5437+
border-radius: 30px;
5438+
cursor: pointer;
5439+
font-weight: bold;
5440+
margin-top: 10px;
5441+
}
5442+
5443+
.inline-info-btn {
5444+
background: none;
5445+
border: none;
5446+
font-size: 1.2rem;
5447+
cursor: pointer;
5448+
color: var(--accent);
5449+
margin-left: 10px;
5450+
transition: transform 0.2s ease;
5451+
}
5452+
5453+
.inline-info-btn:hover {
5454+
transform: scale(1.1);
5455+
}

web-app/index.html

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -598,8 +598,9 @@ <h2 class="playground-title">🐍 Python Playground</h2>
598598
<!-- ── Project Modal ─────────────────────────────────────────── -->
599599
<div aria-hidden="true" aria-labelledby="modalDialogTitle" aria-modal="true" class="modal" id="projectModal" role="dialog">
600600
<div class="modal-content">
601-
<h2 class="modal-dialog-title visually-hidden" id="modalDialogTitle" tabindex="-1">Interactive project</h2>
602-
<button aria-label="Close project dialog" class="modal-close" id="modalClose" type="button">&times;</button>
601+
<div class="modal-header">
602+
<button aria-label="Close project dialog" class="modal-close" id="modalClose" type="button">&times;</button>
603+
</div>
603604
<div id="modalBody"></div>
604605
</div>
605606
</div>
@@ -707,6 +708,15 @@ <h3>Legal</h3>
707708
Press <kbd>Ctrl</kbd><span>+</span><kbd>K</kbd> to focus search
708709
</div>
709710

711+
<!-- Info Modal Popup -->
712+
<div id="infoModalOverlay" class="info-modal-overlay">
713+
<div class="info-modal-content">
714+
<button class="info-modal-close" id="infoModalClose">&times;</button>
715+
<h3 id="infoModalTitle">How to Play</h3>
716+
<ul id="infoModalList"></ul>
717+
<button class="info-modal-btn" id="infoModalGotIt">Got it</button>
718+
</div>
719+
</div>
710720

711721
</body>
712722
</html>

web-app/js/main.js

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,62 @@ function escapeHtml(str) {
2828
return d.innerHTML;
2929
}
3030

31+
// ============================================
32+
// INFO MODAL FUNCTIONS
33+
// ============================================
34+
35+
function showInfoModal(title, steps) {
36+
var overlay = document.getElementById('infoModalOverlay');
37+
var titleEl = document.getElementById('infoModalTitle');
38+
var listEl = document.getElementById('infoModalList');
39+
40+
if (!overlay || !titleEl || !listEl) return;
41+
42+
titleEl.textContent = title;
43+
listEl.innerHTML = steps.map(function(step) {
44+
return '<li>' + step + '</li>';
45+
}).join('');
46+
47+
overlay.classList.add('active');
48+
49+
function closeModal() {
50+
overlay.classList.remove('active');
51+
closeBtn.removeEventListener('click', closeModal);
52+
gotItBtn.removeEventListener('click', closeModal);
53+
overlay.removeEventListener('click', overlayClick);
54+
}
55+
56+
function overlayClick(e) {
57+
if (e.target === overlay) closeModal();
58+
}
59+
60+
var closeBtn = document.getElementById('infoModalClose');
61+
var gotItBtn = document.getElementById('infoModalGotIt');
62+
63+
closeBtn.addEventListener('click', closeModal);
64+
gotItBtn.addEventListener('click', closeModal);
65+
overlay.addEventListener('click', overlayClick);
66+
}
67+
68+
var currentProjectName = '';
69+
70+
function setupModalInfoButton(projectName) {
71+
currentProjectName = projectName;
72+
var infoBtn = document.getElementById('modalInfoBtn');
73+
if (!infoBtn) return;
74+
75+
// Remove old listener by cloning
76+
var newBtn = infoBtn.cloneNode(true);
77+
infoBtn.parentNode.replaceChild(newBtn, infoBtn);
78+
79+
newBtn.addEventListener('click', function() {
80+
if (typeof getProjectInstructions === 'function') {
81+
var info = getProjectInstructions(currentProjectName);
82+
showInfoModal(info.title, info.steps);
83+
}
84+
});
85+
}
86+
3187
/* ── DOMContentLoaded ──────────────────────────────────────── */
3288
document.addEventListener('DOMContentLoaded', function () {
3389
var html = document.documentElement;
@@ -797,7 +853,6 @@ document.addEventListener('DOMContentLoaded', function () {
797853
function openProjectSafe(name, trigger) {
798854
if (!modal || !modalBody) return;
799855
lastFocusedElement = trigger || document.activeElement;
800-
if (modalTitle) modalTitle.textContent = name || 'Interactive project';
801856
modal.classList.add('active');
802857
modal.setAttribute('aria-hidden', 'false');
803858
var scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
@@ -812,6 +867,50 @@ document.addEventListener('DOMContentLoaded', function () {
812867
modalBody.innerHTML = '<div style="padding:1rem;color:var(--text-secondary)">Project content unavailable.</div>';
813868
}
814869
if (typeof initializeProject === 'function') initializeProject(name);
870+
setupModalInfoButton(name);
871+
872+
// Inject info button next to the title (works for all projects)
873+
var projectContent = modalBody.querySelector('.project-content');
874+
if (projectContent) {
875+
// Try to find the title element (could be h2, or other heading)
876+
var firstHeading = projectContent.querySelector('h2, h3, .resume-analyzer-copy h2, .pet-title');
877+
878+
if (!firstHeading) {
879+
// If no heading found, look for any element with a title-like class
880+
firstHeading = projectContent.querySelector('[class*="title"], [class*="header"] h2');
881+
}
882+
883+
if (firstHeading && !projectContent.querySelector('.inline-info-btn')) {
884+
// Create info button
885+
var infoBtn = document.createElement('button');
886+
infoBtn.className = 'inline-info-btn';
887+
infoBtn.innerHTML = 'ⓘ';
888+
infoBtn.setAttribute('aria-label', 'How to use this project');
889+
890+
// Style the button
891+
infoBtn.style.marginLeft = '12px';
892+
infoBtn.style.background = 'none';
893+
infoBtn.style.border = 'none';
894+
infoBtn.style.fontSize = '1.3rem';
895+
infoBtn.style.cursor = 'pointer';
896+
infoBtn.style.color = 'var(--accent)';
897+
infoBtn.style.verticalAlign = 'middle';
898+
899+
infoBtn.addEventListener('click', function(e) {
900+
e.stopPropagation();
901+
if (typeof getProjectInstructions === 'function') {
902+
var info = getProjectInstructions(name);
903+
showInfoModal(info.title, info.steps);
904+
}
905+
});
906+
907+
// Make heading display inline if it's a block element
908+
if (firstHeading.style.display !== 'inline-block') {
909+
firstHeading.style.display = 'inline-block';
910+
}
911+
firstHeading.appendChild(infoBtn);
912+
}
913+
}
815914
});
816915

817916
removeTrap = trapFocus(modal);
@@ -820,7 +919,7 @@ document.addEventListener('DOMContentLoaded', function () {
820919
if (firstFocusable && typeof firstFocusable.focus === 'function') {
821920
firstFocusable.focus({ preventScroll: true });
822921
}
823-
}
922+
}
824923

825924
function closeProjectSafe() {
826925
if (!modal || !modal.classList.contains('active')) return;

0 commit comments

Comments
 (0)