@@ -354,6 +354,156 @@ <h3 style="margin-bottom: var(--spacing-md); font-size: 1.1rem; font-weight: 500
354354 </ div >
355355 </ section >
356356
357+ <!-- Demo Architecture -->
358+ < section class ="section " id ="architecture " style ="background: var(--bg-secondary); ">
359+ < div class ="container container--narrow ">
360+ < div class ="section-header ">
361+ < span class ="section-header__label "> Under the Hood</ span >
362+ < h2 class ="section-header__title ">
363+ How the Demo Environment Is Wired
364+ < a href ="#architecture " aria-label ="Link to this section " style ="color: var(--text-muted); text-decoration: none; font-size: 0.65em; margin-left: 0.4rem; vertical-align: middle; "> #</ a >
365+ </ h2 >
366+ < p class ="section-header__desc ">
367+ Seven hosts, three roles. Click any box to view its source code.
368+ </ p >
369+ </ div >
370+
371+ < div class ="card " style ="padding: var(--spacing-lg); overflow-x: auto; ">
372+ < svg viewBox ="0 0 960 760 " style ="width: 100%; min-width: 720px; height: auto; display: block; " xmlns ="http://www.w3.org/2000/svg ">
373+ < style >
374+ .arch-node { cursor : pointer; transition : opacity 0.15s ease, filter 0.15s ease; }
375+ .arch-node : hover { opacity : 0.85 ; filter : brightness (1.25 ); }
376+ .arch-node : hover rect { stroke-width : 2.5 ; }
377+ </ style >
378+ < defs >
379+ < marker id ="archArrow " markerWidth ="9 " markerHeight ="7 " refX ="8 " refY ="3.5 " orient ="auto ">
380+ < polygon points ="0 0, 9 3.5, 0 7 " fill ="var(--text-muted) "/>
381+ </ marker >
382+ </ defs >
383+
384+ <!-- ===== connecting lines (drawn first, under boxes) ===== -->
385+ <!-- Browser -> server (direct) -->
386+ < line x1 ="125 " y1 ="120 " x2 ="125 " y2 ="300 " stroke ="#06b6d4 " stroke-width ="2 " marker-end ="url(#archArrow) "/>
387+ <!-- stealth-dns -> server (direct) -->
388+ < line x1 ="355 " y1 ="120 " x2 ="355 " y2 ="300 " stroke ="#06b6d4 " stroke-width ="2 " stroke-dasharray ="5,4 " marker-end ="url(#archArrow) "/>
389+ <!-- stealth-dns -> server2 (direct, ducks under relay) -->
390+ < path d ="M 380 120 L 380 253 L 520 253 L 520 300 " fill ="none " stroke ="#06b6d4 " stroke-width ="2 " stroke-dasharray ="5,4 " marker-end ="url(#archArrow) "/>
391+ <!-- agent -> relay -->
392+ < line x1 ="605 " y1 ="120 " x2 ="605 " y2 ="150 " stroke ="#06b6d4 " stroke-width ="2 " marker-end ="url(#archArrow) "/>
393+ <!-- reg -> relay -->
394+ < line x1 ="835 " y1 ="120 " x2 ="835 " y2 ="150 " stroke ="#06b6d4 " stroke-width ="2 " marker-end ="url(#archArrow) "/>
395+ <!-- relay -> server2 -->
396+ < line x1 ="720 " y1 ="230 " x2 ="720 " y2 ="300 " stroke ="#f59e0b " stroke-width ="2 " stroke-dasharray ="5,4 " marker-end ="url(#archArrow) "/>
397+ <!-- relay -> server (also reachable) -->
398+ < path d ="M 600 230 L 600 277 L 400 277 L 400 300 " fill ="none " stroke ="#f59e0b " stroke-width ="2 " stroke-dasharray ="5,4 " marker-end ="url(#archArrow) "/>
399+ <!-- server -> ac -->
400+ < line x1 ="240 " y1 ="400 " x2 ="240 " y2 ="430 " stroke ="#00ff9d " stroke-width ="2 " marker-end ="url(#archArrow) "/>
401+ <!-- server2 -> ac2 -->
402+ < line x1 ="720 " y1 ="400 " x2 ="720 " y2 ="430 " stroke ="#00ff9d " stroke-width ="2 " stroke-dasharray ="5,4 " marker-end ="url(#archArrow) "/>
403+ <!-- ac -> resource -->
404+ < path d ="M 260 520 L 260 535 L 400 535 L 400 550 " fill ="none " stroke ="#a855f7 " stroke-width ="2 " stroke-dasharray ="5,4 " marker-end ="url(#archArrow) "/>
405+ <!-- ac2 -> resource -->
406+ < path d ="M 700 520 L 700 535 L 560 535 L 560 550 " fill ="none " stroke ="#a855f7 " stroke-width ="2 " stroke-dasharray ="5,4 " marker-end ="url(#archArrow) "/>
407+
408+ <!-- ===== Row A: clients ===== -->
409+ < a href ="https://github.com/OpenNHP/opennhp/tree/main/examples/server_plugin/basic " target ="_blank " rel ="noopener " class ="arch-node ">
410+ < rect x ="20 " y ="30 " width ="210 " height ="90 " rx ="10 " fill ="rgba(6,182,212,0.08) " stroke ="#06b6d4 " stroke-width ="1.5 "/>
411+ < text x ="125 " y ="60 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="16 " font-weight ="700 " text-decoration ="underline "> Browser / User</ text >
412+ < text x ="125 " y ="85 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="14 "> opens auth page directly</ text >
413+ < text x ="125 " y ="108 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="13 "> not an NHP component</ text >
414+ </ a >
415+
416+ < a href ="https://github.com/OpenNHP/stealth-dns " target ="_blank " rel ="noopener " class ="arch-node ">
417+ < rect x ="250 " y ="30 " width ="210 " height ="90 " rx ="10 " fill ="rgba(6,182,212,0.08) " stroke ="#06b6d4 " stroke-width ="1.5 "/>
418+ < text x ="355 " y ="60 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="16 " font-weight ="700 " text-decoration ="underline "> stealth-dns</ text >
419+ < text x ="355 " y ="85 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="14 "> NHP-Agent</ text >
420+ < text x ="355 " y ="108 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="13 "> talks to both Servers, no relay</ text >
421+ </ a >
422+
423+ < a href ="https://github.com/OpenNHP/opennhp/blob/main/endpoints/js-agent/examples/relay-test.html " target ="_blank " rel ="noopener " class ="arch-node ">
424+ < rect x ="500 " y ="30 " width ="210 " height ="90 " rx ="10 " fill ="rgba(6,182,212,0.08) " stroke ="#06b6d4 " stroke-width ="1.5 "/>
425+ < text x ="605 " y ="60 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="16 " font-weight ="700 " text-decoration ="underline "> agent.opennhp.org</ text >
426+ < text x ="605 " y ="85 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="14 "> NHP-Agent</ text >
427+ < text x ="605 " y ="108 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="13 "> sends the NHP knock</ text >
428+ </ a >
429+
430+ < a href ="https://github.com/OpenNHP/opennhp/blob/main/endpoints/js-agent/examples/reg.html " target ="_blank " rel ="noopener " class ="arch-node ">
431+ < rect x ="730 " y ="30 " width ="210 " height ="90 " rx ="10 " fill ="rgba(6,182,212,0.08) " stroke ="#06b6d4 " stroke-width ="1.5 "/>
432+ < text x ="835 " y ="60 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="16 " font-weight ="700 " text-decoration ="underline "> reg.opennhp.org</ text >
433+ < text x ="835 " y ="85 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="14 "> NHP-Agent</ text >
434+ < text x ="835 " y ="108 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="13 "> sends registration messages</ text >
435+ </ a >
436+
437+ <!-- ===== Row B: relay ===== -->
438+ < a href ="https://github.com/OpenNHP/opennhp/tree/main/endpoints/relay " target ="_blank " rel ="noopener " class ="arch-node ">
439+ < rect x ="500 " y ="150 " width ="440 " height ="80 " rx ="10 " fill ="rgba(245,158,11,0.08) " stroke ="#f59e0b " stroke-width ="1.5 "/>
440+ < text x ="720 " y ="184 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="16 " font-weight ="700 " text-decoration ="underline "> relay.opennhp.org — NHP-Relay</ text >
441+ < text x ="720 " y ="210 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="14 "> translates HTTP requests into NHP UDP messages</ text >
442+ </ a >
443+
444+ <!-- ===== Row C: servers ===== -->
445+ < a href ="https://github.com/OpenNHP/opennhp/tree/main/endpoints/server " target ="_blank " rel ="noopener " class ="arch-node ">
446+ < rect x ="20 " y ="300 " width ="440 " height ="100 " rx ="10 " fill ="rgba(0,255,157,0.07) " stroke ="#00ff9d " stroke-width ="1.5 "/>
447+ < text x ="240 " y ="334 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="17 " font-weight ="700 " text-decoration ="underline "> server.opennhp.org</ text >
448+ < text x ="240 " y ="360 " text-anchor ="middle " fill ="var(--text-secondary) " font-size ="14.5 "> NHP-Server · plugin auth</ text >
449+ < text x ="240 " y ="383 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="13.5 "> HTTPS exposed — reachable directly</ text >
450+ </ a >
451+
452+ < a href ="https://github.com/OpenNHP/opennhp/tree/main/endpoints/server " target ="_blank " rel ="noopener " class ="arch-node ">
453+ < rect x ="500 " y ="300 " width ="440 " height ="100 " rx ="10 " fill ="rgba(0,255,157,0.05) " stroke ="#00ff9d " stroke-width ="1.5 " stroke-dasharray ="6,4 "/>
454+ < text x ="720 " y ="334 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="17 " font-weight ="700 " text-decoration ="underline "> server2.opennhp.org</ text >
455+ < text x ="720 " y ="360 " text-anchor ="middle " fill ="var(--text-secondary) " font-size ="14.5 "> NHP-Server · NHP knock only</ text >
456+ < text x ="720 " y ="383 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="13.5 "> fully invisible — no exposed surface</ text >
457+ </ a >
458+
459+ <!-- ===== Row D: access controllers ===== -->
460+ < a href ="https://github.com/OpenNHP/opennhp/tree/main/endpoints/ac " target ="_blank " rel ="noopener " class ="arch-node ">
461+ < rect x ="20 " y ="430 " width ="440 " height ="90 " rx ="10 " fill ="rgba(168,85,247,0.07) " stroke ="#a855f7 " stroke-width ="1.5 " stroke-dasharray ="6,4 "/>
462+ < text x ="240 " y ="466 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="17 " font-weight ="700 " text-decoration ="underline "> ac.opennhp.org</ text >
463+ < text x ="240 " y ="492 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="14 "> NHP-AC · invisible · controlled by server</ text >
464+ </ a >
465+
466+ < a href ="https://github.com/OpenNHP/opennhp/tree/main/endpoints/ac " target ="_blank " rel ="noopener " class ="arch-node ">
467+ < rect x ="500 " y ="430 " width ="440 " height ="90 " rx ="10 " fill ="rgba(168,85,247,0.07) " stroke ="#a855f7 " stroke-width ="1.5 " stroke-dasharray ="6,4 "/>
468+ < text x ="720 " y ="466 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="17 " font-weight ="700 " text-decoration ="underline "> ac2.opennhp.org</ text >
469+ < text x ="720 " y ="492 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="14 "> NHP-AC · invisible · controlled by server2</ text >
470+ </ a >
471+
472+ <!-- ===== Row E: shared protected resource ===== -->
473+ < a href ="https://github.com/OpenNHP/opennhp/blob/main/examples/server_plugin/basic/templates/example_acdemo.html " target ="_blank " rel ="noopener " class ="arch-node ">
474+ < rect x ="270 " y ="550 " width ="420 " height ="110 " rx ="10 " fill ="rgba(59,130,246,0.1) " stroke ="#3b82f6 " stroke-width ="2 "/>
475+ < text x ="480 " y ="592 " text-anchor ="middle " fill ="var(--text-primary) " font-size ="17 " font-weight ="700 " text-decoration ="underline "> Protected Demo Page</ text >
476+ < text x ="480 " y ="624 " text-anchor ="middle " fill ="var(--text-muted) " font-size ="16 "> shared by ac and ac2 — behind the invisible doors</ text >
477+ </ a >
478+
479+ <!-- ===== Legend ===== -->
480+ < g transform ="translate(20, 700) ">
481+ < circle cx ="6 " cy ="0 " r ="5 " fill ="none " stroke ="#06b6d4 " stroke-width ="2 "/>
482+ < text x ="18 " y ="4 " fill ="var(--text-secondary) " font-size ="14.5 "> NHP-Agent</ text >
483+ < circle cx ="126 " cy ="0 " r ="5 " fill ="none " stroke ="#f59e0b " stroke-width ="2 "/>
484+ < text x ="138 " y ="4 " fill ="var(--text-secondary) " font-size ="14.5 "> NHP-Relay</ text >
485+ < circle cx ="246 " cy ="0 " r ="5 " fill ="none " stroke ="#00ff9d " stroke-width ="2 "/>
486+ < text x ="258 " y ="4 " fill ="var(--text-secondary) " font-size ="14.5 "> NHP-Server</ text >
487+ < circle cx ="378 " cy ="0 " r ="5 " fill ="none " stroke ="#a855f7 " stroke-width ="2 "/>
488+ < text x ="390 " y ="4 " fill ="var(--text-secondary) " font-size ="14.5 "> NHP-AC</ text >
489+ < circle cx ="470 " cy ="0 " r ="5 " fill ="none " stroke ="#3b82f6 " stroke-width ="2 "/>
490+ < text x ="482 " y ="4 " fill ="var(--text-secondary) " font-size ="14.5 "> Protected Resource</ text >
491+ < line x1 ="660 " y1 ="0 " x2 ="690 " y2 ="0 " stroke ="var(--text-muted) " stroke-width ="2 " stroke-dasharray ="5,4 "/>
492+ < text x ="696 " y ="4 " fill ="var(--text-secondary) " font-size ="14.5 "> invisible pre-auth</ text >
493+ </ g >
494+ </ svg >
495+ </ div >
496+
497+ < div style ="display: flex; flex-direction: column; gap: 0.6rem; margin-top: var(--spacing-lg); color: var(--text-secondary); font-size: 0.92rem; line-height: 1.7; ">
498+ < p style ="margin: 0; "> < strong style ="color: var(--text-primary); "> 1.</ strong > For the plugin-auth path, a person opens < span style ="color: var(--accent-primary); "> server</ span > 's browser auth page directly — no Agent or relay involved at all.</ p >
499+ < p style ="margin: 0; "> < strong style ="color: var(--text-primary); "> 2.</ strong > < code style ="background: rgba(255,255,255,0.06); padding: 0.1rem 0.4rem; border-radius: 4px; font-family: var(--font-mono); font-size: 0.9em; "> stealth-dns</ code > is a third NHP-Agent instance that reaches < strong > both</ strong > < span style ="color: var(--accent-primary); "> server</ span > and < span style ="color: var(--accent-primary); "> server2</ span > directly, bypassing < span style ="color: #f59e0b; "> relay</ span > entirely.</ p >
500+ < p style ="margin: 0; "> < strong style ="color: var(--text-primary); "> 3.</ strong > < code style ="background: rgba(255,255,255,0.06); padding: 0.1rem 0.4rem; border-radius: 4px; font-family: var(--font-mono); font-size: 0.9em; "> agent</ code > (knock) and < code style ="background: rgba(255,255,255,0.06); padding: 0.1rem 0.4rem; border-radius: 4px; font-family: var(--font-mono); font-size: 0.9em; "> reg</ code > (registration) go through < span style ="color: #f59e0b; "> relay</ span > , which translates their HTTP requests into NHP UDP messages and can reach either < span style ="color: var(--accent-primary); "> server</ span > or the fully-invisible < span style ="color: var(--accent-primary); "> server2</ span > .</ p >
501+ < p style ="margin: 0; "> < strong style ="color: var(--text-primary); "> 4.</ strong > Each Server controls its own < span style ="color: #a855f7; "> NHP-AC</ span > — < code style ="background: rgba(255,255,255,0.06); padding: 0.1rem 0.4rem; border-radius: 4px; font-family: var(--font-mono); font-size: 0.9em; "> server → ac</ code > , < code style ="background: rgba(255,255,255,0.06); padding: 0.1rem 0.4rem; border-radius: 4px; font-family: var(--font-mono); font-size: 0.9em; "> server2 → ac2</ code > . Both ACs are invisible regardless of which path was used to authenticate.</ p >
502+ < p style ="margin: 0; "> < strong style ="color: var(--text-primary); "> 5.</ strong > < code style ="background: rgba(255,255,255,0.06); padding: 0.1rem 0.4rem; border-radius: 4px; font-family: var(--font-mono); font-size: 0.9em; "> ac</ code > and < code style ="background: rgba(255,255,255,0.06); padding: 0.1rem 0.4rem; border-radius: 4px; font-family: var(--font-mono); font-size: 0.9em; "> ac2</ code > both reveal the < strong > same</ strong > protected demo page — two independent, invisible doors into one resource, not two separate resources.</ p >
503+ </ div >
504+ </ div >
505+ </ section >
506+
357507 <!-- Hiding Server Ports -->
358508 < section class ="section " style ="background: var(--bg-secondary); ">
359509 < div class ="container ">
0 commit comments