diff --git a/hugo.toml b/hugo.toml index c633050..c6479be 100644 --- a/hugo.toml +++ b/hugo.toml @@ -1,40 +1,87 @@ baseURL = 'https://go-ndarray.github.io/' locale = 'en-us' title = 'go-ndarray' -# No theme — layouts live in ./layouts +# No theme -- the layout is the go-* family template go-fft.github.io uses +# (itself go-authn.github.io's): one self-contained layouts/index.html, +# partials/styles.html (inline :root tokens, a verbatim copy of go-fft's) and +# partials/theme-toggle.html (system / light / dark). -disableKinds = ['taxonomy', 'term', 'rss', 'sitemap'] +disableKinds = ['taxonomy', 'term', 'rss', 'sitemap', '404'] + +[markup.goldmark.renderer] + unsafe = true [params] description = "go-ndarray is a pure-Go (CGO=0) NumPy-style n-dimensional array library for float64 — slicing/views, broadcasting, ufuncs, reductions and linear algebra, multicore + SIMD on amd64 and arm64, with a Workspace arena for loops." - brand = "go-ndarray" - github = "go-ndarray" - heroAccent = "go-ndarray" - heroTagline = "NumPy-style n-dimensional arrays in pure Go. float64, multicore + SIMD." - heroIntro = [ - "go-ndarray is the numpy equivalent for Go: a float64 n-dimensional array with creation routines, strided views that share data, broadcasting elementwise ops and ufuncs, reductions (incl. arg / cumulative / clip / where), manipulation, and linear algebra (a panel-packed, cache-blocked GEMM) — all in pure Go, with cgo disabled.", - "Ruby has no cgo-free ndarray (Numo::NArray and NMatrix are C extensions) and gonum's optimized assembly is amd64-only. go-ndarray pairs a portable pure-Go core with multicore fan-out and, on amd64 and arm64, go-asmgen SIMD kernels; a Workspace arena takes the garbage collector out of loops. Measured against NumPy 2.5.3 on a 16-core Zen 3 it is faster on whole-array and row reductions, on Exp from 256 Ki elements and on MatMul against single-threaded OpenBLAS, and slower against 16-thread BLAS. 100% test coverage is the bar, differentially checked against NumPy." - ] +# The colours are read by partials/styles.html. Emerald, shared with go-fft: +# the two are one scientific-computing family, told apart by their glyphs. +# The values are go-fft's, unchanged. +[params.brand] + base = "#059669" # emerald-600: accent, primary button, gradient start + deep = "#047857" # emerald-700: gradient end + bright = "#34d399" # emerald-400: accent in dark mode + shadow = "rgba(5,150,105,.7)" + chipBg = "#d1fae5" + chipBorder = "#a7f3d0" + chipFg = "#047857" + chipBgDark = "rgba(52,211,153,.14)" + chipBorderDark = "rgba(52,211,153,.32)" + chipFgDark = "#6ee7b7" + glow = "rgba(110,231,183,.22)" + glow2 = "rgba(5,150,105,.10)" + glyphShadow = "rgba(5,150,105,.6)" + statsTint = "#a7f3d0" + bandFg = "#d1fae5" + # White on emerald-600 is 3.77:1, under the 4.5:1 body text needs; the band + # paragraph and the primary button sit on this gradient, so it runs one step + # darker than the accent: 700 -> 800 (white 5.48:1, #d1fae5 4.84:1 at worst). + gradFrom = "#047857" + gradTo = "#065f46" + # The hero's accent words: emerald-400 -> 300 on the dark background (the + # light theme keeps base -> deep, 600 -> 700). + gradTextDark = "linear-gradient(135deg,#34d399,#6ee7b7)" + [params.brand.surfaces] + ink = "#052e22" + slate = "#065f46" + muted2 = "#047857" + line = "#a7f3d0" + bg = "#f0fdf9" + bgSoft = "#d1fae5" + surface = "#ffffff" + navBg = "rgba(240,253,249,.82)" + navBorder = "#a7f3d0" + elevBorder = "#6ee7b7" + ghostHover = "#6ee7b7" + statsBg = "#052e22" + [params.brand.surfacesDark] + ink = "#d1fae5" + slate = "#a7f3d0" + muted2 = "#6ee7b7" + line = "#0a3d2a" + bg = "#04130d" + bgSoft = "#052e22" + surface = "#06291c" + navBg = "rgba(4,19,13,.82)" + navBorder = "#0a3d2a" + elevBorder = "#10593d" + ghostHover = "#10593d" + statsBg = "#06291c" -[menu] - [[menu.main]] - identifier = "why" - name = "Why" - url = "#why" - weight = 10 - [[menu.main]] - identifier = "repos" - name = "Repos" - url = "#repos" - weight = 20 - [[menu.main]] - identifier = "quality" - name = "Quality" - url = "#quality" - weight = 30 - [[menu.main]] - identifier = "docs" - name = "Docs" - url = "https://go-ndarray.github.io/docs/" - weight = 40 +# Each repo. Every field is rendered: "name" in the strip under the hero and on +# its card, "kind" as the card's tag, "href" as both links, "blurb" in full. +[[params.repos]] + name = "ndarray" + kind = "library · v0.2.2" + href = "https://github.com/go-ndarray/ndarray" + blurb = "The library. Creation (Zeros/Arange/Linspace/…), strided views, broadcasting elementwise + ufuncs, reductions (Sum/Max/ArgMax/CumSum/Clip/Where), manipulation (Reshape/Concatenate/Stack), linalg (MatMul/Dot) via a panel-packed cache-blocked GEMM with a go-asmgen SIMD-FMA micro-kernel (NEON 4×8 on arm64, AVX2/FMA 6×8 on amd64), and a Workspace arena for loops (Use/Reset/Detach)." +[[params.repos]] + name = "docs" + kind = "site" + href = "https://go-ndarray.github.io/docs/" + blurb = "Versioned documentation site (MkDocs Material): API reference, the roadmap, and honest benchmark pages versus NumPy 2.5.3 / OpenBLAS 0.3.34. Source →" +[[params.repos]] + name = "brand" + kind = "assets" + href = "https://github.com/go-ndarray/brand" + blurb = "Logos and icons for the organization, in SVG / PNG / JPG / ICO / ICNS across colour, white and black variants." diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html deleted file mode 100644 index 1f54d98..0000000 --- a/layouts/_default/baseof.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - {{ .Site.Title }} — pure-Go NumPy-style arrays - - - - - - - - - {{ partial "nav.html" . }} - -
- {{ block "main" . }}{{ end }} -
- - {{ partial "footer.html" . }} - - - diff --git a/layouts/index.html b/layouts/index.html index 9dccc8f..acc3798 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -1,57 +1,110 @@ -{{ define "main" }} -
- -

