devaanand commited on
Commit
46b1454
·
1 Parent(s): 329d800

Guide: click a section heading to copy a shareable deep-link

Browse files

Sections already had stable ids (so /guide#limits worked), but there was no
way to discover or grab that link. Each id'd heading now shows a "#" on hover
and is click-to-copy: clicking sets the URL hash (scrolling to and briefly
flashing the section) and copies the full link to the clipboard, with a small
toast. Progressive enhancement - if the script fails, headings still render
and the ids still deep-link natively. Respects prefers-reduced-motion.

Files changed (1) hide show
  1. backend/app/guide.html +67 -0
backend/app/guide.html CHANGED
@@ -53,6 +53,30 @@
53
  .lbl { fill: var(--fg); font: 600 13px sans-serif; }
54
  .lbl-sm { fill: var(--muted); font: 11px sans-serif; }
55
  .flow { stroke: var(--muted); stroke-width: 1.5; fill: none; marker-end: url(#arw); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  </style>
57
  </head>
58
  <body>
@@ -411,5 +435,48 @@ and what you saw; screenshots help. DMs to Deva work too, and email as a fallbac
411
 
412
  </main>
413
  <a class="top" href="#">↑ Top</a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  </body>
415
  </html>
 
53
  .lbl { fill: var(--fg); font: 600 13px sans-serif; }
54
  .lbl-sm { fill: var(--muted); font: 11px sans-serif; }
55
  .flow { stroke: var(--muted); stroke-width: 1.5; fill: none; marker-end: url(#arw); }
56
+ /* Shareable section links: a "#" appears on hover; click a heading to copy
57
+ a direct link to that section. */
58
+ h2[id], h3[id] { scroll-margin-top: 1rem; }
59
+ .anchor {
60
+ opacity: 0; text-decoration: none; color: var(--muted); font-weight: 400;
61
+ margin-left: .4rem; cursor: pointer; transition: opacity .12s;
62
+ }
63
+ h2:hover .anchor, h3:hover .anchor, .anchor:focus { opacity: .7; }
64
+ .anchor:hover { opacity: 1; color: var(--accent); }
65
+ :target { animation: flash 1.4s ease-out; }
66
+ @keyframes flash {
67
+ from { background: var(--amber-bg); }
68
+ to { background: transparent; }
69
+ }
70
+ .toast {
71
+ position: fixed; left: 50%; bottom: 1.5rem; transform: translateX(-50%);
72
+ background: var(--accent); color: #fff; padding: .5rem .9rem; border-radius: 8px;
73
+ font-size: .85rem; opacity: 0; pointer-events: none; transition: opacity .2s;
74
+ }
75
+ .toast.show { opacity: 1; }
76
+ @media (prefers-reduced-motion: reduce) {
77
+ :target { animation: none; }
78
+ .anchor { transition: none; }
79
+ }
80
  </style>
81
  </head>
82
  <body>
 
435
 
436
  </main>
437
  <a class="top" href="#">↑ Top</a>
438
+ <div class="toast" id="toast" role="status" aria-live="polite">Link copied</div>
439
+ <script>
440
+ (function () {
441
+ var toast = document.getElementById('toast'), timer;
442
+ function ping(msg) {
443
+ toast.textContent = msg;
444
+ toast.classList.add('show');
445
+ clearTimeout(timer);
446
+ timer = setTimeout(function () { toast.classList.remove('show'); }, 1600);
447
+ }
448
+ function share(id) {
449
+ // location.hash also makes the browser scroll to and :target-flash the section.
450
+ if (history.replaceState) history.replaceState(null, '', '#' + id);
451
+ location.hash = id;
452
+ var url = location.href;
453
+ if (navigator.clipboard && navigator.clipboard.writeText) {
454
+ navigator.clipboard.writeText(url).then(
455
+ function () { ping('Section link copied'); },
456
+ function () { ping('Link in address bar'); }
457
+ );
458
+ } else {
459
+ ping('Link in address bar');
460
+ }
461
+ }
462
+ // Give every id'd heading a click-to-copy "#" affordance.
463
+ var heads = document.querySelectorAll('h2[id], h3[id]');
464
+ Array.prototype.forEach.call(heads, function (h) {
465
+ var a = document.createElement('a');
466
+ a.className = 'anchor';
467
+ a.href = '#' + h.id;
468
+ a.textContent = '#';
469
+ a.setAttribute('aria-label', 'Copy link to this section');
470
+ a.addEventListener('click', function (e) { e.preventDefault(); share(h.id); });
471
+ h.appendChild(a);
472
+ // Clicking the heading text itself (not a link inside it) also shares.
473
+ h.style.cursor = 'pointer';
474
+ h.addEventListener('click', function (e) {
475
+ if (e.target.tagName === 'A') return; // let real links behave normally
476
+ share(h.id);
477
+ });
478
+ });
479
+ })();
480
+ </script>
481
  </body>
482
  </html>