From efc4a976d92c55d92ec8ad25c6a9a122bdee31e2 Mon Sep 17 00:00:00 2001
From: murilopmachado
Date: Tue, 4 Aug 2026 21:27:43 +0200
Subject: [PATCH] feat: directory-tree hero + GSAP motion for structlint
Replace the generic terminal-window hero (shared verbatim across every
AxeForge marketing site) with a hero built around structlint's actual
mechanism: an animated directory tree with connecting branch lines and
one node flagged as a violation, matching the "directory drift" framing
in the product copy.
- New centerpiece: a hand-authored file tree (myapp/, cmd/main.go,
internal/{domain,db}, go.mod, .env.local) drawn with plain CSS
rail/elbow connectors (no SVG, no terminal chrome), with .env.local
flagged via accent color + a visible disallowed_file_pattern badge and
a CLI-style verdict line, grounded in the real --disallowed rule and
frozen violation codes from README.md.
- GSAP (CDN) drives: a hero entrance timeline (kicker -> h1 -> lede ->
ctas -> tree panel), a top-down tree "draw-in" (branches extend via
scaleY/scaleX, nodes fade in row by row) with the flagged node
pulsing once revealed, and once-per-section scroll reveals for the
card grids below the fold.
- Reduced motion is a hard branch via gsap.matchMedia(): nothing is
ever hidden by CSS, so a failed/blocked CDN load or
prefers-reduced-motion both leave the page fully visible immediately
with zero animation -- verified via Playwright's reduced-motion
media emulation (opacity 1, tree fully drawn, no tween).
- Brand shell (topbar, footer cross-links, SEO/JSON-LD, axeforge.css
tokens) untouched; side-by-side hero layout kept, only the
centerpiece and composition change.
---
web/index.html | 247 +++++++++++++++++++++++++++++++++++++------------
1 file changed, 186 insertions(+), 61 deletions(-)
diff --git a/web/index.html b/web/index.html
index 9515c84..d14eb30 100644
--- a/web/index.html
+++ b/web/index.html
@@ -107,35 +107,59 @@
}
.btn-ghost:hover { border-color: color-mix(in srgb, var(--af-accent) 50%, transparent); }
- /* ---- Live check demo ---- */
- .feed-caption { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.6rem; font-size: 0.8rem; color: var(--af-text-muted); }
+ /* ---- Directory tree centerpiece ---- */
+ .diagram-caption { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.6rem; font-size: 0.8rem; color: var(--af-text-muted); }
.pulse-dot { position: relative; width: 8px; height: 8px; border-radius: 50%; background: var(--af-accent); flex: none; }
.pulse-dot::after {
content: ""; position: absolute; inset: 0; border-radius: 50%; background: var(--af-accent);
animation: pulse 2s ease-out infinite;
}
@keyframes pulse { 0% { opacity: 0.6; transform: scale(1); } 100% { opacity: 0; transform: scale(2.8); } }
- #feed-window { box-shadow: 0 30px 60px -20px rgb(0 0 0 / 0.6); max-width: 100%; }
- #feed-body { min-height: 15.5rem; max-height: 15.5rem; padding: 0.85rem 1rem; overflow: hidden; font-family: var(--af-font-mono); font-size: 0.78rem; }
- .feed-line {
- display: grid; grid-template-columns: auto minmax(0, 10.5rem) minmax(0, 1fr) minmax(0, 7rem); gap: 0.6rem; align-items: baseline;
- padding: 0.3rem 0; color: var(--af-text-muted);
- animation: feed-in 0.4s cubic-bezier(0.16, 1, 0.3, 1);
- }
- .feed-line .title { overflow-wrap: anywhere; }
- @keyframes feed-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
- .feed-line .sev { text-transform: none; font-size: 0.7rem; overflow-wrap: anywhere; }
- .feed-line .title { color: var(--af-text); }
- .feed-line .who { color: var(--af-text-muted); opacity: 0.75; text-align: right; overflow-wrap: anywhere; }
- .feed-tone { width: 6px; height: 6px; border-radius: 50%; align-self: center; }
+ .feed-tone { width: 6px; height: 6px; border-radius: 50%; align-self: center; flex: none; }
.feed-tone-info { background: #22d3ee; }
.feed-tone-warn { background: #ffb000; }
.feed-tone-critical { background: var(--af-accent); box-shadow: 0 0 8px color-mix(in srgb, var(--af-accent) 60%, transparent); }
- .feed-summary { color: var(--af-text-muted); }
- .feed-summary .ok { color: #22d3ee; }
- .feed-summary .bad { color: var(--af-accent); }
+
+ .tree-panel { padding: 1.35rem 1.4rem 1.15rem; box-shadow: 0 30px 60px -20px rgb(0 0 0 / 0.6); max-width: 100%; overflow-x: auto; }
+ .file-tree, .file-tree ul { list-style: none; margin: 0; padding: 0; }
+ .file-tree { font-family: var(--af-font-mono); font-size: 0.86rem; }
+ .t-row { display: flex; align-items: stretch; flex-wrap: wrap; min-height: 1.9rem; }
+ .t-row.t-root { min-height: 1.7rem; }
+ .t-rail, .t-elbow { position: relative; width: 1.05rem; flex: none; }
+ .t-vline {
+ position: absolute; left: 50%; top: 0; width: 1px; background: var(--af-line);
+ transform: translateX(-50%) scaleY(1); transform-origin: top;
+ }
+ .t-rail .t-vline, .t-tee .t-vline { height: 100%; }
+ .t-corner .t-vline { height: 50%; }
+ .t-hline {
+ position: absolute; left: 50%; top: 50%; height: 1px; width: 0.6rem; background: var(--af-line);
+ transform: scaleX(1); transform-origin: left;
+ }
+ .node {
+ align-self: center; display: inline-flex; align-items: center; padding: 0.12rem 0.5rem; margin: 0.18rem 0;
+ border-radius: 4px; white-space: nowrap; color: var(--af-text-muted);
+ }
+ .node-dir { color: var(--af-text); font-weight: 600; }
+ .node-file { color: var(--af-text-muted); }
+ .node-violation {
+ color: var(--af-text); font-weight: 600;
+ background: color-mix(in srgb, var(--af-accent) 14%, transparent);
+ border: 1px solid color-mix(in srgb, var(--af-accent) 55%, transparent);
+ }
+ .node-violation.is-pulsing { box-shadow: 0 0 0 4px color-mix(in srgb, var(--af-accent) 25%, transparent); }
+ .violation-flag {
+ align-self: center; flex-basis: 100%; margin: 0.1rem 0 0.2rem calc(1.05rem + 0.5rem);
+ display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.72rem; color: var(--af-text-muted);
+ overflow-wrap: anywhere;
+ }
+ .tree-verdict {
+ margin-top: 0.9rem; padding-top: 0.85rem; border-top: 1px solid var(--af-line);
+ font-family: var(--af-font-mono); font-size: 0.8rem; color: var(--af-text-muted);
+ display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
+ }
+ .tree-verdict strong { color: var(--af-accent); font-weight: 700; }
@media (prefers-reduced-motion: reduce) {
- .feed-line { animation: none; }
.pulse-dot::after { animation: none; opacity: 0; }
}
@@ -200,8 +224,9 @@
@media (max-width: 480px) {
.topbar .wrap { flex-wrap: wrap; row-gap: 0.6rem; padding-top: 0.75rem; padding-bottom: 0.75rem; min-height: 0; }
.topbar { min-height: 0; }
- .feed-line { grid-template-columns: auto minmax(0, 1fr); }
- .feed-line .sev, .feed-line .who { display: none; }
+ .file-tree { font-size: 0.76rem; }
+ .t-rail, .t-elbow { width: 0.85rem; }
+ .violation-flag { margin-left: calc(0.85rem + 0.4rem); }
}
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
@@ -242,13 +267,46 @@ Stop directory drift before it ships.
-
Live demo (simulated output, not a real run)
-
-
-
+
Sample project, one .structlint.yaml run
+
+
+ -
+ myapp/
+
+ -
+
+ cmd/
+
+ -
+
+
+ main.go
+
+ -
+
+ internal/
+
+ -
+
+
+ domain/
+
+ -
+
+
+ db/
+
+ -
+
+ go.mod
+
+ -
+
+ .env.local
+ disallowed_file_pattern
+
+
+
✗ 1 violation found — disallowed_file_pattern on .env.local
@@ -400,43 +458,110 @@ One line to get started
gumshoe.
+
+