- {{ .Site.Params.heroAccent }} -

- {{ with .Site.Params.heroTagline }}

{{ . }}

{{ end }} -
- {{ range .Site.Params.heroIntro }}

{{ . | safeHTML }}

{{ end }} -
-
- -
-

Why go-ndarray

-

- NumPy is the lingua franca of numerical computing, but in Go the choices are - gonum (matrix-centric, amd64-only assembly) or a C extension. go-ndarray - brings the n-dimensional array vocabulary — shapes, strided - views, broadcasting, ufuncs, reductions and linear algebra — to Go with - no cgo, cross-compiling to a static binary everywhere. The hot paths - are multicore, and SIMD on amd64 and arm64, so it beats NumPy on - whole-array and row reductions, large Exp and MatMul against - single-threaded BLAS; a Workspace arena removes the garbage-collector cost - of temporaries in loops. It is the cgo-free ndarray the Go and Ruby ecosystems lacked. -

-
- -
-

Repositories

-
-
-

ndarray

-

The library. Creation (Zeros/Arange/Linspace/…), strided views, broadcasting elementwise + ufuncs, reductions (Sum/Max/ArgMax/CumSum/Clip/Where), manipulation (Reshape/Concatenate/Stack), linalg (MatMul/Dot) via a panel-packed cache-blocked GEMM with a go-asmgen SIMD-FMA micro-kernel (NEON 4×8 on arm64, AVX2/FMA 6×8 on amd64), and a Workspace arena for loops (Use/Reset/Detach).

+ + + + + +{{ .Site.Title }} — pure-Go NumPy-style arrays + + +{{ partial "styles.html" . }} + + + + +
+
+

NumPy-style n-dimensional arrays in pure Go.
float64, multicore + SIMD.

+

go-ndarray is the numpy equivalent for Go: a float64 n-dimensional array with creation routines, strided views that share data, broadcasting elementwise ops and ufuncs, reductions (incl. arg / cumulative / clip / where), manipulation, and linear algebra (a panel-packed, cache-blocked GEMM) — all in pure Go, with cgo disabled.

