;; SX docs layout defcomps + in-page navigation. ;; Layout = root header only. Nav is in-page via ~sx-doc wrapper. ;; --------------------------------------------------------------------------- ;; Nav components — logo header, sibling arrows, children links ;; --------------------------------------------------------------------------- ;; CSSX replaces Tailwind text-*/bg-*/font-* classes — computed via cssx.sx ;; Logo + tagline + copyright — always shown at top of page area. ;; The header itself is an island so the "reactive" word can cycle colours ;; on click — demonstrates inline signals without a separate component. ;; ;; Lakes (server-morphable slots) wrap the static content: logo and copyright. ;; The server can update these during navigation morphs without disturbing ;; the reactive colour-cycling state. This is Level 2-3: the water (server ;; content) flows through the island, around the rocks (reactive signals). (defisland ~sx-header (&key path) (let ((families (list "violet" "rose" "blue" "emerald" "amber" "cyan" "red" "teal" "pink" "indigo")) (idx (signal 0)) (shade (signal 500)) (current-family (computed (fn () (nth families (mod (deref idx) (len families))))))) (div :style (str (display "block") (max-w (get cssx-max-widths "3xl")) (mx-auto) (px 4) (pt 8) (pb 4) (align "center")) ;; Logo — only this navigates home (a :href "/" :sx-get "/" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :style (str (display "block") (decoration "none")) (lake :id "logo" (span :style (str (display "block") (mb 2) (cssx (:text (colour "violet" 699) (size "4xl") (weight "bold") (family "mono")))) "()"))) ;; Tagline — clicking "reactive" cycles colour. (p :style (str (mb 1) (cssx (:text (colour "stone" 500) (size "lg")))) "Framework free " (span :style (str (cssx (:text (colour (deref current-family) (deref shade)) (weight "bold"))) "cursor:pointer;transition:color 0.3s,font-weight 0.3s;") :on-click (fn (e) (batch (fn () (swap! idx inc) (reset! shade (+ 400 (* (mod (* (deref idx) 137) 5) 50)))))) "reactive") " hypermedia") ;; Lake: server morphs copyright on navigation without disturbing signals. (lake :id "copyright" (p :style (cssx (:text (colour "stone" 400) (size "xs"))) "© Giles Bradshaw 2026" (when path (span :style (str (cssx (:text (colour "stone" 300) (size "xs"))) "margin-left:0.5em;") (str "· " path)))))))) ;; @css grid grid-cols-3 ;; Current section with prev/next siblings. ;; 3-column grid: prev is right-aligned, current centered, next left-aligned. ;; Current page is larger in the leaf (bottom) row. (defcomp ~nav-sibling-row (&key node siblings is-leaf level depth) (let* ((sibs (or siblings (list))) (count (len sibs)) ;; opacity = (n/x * 3/4) + 1/4 (row-opacity (if (and level depth (> depth 0)) (+ (* (/ level depth) 0.75) 0.25) 1))) (when (> count 0) (let* ((idx (find-nav-index sibs node)) (prev-idx (mod (+ (- idx 1) count) count)) (next-idx (mod (+ idx 1) count)) (prev-node (nth sibs prev-idx)) (next-node (nth sibs next-idx))) (div :class "max-w-3xl mx-auto px-4 py-2 grid grid-cols-3 items-center" :style (str "opacity:" row-opacity ";transition:opacity 0.3s;") (a :href (get prev-node "href") :sx-get (get prev-node "href") :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-right" :style (cssx (:text (colour "stone" 500) (size "sm"))) (str "← " (get prev-node "label"))) (a :href (get node "href") :sx-get (get node "href") :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-center px-4" :style (if is-leaf (cssx (:text (colour "violet" 700) (size "2xl") (weight "bold"))) (cssx (:text (colour "violet" 700) (size "lg") (weight "semibold")))) (get node "label")) (a :href (get next-node "href") :sx-get (get next-node "href") :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-left" :style (cssx (:text (colour "stone" 500) (size "sm"))) (str (get next-node "label") " →"))))))) ;; Children links — shown as clearly clickable buttons. (defcomp ~nav-children (&key items) (div :class "max-w-3xl mx-auto px-4 py-3" (div :class "flex flex-wrap justify-center gap-2" (map (fn (item) (a :href (get item "href") :sx-get (get item "href") :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "px-3 py-1.5 rounded border transition-colors" :style (cssx (:text (colour "violet" 700) (size "sm")) (:border (colour "violet" 200))) (get item "label"))) items)))) ;; --------------------------------------------------------------------------- ;; ~sx-doc — in-page content wrapper with nav ;; Used by every defpage :content to embed nav inside the page content area. ;; --------------------------------------------------------------------------- (defcomp ~sx-doc (&key path &rest children) :affinity :server (let* ((nav-state (resolve-nav-path sx-nav-tree (or path "/"))) (trail (or (get nav-state "trail") (list))) (trail-len (len trail)) ;; Total nav levels: logo (1) + trail rows (depth (+ trail-len 1))) (<> (div :id "sx-nav" :class "mb-6" ;; Logo opacity = (1/depth * 3/4) + 1/4 ;; Wrapper is outside the island so the server morphs it directly (div :id "logo-opacity" :style (str "opacity:" (+ (* (/ 1 depth) 0.75) 0.25) ";" "transition:opacity 0.3s;") (~sx-header :path (or path "/"))) ;; Sibling arrows for EVERY level in the trail ;; Trail row i is level (i+2) of depth — opacity = (i+2)/depth ;; Last row (leaf) gets is-leaf for larger current page title (map-indexed (fn (i crumb) (~nav-sibling-row :node (get crumb "node") :siblings (get crumb "siblings") :is-leaf (= i (- trail-len 1)) :level (+ i 2) :depth depth)) trail) ;; Children as button links (when (get nav-state "children") (~nav-children :items (get nav-state "children")))) ;; Page content follows children))) ;; --------------------------------------------------------------------------- ;; SX docs layouts — root header only (nav is in page content via ~sx-doc) ;; --------------------------------------------------------------------------- (defcomp ~sx-docs-layout-full () nil) (defcomp ~sx-docs-layout-oob () nil) (defcomp ~sx-docs-layout-mobile () nil) ;; --------------------------------------------------------------------------- ;; Standalone layouts (no root header — for sx-web.org) ;; --------------------------------------------------------------------------- (defcomp ~sx-standalone-docs-layout-full () nil) ;; Standalone OOB: nothing needed — nav is in content. (defcomp ~sx-standalone-docs-layout-oob () nil) ;; Standalone mobile: nothing — nav is in content. (defcomp ~sx-standalone-docs-layout-mobile () nil)