ReviewOS

also looking at this

stacks/lastsoulultra

LSU's own coaching surface, and what auditing the site turned up

#12
Merged glennmichael123 wants to merge claude/last-soul-ultra-web-6e0718 into main
86 files +3943 -1190

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

public/assets/styles/production/coaching.cssmodified+15-3
Changes to public/assets/styles/production/coaching.css
@@ -36,9 +36,21 @@
3636.coaching-surface .coach-nav a:hover {color:var(--lsu-white)}
3737.coaching-surface .coach-nav a[aria-current="page"] {border-bottom-color:var(--lsu-red);color:var(--lsu-white)}
3838
39/* Above the panel, always. A cross-origin frame can decline to load for
40 reasons this page cannot detect, and the embed's own escape hatch sits
41 below 1100px of empty iframe, where nobody finds it. This one says where
42 the panel comes from, which the site's voice wants said anyway. */
43.coaching-surface .coach-panel-bar {display:flex;flex-wrap:wrap;gap:12px 24px;align-items:baseline;justify-content:space-between;padding-bottom:14px;border-bottom:var(--coach-rule)}
44.coaching-surface .coach-panel-bar p {margin:0;max-width:52ch;color:var(--lsu-dim-3);font:400 13px/1.5 var(--lsu-body)}
45.coaching-surface .coach-panel-bar a {color:var(--lsu-white);font:400 13px/1 var(--lsu-body);letter-spacing:.14em;text-transform:uppercase;text-decoration:none;border-bottom:1px solid var(--lsu-red);padding-bottom:3px}
46.coaching-surface .coach-panel-bar a:hover {color:var(--lsu-red)}
47
48/* Reachable by a screen reader, invisible to everyone else. */
49.coaching-surface .coach-sr {position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
50
3951/* The panel. Space is reserved before the remote element upgrades, so HQ
4052 loading cannot shove the footer down the page under the reader. */
41.coaching-surface .coach-panel {min-height:460px;margin:0 0 clamp(60px,8vw,120px)}
53.coaching-surface .coach-panel {min-height:460px;margin:clamp(20px,2vw,28px) 0 clamp(60px,8vw,120px)}
4254.coaching-surface .coach-panel hq-coaching {display:block}
4355.coaching-surface .coach-panel hq-coaching:not(:defined) {display:grid;gap:22px;justify-items:start;padding:clamp(24px,5vw,56px);border:var(--coach-rule);background:var(--lsu-card)}
4456.coaching-surface .coach-panel hq-coaching:not(:defined) p {margin:0;max-width:56ch;color:var(--lsu-dim-1);font:400 16px/1.7 var(--lsu-body)}
@@ -52,7 +64,7 @@
5264
5365/* The storefront. One ruled row per tier, not a grid of pricing cards: there
5466 is one offer today and a three-column grid holding one card looks broken. */
55.coaching-surface .coach-offers {display:grid;margin:0;padding:0;list-style:none;border-top:var(--coach-rule)}
67.coaching-surface .coach-offers {display:grid;margin:0 0 clamp(40px,5vw,72px);padding:0;list-style:none;border-top:var(--coach-rule)}
5668.coaching-surface .coach-offer {display:grid;grid-template-columns:minmax(0,1fr) auto;gap:clamp(18px,4vw,56px);align-items:start;padding:clamp(26px,3vw,40px) 0;border-bottom:var(--coach-rule)}
5769.coaching-surface .coach-offer h3 {margin:0;font:400 clamp(24px,2.8vw,36px)/1.1 var(--lsu-display);text-transform:uppercase}
5870.coaching-surface .coach-offer-by {margin:10px 0 0;color:var(--lsu-red);font:400 13px/1 var(--lsu-body);letter-spacing:.14em;text-transform:uppercase}
@@ -63,7 +75,7 @@
6375.coaching-surface .coach-offer-note {margin:14px 0 0;color:var(--lsu-dim-3);font:400 13px/1.5 var(--lsu-body)}
6476
6577/* Nothing published yet. The honest state, and the one that ships today. */
66.coaching-surface .coach-empty {padding:clamp(30px,5vw,56px);border:var(--coach-rule);background:var(--lsu-card)}
78.coaching-surface .coach-empty {margin:0 0 clamp(40px,5vw,72px);padding:clamp(30px,5vw,56px);border:var(--coach-rule);background:var(--lsu-card)}
6779.coaching-surface .coach-empty h2 {margin:0;font:400 clamp(24px,3vw,34px)/1.1 var(--lsu-display);text-transform:uppercase}
6880.coaching-surface .coach-empty p {margin:16px 0 0;max-width:58ch;color:var(--lsu-dim-1);font:400 16px/1.7 var(--lsu-body)}
6981
resources/assets/styles/site/coaching.cssmodified+15-3
Changes to resources/assets/styles/site/coaching.css
@@ -36,9 +36,21 @@
3636.coaching-surface .coach-nav a:hover {color:var(--lsu-white)}
3737.coaching-surface .coach-nav a[aria-current="page"] {border-bottom-color:var(--lsu-red);color:var(--lsu-white)}
3838
39/* Above the panel, always. A cross-origin frame can decline to load for
40 reasons this page cannot detect, and the embed's own escape hatch sits
41 below 1100px of empty iframe, where nobody finds it. This one says where
42 the panel comes from, which the site's voice wants said anyway. */
43.coaching-surface .coach-panel-bar {display:flex;flex-wrap:wrap;gap:12px 24px;align-items:baseline;justify-content:space-between;padding-bottom:14px;border-bottom:var(--coach-rule)}
44.coaching-surface .coach-panel-bar p {margin:0;max-width:52ch;color:var(--lsu-dim-3);font:400 13px/1.5 var(--lsu-body)}
45.coaching-surface .coach-panel-bar a {color:var(--lsu-white);font:400 13px/1 var(--lsu-body);letter-spacing:.14em;text-transform:uppercase;text-decoration:none;border-bottom:1px solid var(--lsu-red);padding-bottom:3px}
46.coaching-surface .coach-panel-bar a:hover {color:var(--lsu-red)}
47
48/* Reachable by a screen reader, invisible to everyone else. */
49.coaching-surface .coach-sr {position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
50
3951/* The panel. Space is reserved before the remote element upgrades, so HQ
4052 loading cannot shove the footer down the page under the reader. */
41.coaching-surface .coach-panel {min-height:460px;margin:0 0 clamp(60px,8vw,120px)}
53.coaching-surface .coach-panel {min-height:460px;margin:clamp(20px,2vw,28px) 0 clamp(60px,8vw,120px)}
4254.coaching-surface .coach-panel hq-coaching {display:block}
4355.coaching-surface .coach-panel hq-coaching:not(:defined) {display:grid;gap:22px;justify-items:start;padding:clamp(24px,5vw,56px);border:var(--coach-rule);background:var(--lsu-card)}
4456.coaching-surface .coach-panel hq-coaching:not(:defined) p {margin:0;max-width:56ch;color:var(--lsu-dim-1);font:400 16px/1.7 var(--lsu-body)}
@@ -52,7 +64,7 @@
5264
5365/* The storefront. One ruled row per tier, not a grid of pricing cards: there
5466 is one offer today and a three-column grid holding one card looks broken. */
55.coaching-surface .coach-offers {display:grid;margin:0;padding:0;list-style:none;border-top:var(--coach-rule)}
67.coaching-surface .coach-offers {display:grid;margin:0 0 clamp(40px,5vw,72px);padding:0;list-style:none;border-top:var(--coach-rule)}
5668.coaching-surface .coach-offer {display:grid;grid-template-columns:minmax(0,1fr) auto;gap:clamp(18px,4vw,56px);align-items:start;padding:clamp(26px,3vw,40px) 0;border-bottom:var(--coach-rule)}
5769.coaching-surface .coach-offer h3 {margin:0;font:400 clamp(24px,2.8vw,36px)/1.1 var(--lsu-display);text-transform:uppercase}
5870.coaching-surface .coach-offer-by {margin:10px 0 0;color:var(--lsu-red);font:400 13px/1 var(--lsu-body);letter-spacing:.14em;text-transform:uppercase}
@@ -63,7 +75,7 @@
6375.coaching-surface .coach-offer-note {margin:14px 0 0;color:var(--lsu-dim-3);font:400 13px/1.5 var(--lsu-body)}
6476
6577/* Nothing published yet. The honest state, and the one that ships today. */
66.coaching-surface .coach-empty {padding:clamp(30px,5vw,56px);border:var(--coach-rule);background:var(--lsu-card)}
78.coaching-surface .coach-empty {margin:0 0 clamp(40px,5vw,72px);padding:clamp(30px,5vw,56px);border:var(--coach-rule);background:var(--lsu-card)}
6779.coaching-surface .coach-empty h2 {margin:0;font:400 clamp(24px,3vw,34px)/1.1 var(--lsu-display);text-transform:uppercase}
6880.coaching-surface .coach-empty p {margin:16px 0 0;max-width:58ch;color:var(--lsu-dim-1);font:400 16px/1.7 var(--lsu-body)}
6981
resources/components/CoachingModule.stxmodified+15-0
Changes to resources/components/CoachingModule.stx
@@ -4,11 +4,19 @@ const { slug } = defineProps<Props>()
44</script>
55<script server>
66import { coachingModule } from '../data/coaching'
7import { portalHost, portalUrl } from '../functions/coaching-source'
78
89/* The registry is the only place a module's words live, so a page cannot
910 disagree with the nav about what it is called. */
1011const panel = coachingModule(slug)
1112if (!panel) throw new Error(`unknown coaching module: ${slug}`)
13
14/* The panel is a cross-origin frame, and a frame can decline to load for
15 reasons this page cannot see: an origin the brand has not allowed, a
16 browser blocking third-party frames, an extension. When that happens the
17 embed leaves a tall empty box, so the way out never lives inside it. */
18const panelHref = portalUrl(panel.slug)
19const panelHost = portalHost()
1220</script>
1321<div class="coaching-surface">
1422 <header class="coach-head">
@@ -31,6 +39,13 @@ if (!panel) throw new Error(`unknown coaching module: ${slug}`)
3139 <p>Sign in happens inside the panel, with the account that holds your subscription. That is a separate account from the one you use to request a race entry.</p>
3240 </div>
3341 @endif
42 @if(panel.slug === 'plans')
43 <CoachingOffers />
44 @endif
45 <div class="coach-panel-bar">
46 <p>Loaded from {{ panelHost }}. Your training account lives there, not here.</p>
47 <a href="" target="_blank" rel="noopener">Open it there<span class="coach-sr"> (opens in a new tab)</span></a>
48 </div>
3449 <div class="coach-panel">
3550 <CoachingPortal section="" />
3651 </div>
resources/components/CoachingOffers.stxadded+39-0
Changes to resources/components/CoachingOffers.stx
@@ -0,0 +1,39 @@
1<script server>
2import { formatPrice, formatTrial } from '../functions/coaching-brand'
3import { loadBrand, portalHost } from '../functions/coaching-source'
4
5/* The one piece of coaching this site renders itself. The published brand is
6 public, so the tiers, the coach behind each one and the coach's own price
7 can be read here rather than only inside the frame - a search result, a
8 shared link and a visitor with frames blocked all get the same answer.
9 Nothing is composed: no price, no trial and no coach name originates here. */
10const offerBrand = await loadBrand()
11const offers = offerBrand?.offers ?? []
12const offerLocale = offerBrand?.locale ?? 'en'
13const offerHost = portalHost()
14</script>
15@if(offers.length > 0)
16<ul class="coach-offers">
17 @foreach(offers as offer)
18 <li class="coach-offer">
19 <div>
20 <h3>{{ offer.label }}</h3>
21 <p class="coach-offer-by">{{ offer.coach }}</p>
22 <p>{{ offer.method.summary }}</p>
23 <p class="coach-offer-note">{{ offer.method.name }}, version {{ offer.method.version }}. Bought and billed at {{ offerHost }}.</p>
24 </div>
25 <div class="coach-offer-price">
26 <b>{{ formatPrice(offer.product, offerLocale) }}</b>
27 @if(formatTrial(offer.product))
28 <span class="coach-offer-trial">{{ formatTrial(offer.product) }}</span>
29 @endif
30 </div>
31 </li>
32 @endforeach
33</ul>
34@else
35<div class="coach-empty">
36 <h2>Nothing is open yet</h2>
37 <p>No coach has published a method here and no price has been set. When the first tier opens it appears here, under the coach's own name and at the coach's own price. This site does not set either.</p>
38</div>
39@endif
resources/functions/coaching-source.tsmodified+5-0
Changes to resources/functions/coaching-source.ts
@@ -77,6 +77,11 @@ export function portalUrl(section: unknown = '', origin = coaching.origin, slug
7777 return url.toString()
7878}
7979
80/** The host the panel is loaded from, for a page that says so out loud. */
81export function portalHost(origin = coaching.origin): string {
82 return new URL(coachingOrigin(origin)).host
83}
84
8085/** The custom element HQ generates for embedding its portal. */
8186export function portalElementUrl(origin = coaching.origin): string {
8287 return `${coachingOrigin(origin)}/assets/elements/hq-coaching.js`