+ +
{{ len .Site.Params.repos }} repositoriesCGO_ENABLED=0, no BLAS shim100% test coverage
-
-

docs

-

Versioned documentation site (MkDocs Material): API reference, the roadmap, and honest benchmark pages versus NumPy 2.5.3 / OpenBLAS 0.3.34. Source →

+ {{/* Lines taken verbatim from the Example section of go-ndarray/ndarray's README at v0.2.2. */}} +
+
main.go
+
import "github.com/go-ndarray/ndarray"
+
+a, _ := ndarray.Arange(0, 6, 1)     // [0 1 2 3 4 5]
+m, _ := a.Reshape(2, -1)            // -1 inferred -> [[0 1 2] [3 4 5]]
+
+row, _ := ndarray.FromData([]float64{10, 20, 30}, 3)
+sum, _ := m.Add(row)                // broadcast (2,3)+(3,) -> (2,3)
+
+t := m.Transpose()                  // zero-copy (3,2) view
+cols, _ := m.SumAxis(0, false)      // sum down each column -> [3 5 7]
+
+// NumPy basic-indexing views (share data):
+col0, _ := m.Slice(ndarray.All(), ndarray.A(0))     // m[:,0] -> [0 3]
+
+// linear algebra
+b, _ := ndarray.Arange(0, 6, 1)
+b, _ = b.Reshape(3, 2)
+prod, _ := m.MatMul(b)              // (2,3)·(3,2) -> (2,2)
-
-

brand

-

Logos and icons for the organization, in SVG / PNG / JPG / ICO / ICNS across colour, white and black variants.

+
+ +
+ {{ len .Site.Params.repos }} repositories — one cgo-free module that cross-compiles to a static binary on every target +
{{ range .Site.Params.repos }}{{ .name }}{{ end }}
+
+ +
+ Why go-ndarray +

The n-dimensional array, without cgo.

+

NumPy is the lingua franca of numerical computing, but in Go the choices are gonum (matrix-centric, amd64-only assembly) or a C extension. go-ndarray brings the n-dimensional array vocabulary — shapes, strided views, broadcasting, ufuncs, reductions and linear algebra — to Go with no cgo, cross-compiling to a static binary everywhere. The hot paths are multicore, and SIMD on amd64 and arm64.

+
+
🐹

Pure Go

CGO_ENABLED=0 across the org. No cgo, no BLAS shim.

+
✅

100% test coverage

Gated in CI, differentially checked against numpy.

+
🔢

NumPy semantics

Row-major strided arrays, NumPy broadcasting, and NumPy basic-indexing Slice returning strided views that share data.

+
♻️

Workspace arena for loops

Bind the inputs with ws.Use, compute as usual, ws.Reset() at the end of each pass: results stop costing the garbage collector anything. On a Zen 3, x + y on 1 024 elements drops from 2.6 to 0.38 µs (2.1× NumPy).

+
⚡

SIMD on amd64 & arm64

go-asmgen SIMD kernels on amd64 (SSE2, AVX2/FMA picked at run time) and arm64 (NEON); riscv64, loong64, ppc64le, s390x and the 32-bit targets run the same pure-Go code those kernels are tested against.

+
🎯

Accurate Exp/Log

