Skip to content

Commit 05d8eef

Browse files
Add newsletter #34, fix blog links, refactor modal (#53)
* Add newsletter #34, fix blog links, refactor modal to native <dialog> - Add Any Cables #34: CLI-over-WebSocket (Apr 2026) to newsletter - Fix blog/newsletter <a> links opening the company modal instead of navigating — company-modal.ts was attaching preventDefault to all .cases-slide__company-card elements including <a> tags - Replace custom Popup class with native <dialog> element — gets backdrop, Escape key, and focus trapping for free from the browser - Move case study links from JS-injected spans to real <a> tags in the HBS template — no JS needed for navigation - Change customer tiles from <button> to <div> so nested <a> tags are valid HTML - Single delegated click listener that skips <a> clicks, so links navigate normally and only non-link clicks open the modal * Remove rang.ee, use SVG logos for Tasktag/Wawa/Yay, fix Tasktag URL - Remove rang.ee (website unverifiable) - Switch Tasktag, Wawa Fertility, and Yay! logos from PNG to SVG - Fix Tasktag URL: tasktag.co → tasktag.com * Fix Uula description — edtech platform, not wellness classes * Fix customer data: URLs, descriptions, categories Verified all 35 customers against their websites: - Vito: URL vito.io → vi.to (old domain is a personal site) - Vinita: was "women's health" → actually a dating app (vinita.io), moved from Healthtech to Communities - Wawa Fertility: was "benefits platform" → practice management for fertility clinics - Sera: removed false "voice processing and IoT" claim - Joint Academy: URL → jointacademy.ai (domain changed) - Floatcard → Float: rebranded, URL → floatfinancial.com - Qualified: updated to "agentic marketing platform" (was "live chat") - Welcome: URL → gloo.com (acquired by Gloo) - Uscreen: moved from "And more" to Communities (creator platform) * Reorder customers by size, update outdated details Researched all 35 customers — verified URLs, descriptions, and estimated valuations. Tiles now sorted largest-first within each industry section. Updated details: - CompanyCam: $2B valuation (was "$30M+ raised") - Fullscript: ~$2.5B valuation (was "$240M raised") - Dext: acquired by IRIS for ~$636M (was "Backed by Insight Partners") - Wealthbox: $200M PE investment from Sixth Street Growth - Via Transportation: public (NYSE: VIA, IPO 2025) - Qualified: acquired by Salesforce (was "$95M raised") - Vito: community events platform (was "transcription") - Sera: removed false "voice processing and IoT" claim - Circle: 10K+ communities, 4M+ active users
1 parent f4dae6d commit 05d8eef

6 files changed

Lines changed: 283 additions & 276 deletions

File tree

src/index.html

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,19 @@
1414
{{> popup popupClass='try-now-popup'}} {{> popup
1515
popupClass='contact-us-popup' popupTitle = 'Contact us'
1616
typeformLink='https://form.typeform.com/c/wAHm0sRP'}}
17-
<div class="popup company-modal" id="company-modal">
18-
<div class="popup__container company-modal__container">
19-
<button class="popup__close-btn" type="button" aria-label="Close"></button>
20-
<div class="company-modal__content">
21-
<img class="company-modal__logo" id="company-modal-logo" src="" alt="" style="display:none" />
22-
<h2 class="company-modal__name" id="company-modal-name"></h2>
23-
<p class="company-modal__desc" id="company-modal-desc"></p>
24-
<div class="company-modal__detail" id="company-modal-detail"></div>
25-
<div class="company-modal__links">
26-
<a class="company-modal__link" id="company-modal-case-study" href="#" target="_blank" style="display:none">Read case study</a>
27-
<a class="company-modal__link" id="company-modal-link" href="#" target="_blank">Visit website</a>
28-
</div>
17+
<dialog class="company-modal" id="company-modal">
18+
<button class="company-modal__close" type="button" aria-label="Close"></button>
19+
<div class="company-modal__content">
20+
<img class="company-modal__logo" id="company-modal-logo" src="" alt="" style="display:none" />
21+
<h2 class="company-modal__name" id="company-modal-name"></h2>
22+
<p class="company-modal__desc" id="company-modal-desc"></p>
23+
<div class="company-modal__detail" id="company-modal-detail"></div>
24+
<div class="company-modal__links">
25+
<a class="company-modal__link" id="company-modal-case-study" href="#" target="_blank" style="display:none">Read case study</a>
26+
<a class="company-modal__link" id="company-modal-link" href="#" target="_blank">Visit website</a>
2927
</div>
3028
</div>
31-
</div>
29+
</dialog>
3230
<script type="module" src="/index.ts"></script>
3331
<script type="module" src="/js/company-modal.ts"></script>
3432
<anycable-cursors

