Islands survive hypermedia swaps: morph-node skips hydrated data-sx-island elements when the same island exists in new content. dispose-islands-in skips hydrated islands to prevent premature cleanup. - @client directive: .sx files marked ;; @client send define forms to browser - CSSX client-side: cssxgroup renamed (no hyphen) to avoid isRenderExpr matching it as a custom element — was producing [object HTMLElement] - Island wrappers: div→span to avoid block-in-inline HTML parse breakage - ~sx-header is now a defisland with inline reactive colour cycling - bootstrap_js.py defaults output to shared/static/scripts/sx-browser.js - Deleted stale sx-ref.js (sx-browser.js is the canonical browser build) - Hegelian Synthesis essay: dialectic of hypertext and reactivity - component-source helper handles Island types for docs pretty-printing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
220 lines
8.4 KiB
Plaintext
220 lines
8.4 KiB
Plaintext
;; @client — send all define forms to browser for client-side use.
|
|
;; CSSX — computed CSS from s-expressions.
|
|
;;
|
|
;; Generic mechanism: cssx is a macro that groups CSS property declarations.
|
|
;; The vocabulary (property mappings, value functions) is pluggable — the
|
|
;; Tailwind-inspired defaults below are just one possible style system.
|
|
;;
|
|
;; Usage:
|
|
;; (cssx (:text (colour "violet" 699) (size "4xl") (weight "bold") (family "mono"))
|
|
;; (:bg (colour "stone" 50)))
|
|
;;
|
|
;; Each group is (:keyword value ...modifiers):
|
|
;; - keyword maps to a CSS property via cssx-properties dict
|
|
;; - value is the CSS value for that property
|
|
;; - modifiers are extra CSS declaration strings, concatenated in
|
|
;;
|
|
;; Single group:
|
|
;; (cssx (:text (colour "violet" 699)))
|
|
;;
|
|
;; Modifiers without a colour:
|
|
;; (cssx (:text nil (size "4xl") (weight "bold")))
|
|
;;
|
|
;; Unknown keywords pass through as raw CSS property names:
|
|
;; (cssx (:outline (colour "red" 500))) → "outline:hsl(0,72%,53%);"
|
|
;;
|
|
;; Standalone modifiers work outside cssx too:
|
|
;; :style (size "4xl")
|
|
;; :style (str (weight "bold") (family "mono"))
|
|
|
|
;; =========================================================================
|
|
;; Layer 1: Generic mechanism — cssx macro + cssxgroup function
|
|
;; =========================================================================
|
|
|
|
;; Property keyword → CSS property name. Extend this dict for new mappings.
|
|
(define cssx-properties
|
|
{"text" "color"
|
|
"bg" "background-color"
|
|
"border" "border-color"})
|
|
|
|
;; Evaluate one property group: (:text value modifier1 modifier2 ...)
|
|
;; If value is nil, only modifiers are emitted (no property declaration).
|
|
;; NOTE: name must NOT contain hyphens — the evaluator's isRenderExpr check
|
|
;; treats (hyphenated-name :keyword ...) as a custom HTML element.
|
|
(define cssxgroup
|
|
(fn (prop value b c d e)
|
|
(let ((css-prop (or (get cssx-properties prop) prop)))
|
|
(str (if (nil? value) "" (str css-prop ":" value ";"))
|
|
(or b "") (or c "") (or d "") (or e "")))))
|
|
|
|
;; cssx macro — takes one or more property groups, expands to (str ...).
|
|
;; (cssx (:text val ...) (:bg val ...))
|
|
;; → (str (cssxgroup :text val ...) (cssxgroup :bg val ...))
|
|
(defmacro cssx (&rest groups)
|
|
`(str ,@(map (fn (g) (cons 'cssxgroup g)) groups)))
|
|
|
|
;; =========================================================================
|
|
;; Layer 2: Value vocabulary — colour, size, weight, family
|
|
;; These are independent functions. Use inside cssx groups or standalone.
|
|
;; Replace or extend with any style system.
|
|
;; =========================================================================
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Colour — compute CSS colour value from name + shade
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(define colour-bases
|
|
{"violet" {"h" 263 "s" 70}
|
|
"purple" {"h" 271 "s" 81}
|
|
"indigo" {"h" 239 "s" 84}
|
|
"blue" {"h" 217 "s" 91}
|
|
"sky" {"h" 199 "s" 89}
|
|
"cyan" {"h" 188 "s" 94}
|
|
"teal" {"h" 173 "s" 80}
|
|
"emerald" {"h" 160 "s" 84}
|
|
"green" {"h" 142 "s" 71}
|
|
"lime" {"h" 84 "s" 78}
|
|
"yellow" {"h" 48 "s" 96}
|
|
"amber" {"h" 38 "s" 92}
|
|
"orange" {"h" 25 "s" 95}
|
|
"red" {"h" 0 "s" 72}
|
|
"rose" {"h" 350 "s" 89}
|
|
"pink" {"h" 330 "s" 81}
|
|
"stone" {"h" 25 "s" 6}
|
|
"slate" {"h" 215 "s" 16}
|
|
"gray" {"h" 220 "s" 9}
|
|
"zinc" {"h" 240 "s" 5}
|
|
"neutral" {"h" 0 "s" 0}})
|
|
|
|
(define lerp (fn (a b t) (+ a (* t (- b a)))))
|
|
|
|
(define shade-to-lightness
|
|
(fn (shade)
|
|
(cond
|
|
(<= shade 50) (lerp 100 97 (/ shade 50))
|
|
(<= shade 100) (lerp 97 93 (/ (- shade 50) 50))
|
|
(<= shade 200) (lerp 93 87 (/ (- shade 100) 100))
|
|
(<= shade 300) (lerp 87 77 (/ (- shade 200) 100))
|
|
(<= shade 400) (lerp 77 64 (/ (- shade 300) 100))
|
|
(<= shade 500) (lerp 64 53 (/ (- shade 400) 100))
|
|
(<= shade 600) (lerp 53 45 (/ (- shade 500) 100))
|
|
(<= shade 700) (lerp 45 38 (/ (- shade 600) 100))
|
|
(<= shade 800) (lerp 38 30 (/ (- shade 700) 100))
|
|
(<= shade 900) (lerp 30 21 (/ (- shade 800) 100))
|
|
(<= shade 950) (lerp 21 13 (/ (- shade 900) 50))
|
|
true 13)))
|
|
|
|
(define colour
|
|
(fn (name shade)
|
|
(let ((base (get colour-bases name)))
|
|
(if (nil? base)
|
|
name
|
|
(let ((h (get base "h"))
|
|
(s (get base "s"))
|
|
(l (shade-to-lightness shade)))
|
|
(str "hsl(" h "," s "%," (round l) "%)"))))))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Font sizes — named size → font-size + line-height (Tailwind v3 scale)
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(define cssx-sizes
|
|
{"xs" "font-size:0.75rem;line-height:1rem;"
|
|
"sm" "font-size:0.875rem;line-height:1.25rem;"
|
|
"base" "font-size:1rem;line-height:1.5rem;"
|
|
"lg" "font-size:1.125rem;line-height:1.75rem;"
|
|
"xl" "font-size:1.25rem;line-height:1.75rem;"
|
|
"2xl" "font-size:1.5rem;line-height:2rem;"
|
|
"3xl" "font-size:1.875rem;line-height:2.25rem;"
|
|
"4xl" "font-size:2.25rem;line-height:2.5rem;"
|
|
"5xl" "font-size:3rem;line-height:1;"
|
|
"6xl" "font-size:3.75rem;line-height:1;"
|
|
"7xl" "font-size:4.5rem;line-height:1;"
|
|
"8xl" "font-size:6rem;line-height:1;"
|
|
"9xl" "font-size:8rem;line-height:1;"})
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Font weights — named weight → numeric value
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(define cssx-weights
|
|
{"thin" "100"
|
|
"extralight" "200"
|
|
"light" "300"
|
|
"normal" "400"
|
|
"medium" "500"
|
|
"semibold" "600"
|
|
"bold" "700"
|
|
"extrabold" "800"
|
|
"black" "900"})
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Font families — named family → CSS font stack
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(define cssx-families
|
|
{"sans" "ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,\"Noto Sans\",sans-serif"
|
|
"serif" "ui-serif,Georgia,Cambria,\"Times New Roman\",Times,serif"
|
|
"mono" "ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace"})
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Standalone modifier functions — return CSS declaration strings
|
|
;; Each returns a complete CSS declaration string. Use inside cssx groups
|
|
;; or standalone on :style with str.
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
;; -- Typography --
|
|
|
|
(define size
|
|
(fn (s) (or (get cssx-sizes s) (str "font-size:" s ";"))))
|
|
|
|
(define weight
|
|
(fn (w)
|
|
(let ((v (get cssx-weights w)))
|
|
(str "font-weight:" (or v w) ";"))))
|
|
|
|
(define family
|
|
(fn (f)
|
|
(let ((v (get cssx-families f)))
|
|
(str "font-family:" (or v f) ";"))))
|
|
|
|
(define align
|
|
(fn (a) (str "text-align:" a ";")))
|
|
|
|
(define decoration
|
|
(fn (d) (str "text-decoration:" d ";")))
|
|
|
|
;; -- Spacing (Tailwind scale: 1 unit = 0.25rem) --
|
|
|
|
(define spacing (fn (n) (str (* n 0.25) "rem")))
|
|
|
|
(define p (fn (n) (str "padding:" (spacing n) ";")))
|
|
(define px (fn (n) (str "padding-left:" (spacing n) ";padding-right:" (spacing n) ";")))
|
|
(define py (fn (n) (str "padding-top:" (spacing n) ";padding-bottom:" (spacing n) ";")))
|
|
(define pt (fn (n) (str "padding-top:" (spacing n) ";")))
|
|
(define pb (fn (n) (str "padding-bottom:" (spacing n) ";")))
|
|
(define pl (fn (n) (str "padding-left:" (spacing n) ";")))
|
|
(define pr (fn (n) (str "padding-right:" (spacing n) ";")))
|
|
|
|
(define m (fn (n) (str "margin:" (spacing n) ";")))
|
|
(define mx (fn (n) (str "margin-left:" (spacing n) ";margin-right:" (spacing n) ";")))
|
|
(define my (fn (n) (str "margin-top:" (spacing n) ";margin-bottom:" (spacing n) ";")))
|
|
(define mt (fn (n) (str "margin-top:" (spacing n) ";")))
|
|
(define mb (fn (n) (str "margin-bottom:" (spacing n) ";")))
|
|
(define ml (fn (n) (str "margin-left:" (spacing n) ";")))
|
|
(define mr (fn (n) (str "margin-right:" (spacing n) ";")))
|
|
(define mx-auto (fn () "margin-left:auto;margin-right:auto;"))
|
|
|
|
;; -- Display & layout --
|
|
|
|
(define display (fn (d) (str "display:" d ";")))
|
|
(define max-w (fn (w) (str "max-width:" w ";")))
|
|
|
|
;; Named max-widths (Tailwind scale)
|
|
(define cssx-max-widths
|
|
{"xs" "20rem" "sm" "24rem" "md" "28rem"
|
|
"lg" "32rem" "xl" "36rem" "2xl" "42rem"
|
|
"3xl" "48rem" "4xl" "56rem" "5xl" "64rem"
|
|
"6xl" "72rem" "7xl" "80rem"
|
|
"full" "100%" "none" "none"})
|