Ports of Arm's optimized-routines: worst 0.504 / 0.508 ULP against a 300-bit reference, and correct where Go's amd64 math.Exp returns +Inf (golang/go#81995) and math.Log is wrong on subnormals (golang/go#56600).

+
🧪

Multi-arch CI

The suite runs on amd64, arm64 and 386 natively and on riscv64/loong64/ppc64le/s390x/arm under qemu, on Linux, macOS and Windows, and compiles for every GOOS/GOARCH pair Go supports; v0.2.0 was also run on real amd64, arm64, ppc64le, riscv64 and loong64 hardware.

+
💎

The cgo-free ndarray that was missing

Ruby has no cgo-free ndarray (Numo::NArray and NMatrix are C extensions) and gonum's optimized assembly is amd64-only. go-ndarray pairs a portable pure-Go core with multicore fan-out and SIMD kernels.

+
⚖️

BSD-3-Clause

Exp and Log are MIT-licensed ports of Arm's optimized-routines.

-
-
- -
-

Quality bar

-
    -
  • Pure Go. CGO_ENABLED=0 across the org. No cgo, no BLAS shim.
  • -
  • 100% test coverage. Gated in CI, differentially checked against numpy.
  • -
  • Measured, wins and losses. Against NumPy 2.5.3 + OpenBLAS 0.3.34 on a 16-core AMD Zen 3: faster on whole-array Sum/Mean/Max at 4 Mi (2.3–4.5×), SumAxis(1) (2.2×), MaxAxis(1) (1.7×), Exp from 256 Ki elements (2.4–5.6×) and MatMul against single-threaded OpenBLAS (3.7–5.9× from 512²); slower against 16-thread OpenBLAS (MatMul 0.55× at 1024², Dot/mat·vec 0.16×) and on allocating elementwise forms outside a Workspace. In a Workspace, x + y on 1 024 elements drops from 2.6 to 0.38 µs (2.1× NumPy). On Apple silicon the packed GEMM reaches tuned-BLAS parity at 1024² and beats the pure-Go gonum 4–10×.
  • -
  • Accurate Exp/Log. Ports of Arm's optimized-routines: worst 0.504 / 0.508 ULP against a 300-bit reference, and correct where Go's amd64 math.Exp returns +Inf (golang/go#81995) and math.Log is wrong on subnormals (golang/go#56600).
  • -
  • Multi-arch. go-asmgen SIMD kernels on amd64 (SSE2, AVX2/FMA picked at run time) and arm64 (NEON); riscv64, loong64, ppc64le, s390x and the 32-bit targets run the same pure-Go code those kernels are tested against. CI runs the suite on amd64, arm64 and 386 natively and on riscv64/loong64/ppc64le/s390x/arm under qemu, on Linux, macOS and Windows, and compiles it for every GOOS/GOARCH pair Go supports; v0.2.0 was also run on real amd64, arm64, ppc64le, riscv64 and loong64 hardware.
  • -
  • BSD-3-Clause licensed; Exp and Log are MIT-licensed ports of Arm's optimized-routines.
  • -
-
-{{ end }} +
+ +
+
+
100%
test coverage, differentially checked against numpy
+
2.3–4.5×
NumPy's speed on whole-array Sum/Mean/Max at 4 Mi (NumPy 2.5.3, 16-core Zen 3)
+
3.7–5.9×
single-threaded OpenBLAS 0.3.34 on MatMul from 512² (Zen 3)
+
≤ 0.51
ULP for Exp/Log, ports of Arm's optimized-routines
+
+

Measured, wins and losses. Against NumPy 2.5.3 + OpenBLAS 0.3.34 on a 16-core AMD Zen 3: faster on whole-array Sum/Mean/Max at 4 Mi (2.3–4.5×), SumAxis(1) (2.2×), MaxAxis(1) (1.7×), Exp from 256 Ki elements (2.4–5.6×) and MatMul against single-threaded OpenBLAS (3.7–5.9× from 512²); slower against 16-thread OpenBLAS (MatMul 0.55× at 1024², Dot/mat·vec 0.16×) and on allocating elementwise forms outside a Workspace. On Apple silicon the packed GEMM reaches tuned-BLAS parity at 1024² and beats the pure-Go gonum 4–10×.

+
+ +
+ Repositories +

What is here.

+
+ {{ range .Site.Params.repos }} +
+

{{ .name }}

{{ .kind }}
+

{{ .blurb | safeHTML }}

+
+ {{ end }} +
+
+ +
+
+

The cgo-free ndarray the Go and Ruby ecosystems lacked.

+

No cgo, no BLAS shim. A portable pure-Go core, multicore, with SIMD kernels on amd64 and arm64 and a Workspace arena that takes the garbage collector out of loops. Faster than NumPy on whole-array and row reductions, on large Exp and on MatMul against single-threaded BLAS, slower against 16-thread BLAS — the numbers are on the performance page.

+ Read the source on GitHub → +
+
+ + + + diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html deleted file mode 100644 index a145f73..0000000 --- a/layouts/partials/footer.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/layouts/partials/nav.html b/layouts/partials/nav.html deleted file mode 100644 index f5f668c..0000000 --- a/layouts/partials/nav.html +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/layouts/partials/styles.html b/layouts/partials/styles.html new file mode 100644 index 0000000..519456b --- /dev/null +++ b/layouts/partials/styles.html @@ -0,0 +1,235 @@ +{{/* The brand colours are the ONLY thing that differs between the orgs using + this stylesheet, and they are read from [params.brand] rather than written + in. Two byte-identical copies of this file already existed in go-authn and + go-fileshare; a third differing only in a colour block is how a shared + stylesheet stops being shared. + + Every default below is the cyan those two use TODAY, so they can adopt this + file with no params and render exactly what they render now. That property + is checked, not assumed: see the README. + + safeCSS on every value, including the hex ones that would not need it. + html/template rejects rgba(...) in a CSS context and substitutes + ZgotmplZ for the whole declaration -- it does not fail, it renders + nonsense -- so the chip tints came out as `--chip-bg:ZgotmplZ` until a + render-against-render check caught it. Marking only the values that + need it would leave the next person working out which kind each one is. */}} +{{ $br := .Site.Params.brand | default dict }} +{{ $base := $br.base | default "#0079A8" }} +{{ $deep := $br.deep | default "#054A66" }} +{{ $bright := $br.bright | default "#22CCE2" }} +{{ $shadow := $br.shadow | default "rgba(0,121,168,.7)" }} +{{ $chipBg := $br.chipBg | default "#e8f8fc" }} +{{ $chipBorder := $br.chipBorder | default "#bde9f4" }} +{{ $chipFg := $br.chipFg | default "#054A66" }} +{{ $chipBgD := $br.chipBgDark | default "rgba(34,204,226,.14)" }} +{{ $chipBorderD := $br.chipBorderDark | default "rgba(34,204,226,.32)" }} +{{ $chipFgD := $br.chipFgDark | default "#7fe4f3" }} +{{/* Added for go-fft: the tints that were still written in as cyan, and the + neutral surfaces, which go-fft keeps emerald-tinted. Every default is the + value go-authn renders today, so a copy with no such params is unchanged. */}} +{{ $glow := $br.glow | default "rgba(0,121,168,.10)" }} +{{ $glow2 := $br.glow2 | default "rgba(5,74,102,.06)" }} +{{ $glyphShadow := $br.glyphShadow | default "rgba(0,121,168,.6)" }} +{{ $statsTint := $br.statsTint | default "#cdf1fa" }} +{{ $bandFg := $br.bandFg | default "#d8f3fb" }} +{{/* The gradient behind white text (primary button, CTA band). It defaults to + base -> deep, as before; an org whose base is too light to carry white + text at 4.5:1 moves it one step darker without changing its accent. */}} +{{ $gradFrom := $br.gradFrom | default $base }} +{{ $gradTo := $br.gradTo | default $deep }} +{{/* The gradient the hero's accent words are painted with. It stays base -> + deep in both themes unless gradTextDark is set: with the darker button + gradient above, the same words would vanish into a dark background. */}} +{{ $gradText := printf "linear-gradient(135deg,%s,%s)" $base $deep }} +{{ $gradTextD := $br.gradTextDark | default $gradText }} +{{ $sf := $br.surfaces | default dict }} +{{ $sfD := $br.surfacesDark | default dict }} + + + diff --git a/layouts/partials/theme-toggle.html b/layouts/partials/theme-toggle.html new file mode 100644 index 0000000..a7cccef --- /dev/null +++ b/layouts/partials/theme-toggle.html @@ -0,0 +1 @@ + diff --git a/static/css/main.css b/static/css/main.css deleted file mode 100644 index d182516..0000000 --- a/static/css/main.css +++ /dev/null @@ -1,355 +0,0 @@ -/* go-ndarray landing — emerald palette, sibling of the go-* family pages. - Same token shape as the other org landings; only the hex values change - so the pages read as one family. Accent: emerald (#059669 green-600). */ -:root { - --fg: #052e22; - --bg: #f0fdf9; - --bg-2: #d1fae5; - --muted: #047857; - --accent: #059669; - --accent-2: #10b981; - --accent-3: #6ee7b7; - --code-bg: #d1fae5; - --border: #a7f3d0; - --thread: #059669; - --mesh-edge: rgba(5, 150, 105, 0.32); - --mesh-edge-hi: rgba(5, 150, 105, 0.85); - --mesh-arch-fill: #059669; - --mesh-arch-halo: rgba(5, 150, 105, 0.30); - --mesh-distro-fill: #10b981; - --mesh-distro-halo: rgba(245, 158, 11, 0.22); - --mesh-hub-fill: #b45309; - --mesh-hub-halo: rgba(180, 83, 9, 0.36); - --mesh-label-fg: #78350f; - --mesh-container-dc-fill: rgba(5, 150, 105, 0.06); - --mesh-container-dc-stroke: rgba(5, 150, 105, 0.55); - --mesh-container-rack-fill: rgba(5, 150, 105, 0.14); - --mesh-container-rack-stroke: rgba(5, 150, 105, 0.45); - --mesh-container-label-fg: rgba(120, 53, 15, 0.85); - --hero-gradient-1: rgba(110, 231, 183, 0.30); - --hero-gradient-2: rgba(5, 150, 105, 0.18); -} -@media (prefers-color-scheme: dark) { - :root { - --fg: #d1fae5; - --bg: #04130d; - --bg-2: #052e22; - --muted: #6ee7b7; - --accent: #34d399; - --accent-2: #6ee7b7; - --accent-3: #a7f3d0; - --code-bg: #06291c; - --border: #0a3d2a; - --thread: #34d399; - --mesh-edge: rgba(110, 231, 183, 0.32); - --mesh-edge-hi: rgba(253, 230, 138, 0.85); - --mesh-arch-fill: #6ee7b7; - --mesh-arch-halo: rgba(110, 231, 183, 0.30); - --mesh-distro-fill: #34d399; - --mesh-distro-halo: rgba(251, 191, 36, 0.22); - --mesh-hub-fill: #a7f3d0; - --mesh-hub-halo: rgba(253, 230, 138, 0.36); - --mesh-label-fg: #a7f3d0; - --mesh-container-dc-fill: rgba(110, 231, 183, 0.08); - --mesh-container-dc-stroke: rgba(110, 231, 183, 0.65); - --mesh-container-rack-fill: rgba(110, 231, 183, 0.15); - --mesh-container-rack-stroke: rgba(110, 231, 183, 0.55); - --mesh-container-label-fg: rgba(254, 243, 199, 0.95); - --hero-gradient-1: rgba(110, 231, 183, 0.22); - --hero-gradient-2: rgba(5, 150, 105, 0.30); - } -} -/* Explicit overrides for the manual toggle (button in nav), independent of - the OS-level prefers-color-scheme media query above. No [data-theme] - attribute = "system", which the media query already handles. */ -:root[data-theme="dark"] { - --fg: #d1fae5; - --bg: #04130d; - --bg-2: #052e22; - --muted: #6ee7b7; - --accent: #34d399; - --accent-2: #6ee7b7; - --accent-3: #a7f3d0; - --code-bg: #06291c; - --border: #0a3d2a; - --thread: #34d399; - --mesh-edge: rgba(110, 231, 183, 0.32); - --mesh-edge-hi: rgba(253, 230, 138, 0.85); - --mesh-arch-fill: #6ee7b7; - --mesh-arch-halo: rgba(110, 231, 183, 0.30); - --mesh-distro-fill: #34d399; - --mesh-distro-halo: rgba(251, 191, 36, 0.22); - --mesh-hub-fill: #a7f3d0; - --mesh-hub-halo: rgba(253, 230, 138, 0.36); - --mesh-label-fg: #a7f3d0; - --mesh-container-dc-fill: rgba(110, 231, 183, 0.08); - --mesh-container-dc-stroke: rgba(110, 231, 183, 0.65); - --mesh-container-rack-fill: rgba(110, 231, 183, 0.15); - --mesh-container-rack-stroke: rgba(110, 231, 183, 0.55); - --mesh-container-label-fg: rgba(254, 243, 199, 0.95); - --hero-gradient-1: rgba(110, 231, 183, 0.22); - --hero-gradient-2: rgba(5, 150, 105, 0.30); - -} -:root[data-theme="light"] { - --fg: #052e22; - --bg: #f0fdf9; - --bg-2: #d1fae5; - --muted: #047857; - --accent: #059669; - --accent-2: #10b981; - --accent-3: #6ee7b7; - --code-bg: #d1fae5; - --border: #a7f3d0; - --thread: #059669; - --mesh-edge: rgba(5, 150, 105, 0.32); - --mesh-edge-hi: rgba(5, 150, 105, 0.85); - --mesh-arch-fill: #059669; - --mesh-arch-halo: rgba(5, 150, 105, 0.30); - --mesh-distro-fill: #10b981; - --mesh-distro-halo: rgba(245, 158, 11, 0.22); - --mesh-hub-fill: #b45309; - --mesh-hub-halo: rgba(180, 83, 9, 0.36); - --mesh-label-fg: #78350f; - --mesh-container-dc-fill: rgba(5, 150, 105, 0.06); - --mesh-container-dc-stroke: rgba(5, 150, 105, 0.55); - --mesh-container-rack-fill: rgba(5, 150, 105, 0.14); - --mesh-container-rack-stroke: rgba(5, 150, 105, 0.45); - --mesh-container-label-fg: rgba(120, 53, 15, 0.85); - --hero-gradient-1: rgba(110, 231, 183, 0.30); - --hero-gradient-2: rgba(5, 150, 105, 0.18); -} - -.theme-toggle { - margin-left: 1.25rem; - width: 1.9rem; - height: 1.9rem; - padding: 0; - border: 1px solid var(--border); - border-radius: 999px; - background: var(--bg-2); - color: var(--muted); - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - vertical-align: middle; - transition: border-color 0.18s, color 0.18s; -} -.theme-toggle:hover { border-color: var(--accent); color: var(--accent); } -.theme-toggle-icon::before { content: "\1F310"; font-size: 0.95rem; } /* system: globe */ -.theme-toggle[data-mode="light"] .theme-toggle-icon::before { content: "\2600\FE0F"; } /* sun */ -.theme-toggle[data-mode="dark"] .theme-toggle-icon::before { content: "\1F319"; } /* moon */ -* { box-sizing: border-box; } -html { scroll-behavior: smooth; } -body { - font-family: "Inter", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - color: var(--fg); - background: var(--bg); - margin: 0; - line-height: 1.6; -} -.container { max-width: 1080px; margin: 0 auto; padding: 0 1.5rem; } -.hero .container { max-width: 1500px; } -nav { - border-bottom: 1px solid var(--border); - padding: 1rem 0; - font-size: 0.9rem; - background: var(--bg); -} -nav .container { display: flex; justify-content: space-between; align-items: center; } -nav .brand { font-weight: 600; letter-spacing: -0.01em; color: var(--accent); display: inline-flex; align-items: center; gap: 0.5rem; } -nav .brand-logo { width: 28px; height: 28px; border-radius: 7px; vertical-align: middle; } -nav a { color: var(--muted); text-decoration: none; margin-left: 1.25rem; } -nav a:hover { color: var(--fg); } -nav .nav-github { display: inline-flex; align-items: center; gap: 0.4em; } -nav .nav-icon { width: 1em; height: 1em; flex: none; } - -/* ---------- HERO ---------- */ -.hero { - position: relative; - padding: 3.5rem 0 2rem; - overflow: hidden; -} -.hero::before { - content: ""; - position: absolute; - inset: -10% -10% 30% -10%; - background: - radial-gradient(60% 60% at 20% 30%, var(--hero-gradient-1) 0%, transparent 70%), - radial-gradient(50% 50% at 80% 70%, var(--hero-gradient-2) 0%, transparent 70%); - pointer-events: none; - z-index: 0; -} -.hero .container { position: relative; z-index: 1; } -.hero-logo { - position: relative; - z-index: 1; - display: block; - width: 96px; - height: 96px; - border-radius: 22px; - margin: 0 0 1.25rem; - box-shadow: 0 10px 30px -12px rgba(0,0,0,0.35); -} -.hero h1 { - font-size: 3rem; - margin: 0 0 1rem; - letter-spacing: -0.03em; - font-weight: 700; -} -.hero h1 .accent { - background: linear-gradient(135deg, var(--accent) 20%, var(--accent-2) 60%, var(--accent-3) 100%); - -webkit-background-clip: text; - background-clip: text; - color: transparent; -} -.hero .tag { - font-size: 1.2rem; - color: var(--muted); - margin: 0 0 1.6rem; - font-style: italic; -} -.hero .intro p { - font-size: 1.05rem; - margin: 0 0 1.2rem; - max-width: 72ch; -} - -/* ---------- MESH ---------- */ -.mesh { - position: relative; - margin: 2rem 0 1rem; - padding: 1rem 0 0.5rem; - border-top: 1px solid var(--border); - border-bottom: 1px solid var(--border); - background: linear-gradient(180deg, transparent 0%, var(--bg-2) 60%, transparent 100%); -} -.mesh svg { - display: block; - width: 100%; - height: auto; -} -.mesh-caption { - margin: 0.5rem 0 0; - font-size: 0.82rem; - color: var(--muted); - text-align: center; - letter-spacing: 0.02em; -} -.mesh-caption strong { color: var(--accent); } - -.mesh .edge { stroke: var(--mesh-edge); stroke-width: 1.4; fill: none; stroke-linecap: round; } -.mesh .edge.hi { stroke: var(--mesh-edge-hi); stroke-width: 2.2; } -.mesh .label { fill: var(--mesh-label-fg); font: 600 13px "Inter", ui-sans-serif, system-ui, sans-serif; text-anchor: middle; letter-spacing: 0.04em; } - -.mesh .container-dc { - fill: var(--mesh-container-dc-fill); - stroke: var(--mesh-container-dc-stroke); - stroke-width: 1.5; - stroke-dasharray: 5 6; -} -.mesh .container-rack { - fill: var(--mesh-container-rack-fill); - stroke: var(--mesh-container-rack-stroke); - stroke-width: 1; -} -.mesh .container-label { - fill: var(--mesh-container-label-fg); - font: 700 14px "Inter", ui-sans-serif, system-ui, sans-serif; - letter-spacing: 0.14em; - text-transform: uppercase; -} -.mesh .container-label-rack { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.10em; - text-transform: none; -} - -.mesh .kind-arch .halo { fill: var(--mesh-arch-halo); animation: nodepulse 3.2s ease-in-out infinite; transform-origin: center; transform-box: fill-box; } -.mesh .kind-arch .core { fill: var(--mesh-arch-fill); } -.mesh .kind-distro .halo { fill: var(--mesh-distro-halo); animation: nodepulse 4s ease-in-out infinite; transform-origin: center; transform-box: fill-box; } -.mesh .kind-distro .core { fill: var(--mesh-distro-fill); } -.mesh .kind-hub .halo { fill: var(--mesh-hub-halo); animation: nodepulse 2.4s ease-in-out infinite; transform-origin: center; transform-box: fill-box; } -.mesh .kind-hub .core { fill: var(--mesh-hub-fill); } - -@keyframes nodepulse { - 0%, 100% { transform: scale(1); opacity: 0.85; } - 50% { transform: scale(1.45); opacity: 0.25; } -} - -@media (prefers-reduced-motion: reduce) { - .mesh .halo { animation: none; } - .mesh animateMotion { display: none; } -} - -/* ---------- BODY SECTIONS ---------- */ -h2 { - margin-top: 3rem; - font-size: 1.5rem; - letter-spacing: -0.02em; - border-bottom: 1px solid var(--border); - padding-bottom: 0.4rem; -} -h3 { font-size: 1.05rem; margin-top: 1.5rem; } -code { - font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; - background: var(--code-bg); - padding: 0.1em 0.35em; - border-radius: 3px; - font-size: 0.9em; -} -pre { - background: var(--code-bg); - padding: 1rem 1.25rem; - border-radius: 6px; - overflow-x: auto; - font-size: 0.85rem; - line-height: 1.5; - border: 1px solid var(--border); -} -pre code { background: none; padding: 0; font-size: 1em; } -ul.features { padding-left: 1.2rem; } -ul.features li { margin-bottom: 0.5rem; } -.grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 1rem; - margin: 1.5rem 0; -} -.card { - border: 1px solid var(--border); - padding: 1rem 1.2rem; - border-radius: 8px; - background: var(--bg); - transition: border-color 0.18s, transform 0.18s; -} -.card:hover { border-color: var(--accent); transform: translateY(-1px); } -.card h4 { margin: 0 0 0.3rem; font-size: 0.95rem; font-weight: 600; } -.card p { margin: 0; font-size: 0.88rem; color: var(--muted); } -.card h4 a { color: inherit; text-decoration: none; } -.card h4 a:hover { color: var(--accent); } - -.status { - display: inline-block; - padding: 0.15rem 0.6rem; - background: var(--thread); - color: #fff; - border-radius: 999px; - font-size: 0.75rem; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - margin-left: 0.5rem; - vertical-align: middle; -} -footer { - margin-top: 4rem; - border-top: 1px solid var(--border); - padding: 1.5rem 0; - color: var(--muted); - font-size: 0.85rem; -} -a { color: var(--accent); } -a:hover { color: var(--accent-2); }