src/js/company-modal.ts

Lines changed: 55 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,30 @@
1-
import Popup from './components/Popup';
2-
31
const LOGOS: Record<string, string> = {
42
'Doximity': '/images/logos/doximity.svg',
53
'Healthie': '/images/logos/healthie-text.png',
64
'Headway': '/images/logos/headway.png',
75
'Jane': '/images/logos/jane.png',
86
'Fullscript': '/images/logos/fullscript.png',
97
'Joint Academy': '/images/logos/joint-academy-text.png',
10-
'Wawa Fertility': '/images/logos/wawafertility.png',
8+
'Wawa Fertility': '/images/logos/wawa.svg',
119
'Sessions Health': '/images/logos/sessionshealth.png',
1210
'Vinita': '/images/logos/vinita.png',
1311
'CoinGecko': '/images/logos/coingecko.svg',
1412
'Dext': '/images/logos/dext.png',
1513
'FreeAgent': '/images/logos/freeagent.png',
1614
'Wealthbox': '/images/logos/wealthbox.png',
17-
'Floatcard': '/images/logos/floatcard.png',
15+
'Float': '/images/logos/floatcard.png',
1816
'Jobber': '/images/logos/jobber.png',
1917
'CompanyCam': '/images/logos/companycam.png',
2018
'EV Connection': '/images/logos/ev-connection.png',
2119
'Via Transportation': '/images/logos/via.png',
2220
'Agero': '/images/logos/agero.svg',
23-
'rang.ee': '/images/logos/rangee.png',
24-
'Tasktag': '/images/logos/tasktag.png',
21+
'Tasktag': '/images/logos/tasktag.svg',
2522
'Circle': '/images/logos/circle.png',
2623
'Mighty Networks': '/images/logos/mighty-networks.png',
2724
'LiveVoice': '/images/logos/live-voice.png',
2825
'Welcome': '/images/logos/welcome.png',
2926
'Uula': '/images/logos/uula.png',
30-
'Yay!': '/images/logos/yay.png',
27+
'Yay!': '/images/logos/yay.svg',
3128
'ClickFunnels': '/images/logos/clickfunnels.png',
3229
'Poll Everywhere': '/images/logos/poll-everywhere.png',
3330
'Callbell': '/images/logos/callbell-text.png',
@@ -39,86 +36,72 @@ const LOGOS: Record<string, string> = {
3936
'Vito': '/images/logos/vito.png',
4037
};
4138

42-
const modal = document.getElementById('company-modal');
43-
if (modal) {
44-
const popup = new Popup('#company-modal');
45-
popup.init();
39+
const dialog = document.getElementById('company-modal') as HTMLDialogElement;
4640

41+
if (dialog) {
4742
const logoEl = document.getElementById('company-modal-logo') as HTMLImageElement;
4843
const nameEl = document.getElementById('company-modal-name');
4944
const descEl = document.getElementById('company-modal-desc');
5045
const detailEl = document.getElementById('company-modal-detail');
5146
const linkEl = document.getElementById('company-modal-link') as HTMLAnchorElement;
5247
const caseStudyEl = document.getElementById('company-modal-case-study') as HTMLAnchorElement;
5348

54-
// Inject a "Read case study →" element into every tile that has one.
55-
// We use a <span> (not <a>) because the tile is a <button>, and <a> inside
56-
// <button> is invalid HTML that Chrome/Safari block from navigating.
57-
// Clicking this span opens the case study in a new tab via window.open;
58-
// clicking anywhere else on the tile opens the company popup.
59-
document.querySelectorAll<HTMLElement>('.cases-slide__company-card[data-case-study]').forEach(card => {
60-
const caseStudyUrl = card.getAttribute('data-case-study');
61-
if (!caseStudyUrl) return;
62-
const ctaText = card.getAttribute('data-case-study-cta') || 'Read case study →';
63-
const link = document.createElement('span');
64-
link.className = 'cases-slide__company-case-study';
65-
link.setAttribute('role', 'link');
66-
link.setAttribute('tabindex', '0');
67-
link.textContent = ctaText;
68-
link.addEventListener('click', (e) => {
69-
e.stopPropagation();
70-
window.open(caseStudyUrl, '_blank', 'noopener');
71-
});
72-
link.addEventListener('keydown', (e) => {
73-
if (e.key === 'Enter' || e.key === ' ') {
74-
e.preventDefault();
75-
e.stopPropagation();
76-
window.open(caseStudyUrl, '_blank', 'noopener');
77-
}
78-
});
79-
card.appendChild(link);
49+
// Close on backdrop click (native <dialog> only closes on Escape by default)
50+
dialog.addEventListener('click', (e) => {
51+
if (e.target === dialog) dialog.close();
8052
});
8153

82-
document.querySelectorAll<HTMLElement>('.cases-slide__company-card').forEach(card => {
83-
card.addEventListener('click', (e) => {
84-
e.preventDefault();
85-
const name = card.querySelector('.cases-slide__company-name')?.textContent || '';
86-
const desc = card.querySelector('.cases-slide__company-desc')?.textContent || '';
87-
const detail = card.getAttribute('data-detail') || '';
88-
const url = card.getAttribute('data-url') || '#';
89-
const caseStudyUrl = card.getAttribute('data-case-study');
90-
const caseStudyTitle = card.getAttribute('data-case-study-title');
91-
const logo = LOGOS[name];
54+
dialog.querySelector('.company-modal__close')?.addEventListener('click', () => {
55+
dialog.close();
56+
});
9257

93-
if (logoEl) {
94-
if (logo) {
95-
logoEl.src = logo;
96-
logoEl.alt = name;
97-
logoEl.style.display = '';
98-
} else {
99-
logoEl.style.display = 'none';
100-
}
101-
}
58+
// Single event listener — opens modal when a customer card is clicked,
59+
// but ignores clicks on <a> tags inside the card (case study links navigate normally).
60+
document.addEventListener('click', (e) => {
61+
const target = e.target as HTMLElement;
10262

103-
if (nameEl) nameEl.textContent = name;
104-
if (descEl) descEl.textContent = desc;
105-
if (detailEl) detailEl.textContent = detail;
106-
if (linkEl) {
107-
linkEl.href = url;
108-
linkEl.textContent = `Visit ${name}`;
63+
// Don't intercept real links
64+
if (target.closest('a')) return;
65+
66+
const card = target.closest('.cases-slide__company-card[data-detail]');
67+
if (!card) return;
68+
69+
const name = card.querySelector('.cases-slide__company-name')?.textContent || '';
70+
const desc = card.querySelector('.cases-slide__company-desc')?.textContent || '';
71+
const detail = card.getAttribute('data-detail') || '';
72+
const url = card.getAttribute('data-url') || '#';
73+
const caseStudyUrl = card.getAttribute('data-case-study');
74+
const caseStudyTitle = card.getAttribute('data-case-study-title');
75+
const logo = LOGOS[name];
76+
77+
if (logoEl) {
78+
if (logo) {
79+
logoEl.src = logo;
80+
logoEl.alt = name;
81+
logoEl.style.display = '';
82+
} else {
83+
logoEl.style.display = 'none';
10984
}
85+
}
86+
87+
if (nameEl) nameEl.textContent = name;
88+
if (descEl) descEl.textContent = desc;
89+
if (detailEl) detailEl.textContent = detail;
90+
if (linkEl) {
91+
linkEl.href = url;
92+
linkEl.textContent = `Visit ${name}`;
93+
}
11094

111-
if (caseStudyEl) {
112-
if (caseStudyUrl) {
113-
caseStudyEl.href = caseStudyUrl;
114-
caseStudyEl.textContent = caseStudyTitle || 'Read case study';
115-
caseStudyEl.style.display = '';
116-
} else {
117-
caseStudyEl.style.display = 'none';
118-
}
95+
if (caseStudyEl) {
96+
if (caseStudyUrl) {
97+
caseStudyEl.href = caseStudyUrl;
98+
caseStudyEl.textContent = caseStudyTitle || 'Read case study';
99+
caseStudyEl.style.display = '';
100+
} else {
101+
caseStudyEl.style.display = 'none';
119102
}
103+
}
120104

121-
popup.open();
122-
});
105+
dialog.showModal();
123106
});
124107
}

src/modules/blocks/cases-slide.scss

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -288,27 +288,18 @@ $className: 'cases-slide';
288288
color: $fontSecondaryColor;
289289
}
290290

291-
// Direct link to a case study, injected on tiles that have one.
292-
// Clicking this opens the case study; clicking elsewhere on the tile
293-
// opens the company popup.
294291
&__company-case-study {
295292
display: inline-block;
296293
margin-top: 6px;
297294
font-size: 12px;
298295
font-weight: 600;
299296
color: $accentPrimaryColor;
300-
cursor: pointer;
297+
text-decoration: none;
301298
transition: opacity 200ms;
302299

303300
&:hover {
304301
text-decoration: underline;
305302
opacity: $opacityPrimaryValue;
306303
}
307-
308-
&:focus-visible {
309-
outline: 2px solid $accentPrimaryColor;
310-
outline-offset: 2px;
311-
border-radius: 2px;
312-
}
313304
}
314305
}

src/modules/blocks/popup.scss

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -94,35 +94,63 @@ $className: 'popup';
9494
}
9595
}
9696

97-
// Company Modal
97+
// Company Modal — native <dialog> element
98+
// Browser provides: backdrop, Escape key, focus trapping, centering.
9899
$className: 'company-modal';
99100

100101
.#{$className} {
101-
// The base .popup never gets display:flex (uses display:inherit), so we
102-
// need to position the dialog manually instead of relying on parent flex.
103-
&__container {
102+
width: 90%;
103+
max-width: 520px;
104+
max-height: 80vh;
105+
margin: 15vh auto auto;
106+
padding: 0;
107+
border: none;
108+
border-radius: 8px;
109+
overflow-y: auto;
110+
background-color: $backgroundPrimaryColor;
111+
112+
&::backdrop {
113+
background-color: rgba(0, 0, 0, 0.5);
114+
}
115+
116+
@include mediaMax($tablet) {
117+
width: calc(100% - 24px);
118+
margin-top: 10vh;
119+
}
120+
121+
&__close {
104122
position: absolute;
105-
top: 15vh;
106-
left: 50%;
107-
width: 90%;
108-
max-width: 520px;
109-
height: auto;
110-
max-height: 80vh;
111-
border-radius: 8px;
112-
overflow-y: auto;
113-
transform: translate(-50%, 20px);
114-
opacity: 0;
115-
transition: transform 200ms, opacity 200ms;
116-
117-
.popup_opened & {
118-
transform: translate(-50%, 0);
119-
opacity: 1;
123+
top: 24px;
124+
right: 24px;
125+
width: 24px;
126+
height: 24px;
127+
border: none;
128+
background: transparent;
129+
cursor: pointer;
130+
transition: opacity 200ms;
131+
132+
&::after,
133+
&::before {
134+
content: '';
135+
position: absolute;
136+
top: 11px;
137+
width: 22.5px;
138+
height: 2px;
139+
background-color: $accentPrimaryColor;
120140
}
121141

122-
@include mediaMax($tablet) {
123-
top: 10vh;
124-
width: calc(100% - 24px);
125-
border-radius: 8px;
142+
&::after {
143+
left: 1px;
144+
transform: rotate(45deg);
145+
}
146+
147+
&::before {
148+
right: 1px;
149+
transform: rotate(-45deg);
150+
}
151+
152+
&:hover {
153+
opacity: $opacityPrimaryValue;
126154
}
127155
}
128156

0 commit comments

Comments
 (0)