Merge curated fork work: 12 new diagram types, icons, export, onboarding v2
Imports the substantive work from the aristide/diagram-design fork (PR #9) as a curated set: - 12 new diagram types with reference docs: radar, bar, line, gantt, scatter, it-state, high-level, process, medallion, data-flow, dp-integration, dp-security-matrix (26 types total) - 86-icon monochrome IT/cloud primitive, build script, and gallery - /diagram-design:export command with PNG/SVG export reference - Onboarding v2: URL, skill, and folder design-token sources - Mandatory connector rules (orthogonal elbows, label gaps, fanned attach points, bridge crossings) + architecture/ER connector upgrades - Series palette tokens for multi-series chart types - THIRD_PARTY_LICENSES.md covering Tabler, Simple Icons, log-z, Devicon Not taken from the fork: plugin-manifest identity changes, the client-brand takeover of style-guide.md, and client-branded example files. The six affected type examples were regenerated in the neutral editorial skin with generic content, and their README screenshots recaptured. Reference docs for those types were rewritten to semantic tokens and typography roles. Version: 1.0.0 -> 2.0.0. Co-authored-by: Aristide <mendoo.aristide@gmail.com>
This commit is contained in:
co-authored by
Aristide
parent
0ab077f229
commit
2e9cefb9f3
@@ -1,16 +1,16 @@
|
||||
---
|
||||
name: diagram-design
|
||||
description: Create technical and product diagrams — architecture, flowchart, sequence, state machine, ER / data model, timeline, swimlane, quadrant, nested, tree, org chart, layer stack, venn, pyramid — as standalone HTML files with inline SVG. Ships with a neutral editorial skin and a first-run gate that prompts users to customize the style guide (colors, fonts) from their own website before generating. Includes annotation-callout primitive and optional sketchy variant.
|
||||
description: Create technical and product diagrams — architecture, IT current-state, flowchart, sequence, state machine, ER / data model, timeline, swimlane, quadrant, radar / spider, nested, tree, org chart, layer stack, venn, pyramid / funnel, bar chart, line chart, Gantt, scatter plot, high-level, process, medallion, data flow, DP integration, DP security matrix — as standalone HTML files with inline SVG. Ships with a neutral editorial skin and a first-run gate that prompts users to customize the style guide (colors, fonts) from their own website before generating. Includes annotation-callout primitive and optional sketchy variant.
|
||||
license: MIT
|
||||
metadata:
|
||||
version: "1.0"
|
||||
version: "2.0"
|
||||
---
|
||||
|
||||
# Diagram Design
|
||||
|
||||
Create visual diagrams as self-contained HTML files with inline SVG and CSS, following an opinionated editorial design system.
|
||||
|
||||
Fourteen diagram types. One shared design system, complexity budget, and taste gate. Type-specific conventions live in `references/` and are loaded only when you pick a type.
|
||||
Twenty-six diagram types. One shared design system, complexity budget, and taste gate. Type-specific conventions live in `references/` and are loaded only when you pick a type.
|
||||
|
||||
---
|
||||
|
||||
@@ -20,12 +20,15 @@ Fourteen diagram types. One shared design system, complexity budget, and taste g
|
||||
|
||||
Open [`references/style-guide.md`](references/style-guide.md) and check the default tokens. If they're still the shipped defaults (paper `#faf7f2`, ink `#1c1917`, accent `#b5523a` rust), **pause and ask the user**:
|
||||
|
||||
> *"This is your first Schematic in this project. The style guide is still at the default (neutral stone + rust). Do you want to customize it to match your brand first? Options: (a) run onboarding — I'll pull colors and fonts from your website, (b) paste your tokens manually, (c) proceed with the default for now."*
|
||||
> *"This is your first Schematic in this project. The style guide is still at the default (neutral stone + rust). Do you want to customize it to match your brand first? Options: (a) pull from your website URL, (b) extract from an installed skill, (c) extract from a local folder / design-system directory, (d) paste tokens manually, (e) proceed with the default for now."*
|
||||
|
||||
Then branch:
|
||||
- **(a)** → follow [`references/onboarding.md`](references/onboarding.md) to fetch the site, extract palette + fonts, propose a diff, and write `style-guide.md`.
|
||||
- **(b)** → accept the user's tokens and write them into `style-guide.md` under a new "Custom tokens" section.
|
||||
- **(c)** → proceed; optionally remind the user they can run onboarding later.
|
||||
|
||||
- **(a)** → follow [`references/onboarding.md § URL`](references/onboarding.md) to fetch the site, extract palette + fonts, propose a diff, and write `style-guide.md`.
|
||||
- **(b)** → follow [`references/onboarding.md § Skill`](references/onboarding.md) — ask which skill, read its SKILL.md / CSS / token files, map to semantic roles, propose diff.
|
||||
- **(c)** → follow [`references/onboarding.md § Folder`](references/onboarding.md) — ask for the path, glob for CSS/JSON/MD token files, map to semantic roles, propose diff.
|
||||
- **(d)** → accept the user's tokens and write them into `style-guide.md` under a new "Custom tokens" section.
|
||||
- **(e)** → proceed; optionally remind the user they can run onboarding later.
|
||||
|
||||
**Once the style guide has been customized** (or the user explicitly opted for default), skip this gate on subsequent runs. A simple way to detect customization: if the `accent` value in `style-guide.md` differs from `#b5523a`, assume custom.
|
||||
|
||||
@@ -40,6 +43,7 @@ Don't silently ship default-skinned diagrams into a branded project — that's t
|
||||
From `.impeccable.md`: *"Confident restraint. Earn every element. One color accent, two families, a small spacing vocabulary. If removing it wouldn't hurt the page, remove it."*
|
||||
|
||||
Applied to schematics:
|
||||
|
||||
- Every node represents a distinct idea. Two nodes that always travel together are one node.
|
||||
- Every connection carries information. If the relationship is obvious from layout, remove the line.
|
||||
- Coral is **editorial, not a flag.** 1–2 focal nodes per diagram. Using it on 5 nodes erases the signal.
|
||||
@@ -51,9 +55,10 @@ Applied to schematics:
|
||||
|
||||
## 2. When to Use
|
||||
|
||||
Use for any of the 14 diagram types (§3) when a reader will learn more from a visual than from prose, a table, or a bulleted list.
|
||||
Use for any of the 26 diagram types (§3) when a reader will learn more from a visual than from prose, a table, or a bulleted list.
|
||||
|
||||
**Don't use for:**
|
||||
|
||||
- Quick unicode diagrams → use **wiretext**.
|
||||
- Lists of things → table or bullets.
|
||||
- Simple before/after → table.
|
||||
@@ -70,6 +75,7 @@ Before drawing, ask: *Would the reader learn more from this than from a well-wri
|
||||
| If you're showing… | Use | Reference |
|
||||
|---|---|---|
|
||||
| Components + connections in a system | **Architecture** | [type-architecture.md](references/type-architecture.md) |
|
||||
| Legacy IT landscape grouped by phase/department; documents the *before* state in modernization proposals | **IT current-state** | [type-it-state.md](references/type-it-state.md) |
|
||||
| Decision logic with branches | **Flowchart** | [type-flowchart.md](references/type-flowchart.md) |
|
||||
| Time-ordered messages between actors | **Sequence** | [type-sequence.md](references/type-sequence.md) |
|
||||
| States + transitions + guards | **State machine** | [type-state.md](references/type-state.md) |
|
||||
@@ -77,14 +83,26 @@ Before drawing, ask: *Would the reader learn more from this than from a well-wri
|
||||
| Events positioned in time | **Timeline** | [type-timeline.md](references/type-timeline.md) |
|
||||
| Cross-functional process with handoffs | **Swimlane** | [type-swimlane.md](references/type-swimlane.md) |
|
||||
| Two-axis positioning / prioritization | **Quadrant** | [type-quadrant.md](references/type-quadrant.md) |
|
||||
| Multiple entities scored across 3–5 quantitative criteria | **Radar / Spider** | [type-radar.md](references/type-radar.md) |
|
||||
| Hierarchy through containment / scope | **Nested** | [type-nested.md](references/type-nested.md) |
|
||||
| Parent → children relationships | **Tree** | [type-tree.md](references/type-tree.md) |
|
||||
| Human/agent/team ownership, reporting, routing, escalation | **Org chart** | [type-org-chart.md](references/type-org-chart.md) |
|
||||
| Stacked abstraction levels | **Layer stack** | [type-layers.md](references/type-layers.md) |
|
||||
| Overlap between sets | **Venn** | [type-venn.md](references/type-venn.md) |
|
||||
| Ranked hierarchy or conversion drop-off | **Pyramid / funnel** | [type-pyramid.md](references/type-pyramid.md) |
|
||||
| Quantitative comparison across categories | **Bar chart** | [type-bar.md](references/type-bar.md) |
|
||||
| Continuous trends over time | **Line chart** | [type-line.md](references/type-line.md) |
|
||||
| Tasks and phases on a timeline | **Gantt** | [type-gantt.md](references/type-gantt.md) |
|
||||
| Distribution and correlation between two variables | **Scatter plot** | [type-scatter.md](references/type-scatter.md) |
|
||||
| End-to-end data stack on a container cluster | **High-Level** | [type-high-level.md](references/type-high-level.md) |
|
||||
| Multi-actor sequential process with data handoffs | **Process** | [type-process.md](references/type-process.md) |
|
||||
| Multi-tier data storage with quality levels and access policies | **Medallion** | [type-medallion.md](references/type-medallion.md) |
|
||||
| Role-scoped data flow: who does what at each pipeline step | **Data flow** | [type-data-flow.md](references/type-data-flow.md) |
|
||||
| Integration topology of a data platform — sources → core → consumers | **DP integration** | [type-dp-integration.md](references/type-dp-integration.md) |
|
||||
| Per-role / per-component access permissions matrix | **DP security matrix** | [type-dp-security-matrix.md](references/type-dp-security-matrix.md) |
|
||||
|
||||
Rules of thumb:
|
||||
|
||||
- If a 3-column table communicates the same thing, pick the table.
|
||||
- If you're combining two types, pick the dominant axis — don't hybridize grammars.
|
||||
- If you're past the complexity budget (§7), split into an overview + detail.
|
||||
@@ -109,6 +127,11 @@ These mark "AI slop" schematics of any type:
|
||||
| Shadow on any element | Shadows are out. Borders are in. |
|
||||
| `rounded-2xl` on boxes | Max radius 6–10px or none |
|
||||
| Coral on every "important" node | Coral is 1–2 editorial accents, not a signaling system |
|
||||
| Diagonal / slanted connectors between off-axis nodes | Rounded right-angle (orthogonal) elbows are mandatory — see §6 Mandatory connector rules |
|
||||
| Arrow label sitting on or touching its connector | Label must have a 6–10px gap above the line so the connector stays visible |
|
||||
| Two connectors overlapping or running on the same path | Each connection must be independently traceable — bridge crossings, offset parallels |
|
||||
| Two connectors sharing a single attach point on a box | Fan attach points along the edge (≥12px apart) so every arrow is clearly distinct — see §6 rule 4 |
|
||||
| Connector routed behind a non-endpoint box without need | Reroute around intervening boxes; the dashed-transit exception (§6 rule 5) only applies when an unavoidable intervening box sits on the direct path |
|
||||
|
||||
Type-specific anti-patterns live in each `references/type-*.md`.
|
||||
|
||||
@@ -165,8 +188,10 @@ Type-specific anti-patterns live in each `references/type-*.md`.
|
||||
## 6. Core SVG Primitives
|
||||
|
||||
Universal building blocks. Type-specialized primitives (lifeline, activation bar, region) live in the relevant `references/type-*.md`. Optional primitives:
|
||||
|
||||
- Editorial callouts → [primitive-annotation.md](references/primitive-annotation.md)
|
||||
- Hand-drawn variant → [primitive-sketchy.md](references/primitive-sketchy.md)
|
||||
- Icon set (laptop, server, DB, K8s, Docker, AWS, …) → [primitive-icons.md](references/primitive-icons.md). Browse the gallery at [`assets/icons.html`](../assets/icons.html).
|
||||
|
||||
### Background
|
||||
|
||||
@@ -213,6 +238,30 @@ Don't use the dot pattern when the diagram sits inside a product page, slide, or
|
||||
|
||||
**Draw arrows before boxes** so z-order puts lines behind nodes.
|
||||
|
||||
### Mandatory connector rules
|
||||
|
||||
These five rules are **non-negotiable**. Run the pre-output checklist (§9) to verify before producing any diagram.
|
||||
|
||||
1. **Rounded right-angle (orthogonal) connectors are mandatory.** Never use diagonal `<line>` or straight slanted paths between nodes that don't share an x or y axis. Every bend must be a quarter-arc with `r=8` (or `r=6` minimum for tight layouts). See `references/type-architecture.md` for the elbow-path formula. Reserve plain straight `<line>` only for connections whose endpoints share the same x or y coordinate. Diagonal connectors are an automatic fail.
|
||||
|
||||
2. **Label-to-connector margin: 6–10px gap, always.** A label must never sit *on* its arrow — the connector must remain visible. Place the label centered above (or beside, for vertical segments) the line with a **minimum 6px gap** between the bottom of the label's mask rect and the connector stroke. The opaque mask rect prevents the arrow from bleeding through, but the *visible* gap between mask edge and line preserves the reader's ability to trace the connection. If the label is large enough that 6px feels cramped, push it to 8–10px. Never let the mask rect touch or overlap the stroke.
|
||||
|
||||
3. **No overlapping connectors.** Two connectors must never share the same stroke path, run parallel on top of each other, or be drawn on top of each other for any segment. When two orthogonal arrows must cross at a single point, apply the **bridge / hop** primitive (see `references/type-architecture.md` § Crossing arrows). When two arrows naturally want to overlap, offset their routing by ≥12px so each line is independently traceable. If you find yourself stacking connectors, redesign the layout — it means two nodes are too close, or the diagram is over budget (split into overview + detail).
|
||||
|
||||
4. **Shared edge → fan the attach points.** When two or more connectors enter or exit the *same edge* of a box, each must have its own distinct attach point along that edge — **no two connectors may share a single point on a box**. Spread the attach points evenly along the edge with **≥12px** between adjacent points (8px minimum for very small boxes). Routing rules:
|
||||
- For N connectors on an edge of length L, attach point `k` (1..N) sits at offset `L * k / (N + 1)` from the edge's leading corner.
|
||||
- When the connectors fan out to destinations on different sides, route each one orthogonally from its own attach point — no merging strokes near the box.
|
||||
- When two parallel connectors run in the same direction, keep them ≥12px apart along their entire length, not just at the attach point. Each arrow must remain independently traceable end-to-end.
|
||||
|
||||
No connector may hide another. If you can't tell two arrows apart at a glance, the layout has failed.
|
||||
|
||||
5. **A connector must not pass behind a box that isn't its source or destination — except when the box is geometrically unavoidable on a direct orthogonal path.** Reroute around intervening boxes by default. The only legitimate exception is when a cross-cutting node (e.g., a footer service, a horizontal layer bar) physically sits between the connector's source and destination on the only straight path between them — for example, a `METRICS` arrow exiting an `Observability` footer bar and rising into a zone above must cross the `Active Directory` footer bar that sits between them. In that exception:
|
||||
- The stroke must be **dashed** (e.g., `stroke-dasharray="4,3"`) to signal "transit, not interaction" — it tells the reader the intervening box is not an endpoint.
|
||||
- The label sits at the **visible end** of the connector (typically near the source) so it doesn't fall behind the intervening box.
|
||||
- No marker (arrowhead) may land on the intervening box's edge — the marker resolves at the true destination only.
|
||||
|
||||
When in doubt, reroute. The exception exists for the narrow case where rerouting is geometrically impossible, not as a shortcut to avoid layout work.
|
||||
|
||||
### Node box — full pattern
|
||||
|
||||
```svg
|
||||
@@ -232,17 +281,23 @@ Don't use the dot pattern when the diagram sits inside a product page, slide, or
|
||||
font-family="'Geist Mono', monospace" text-anchor="middle">tech:port</text>
|
||||
```
|
||||
|
||||
### Arrow labels — always mask
|
||||
### Arrow labels — always mask, always with margin
|
||||
|
||||
Every arrow label needs an opaque rect behind it. Without one it bleeds through the line.
|
||||
Every arrow label needs an opaque rect behind it. Without one it bleeds through the line. **And the label must sit with a visible gap above the connector — never on top of it.**
|
||||
|
||||
```svg
|
||||
<rect x="MID_X-18" y="ARROW_Y-12" width="36" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="MID_X" y="ARROW_Y-3" fill="#7a8399" font-size="8"
|
||||
<!-- Mask sits 14px above the arrow (8px text height + 6px gap). Stroke is at ARROW_Y. -->
|
||||
<rect x="MID_X-18" y="ARROW_Y-20" width="36" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="MID_X" y="ARROW_Y-11" fill="#7a8399" font-size="8"
|
||||
font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.06em">WRITE</text>
|
||||
```
|
||||
|
||||
Rules: ≤14 characters, all-caps, centered on segment midpoint, 8–10px above line. Never `writing-mode` vertical.
|
||||
Rules:
|
||||
|
||||
- ≤14 characters, all-caps, centered on segment midpoint.
|
||||
- **Mandatory 6–10px gap** between the bottom of the mask rect and the arrow stroke. The connector must remain visible — a label that hides its own arrow is a hard fail.
|
||||
- Never `writing-mode` vertical.
|
||||
- For vertical segments, place the label to the side (not on the line) with the same 6–10px horizontal gap.
|
||||
|
||||
### Legend — horizontal strip at the bottom
|
||||
|
||||
@@ -297,6 +352,13 @@ Quick check: if a coordinate ends in 1, 2, 3, 5, 6, 7, 9 — fix it.
|
||||
| Max layers (layer stack) | 6 |
|
||||
| Max circles (venn) | 3 |
|
||||
| Max layers (pyramid) | 6 |
|
||||
| Max radar axes | 5 |
|
||||
| Max radar series | 5 |
|
||||
| Max focal radar series | 1 |
|
||||
| Max bars (bar chart) | 8 |
|
||||
| Max series (line chart) | 5 |
|
||||
| Max tasks (Gantt) | 12 |
|
||||
| Max points (scatter plot) | 30 |
|
||||
| Max annotation callouts | 2 |
|
||||
|
||||
If you exceed, split into two diagrams (overview + detail).
|
||||
@@ -326,6 +388,7 @@ Don't use 3 identical generic cards. Vary the treatment:
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `background: #ffffff` (not paper — slight lift without shadow)
|
||||
- `border: 1px solid rgba(45,49,66,0.12)`
|
||||
- `border-radius: 6px`, `padding: 1.25rem`
|
||||
@@ -339,23 +402,32 @@ Rules:
|
||||
Run before producing any diagram.
|
||||
|
||||
**Type fit:**
|
||||
|
||||
- [ ] Right type for what I'm showing? (§3 selection guide)
|
||||
- [ ] Would a table / paragraph do the same job? (If yes — don't draw.)
|
||||
- [ ] Loaded the matching `references/type-*.md`?
|
||||
|
||||
**Remove test:**
|
||||
|
||||
- [ ] Can I remove any node? (Would a reader still understand?)
|
||||
- [ ] Can I merge any two nodes? (Do they always travel together?)
|
||||
- [ ] Can I remove any arrow? (Is the relationship obvious from layout?)
|
||||
- [ ] Can I remove any label? (Does color or shape already signal it?)
|
||||
|
||||
**Signal:**
|
||||
|
||||
- [ ] Coral used on ≤2 elements? If more, which actually deserve focal status?
|
||||
- [ ] Legend covers every type used — and nothing extra?
|
||||
- [ ] Within the type's complexity budget (§7)?
|
||||
|
||||
**Technical:**
|
||||
|
||||
- [ ] Arrows drawn before boxes?
|
||||
- [ ] **Every connector between off-axis nodes uses a rounded right-angle elbow (`r=8`)? No diagonal `<line>` slants?**
|
||||
- [ ] **Every arrow label has a visible 6–10px gap above its connector? (Mask rect not touching the stroke.)**
|
||||
- [ ] **No two connectors overlap, share a stroke path, or run on top of each other? Crossings use the bridge/hop primitive?**
|
||||
- [ ] **When several connectors enter or exit the same edge of a box, each has its own attach point (≥12px apart)? No connector hides another?**
|
||||
- [ ] **No connector passes behind a non-endpoint box, except the unavoidable-intervening-box case (§6 rule 5) — and in that case, the stroke is dashed and the label sits at the visible end?**
|
||||
- [ ] Every arrow label has an opaque `fill="#f5f5f5"` rect behind it?
|
||||
- [ ] Legend is a horizontal bottom strip, not floating?
|
||||
- [ ] No vertical `writing-mode` text?
|
||||
@@ -363,6 +435,7 @@ Run before producing any diagram.
|
||||
- [ ] Every font size, coord, width, height, gap divisible by 4?
|
||||
|
||||
**Typography:**
|
||||
|
||||
- [ ] Human-readable names in Geist sans, not Geist Mono?
|
||||
- [ ] Technical sublabels (ports, commands, URLs) in Geist Mono?
|
||||
- [ ] Page title in Instrument Serif?
|
||||
@@ -396,8 +469,13 @@ Every diagram ships in three variants (see `assets/`):
|
||||
## 11. Output
|
||||
|
||||
Always produce a single self-contained `.html` file:
|
||||
|
||||
- Embedded CSS (no external except Google Fonts)
|
||||
- Inline SVG (no external images)
|
||||
- No JavaScript required
|
||||
|
||||
Renders correctly in any modern browser.
|
||||
|
||||
### Exporting to PNG / SVG
|
||||
|
||||
When the user asks to export, save, rasterize, or convert a generated diagram to `.png` or `.svg`, load [`references/export.md`](references/export.md) and follow the procedure there. Both formats deliver the diagram only (the `<svg>` node) — editorial wrappers like cards and headers are dropped by design. Export is **manual** — never produce export files unprompted.
|
||||
|
||||
@@ -71,24 +71,39 @@
|
||||
<rect width="100%" height="100%" fill="#2d3142"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Zone: Content services (drawn before arrows and nodes) -->
|
||||
<rect x="616" y="128" width="164" height="272" rx="8"
|
||||
fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<rect x="672" y="132" width="52" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="698" y="141" fill="rgba(245,245,245,0.35)" font-size="7" font-family="'Geist Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.14em">CONTENT</text>
|
||||
|
||||
<!-- Arrows first (behind boxes) -->
|
||||
<line x1="168" y1="272" x2="220" y2="272" stroke="#6a95d8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<line x1="364" y1="272" x2="416" y2="272" stroke="#f08a59" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="576" y1="256" x2="628" y2="212" stroke="#bfc0c0" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="576" y1="288" x2="628" y2="332" stroke="#bfc0c0" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<path d="M 496,240 H 692 Q 700,240 700,232 V 224"
|
||||
fill="none" stroke="#bfc0c0" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<path d="M 496,304 H 692 Q 700,304 700,312 V 320"
|
||||
fill="none" stroke="#bfc0c0" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dashed return: Cloudflare→Reader (cached HTML) -->
|
||||
<path d="M 220,288 H 168" fill="none" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Arrow labels -->
|
||||
<rect x="172" y="252" width="48" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="196" y="262" fill="#6a95d8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">HTTPS</text>
|
||||
|
||||
<rect x="172" y="278" width="32" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="188" y="287" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">RESP</text>
|
||||
|
||||
<rect x="368" y="252" width="48" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="392" y="262" fill="#f08a59" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">SSR</text>
|
||||
|
||||
<rect x="560" y="212" width="56" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="588" y="222" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">READ MDX</text>
|
||||
<rect x="564" y="234" width="60" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="594" y="243" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">READ MDX</text>
|
||||
|
||||
<rect x="560" y="320" width="52" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="586" y="330" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
<rect x="568" y="298" width="44" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="590" y="307" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
|
||||
<!-- Node: Reader -->
|
||||
<rect x="40" y="240" width="128" height="64" rx="6" fill="#2d3142"/>
|
||||
@@ -156,6 +171,9 @@
|
||||
|
||||
<line x1="784" y1="442" x2="812" y2="442" stroke="#f08a59" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
||||
<text x="820" y="444" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Primary flow</text>
|
||||
|
||||
<line x1="900" y1="442" x2="928" y2="442" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<text x="936" y="444" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Return / async</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -61,24 +61,39 @@
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Zone: Content services (drawn before arrows and nodes) -->
|
||||
<rect x="616" y="128" width="164" height="272" rx="8"
|
||||
fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="672" y="132" width="52" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="698" y="141" fill="rgba(45,49,66,0.40)" font-size="7" font-family="'Geist Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.14em">CONTENT</text>
|
||||
|
||||
<!-- Arrows first (behind boxes) -->
|
||||
<line x1="168" y1="272" x2="220" y2="272" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<line x1="364" y1="272" x2="416" y2="272" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="576" y1="256" x2="628" y2="212" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="576" y1="288" x2="628" y2="332" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<path d="M 496,240 H 692 Q 700,240 700,232 V 224"
|
||||
fill="none" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<path d="M 496,304 H 692 Q 700,304 700,312 V 320"
|
||||
fill="none" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dashed return: Cloudflare→Reader (cached HTML) -->
|
||||
<path d="M 220,288 H 168" fill="none" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Arrow labels -->
|
||||
<rect x="172" y="252" width="48" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="196" y="262" fill="#2e5aa8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">HTTPS</text>
|
||||
|
||||
<rect x="172" y="278" width="32" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="188" y="287" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">RESP</text>
|
||||
|
||||
<rect x="368" y="252" width="48" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="392" y="262" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">SSR</text>
|
||||
|
||||
<rect x="560" y="212" width="56" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="588" y="222" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">READ MDX</text>
|
||||
<rect x="564" y="234" width="60" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="594" y="243" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">READ MDX</text>
|
||||
|
||||
<rect x="560" y="320" width="52" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="586" y="330" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
<rect x="568" y="298" width="44" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="590" y="307" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
|
||||
<!-- Node: Reader -->
|
||||
<rect x="40" y="240" width="128" height="64" rx="6" fill="#f5f5f5"/>
|
||||
@@ -146,6 +161,9 @@
|
||||
|
||||
<line x1="784" y1="442" x2="812" y2="442" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
||||
<text x="820" y="444" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Primary flow</text>
|
||||
|
||||
<line x1="900" y1="442" x2="928" y2="442" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<text x="936" y="444" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Return / async</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -71,24 +71,40 @@
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Zone: Content services (drawn before arrows and nodes) -->
|
||||
<rect x="616" y="128" width="164" height="272" rx="8"
|
||||
fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="672" y="132" width="52" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="698" y="141" fill="rgba(45,49,66,0.40)" font-size="7" font-family="'Geist Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.14em">CONTENT</text>
|
||||
|
||||
<!-- Arrows first (behind boxes) -->
|
||||
<line x1="168" y1="272" x2="220" y2="272" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<line x1="364" y1="272" x2="416" y2="272" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="576" y1="256" x2="628" y2="212" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="576" y1="288" x2="628" y2="332" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<!-- Exit Astro top→enter MDX bottom; exit Astro bottom→enter CMS top -->
|
||||
<path d="M 496,240 H 692 Q 700,240 700,232 V 224"
|
||||
fill="none" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<path d="M 496,304 H 692 Q 700,304 700,312 V 320"
|
||||
fill="none" stroke="#4f5d75" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Dashed return: Cloudflare→Reader (cached HTML — same orthogonal rules as solid) -->
|
||||
<path d="M 220,288 H 168" fill="none" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Arrow labels -->
|
||||
<rect x="172" y="252" width="48" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="196" y="262" fill="#2e5aa8" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">HTTPS</text>
|
||||
|
||||
<rect x="172" y="278" width="32" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="188" y="287" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">RESP</text>
|
||||
|
||||
<rect x="368" y="252" width="48" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="392" y="262" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">SSR</text>
|
||||
|
||||
<rect x="560" y="212" width="56" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="588" y="222" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">READ MDX</text>
|
||||
<rect x="564" y="234" width="60" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="594" y="243" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">READ MDX</text>
|
||||
|
||||
<rect x="560" y="320" width="52" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="586" y="330" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
<rect x="568" y="298" width="44" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="590" y="307" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
|
||||
<!-- Node: Reader -->
|
||||
<rect x="40" y="240" width="128" height="64" rx="6" fill="#f5f5f5"/>
|
||||
@@ -156,6 +172,9 @@
|
||||
|
||||
<line x1="784" y1="442" x2="812" y2="442" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
||||
<text x="820" y="444" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Primary flow</text>
|
||||
|
||||
<line x1="900" y1="442" x2="928" y2="442" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<text x="936" y="444" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Return / async</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sprint velocity · Bar chart · dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #2d3142;
|
||||
--color-ink: #f5f5f5;
|
||||
--color-muted: #bfc0c0;
|
||||
--color-accent: #f08a59;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Bar · Diagram Design</p>
|
||||
<h1>Sprint velocity · 8-sprint view</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Y-axis rotated label -->
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORY POINTS</text>
|
||||
|
||||
<!-- Horizontal gridlines (drawn before bars) -->
|
||||
<line x1="80" y1="357" x2="960" y2="357" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="293" x2="960" y2="293" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="167" x2="960" y2="167" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="103" x2="960" y2="103" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(245,245,245,0.06)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Y-axis line and X-axis baseline -->
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
|
||||
<!-- Y-axis tick labels -->
|
||||
<text x="72" y="361" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">20</text>
|
||||
<text x="72" y="297" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">40</text>
|
||||
<text x="72" y="234" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">60</text>
|
||||
<text x="72" y="171" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">80</text>
|
||||
<text x="72" y="107" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">100</text>
|
||||
<text x="72" y="44" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">120</text>
|
||||
|
||||
<!-- ─── BARS (pitch=110, width=72, pad=19) ─── -->
|
||||
<!-- S1: v=72, h=228, y=192 -->
|
||||
<rect x="99" y="192" width="72" height="228" fill="#f5f5f5"/>
|
||||
<rect x="99" y="192" width="72" height="228" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="135" y="184" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">72</text>
|
||||
|
||||
<!-- S2: v=88, h=280, y=140 -->
|
||||
<rect x="209" y="140" width="72" height="280" fill="#f5f5f5"/>
|
||||
<rect x="209" y="140" width="72" height="280" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="245" y="132" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">88</text>
|
||||
|
||||
<!-- S3: v=95, h=300, y=120 -->
|
||||
<rect x="319" y="120" width="72" height="300" fill="#f5f5f5"/>
|
||||
<rect x="319" y="120" width="72" height="300" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="355" y="112" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">95</text>
|
||||
|
||||
<!-- S4: v=78, h=248, y=172 -->
|
||||
<rect x="429" y="172" width="72" height="248" fill="#f5f5f5"/>
|
||||
<rect x="429" y="172" width="72" height="248" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="465" y="164" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">78</text>
|
||||
|
||||
<!-- S5 FOCAL: v=110, h=348, y=72 -->
|
||||
<rect x="539" y="72" width="72" height="348" fill="#f5f5f5"/>
|
||||
<rect x="539" y="72" width="72" height="348" fill="rgba(240,138,89,0.14)" stroke="#f08a59" stroke-width="1"/>
|
||||
<text x="575" y="64" fill="#f08a59" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" font-weight="600">110</text>
|
||||
|
||||
<!-- S6: v=102, h=324, y=96 -->
|
||||
<rect x="649" y="96" width="72" height="324" fill="#f5f5f5"/>
|
||||
<rect x="649" y="96" width="72" height="324" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="685" y="88" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">102</text>
|
||||
|
||||
<!-- S7: v=85, h=268, y=152 -->
|
||||
<rect x="759" y="152" width="72" height="268" fill="#f5f5f5"/>
|
||||
<rect x="759" y="152" width="72" height="268" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="795" y="144" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">85</text>
|
||||
|
||||
<!-- S8: v=93, h=296, y=124 -->
|
||||
<rect x="869" y="124" width="72" height="296" fill="#f5f5f5"/>
|
||||
<rect x="869" y="124" width="72" height="296" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="905" y="116" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">93</text>
|
||||
|
||||
<!-- X-axis category labels -->
|
||||
<text x="135" y="440" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S1</text>
|
||||
<text x="245" y="440" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S2</text>
|
||||
<text x="355" y="440" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S3</text>
|
||||
<text x="465" y="440" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S4</text>
|
||||
<text x="575" y="440" fill="#f08a59" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S5</text>
|
||||
<text x="685" y="440" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S6</text>
|
||||
<text x="795" y="440" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S7</text>
|
||||
<text x="905" y="440" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S8</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="488" width="16" height="10" rx="2" fill="rgba(240,138,89,0.14)" stroke="#f08a59" stroke-width="1"/>
|
||||
<text x="64" y="497" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Sprint 5 · record high</text>
|
||||
|
||||
<rect x="220" y="488" width="16" height="10" rx="2" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="244" y="497" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Other sprints</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sprint velocity · Bar chart</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
|
||||
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 58ch; }
|
||||
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
@media (max-width: 720px) { .cards { grid-template-columns: 1fr; } }
|
||||
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
|
||||
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--color-accent); } .card-dot.ink { background: var(--color-ink); } .card-dot.muted { background: var(--color-muted); }
|
||||
.card h3 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; }
|
||||
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-soft); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<p class="header-eyebrow">Bar · Diagram Design</p>
|
||||
<h1>Sprint velocity · 8-sprint view</h1>
|
||||
<p class="subtitle">Story points delivered per two-week sprint over one product cycle. Sprint 5 is the standout — a focused scope and no mid-sprint scope additions.</p>
|
||||
</div>
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs><pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/></pattern></defs>
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORY POINTS</text>
|
||||
<line x1="80" y1="357" x2="960" y2="357" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="293" x2="960" y2="293" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="167" x2="960" y2="167" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="103" x2="960" y2="103" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(45,49,66,0.06)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<text x="72" y="361" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">20</text>
|
||||
<text x="72" y="297" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">40</text>
|
||||
<text x="72" y="234" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">60</text>
|
||||
<text x="72" y="171" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">80</text>
|
||||
<text x="72" y="107" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">100</text>
|
||||
<text x="72" y="44" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">120</text>
|
||||
<rect x="99" y="192" width="72" height="228" fill="#f5f5f5"/><rect x="99" y="192" width="72" height="228" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/><text x="135" y="184" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">72</text>
|
||||
<rect x="209" y="140" width="72" height="280" fill="#f5f5f5"/><rect x="209" y="140" width="72" height="280" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/><text x="245" y="132" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">88</text>
|
||||
<rect x="319" y="120" width="72" height="300" fill="#f5f5f5"/><rect x="319" y="120" width="72" height="300" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/><text x="355" y="112" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">95</text>
|
||||
<rect x="429" y="172" width="72" height="248" fill="#f5f5f5"/><rect x="429" y="172" width="72" height="248" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/><text x="465" y="164" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">78</text>
|
||||
<rect x="539" y="72" width="72" height="348" fill="#f5f5f5"/><rect x="539" y="72" width="72" height="348" fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/><text x="575" y="64" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" font-weight="600">110</text>
|
||||
<rect x="649" y="96" width="72" height="324" fill="#f5f5f5"/><rect x="649" y="96" width="72" height="324" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/><text x="685" y="88" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">102</text>
|
||||
<rect x="759" y="152" width="72" height="268" fill="#f5f5f5"/><rect x="759" y="152" width="72" height="268" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/><text x="795" y="144" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">85</text>
|
||||
<rect x="869" y="124" width="72" height="296" fill="#f5f5f5"/><rect x="869" y="124" width="72" height="296" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/><text x="905" y="116" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">93</text>
|
||||
<text x="135" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S1</text>
|
||||
<text x="245" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S2</text>
|
||||
<text x="355" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S3</text>
|
||||
<text x="465" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S4</text>
|
||||
<text x="575" y="440" fill="#eb6c36" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S5</text>
|
||||
<text x="685" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S6</text>
|
||||
<text x="795" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S7</text>
|
||||
<text x="905" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S8</text>
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
<rect x="40" y="488" width="16" height="10" rx="2" fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/>
|
||||
<text x="64" y="497" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Sprint 5 · record high</text>
|
||||
<rect x="220" y="488" width="16" height="10" rx="2" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="244" y="497" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Other sprints</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="eyebrow">RECORD SPRINT</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h3>Sprint 5 · 110 points</h3></div>
|
||||
<p>No mid-sprint additions, zero carry-over from S4, and the team's highest-complexity stories were completed two days early. The bar is not the goal — the conditions are.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot ink"></span><h3>Trend: rising with one dip</h3></div>
|
||||
<p>Average across 8 sprints: 89 points. S4 dipped to 78 — a dependency on an external API that shipped late. Excluding S4, the underlying trajectory is +4 pts/sprint.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot muted"></span><h3>What the chart doesn't show</h3></div>
|
||||
<p>Story points measure scope delivered, not value shipped. S3 at 95 points included three stories that were deprioritised before the sprint ended — they don't appear in the count.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span>sprint velocity · 8-sprint view</span>
|
||||
<span>example · diagram design</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sprint velocity · Bar chart</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #f5f5f5;
|
||||
--color-ink: #2d3142;
|
||||
--color-muted: #4f5d75;
|
||||
--color-accent: #eb6c36;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Bar · Diagram Design</p>
|
||||
<h1>Sprint velocity · 8-sprint view</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Y-axis rotated label -->
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORY POINTS</text>
|
||||
|
||||
<!-- Horizontal gridlines (drawn before bars) -->
|
||||
<line x1="80" y1="357" x2="960" y2="357" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="293" x2="960" y2="293" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="167" x2="960" y2="167" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="103" x2="960" y2="103" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(45,49,66,0.06)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Y-axis line and X-axis baseline -->
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
|
||||
<!-- Y-axis tick labels -->
|
||||
<text x="72" y="361" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">20</text>
|
||||
<text x="72" y="297" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">40</text>
|
||||
<text x="72" y="234" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">60</text>
|
||||
<text x="72" y="171" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">80</text>
|
||||
<text x="72" y="107" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">100</text>
|
||||
<text x="72" y="44" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">120</text>
|
||||
|
||||
<!-- ─── BARS (pitch=110, width=72, pad=19) ─── -->
|
||||
<!-- S1: v=72, h=228, y=192 -->
|
||||
<rect x="99" y="192" width="72" height="228" fill="#f5f5f5"/>
|
||||
<rect x="99" y="192" width="72" height="228" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="135" y="184" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">72</text>
|
||||
|
||||
<!-- S2: v=88, h=280, y=140 -->
|
||||
<rect x="209" y="140" width="72" height="280" fill="#f5f5f5"/>
|
||||
<rect x="209" y="140" width="72" height="280" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="245" y="132" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">88</text>
|
||||
|
||||
<!-- S3: v=95, h=300, y=120 -->
|
||||
<rect x="319" y="120" width="72" height="300" fill="#f5f5f5"/>
|
||||
<rect x="319" y="120" width="72" height="300" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="355" y="112" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">95</text>
|
||||
|
||||
<!-- S4: v=78, h=248, y=172 -->
|
||||
<rect x="429" y="172" width="72" height="248" fill="#f5f5f5"/>
|
||||
<rect x="429" y="172" width="72" height="248" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="465" y="164" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">78</text>
|
||||
|
||||
<!-- S5 FOCAL: v=110, h=348, y=72 -->
|
||||
<rect x="539" y="72" width="72" height="348" fill="#f5f5f5"/>
|
||||
<rect x="539" y="72" width="72" height="348" fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/>
|
||||
<text x="575" y="64" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" font-weight="600">110</text>
|
||||
|
||||
<!-- S6: v=102, h=324, y=96 -->
|
||||
<rect x="649" y="96" width="72" height="324" fill="#f5f5f5"/>
|
||||
<rect x="649" y="96" width="72" height="324" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="685" y="88" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">102</text>
|
||||
|
||||
<!-- S7: v=85, h=268, y=152 -->
|
||||
<rect x="759" y="152" width="72" height="268" fill="#f5f5f5"/>
|
||||
<rect x="759" y="152" width="72" height="268" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="795" y="144" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">85</text>
|
||||
|
||||
<!-- S8: v=93, h=296, y=124 -->
|
||||
<rect x="869" y="124" width="72" height="296" fill="#f5f5f5"/>
|
||||
<rect x="869" y="124" width="72" height="296" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="905" y="116" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">93</text>
|
||||
|
||||
<!-- X-axis category labels -->
|
||||
<text x="135" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S1</text>
|
||||
<text x="245" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S2</text>
|
||||
<text x="355" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S3</text>
|
||||
<text x="465" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S4</text>
|
||||
<text x="575" y="440" fill="#eb6c36" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S5</text>
|
||||
<text x="685" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S6</text>
|
||||
<text x="795" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S7</text>
|
||||
<text x="905" y="440" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">S8</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="488" width="16" height="10" rx="2" fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/>
|
||||
<text x="64" y="497" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Sprint 5 · record high</text>
|
||||
|
||||
<rect x="220" y="488" width="16" height="10" rx="2" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="244" y="497" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Other sprints</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,143 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analytics pipeline · Data flow · Dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #2d3142; --paper-2: #393e53; --ink: #f5f5f5; --muted: #bfc0c0;
|
||||
--soft: #8e98ac; --rule: rgba(245,245,245,0.12); --accent: #f08a59;
|
||||
--accent-tint: rgba(240,138,89,0.10); --link: #6a95d8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.frame { width: 100%; max-width: 1200px; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 1.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
svg { display: block; width: 100%; min-width: 728px; }
|
||||
.step-number, .step-label, .lane-label, .role-text, .chip-text, .legend-label { font-family: var(--mono); text-anchor: middle; }
|
||||
.step-number { fill: var(--ink); font-size: 7px; font-weight: 600; }
|
||||
.step-label { fill: var(--muted); font-size: 7px; font-weight: 500; letter-spacing: 0.12em; }
|
||||
.lane-label { fill: var(--muted); font-size: 8px; font-weight: 500; letter-spacing: 0.14em; }
|
||||
.role-text { fill: var(--ink); font-size: 6px; font-weight: 600; }
|
||||
.node-title { fill: var(--ink); font: 600 9px var(--sans); text-anchor: middle; }
|
||||
.node-sub { fill: var(--muted); font: 400 6.5px var(--mono); text-anchor: middle; }
|
||||
.node-tool { fill: var(--soft); font: 400 6.5px var(--mono); text-anchor: middle; }
|
||||
.chip-text { fill: #fff; font-size: 5px; font-weight: 700; }
|
||||
.focal-text { fill: var(--accent); }
|
||||
.legend-label { fill: var(--muted); font-size: 7px; font-weight: 500; letter-spacing: 0.12em; text-anchor: end; }
|
||||
.legend-text { fill: var(--muted); font: 400 7px var(--sans); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">Data flow · Diagram Design</p>
|
||||
<h1>Role-scoped analytics pipeline</h1>
|
||||
|
||||
<svg viewBox="0 0 728 356" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="flow-title flow-desc">
|
||||
<title id="flow-title">Role-scoped analytics data flow</title>
|
||||
<desc id="flow-desc">A Data Engineer ingests and stores commerce data, a Data Scientist transforms and analyzes it, and an Analyst publishes a dashboard.</desc>
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="11" cy="11" r="0.8" fill="rgba(245,245,245,0.10)"/></pattern>
|
||||
<marker id="arr-muted" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#bfc0c0"/></marker>
|
||||
<marker id="arr-accent" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#f08a59"/></marker>
|
||||
<marker id="arr-link" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#6a95d8"/></marker>
|
||||
</defs>
|
||||
<rect width="728" height="356" fill="#2d3142"/>
|
||||
<rect width="728" height="356" fill="url(#dots)"/>
|
||||
<rect x="0" y="36" width="728" height="80" fill="rgba(245,245,245,0.025)"/>
|
||||
<rect x="0" y="196" width="728" height="80" fill="rgba(245,245,245,0.025)"/>
|
||||
|
||||
<!-- Header and lane structure -->
|
||||
<line x1="0" y1="36" x2="728" y2="36" stroke="rgba(245,245,245,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="116" x2="728" y2="116" stroke="rgba(245,245,245,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="196" x2="728" y2="196" stroke="rgba(245,245,245,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="276" x2="728" y2="276" stroke="rgba(245,245,245,0.12)" stroke-width="0.8"/>
|
||||
<line x1="140" y1="36" x2="140" y2="276" stroke="rgba(245,245,245,0.12)" stroke-width="0.8"/>
|
||||
|
||||
<g aria-label="Pipeline steps">
|
||||
<rect x="180" y="6" width="32" height="16" rx="8" fill="rgba(245,245,245,0.12)"/><text x="196" y="14" class="step-number">01</text><text x="196" y="29" class="step-label">INGEST</text>
|
||||
<rect x="292" y="6" width="32" height="16" rx="8" fill="rgba(245,245,245,0.12)"/><text x="308" y="14" class="step-number">02</text><text x="308" y="29" class="step-label">STORE</text>
|
||||
<rect x="404" y="6" width="32" height="16" rx="8" fill="rgba(240,138,89,0.22)"/><text x="420" y="14" class="step-number focal-text">03</text><text x="420" y="29" class="step-label focal-text">TRANSFORM</text>
|
||||
<rect x="516" y="6" width="32" height="16" rx="8" fill="rgba(245,245,245,0.12)"/><text x="532" y="14" class="step-number">04</text><text x="532" y="29" class="step-label">ANALYZE</text>
|
||||
<rect x="628" y="6" width="32" height="16" rx="8" fill="rgba(245,245,245,0.12)"/><text x="644" y="14" class="step-number">05</text><text x="644" y="29" class="step-label">PUBLISH</text>
|
||||
</g>
|
||||
|
||||
<g aria-label="Role lanes">
|
||||
<text x="70" y="72" class="lane-label">DATA</text><text x="70" y="84" class="lane-label">ENGINEER</text>
|
||||
<text x="70" y="152" class="lane-label">DATA</text><text x="70" y="164" class="lane-label">SCIENTIST</text>
|
||||
<text x="70" y="232" class="lane-label">ANALYTICS</text><text x="70" y="244" class="lane-label">ANALYST</text>
|
||||
</g>
|
||||
|
||||
<!-- Connectors first. Cross-lane paths use r=8 orthogonal elbows. -->
|
||||
<path d="M246 76 H258" fill="none" stroke="#bfc0c0" marker-end="url(#arr-muted)"/>
|
||||
<path d="M358 76 H412 Q420 76 420 84 V124" fill="none" stroke="#f08a59" stroke-width="1.2" marker-end="url(#arr-accent)"/>
|
||||
<path d="M470 156 H482" fill="none" stroke="#bfc0c0" marker-end="url(#arr-muted)"/>
|
||||
<path d="M582 156 H636 Q644 156 644 164 V204" fill="none" stroke="#6a95d8" marker-end="url(#arr-link)"/>
|
||||
<rect x="368" y="56" width="48" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="392" y="65" fill="#f08a59" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.06em">RAW TABLE</text>
|
||||
|
||||
<!-- Data Engineer: Capture Events -->
|
||||
<rect x="146" y="44" width="100" height="64" rx="6" fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.20)"/>
|
||||
<rect x="150" y="48" width="18" height="10" rx="3" fill="rgba(245,245,245,0.12)"/><text x="159" y="53" class="role-text">ENG</text>
|
||||
<text x="196" y="67" class="node-title">Capture Events</text><text x="196" y="79" class="node-sub">shop events → batch</text><text x="196" y="91" class="node-tool">NiFi ingest</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#4a7c59"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="226" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="234" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Engineer: Land Records -->
|
||||
<rect x="258" y="44" width="100" height="64" rx="6" fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.20)"/>
|
||||
<rect x="262" y="48" width="18" height="10" rx="3" fill="rgba(245,245,245,0.12)"/><text x="271" y="53" class="role-text">ENG</text>
|
||||
<text x="308" y="67" class="node-title">Land Records</text><text x="308" y="79" class="node-sub">events · orders</text><text x="308" y="91" class="node-tool">Object storage</text>
|
||||
<rect x="262" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="270" y="104" class="chip-text">DB</text>
|
||||
<rect x="338" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="346" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Scientist: Clean and Model (focal) -->
|
||||
<rect x="370" y="124" width="100" height="64" rx="6" fill="rgba(240,138,89,0.12)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<rect x="374" y="128" width="18" height="10" rx="3" fill="rgba(240,138,89,0.22)"/><text x="383" y="133" class="role-text focal-text">SCI</text>
|
||||
<text x="420" y="147" class="node-title">Clean & Model</text><text x="420" y="159" class="node-sub">raw → trusted table</text><text x="420" y="171" class="node-tool">Trino · notebooks</text>
|
||||
<rect x="374" y="178" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="382" y="184" class="chip-text">DB</text>
|
||||
<rect x="450" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="458" y="184" class="chip-text">TB</text>
|
||||
|
||||
<!-- Data Scientist: Curate Metrics -->
|
||||
<rect x="482" y="124" width="100" height="64" rx="6" fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.20)"/>
|
||||
<rect x="486" y="128" width="18" height="10" rx="3" fill="rgba(245,245,245,0.12)"/><text x="495" y="133" class="role-text">SCI</text>
|
||||
<text x="532" y="147" class="node-title">Curate Metrics</text><text x="532" y="159" class="node-sub">conversion · revenue</text><text x="532" y="171" class="node-tool">Trino SQL</text>
|
||||
<rect x="486" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="494" y="184" class="chip-text">TB</text>
|
||||
<rect x="562" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="570" y="184" class="chip-text">TB</text>
|
||||
|
||||
<!-- Analyst: Publish Dashboard -->
|
||||
<rect x="594" y="204" width="100" height="64" rx="6" fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.20)"/>
|
||||
<rect x="598" y="208" width="18" height="10" rx="3" fill="rgba(245,245,245,0.12)"/><text x="607" y="213" class="role-text">ANL</text>
|
||||
<text x="644" y="227" class="node-title">Publish Dashboard</text><text x="644" y="239" class="node-sub">metrics → decisions</text><text x="644" y="251" class="node-tool">BI workspace</text>
|
||||
<rect x="598" y="258" width="16" height="8" rx="3" fill="#b8915a"/><text x="606" y="264" class="chip-text">TB</text>
|
||||
<rect x="674" y="258" width="16" height="8" rx="3" fill="#9c6b50"/><text x="682" y="264" class="chip-text">FL</text>
|
||||
|
||||
<!-- Legend: steps -->
|
||||
<text x="164" y="293" class="legend-label">STEPS</text>
|
||||
<rect x="180" y="284" width="24" height="12" rx="6" fill="rgba(245,245,245,0.12)"/><text x="192" y="292" class="step-number">01</text><text x="212" y="293" class="legend-text">Ingest</text>
|
||||
<rect x="272" y="284" width="24" height="12" rx="6" fill="rgba(245,245,245,0.12)"/><text x="284" y="292" class="step-number">02</text><text x="304" y="293" class="legend-text">Store</text>
|
||||
<rect x="360" y="284" width="24" height="12" rx="6" fill="rgba(240,138,89,0.22)"/><text x="372" y="292" class="step-number focal-text">03</text><text x="392" y="293" class="legend-text">Transform</text>
|
||||
<rect x="468" y="284" width="24" height="12" rx="6" fill="rgba(245,245,245,0.12)"/><text x="480" y="292" class="step-number">04</text><text x="500" y="293" class="legend-text">Analyze</text>
|
||||
<rect x="568" y="284" width="24" height="12" rx="6" fill="rgba(245,245,245,0.12)"/><text x="580" y="292" class="step-number">05</text><text x="600" y="293" class="legend-text">Publish</text>
|
||||
|
||||
<!-- Legend: data types -->
|
||||
<text x="164" y="314" class="legend-label">DATA TYPE</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#4a7c59"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="252" y="306" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="260" y="312" class="chip-text">DB</text><text x="274" y="314" class="legend-text">Dataset</text>
|
||||
<rect x="332" y="306" width="16" height="8" rx="3" fill="#b8915a"/><text x="340" y="312" class="chip-text">TB</text><text x="354" y="314" class="legend-text">Table</text>
|
||||
<rect x="404" y="306" width="16" height="8" rx="3" fill="#9c6b50"/><text x="412" y="312" class="chip-text">FL</text><text x="426" y="314" class="legend-text">Dashboard</text>
|
||||
<text x="500" y="314" class="legend-text">left chip = input · right chip = output</text>
|
||||
|
||||
<!-- Legend: flow -->
|
||||
<text x="164" y="336" class="legend-label">FLOW</text>
|
||||
<line x1="180" y1="333" x2="204" y2="333" stroke="#bfc0c0" marker-end="url(#arr-muted)"/><text x="212" y="336" class="legend-text">Standard handoff</text>
|
||||
<line x1="324" y1="333" x2="348" y2="333" stroke="#f08a59" stroke-width="1.2" marker-end="url(#arr-accent)"/><text x="356" y="336" class="legend-text">Focal handoff</text>
|
||||
<line x1="460" y1="333" x2="484" y2="333" stroke="#6a95d8" marker-end="url(#arr-link)"/><text x="492" y="336" class="legend-text">Published output</text>
|
||||
</svg>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,185 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analytics pipeline · Data flow · Full</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #f5f5f5; --paper-2: #ececec; --ink: #2d3142; --muted: #4f5d75;
|
||||
--soft: #7a8399; --rule: rgba(45,49,66,0.12); --accent: #eb6c36;
|
||||
--accent-tint: rgba(235,108,54,0.08); --link: #2e5aa8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.container { width: 100%; max-width: 1200px; margin: 0 auto; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 1.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
svg { display: block; width: 100%; min-width: 728px; }
|
||||
.step-number, .step-label, .lane-label, .role-text, .chip-text, .legend-label { font-family: var(--mono); text-anchor: middle; }
|
||||
.step-number { fill: var(--ink); font-size: 7px; font-weight: 600; }
|
||||
.step-label { fill: var(--muted); font-size: 7px; font-weight: 500; letter-spacing: 0.12em; }
|
||||
.lane-label { fill: var(--muted); font-size: 8px; font-weight: 500; letter-spacing: 0.14em; }
|
||||
.role-text { fill: var(--ink); font-size: 6px; font-weight: 600; }
|
||||
.node-title { fill: var(--ink); font: 600 9px var(--sans); text-anchor: middle; }
|
||||
.node-sub { fill: var(--muted); font: 400 6.5px var(--mono); text-anchor: middle; }
|
||||
.node-tool { fill: var(--soft); font: 400 6.5px var(--mono); text-anchor: middle; }
|
||||
.chip-text { fill: #fff; font-size: 5px; font-weight: 700; }
|
||||
.focal-text { fill: var(--accent); }
|
||||
.legend-label { fill: var(--muted); font-size: 7px; font-weight: 500; letter-spacing: 0.12em; text-anchor: end; }
|
||||
.legend-text { fill: var(--muted); font: 400 7px var(--sans); }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header h1 { margin-bottom: 0.5rem; }
|
||||
.subtitle { max-width: 62ch; color: var(--muted); font-size: 1rem; line-height: 1.55; }
|
||||
.diagram-container { overflow-x: auto; padding: 1.5rem; background: var(--paper-2); border: 1px solid var(--rule); border-radius: 8px; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
.card { padding: 1.25rem; background: #fff; border: 1px solid var(--rule); border-radius: 6px; }
|
||||
.card .card-eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.5rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
.card-header { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--accent); } .card-dot.ink { background: var(--ink); } .card-dot.muted { background: var(--muted); }
|
||||
.card h2 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p, .card ul { color: var(--muted); font-size: 0.8125rem; line-height: 1.55; list-style: none; }
|
||||
.card li { position: relative; margin-bottom: 0.3rem; padding-left: 0.875rem; }
|
||||
.card li::before { position: absolute; left: 0; color: rgba(45,49,66,0.25); content: '—'; }
|
||||
.footer { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.5rem; margin-top: 2rem; padding-top: 1.5rem; color: var(--soft); border-top: 1px solid rgba(45,49,66,0.10); font: 400 0.72rem var(--mono); letter-spacing: 0.06em; }
|
||||
@media (max-width: 820px) { .cards { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<header class="header">
|
||||
<p class="eyebrow">Data flow · Diagram Design</p>
|
||||
<h1>Role-scoped analytics pipeline</h1>
|
||||
<p class="subtitle">Typed commerce data crosses clear ownership boundaries: engineering lands it, data science makes it trustworthy, and analytics publishes it.</p>
|
||||
</header>
|
||||
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 728 356" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="flow-title flow-desc">
|
||||
<title id="flow-title">Role-scoped analytics data flow</title>
|
||||
<desc id="flow-desc">A Data Engineer ingests and stores commerce data, a Data Scientist transforms and analyzes it, and an Analyst publishes a dashboard.</desc>
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="11" cy="11" r="0.8" fill="rgba(45,49,66,0.10)"/></pattern>
|
||||
<marker id="arr-muted" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#4f5d75"/></marker>
|
||||
<marker id="arr-accent" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#eb6c36"/></marker>
|
||||
<marker id="arr-link" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#2e5aa8"/></marker>
|
||||
</defs>
|
||||
<rect width="728" height="356" fill="#f5f5f5"/>
|
||||
<rect width="728" height="356" fill="url(#dots)"/>
|
||||
<rect x="0" y="36" width="728" height="80" fill="rgba(45,49,66,0.018)"/>
|
||||
<rect x="0" y="196" width="728" height="80" fill="rgba(45,49,66,0.018)"/>
|
||||
|
||||
<!-- Header and lane structure -->
|
||||
<line x1="0" y1="36" x2="728" y2="36" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="116" x2="728" y2="116" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="196" x2="728" y2="196" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="276" x2="728" y2="276" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<line x1="140" y1="36" x2="140" y2="276" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
|
||||
<g aria-label="Pipeline steps">
|
||||
<rect x="180" y="6" width="32" height="16" rx="8" fill="rgba(45,49,66,0.12)"/><text x="196" y="14" class="step-number">01</text><text x="196" y="29" class="step-label">INGEST</text>
|
||||
<rect x="292" y="6" width="32" height="16" rx="8" fill="rgba(45,49,66,0.12)"/><text x="308" y="14" class="step-number">02</text><text x="308" y="29" class="step-label">STORE</text>
|
||||
<rect x="404" y="6" width="32" height="16" rx="8" fill="rgba(235,108,54,0.20)"/><text x="420" y="14" class="step-number focal-text">03</text><text x="420" y="29" class="step-label focal-text">TRANSFORM</text>
|
||||
<rect x="516" y="6" width="32" height="16" rx="8" fill="rgba(45,49,66,0.12)"/><text x="532" y="14" class="step-number">04</text><text x="532" y="29" class="step-label">ANALYZE</text>
|
||||
<rect x="628" y="6" width="32" height="16" rx="8" fill="rgba(45,49,66,0.12)"/><text x="644" y="14" class="step-number">05</text><text x="644" y="29" class="step-label">PUBLISH</text>
|
||||
</g>
|
||||
|
||||
<g aria-label="Role lanes">
|
||||
<text x="70" y="72" class="lane-label">DATA</text><text x="70" y="84" class="lane-label">ENGINEER</text>
|
||||
<text x="70" y="152" class="lane-label">DATA</text><text x="70" y="164" class="lane-label">SCIENTIST</text>
|
||||
<text x="70" y="232" class="lane-label">ANALYTICS</text><text x="70" y="244" class="lane-label">ANALYST</text>
|
||||
</g>
|
||||
|
||||
<!-- Connectors first. Cross-lane paths use r=8 orthogonal elbows. -->
|
||||
<path d="M246 76 H258" fill="none" stroke="#4f5d75" marker-end="url(#arr-muted)"/>
|
||||
<path d="M358 76 H412 Q420 76 420 84 V124" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arr-accent)"/>
|
||||
<path d="M470 156 H482" fill="none" stroke="#4f5d75" marker-end="url(#arr-muted)"/>
|
||||
<path d="M582 156 H636 Q644 156 644 164 V204" fill="none" stroke="#2e5aa8" marker-end="url(#arr-link)"/>
|
||||
<rect x="368" y="56" width="48" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="392" y="65" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.06em">RAW TABLE</text>
|
||||
|
||||
<!-- Data Engineer: Capture Events -->
|
||||
<rect x="146" y="44" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="150" y="48" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="159" y="53" class="role-text">ENG</text>
|
||||
<text x="196" y="67" class="node-title">Capture Events</text><text x="196" y="79" class="node-sub">shop events → batch</text><text x="196" y="91" class="node-tool">NiFi ingest</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#4a7c59"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="226" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="234" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Engineer: Land Records -->
|
||||
<rect x="258" y="44" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="262" y="48" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="271" y="53" class="role-text">ENG</text>
|
||||
<text x="308" y="67" class="node-title">Land Records</text><text x="308" y="79" class="node-sub">events · orders</text><text x="308" y="91" class="node-tool">Object storage</text>
|
||||
<rect x="262" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="270" y="104" class="chip-text">DB</text>
|
||||
<rect x="338" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="346" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Scientist: Clean and Model (focal) -->
|
||||
<rect x="370" y="124" width="100" height="64" rx="6" fill="rgba(235,108,54,0.07)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="374" y="128" width="18" height="10" rx="3" fill="rgba(235,108,54,0.20)"/><text x="383" y="133" class="role-text focal-text">SCI</text>
|
||||
<text x="420" y="147" class="node-title">Clean & Model</text><text x="420" y="159" class="node-sub">raw → trusted table</text><text x="420" y="171" class="node-tool">Trino · notebooks</text>
|
||||
<rect x="374" y="178" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="382" y="184" class="chip-text">DB</text>
|
||||
<rect x="450" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="458" y="184" class="chip-text">TB</text>
|
||||
|
||||
<!-- Data Scientist: Curate Metrics -->
|
||||
<rect x="482" y="124" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="486" y="128" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="495" y="133" class="role-text">SCI</text>
|
||||
<text x="532" y="147" class="node-title">Curate Metrics</text><text x="532" y="159" class="node-sub">conversion · revenue</text><text x="532" y="171" class="node-tool">Trino SQL</text>
|
||||
<rect x="486" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="494" y="184" class="chip-text">TB</text>
|
||||
<rect x="562" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="570" y="184" class="chip-text">TB</text>
|
||||
|
||||
<!-- Analyst: Publish Dashboard -->
|
||||
<rect x="594" y="204" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="598" y="208" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="607" y="213" class="role-text">ANL</text>
|
||||
<text x="644" y="227" class="node-title">Publish Dashboard</text><text x="644" y="239" class="node-sub">metrics → decisions</text><text x="644" y="251" class="node-tool">BI workspace</text>
|
||||
<rect x="598" y="258" width="16" height="8" rx="3" fill="#b8915a"/><text x="606" y="264" class="chip-text">TB</text>
|
||||
<rect x="674" y="258" width="16" height="8" rx="3" fill="#9c6b50"/><text x="682" y="264" class="chip-text">FL</text>
|
||||
|
||||
<!-- Legend: steps -->
|
||||
<text x="164" y="293" class="legend-label">STEPS</text>
|
||||
<rect x="180" y="284" width="24" height="12" rx="6" fill="rgba(45,49,66,0.12)"/><text x="192" y="292" class="step-number">01</text><text x="212" y="293" class="legend-text">Ingest</text>
|
||||
<rect x="272" y="284" width="24" height="12" rx="6" fill="rgba(45,49,66,0.12)"/><text x="284" y="292" class="step-number">02</text><text x="304" y="293" class="legend-text">Store</text>
|
||||
<rect x="360" y="284" width="24" height="12" rx="6" fill="rgba(235,108,54,0.20)"/><text x="372" y="292" class="step-number focal-text">03</text><text x="392" y="293" class="legend-text">Transform</text>
|
||||
<rect x="468" y="284" width="24" height="12" rx="6" fill="rgba(45,49,66,0.12)"/><text x="480" y="292" class="step-number">04</text><text x="500" y="293" class="legend-text">Analyze</text>
|
||||
<rect x="568" y="284" width="24" height="12" rx="6" fill="rgba(45,49,66,0.12)"/><text x="580" y="292" class="step-number">05</text><text x="600" y="293" class="legend-text">Publish</text>
|
||||
|
||||
<!-- Legend: data types -->
|
||||
<text x="164" y="314" class="legend-label">DATA TYPE</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#4a7c59"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="252" y="306" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="260" y="312" class="chip-text">DB</text><text x="274" y="314" class="legend-text">Dataset</text>
|
||||
<rect x="332" y="306" width="16" height="8" rx="3" fill="#b8915a"/><text x="340" y="312" class="chip-text">TB</text><text x="354" y="314" class="legend-text">Table</text>
|
||||
<rect x="404" y="306" width="16" height="8" rx="3" fill="#9c6b50"/><text x="412" y="312" class="chip-text">FL</text><text x="426" y="314" class="legend-text">Dashboard</text>
|
||||
<text x="500" y="314" class="legend-text">left chip = input · right chip = output</text>
|
||||
|
||||
<!-- Legend: flow -->
|
||||
<text x="164" y="336" class="legend-label">FLOW</text>
|
||||
<line x1="180" y1="333" x2="204" y2="333" stroke="#4f5d75" marker-end="url(#arr-muted)"/><text x="212" y="336" class="legend-text">Standard handoff</text>
|
||||
<line x1="324" y1="333" x2="348" y2="333" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arr-accent)"/><text x="356" y="336" class="legend-text">Focal handoff</text>
|
||||
<line x1="460" y1="333" x2="484" y2="333" stroke="#2e5aa8" marker-end="url(#arr-link)"/><text x="492" y="336" class="legend-text">Published output</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<section class="cards" aria-label="Diagram summary">
|
||||
<article class="card">
|
||||
<p class="card-eyebrow">THE HANDOFF</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h2>Raw becomes analysis-ready</h2></div>
|
||||
<p>The focal handoff marks the moment stored records become a trusted table owned by data science.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-header"><span class="card-dot ink"></span><h2>Roles stay explicit</h2></div>
|
||||
<ul><li>Data Engineer ingests and lands</li><li>Data Scientist transforms and models</li><li>Analyst publishes the dashboard</li></ul>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-header"><span class="card-dot muted"></span><h2>Payloads explain change</h2></div>
|
||||
<p>Left and right chips show the input and output type at every active pipeline step.</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<span>commerce analytics · data flow</span>
|
||||
<span>example · schematic skill</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,143 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Analytics pipeline · Data flow</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #f5f5f5; --paper-2: #ececec; --ink: #2d3142; --muted: #4f5d75;
|
||||
--soft: #7a8399; --rule: rgba(45,49,66,0.12); --accent: #eb6c36;
|
||||
--accent-tint: rgba(235,108,54,0.08); --link: #2e5aa8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.frame { width: 100%; max-width: 1200px; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 1.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
svg { display: block; width: 100%; min-width: 728px; }
|
||||
.step-number, .step-label, .lane-label, .role-text, .chip-text, .legend-label { font-family: var(--mono); text-anchor: middle; }
|
||||
.step-number { fill: var(--ink); font-size: 7px; font-weight: 600; }
|
||||
.step-label { fill: var(--muted); font-size: 7px; font-weight: 500; letter-spacing: 0.12em; }
|
||||
.lane-label { fill: var(--muted); font-size: 8px; font-weight: 500; letter-spacing: 0.14em; }
|
||||
.role-text { fill: var(--ink); font-size: 6px; font-weight: 600; }
|
||||
.node-title { fill: var(--ink); font: 600 9px var(--sans); text-anchor: middle; }
|
||||
.node-sub { fill: var(--muted); font: 400 6.5px var(--mono); text-anchor: middle; }
|
||||
.node-tool { fill: var(--soft); font: 400 6.5px var(--mono); text-anchor: middle; }
|
||||
.chip-text { fill: #fff; font-size: 5px; font-weight: 700; }
|
||||
.focal-text { fill: var(--accent); }
|
||||
.legend-label { fill: var(--muted); font-size: 7px; font-weight: 500; letter-spacing: 0.12em; text-anchor: end; }
|
||||
.legend-text { fill: var(--muted); font: 400 7px var(--sans); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">Data flow · Diagram Design</p>
|
||||
<h1>Role-scoped analytics pipeline</h1>
|
||||
|
||||
<svg viewBox="0 0 728 356" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="flow-title flow-desc">
|
||||
<title id="flow-title">Role-scoped analytics data flow</title>
|
||||
<desc id="flow-desc">A Data Engineer ingests and stores commerce data, a Data Scientist transforms and analyzes it, and an Analyst publishes a dashboard.</desc>
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="11" cy="11" r="0.8" fill="rgba(45,49,66,0.10)"/></pattern>
|
||||
<marker id="arr-muted" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#4f5d75"/></marker>
|
||||
<marker id="arr-accent" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#eb6c36"/></marker>
|
||||
<marker id="arr-link" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0 0 L6 3 L0 6 Z" fill="#2e5aa8"/></marker>
|
||||
</defs>
|
||||
<rect width="728" height="356" fill="#f5f5f5"/>
|
||||
<rect width="728" height="356" fill="url(#dots)"/>
|
||||
<rect x="0" y="36" width="728" height="80" fill="rgba(45,49,66,0.018)"/>
|
||||
<rect x="0" y="196" width="728" height="80" fill="rgba(45,49,66,0.018)"/>
|
||||
|
||||
<!-- Header and lane structure -->
|
||||
<line x1="0" y1="36" x2="728" y2="36" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="116" x2="728" y2="116" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="196" x2="728" y2="196" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<line x1="0" y1="276" x2="728" y2="276" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<line x1="140" y1="36" x2="140" y2="276" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
|
||||
<g aria-label="Pipeline steps">
|
||||
<rect x="180" y="6" width="32" height="16" rx="8" fill="rgba(45,49,66,0.12)"/><text x="196" y="14" class="step-number">01</text><text x="196" y="29" class="step-label">INGEST</text>
|
||||
<rect x="292" y="6" width="32" height="16" rx="8" fill="rgba(45,49,66,0.12)"/><text x="308" y="14" class="step-number">02</text><text x="308" y="29" class="step-label">STORE</text>
|
||||
<rect x="404" y="6" width="32" height="16" rx="8" fill="rgba(235,108,54,0.20)"/><text x="420" y="14" class="step-number focal-text">03</text><text x="420" y="29" class="step-label focal-text">TRANSFORM</text>
|
||||
<rect x="516" y="6" width="32" height="16" rx="8" fill="rgba(45,49,66,0.12)"/><text x="532" y="14" class="step-number">04</text><text x="532" y="29" class="step-label">ANALYZE</text>
|
||||
<rect x="628" y="6" width="32" height="16" rx="8" fill="rgba(45,49,66,0.12)"/><text x="644" y="14" class="step-number">05</text><text x="644" y="29" class="step-label">PUBLISH</text>
|
||||
</g>
|
||||
|
||||
<g aria-label="Role lanes">
|
||||
<text x="70" y="72" class="lane-label">DATA</text><text x="70" y="84" class="lane-label">ENGINEER</text>
|
||||
<text x="70" y="152" class="lane-label">DATA</text><text x="70" y="164" class="lane-label">SCIENTIST</text>
|
||||
<text x="70" y="232" class="lane-label">ANALYTICS</text><text x="70" y="244" class="lane-label">ANALYST</text>
|
||||
</g>
|
||||
|
||||
<!-- Connectors first. Cross-lane paths use r=8 orthogonal elbows. -->
|
||||
<path d="M246 76 H258" fill="none" stroke="#4f5d75" marker-end="url(#arr-muted)"/>
|
||||
<path d="M358 76 H412 Q420 76 420 84 V124" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arr-accent)"/>
|
||||
<path d="M470 156 H482" fill="none" stroke="#4f5d75" marker-end="url(#arr-muted)"/>
|
||||
<path d="M582 156 H636 Q644 156 644 164 V204" fill="none" stroke="#2e5aa8" marker-end="url(#arr-link)"/>
|
||||
<rect x="368" y="56" width="48" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="392" y="65" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.06em">RAW TABLE</text>
|
||||
|
||||
<!-- Data Engineer: Capture Events -->
|
||||
<rect x="146" y="44" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="150" y="48" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="159" y="53" class="role-text">ENG</text>
|
||||
<text x="196" y="67" class="node-title">Capture Events</text><text x="196" y="79" class="node-sub">shop events → batch</text><text x="196" y="91" class="node-tool">NiFi ingest</text>
|
||||
<rect x="150" y="98" width="16" height="8" rx="3" fill="#4a7c59"/><text x="158" y="104" class="chip-text">LS</text>
|
||||
<rect x="226" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="234" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Engineer: Land Records -->
|
||||
<rect x="258" y="44" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="262" y="48" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="271" y="53" class="role-text">ENG</text>
|
||||
<text x="308" y="67" class="node-title">Land Records</text><text x="308" y="79" class="node-sub">events · orders</text><text x="308" y="91" class="node-tool">Object storage</text>
|
||||
<rect x="262" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="270" y="104" class="chip-text">DB</text>
|
||||
<rect x="338" y="98" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="346" y="104" class="chip-text">DB</text>
|
||||
|
||||
<!-- Data Scientist: Clean and Model (focal) -->
|
||||
<rect x="370" y="124" width="100" height="64" rx="6" fill="rgba(235,108,54,0.07)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="374" y="128" width="18" height="10" rx="3" fill="rgba(235,108,54,0.20)"/><text x="383" y="133" class="role-text focal-text">SCI</text>
|
||||
<text x="420" y="147" class="node-title">Clean & Model</text><text x="420" y="159" class="node-sub">raw → trusted table</text><text x="420" y="171" class="node-tool">Trino · notebooks</text>
|
||||
<rect x="374" y="178" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="382" y="184" class="chip-text">DB</text>
|
||||
<rect x="450" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="458" y="184" class="chip-text">TB</text>
|
||||
|
||||
<!-- Data Scientist: Curate Metrics -->
|
||||
<rect x="482" y="124" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="486" y="128" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="495" y="133" class="role-text">SCI</text>
|
||||
<text x="532" y="147" class="node-title">Curate Metrics</text><text x="532" y="159" class="node-sub">conversion · revenue</text><text x="532" y="171" class="node-tool">Trino SQL</text>
|
||||
<rect x="486" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="494" y="184" class="chip-text">TB</text>
|
||||
<rect x="562" y="178" width="16" height="8" rx="3" fill="#b8915a"/><text x="570" y="184" class="chip-text">TB</text>
|
||||
|
||||
<!-- Analyst: Publish Dashboard -->
|
||||
<rect x="594" y="204" width="100" height="64" rx="6" fill="#f5f5f5" stroke="rgba(45,49,66,0.25)"/>
|
||||
<rect x="598" y="208" width="18" height="10" rx="3" fill="rgba(45,49,66,0.12)"/><text x="607" y="213" class="role-text">ANL</text>
|
||||
<text x="644" y="227" class="node-title">Publish Dashboard</text><text x="644" y="239" class="node-sub">metrics → decisions</text><text x="644" y="251" class="node-tool">BI workspace</text>
|
||||
<rect x="598" y="258" width="16" height="8" rx="3" fill="#b8915a"/><text x="606" y="264" class="chip-text">TB</text>
|
||||
<rect x="674" y="258" width="16" height="8" rx="3" fill="#9c6b50"/><text x="682" y="264" class="chip-text">FL</text>
|
||||
|
||||
<!-- Legend: steps -->
|
||||
<text x="164" y="293" class="legend-label">STEPS</text>
|
||||
<rect x="180" y="284" width="24" height="12" rx="6" fill="rgba(45,49,66,0.12)"/><text x="192" y="292" class="step-number">01</text><text x="212" y="293" class="legend-text">Ingest</text>
|
||||
<rect x="272" y="284" width="24" height="12" rx="6" fill="rgba(45,49,66,0.12)"/><text x="284" y="292" class="step-number">02</text><text x="304" y="293" class="legend-text">Store</text>
|
||||
<rect x="360" y="284" width="24" height="12" rx="6" fill="rgba(235,108,54,0.20)"/><text x="372" y="292" class="step-number focal-text">03</text><text x="392" y="293" class="legend-text">Transform</text>
|
||||
<rect x="468" y="284" width="24" height="12" rx="6" fill="rgba(45,49,66,0.12)"/><text x="480" y="292" class="step-number">04</text><text x="500" y="293" class="legend-text">Analyze</text>
|
||||
<rect x="568" y="284" width="24" height="12" rx="6" fill="rgba(45,49,66,0.12)"/><text x="580" y="292" class="step-number">05</text><text x="600" y="293" class="legend-text">Publish</text>
|
||||
|
||||
<!-- Legend: data types -->
|
||||
<text x="164" y="314" class="legend-label">DATA TYPE</text>
|
||||
<rect x="180" y="306" width="16" height="8" rx="3" fill="#4a7c59"/><text x="188" y="312" class="chip-text">LS</text><text x="202" y="314" class="legend-text">Stream</text>
|
||||
<rect x="252" y="306" width="16" height="8" rx="3" fill="#5e7a9b"/><text x="260" y="312" class="chip-text">DB</text><text x="274" y="314" class="legend-text">Dataset</text>
|
||||
<rect x="332" y="306" width="16" height="8" rx="3" fill="#b8915a"/><text x="340" y="312" class="chip-text">TB</text><text x="354" y="314" class="legend-text">Table</text>
|
||||
<rect x="404" y="306" width="16" height="8" rx="3" fill="#9c6b50"/><text x="412" y="312" class="chip-text">FL</text><text x="426" y="314" class="legend-text">Dashboard</text>
|
||||
<text x="500" y="314" class="legend-text">left chip = input · right chip = output</text>
|
||||
|
||||
<!-- Legend: flow -->
|
||||
<text x="164" y="336" class="legend-label">FLOW</text>
|
||||
<line x1="180" y1="333" x2="204" y2="333" stroke="#4f5d75" marker-end="url(#arr-muted)"/><text x="212" y="336" class="legend-text">Standard handoff</text>
|
||||
<line x1="324" y1="333" x2="348" y2="333" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arr-accent)"/><text x="356" y="336" class="legend-text">Focal handoff</text>
|
||||
<line x1="460" y1="333" x2="484" y2="333" stroke="#2e5aa8" marker-end="url(#arr-link)"/><text x="492" y="336" class="legend-text">Published output</text>
|
||||
</svg>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,241 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Open data lake · Architecture</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #2d3142;
|
||||
--color-ink: #f5f5f5;
|
||||
--color-muted: #bfc0c0;
|
||||
--color-accent: #f08a59;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture · Diagram Design</p>
|
||||
<h1>Open data lake · End-to-end stack</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 488" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#bfc0c0"/>
|
||||
</marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#f08a59"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#2d3142"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<rect x="4" y="32" width="160" height="200" rx="8" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<rect x="176" y="32" width="160" height="200" rx="8" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<rect x="380" y="32" width="184" height="160" rx="8" fill="rgba(240,138,89,0.06)" stroke="rgba(240,138,89,0.22)" stroke-width="0.8"/>
|
||||
<rect x="600" y="32" width="168" height="200" rx="8" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<rect x="804" y="32" width="164" height="280" rx="8" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
|
||||
<text x="84" y="44" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">SOURCES</text>
|
||||
<text x="256" y="44" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">INGEST</text>
|
||||
<text x="472" y="44" fill="#f08a59" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">DATA LAKE</text>
|
||||
<text x="688" y="44" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">QUERY</text>
|
||||
<text x="876" y="44" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">CONSUME</text>
|
||||
|
||||
<line x1="152" y1="88" x2="187" y2="88" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<line x1="152" y1="192" x2="187" y2="192" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<path d="M 324,80 H 350 Q 358,80 358,88 V 112 Q 358,120 366,120 H 391" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 324,200 H 350 Q 358,200 358,192 V 168 Q 358,160 366,160 H 391" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<path d="M 553,128 H 576 Q 584,128 584,120 V 96 Q 584,88 592,88 H 615" fill="none" stroke="#f08a59" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<path d="M 553,152 H 576 Q 584,152 584,160 V 180 Q 584,188 592,188 H 615" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<rect x="586" y="102" width="28" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="600" y="111" fill="#f08a59" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">SQL</text>
|
||||
|
||||
<path d="M 760,76 H 784 Q 788,76 788,68 H 815" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 760,100 H 780 Q 788,100 788,108 V 148 Q 788,156 796,156 H 815" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 760,180 H 782 Q 788,180 788,168 H 815" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 760,204 H 780 Q 788,204 788,212 V 232 Q 788,240 796,240 H 815" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<path d="M 884,300 V 312 Q 884,320 876,320 H 480 Q 472,320 472,312 V 180" fill="none" stroke="rgba(245,245,245,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<rect x="660" y="314" width="36" height="12" rx="2" fill="#2d3142"/>
|
||||
<text x="678" y="323" fill="#8e98ac" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">WRITE</text>
|
||||
|
||||
<!-- App servers -->
|
||||
<rect x="16" y="60" width="136" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="16" y="60" width="136" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="24" y="66" width="26" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="37" y="75" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="72" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/>
|
||||
<path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/>
|
||||
<path d="M7 8l0 .01"/><path d="M7 16l0 .01"/>
|
||||
</svg>
|
||||
<text x="84" y="102" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">App servers</text>
|
||||
<text x="84" y="114" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Events · logs</text>
|
||||
|
||||
<!-- Databases -->
|
||||
<rect x="16" y="164" width="136" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="16" y="164" width="136" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="24" y="170" width="26" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="37" y="179" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="72" y="170" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
|
||||
</svg>
|
||||
<text x="84" y="206" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Databases</text>
|
||||
<text x="84" y="218" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · exports</text>
|
||||
|
||||
<!-- Apache NiFi -->
|
||||
<rect x="188" y="60" width="136" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="188" y="60" width="136" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="196" y="66" width="28" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="210" y="75" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">FLOW</text>
|
||||
<svg x="244" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0zm-1.632 3.867c.05 0 .08.034.037.112-.11.197-.218.397-.328.593-.396.702-.819 1.389-1.23 2.08-.196-.032-.39-.06-.585-.088.495-.651 1-1.296 1.48-1.959.153-.209.302-.423.454-.634a.24.24 0 0 1 .172-.104zM7.44 7.186c.221.035.444.076.666.119-.073.129-.15.256-.223.383a29.073 29.073 0 0 0-1.625 3.261c-.874 2.123-1.383 4.444-.77 6.707a8.222 8.222 0 0 0 2.217 3.74c.083.083-.02.216-.119.155a7.568 7.568 0 0 1-.93-.686A7.674 7.674 0 0 1 4.1 16.248c-.329-2.156.387-4.246 1.418-6.115a27.44 27.44 0 0 1 1.92-2.947zm7.931 8.435a.193.193 0 0 0-.191.191V18.3h2.677V15.62zm3.299 0V18.3h1.348a7.975 7.975 0 0 0 .515-2.678zm-6.303 3.004a.193.193 0 0 0-.191.193v2.485h2.678v-2.678Zm3.295.484v2.68h2.115a.562.562 0 0 0 .399-.162v-.004a.562.562 0 0 0 .16-.397V19.11zm3.674.182v1.98a7.999 7.999 0 0 0 1.217-1.98zm-6.969 2.824a.193.193 0 0 0-.191.192v1.672a7.997 7.997 0 0 0 2.678-.516v-1.348zm3.48.668V24a8.008 8.008 0 0 0 1.98-1.217z"/>
|
||||
</svg>
|
||||
<text x="256" y="102" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="256" y="114" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Route · transform</text>
|
||||
|
||||
<!-- Airflow -->
|
||||
<rect x="188" y="164" width="136" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="188" y="164" width="136" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="196" y="170" width="28" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="210" y="179" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ORCH</text>
|
||||
<svg x="244" y="170" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/>
|
||||
</svg>
|
||||
<text x="256" y="206" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Airflow</text>
|
||||
<text x="256" y="218" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">DAG scheduling</text>
|
||||
|
||||
<!-- MinIO (FOCAL) -->
|
||||
<rect x="392" y="100" width="160" height="80" rx="8" fill="#2d3142"/>
|
||||
<rect x="392" y="100" width="160" height="80" rx="8" fill="rgba(240,138,89,0.10)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<rect x="400" y="106" width="36" height="12" rx="2" fill="transparent" stroke="rgba(240,138,89,0.50)" stroke-width="0.8"/>
|
||||
<text x="418" y="115" fill="rgba(240,138,89,0.80)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="460" y="108" width="24" height="24" viewBox="0 0 24 24" fill="#f08a59">
|
||||
<path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Zm-.9024 9.8652v2.9941l-4.1523 2.1484a13.9787 13.9787 0 0 1 2.7676-3.9277 14.1784 14.1784 0 0 1 1.3847-1.2148z"/>
|
||||
</svg>
|
||||
<text x="472" y="148" fill="#f08a59" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO</text>
|
||||
<text x="472" y="164" fill="#f08a59" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.75">Object store · S3-API</text>
|
||||
|
||||
<!-- Trino -->
|
||||
<rect x="616" y="60" width="144" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="616" y="60" width="144" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="624" y="66" width="36" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="642" y="75" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
<svg x="676" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075c.2614-1.2682.8762-3.574 2.0292-5.1958 1.6717-2.352 3.4357-4.7808 4.6116-4.1006 1.176.6802-.3074 3.1398-1.3297 4.4272-1.0222 1.2874-2.7862 3.2089-3.3742 4.2274-.2114.3843-.4304.8032-.5956 1.1529a5.7375 5.7375 0 0 1 2.9169 3.6125h.073v-2.3058a.3075.3075 0 0 0-.1806-.2844.9148.9148 0 0 1-.5573-.8148 1.0184 1.0184 0 0 1 .9045-.9044c.5593-.0598 1.061.3452 1.1208.9044a.9187.9187 0 0 1-.5534.8148.3074.3074 0 0 0-.1691.2844v2.1522a.3113.3113 0 0 0 .1691.2805.9724.9724 0 0 1 .5648.857z"/>
|
||||
</svg>
|
||||
<text x="688" y="102" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="688" y="114" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · any format</text>
|
||||
|
||||
<!-- StarRocks -->
|
||||
<rect x="616" y="164" width="144" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="616" y="164" width="144" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="624" y="170" width="28" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="638" y="179" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">OLAP</text>
|
||||
<svg x="676" y="170" width="24" height="24" viewBox="0 0 100 100" fill="#f5f5f5">
|
||||
<path d="M11.8,26.4c-0.1,2.2,0.9,3.4,2.3,4.5c9,7.4,18,14.8,27,22.2c2.5,2.1,2.7,3.5,1,6.2c-4.4,6.7-8.8,13.4-13.2,20.1c-1.7,2.5-3.2,2.9-5.9,1.4c-2.8-1.6-5.6-3.2-8.4-4.8c-3.2-1.8-4.8-4.6-4.8-8.3c0-11.8,0-23.6,0-35.5C9.8,30.2,10.3,28.4,11.8,26.4z"/>
|
||||
<path d="M87.9,73.8c0.6-2.3-0.5-3.5-1.9-4.6c-9-7.4-17.9-14.7-26.8-22.1c-2.9-2.4-3.1-3.6-1.1-6.7c4.3-6.6,8.7-13.2,13-19.8c1.7-2.6,3.3-2.9,6-1.4c2.8,1.6,5.6,3.2,8.3,4.8c3.2,1.8,4.7,4.5,4.7,8.2c0,11.9,0,23.7,0,35.6C90.2,69.9,89.6,71.9,87.9,73.8z"/>
|
||||
<path d="M67.1,56.8c0.6,0.4,17.3,14.1,17.5,14.3c2.4,2.2,2.2,4.1-0.6,5.8C76.8,81,57.3,92.1,54.8,93.6c-3.2,1.9-6.4,1.9-9.6,0c-4.6-2.7-9.2-5.3-13.8-8c-2.2-1.3-2.2-2.7,0-3.9C42.3,75.5,53.1,69.2,64,63C66.5,61.6,68.2,60.1,67.1,56.8z"/>
|
||||
<path d="M32.9,43.2c-0.6-0.4-17.3-14.1-17.5-14.3c-2.4-2.2-2.2-4.1,0.6-5.8C23.2,19,42.7,7.9,45.2,6.4c3.2-1.9,6.4-1.9,9.6,0c4.6,2.7,9.2,5.3,13.8,8c2.2,1.3,2.2,2.7,0,3.9C57.7,24.5,46.9,30.8,36,37C33.5,38.4,31.8,39.9,32.9,43.2z"/>
|
||||
</svg>
|
||||
<text x="688" y="206" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">StarRocks</text>
|
||||
<text x="688" y="218" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">MPP · hot layer</text>
|
||||
|
||||
<!-- Superset -->
|
||||
<rect x="816" y="60" width="136" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="816" y="60" width="136" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="824" y="66" width="18" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="833" y="75" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">BI</text>
|
||||
<svg x="872" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/>
|
||||
</svg>
|
||||
<text x="884" y="102" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Superset</text>
|
||||
<text x="884" y="114" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Dashboards</text>
|
||||
|
||||
<!-- JupyterLab -->
|
||||
<rect x="816" y="152" width="136" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="816" y="152" width="136" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="824" y="158" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="835" y="167" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">NB</text>
|
||||
<svg x="872" y="158" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424zm.001 13.295c-3.768 0-7.06-1.381-8.768-3.425a9.363 9.363 0 0 0 3.394 4.547A9.238 9.238 0 0 0 12.33 21a9.238 9.238 0 0 0 5.377-1.729 9.363 9.363 0 0 0 3.393-4.547c-1.712 2.044-5.003 3.425-8.772 3.425z"/>
|
||||
</svg>
|
||||
<text x="884" y="194" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">JupyterLab</text>
|
||||
<text x="884" y="206" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Exploration</text>
|
||||
|
||||
<!-- Python -->
|
||||
<rect x="816" y="244" width="136" height="56" rx="6" fill="#2d3142"/>
|
||||
<rect x="816" y="244" width="136" height="56" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="824" y="250" width="32" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="840" y="259" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">PROC</text>
|
||||
<svg x="872" y="250" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/>
|
||||
</svg>
|
||||
<text x="884" y="286" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Python</text>
|
||||
<text x="884" y="298" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Batch · ML</text>
|
||||
|
||||
<line x1="40" y1="432" x2="960" y2="432" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="448" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="460" width="20" height="12" rx="3" fill="rgba(240,138,89,0.12)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<text x="68" y="471" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">MinIO data lake (focal)</text>
|
||||
|
||||
<rect x="220" y="460" width="20" height="12" rx="3" fill="rgba(245,245,245,0.08)" stroke="rgba(245,245,245,0.25)" stroke-width="1"/>
|
||||
<text x="248" y="471" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Ingest · query tools</text>
|
||||
|
||||
<rect x="400" y="460" width="20" height="12" rx="3" fill="rgba(245,245,245,0.08)" stroke="rgba(245,245,245,0.25)" stroke-width="1"/>
|
||||
<text x="428" y="471" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">BI · notebooks · pipelines</text>
|
||||
|
||||
<line x1="648" y1="462" x2="668" y2="462" stroke="rgba(245,245,245,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="676" y="468" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Write-back path (batch)</text>
|
||||
|
||||
<line x1="820" y1="462" x2="840" y2="462" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<text x="848" y="468" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Primary query path</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,246 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Open data lake · Architecture</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
|
||||
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 64ch; }
|
||||
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
@media (max-width: 980px) { .cards { grid-template-columns: 1fr 1fr; } }
|
||||
@media (max-width: 600px) { .cards { grid-template-columns: 1fr; } }
|
||||
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
|
||||
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--color-accent); }
|
||||
.card-dot.ink { background: var(--color-ink); }
|
||||
.card-dot.muted { background: var(--color-muted); }
|
||||
.card-dot.soft { background: var(--color-soft); }
|
||||
.card h3 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p, .card ul { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; list-style: none; }
|
||||
.card li { margin-bottom: 0.3rem; padding-left: 0.875rem; position: relative; }
|
||||
.card li::before { content: '—'; position: absolute; left: 0; color: rgba(45,49,66,0.25); font-size: 0.75rem; }
|
||||
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-soft); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<p class="header-eyebrow">Architecture · Diagram Design</p>
|
||||
<h1>Open data lake · End-to-end stack</h1>
|
||||
<p class="subtitle">Five functional zones from raw event ingestion to analytics consumption. MinIO is the focal centre — every other tool either lands data into it or reads out of it.</p>
|
||||
</div>
|
||||
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 488" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/>
|
||||
</marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<rect x="4" y="32" width="160" height="200" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="176" y="32" width="160" height="200" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="380" y="32" width="184" height="160" rx="8" fill="rgba(235,108,54,0.04)" stroke="rgba(235,108,54,0.20)" stroke-width="0.8"/>
|
||||
<rect x="600" y="32" width="168" height="200" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="804" y="32" width="164" height="280" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
|
||||
<text x="84" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">SOURCES</text>
|
||||
<text x="256" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">INGEST</text>
|
||||
<text x="472" y="44" fill="#eb6c36" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">DATA LAKE</text>
|
||||
<text x="688" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">QUERY</text>
|
||||
<text x="876" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">CONSUME</text>
|
||||
|
||||
<line x1="152" y1="88" x2="187" y2="88" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<line x1="152" y1="192" x2="187" y2="192" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 324,80 H 350 Q 358,80 358,88 V 112 Q 358,120 366,120 H 391" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 324,200 H 350 Q 358,200 358,192 V 168 Q 358,160 366,160 H 391" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 553,128 H 576 Q 584,128 584,120 V 96 Q 584,88 592,88 H 615" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<path d="M 553,152 H 576 Q 584,152 584,160 V 180 Q 584,188 592,188 H 615" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<rect x="586" y="102" width="28" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="600" y="111" fill="#eb6c36" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">SQL</text>
|
||||
<path d="M 760,76 H 784 Q 788,76 788,68 H 815" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 760,100 H 780 Q 788,100 788,108 V 148 Q 788,156 796,156 H 815" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 760,180 H 782 Q 788,180 788,168 H 815" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 760,204 H 780 Q 788,204 788,212 V 232 Q 788,240 796,240 H 815" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 884,300 V 312 Q 884,320 876,320 H 480 Q 472,320 472,312 V 180" fill="none" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<rect x="660" y="314" width="36" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="678" y="323" fill="#7a8399" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">WRITE</text>
|
||||
|
||||
<rect x="16" y="60" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="16" y="60" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="24" y="66" width="26" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="37" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="72" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/>
|
||||
<path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/>
|
||||
<path d="M7 8l0 .01"/><path d="M7 16l0 .01"/>
|
||||
</svg>
|
||||
<text x="84" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">App servers</text>
|
||||
<text x="84" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Events · logs</text>
|
||||
|
||||
<rect x="16" y="164" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="16" y="164" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="24" y="170" width="26" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="37" y="179" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="72" y="170" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
|
||||
</svg>
|
||||
<text x="84" y="206" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Databases</text>
|
||||
<text x="84" y="218" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · exports</text>
|
||||
|
||||
<rect x="188" y="60" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="188" y="60" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="196" y="66" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="210" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">FLOW</text>
|
||||
<svg x="244" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0zm-1.632 3.867c.05 0 .08.034.037.112-.11.197-.218.397-.328.593-.396.702-.819 1.389-1.23 2.08-.196-.032-.39-.06-.585-.088.495-.651 1-1.296 1.48-1.959.153-.209.302-.423.454-.634a.24.24 0 0 1 .172-.104zM7.44 7.186c.221.035.444.076.666.119-.073.129-.15.256-.223.383a29.073 29.073 0 0 0-1.625 3.261c-.874 2.123-1.383 4.444-.77 6.707a8.222 8.222 0 0 0 2.217 3.74c.083.083-.02.216-.119.155a7.568 7.568 0 0 1-.93-.686A7.674 7.674 0 0 1 4.1 16.248c-.329-2.156.387-4.246 1.418-6.115a27.44 27.44 0 0 1 1.92-2.947zm7.931 8.435a.193.193 0 0 0-.191.191V18.3h2.677V15.62zm3.299 0V18.3h1.348a7.975 7.975 0 0 0 .515-2.678zm-6.303 3.004a.193.193 0 0 0-.191.193v2.485h2.678v-2.678Zm3.295.484v2.68h2.115a.562.562 0 0 0 .399-.162v-.004a.562.562 0 0 0 .16-.397V19.11zm3.674.182v1.98a7.999 7.999 0 0 0 1.217-1.98zm-6.969 2.824a.193.193 0 0 0-.191.192v1.672a7.997 7.997 0 0 0 2.678-.516v-1.348zm3.48.668V24a8.008 8.008 0 0 0 1.98-1.217z"/>
|
||||
</svg>
|
||||
<text x="256" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="256" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Route · transform</text>
|
||||
|
||||
<rect x="188" y="164" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="188" y="164" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="196" y="170" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="210" y="179" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ORCH</text>
|
||||
<svg x="244" y="170" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/>
|
||||
</svg>
|
||||
<text x="256" y="206" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Airflow</text>
|
||||
<text x="256" y="218" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">DAG scheduling</text>
|
||||
|
||||
<rect x="392" y="100" width="160" height="80" rx="8" fill="#f5f5f5"/>
|
||||
<rect x="392" y="100" width="160" height="80" rx="8" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="400" y="106" width="36" height="12" rx="2" fill="transparent" stroke="rgba(235,108,54,0.50)" stroke-width="0.8"/>
|
||||
<text x="418" y="115" fill="rgba(235,108,54,0.80)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="460" y="108" width="24" height="24" viewBox="0 0 24 24" fill="#eb6c36">
|
||||
<path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Zm-.9024 9.8652v2.9941l-4.1523 2.1484a13.9787 13.9787 0 0 1 2.7676-3.9277 14.1784 14.1784 0 0 1 1.3847-1.2148z"/>
|
||||
</svg>
|
||||
<text x="472" y="148" fill="#eb6c36" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO</text>
|
||||
<text x="472" y="164" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.75">Object store · S3-API</text>
|
||||
|
||||
<rect x="616" y="60" width="144" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="616" y="60" width="144" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="624" y="66" width="36" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="642" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
<svg x="676" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075c.2614-1.2682.8762-3.574 2.0292-5.1958 1.6717-2.352 3.4357-4.7808 4.6116-4.1006 1.176.6802-.3074 3.1398-1.3297 4.4272-1.0222 1.2874-2.7862 3.2089-3.3742 4.2274-.2114.3843-.4304.8032-.5956 1.1529a5.7375 5.7375 0 0 1 2.9169 3.6125h.073v-2.3058a.3075.3075 0 0 0-.1806-.2844.9148.9148 0 0 1-.5573-.8148 1.0184 1.0184 0 0 1 .9045-.9044c.5593-.0598 1.061.3452 1.1208.9044a.9187.9187 0 0 1-.5534.8148.3074.3074 0 0 0-.1691.2844v2.1522a.3113.3113 0 0 0 .1691.2805.9724.9724 0 0 1 .5648.857z"/>
|
||||
</svg>
|
||||
<text x="688" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="688" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · any format</text>
|
||||
|
||||
<rect x="616" y="164" width="144" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="616" y="164" width="144" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="624" y="170" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="638" y="179" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">OLAP</text>
|
||||
<svg x="676" y="170" width="24" height="24" viewBox="0 0 100 100" fill="#2d3142">
|
||||
<path d="M11.8,26.4c-0.1,2.2,0.9,3.4,2.3,4.5c9,7.4,18,14.8,27,22.2c2.5,2.1,2.7,3.5,1,6.2c-4.4,6.7-8.8,13.4-13.2,20.1c-1.7,2.5-3.2,2.9-5.9,1.4c-2.8-1.6-5.6-3.2-8.4-4.8c-3.2-1.8-4.8-4.6-4.8-8.3c0-11.8,0-23.6,0-35.5C9.8,30.2,10.3,28.4,11.8,26.4z"/>
|
||||
<path d="M87.9,73.8c0.6-2.3-0.5-3.5-1.9-4.6c-9-7.4-17.9-14.7-26.8-22.1c-2.9-2.4-3.1-3.6-1.1-6.7c4.3-6.6,8.7-13.2,13-19.8c1.7-2.6,3.3-2.9,6-1.4c2.8,1.6,5.6,3.2,8.3,4.8c3.2,1.8,4.7,4.5,4.7,8.2c0,11.9,0,23.7,0,35.6C90.2,69.9,89.6,71.9,87.9,73.8z"/>
|
||||
<path d="M67.1,56.8c0.6,0.4,17.3,14.1,17.5,14.3c2.4,2.2,2.2,4.1-0.6,5.8C76.8,81,57.3,92.1,54.8,93.6c-3.2,1.9-6.4,1.9-9.6,0c-4.6-2.7-9.2-5.3-13.8-8c-2.2-1.3-2.2-2.7,0-3.9C42.3,75.5,53.1,69.2,64,63C66.5,61.6,68.2,60.1,67.1,56.8z"/>
|
||||
<path d="M32.9,43.2c-0.6-0.4-17.3-14.1-17.5-14.3c-2.4-2.2-2.2-4.1,0.6-5.8C23.2,19,42.7,7.9,45.2,6.4c3.2-1.9,6.4-1.9,9.6,0c4.6,2.7,9.2,5.3,13.8,8c2.2,1.3,2.2,2.7,0,3.9C57.7,24.5,46.9,30.8,36,37C33.5,38.4,31.8,39.9,32.9,43.2z"/>
|
||||
</svg>
|
||||
<text x="688" y="206" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">StarRocks</text>
|
||||
<text x="688" y="218" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">MPP · hot layer</text>
|
||||
|
||||
<rect x="816" y="60" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="816" y="60" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="824" y="66" width="18" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="833" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">BI</text>
|
||||
<svg x="872" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/>
|
||||
</svg>
|
||||
<text x="884" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Superset</text>
|
||||
<text x="884" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Dashboards</text>
|
||||
|
||||
<rect x="816" y="152" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="816" y="152" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="824" y="158" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="835" y="167" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">NB</text>
|
||||
<svg x="872" y="158" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424zm.001 13.295c-3.768 0-7.06-1.381-8.768-3.425a9.363 9.363 0 0 0 3.394 4.547A9.238 9.238 0 0 0 12.33 21a9.238 9.238 0 0 0 5.377-1.729 9.363 9.363 0 0 0 3.393-4.547c-1.712 2.044-5.003 3.425-8.772 3.425z"/>
|
||||
</svg>
|
||||
<text x="884" y="194" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">JupyterLab</text>
|
||||
<text x="884" y="206" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Exploration</text>
|
||||
|
||||
<rect x="816" y="244" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="816" y="244" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="824" y="250" width="32" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="840" y="259" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">PROC</text>
|
||||
<svg x="872" y="250" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/>
|
||||
</svg>
|
||||
<text x="884" y="286" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Python</text>
|
||||
<text x="884" y="298" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Batch · ML</text>
|
||||
|
||||
<line x1="40" y1="432" x2="960" y2="432" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="448" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
<rect x="40" y="460" width="20" height="12" rx="3" fill="rgba(235,108,54,0.10)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="68" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">MinIO data lake (focal)</text>
|
||||
<rect x="220" y="460" width="20" height="12" rx="3" fill="white" stroke="rgba(45,49,66,0.30)" stroke-width="1"/>
|
||||
<text x="248" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Ingest · query tools</text>
|
||||
<rect x="400" y="460" width="20" height="12" rx="3" fill="white" stroke="rgba(45,49,66,0.30)" stroke-width="1"/>
|
||||
<text x="428" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">BI · notebooks · pipelines</text>
|
||||
<line x1="648" y1="462" x2="668" y2="462" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="676" y="468" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Write-back path (batch)</text>
|
||||
<line x1="820" y1="462" x2="840" y2="462" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="848" y="468" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Primary query path</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="eyebrow">FOCAL · STORAGE</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h3>MinIO — data lake core</h3></div>
|
||||
<p>S3-compatible object storage. Every tool in the stack either lands data here or reads from it. All formats land as raw files; Iceberg table metadata sits alongside them. When in doubt, write to MinIO.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot ink"></span><h3>Ingest — NiFi + Airflow</h3></div>
|
||||
<ul>
|
||||
<li>NiFi: real-time routing, record-level transformation, CDC fan-out</li>
|
||||
<li>Airflow: batch scheduling, cross-system DAGs, retry semantics</li>
|
||||
<li>Two tools, two tempos — they complement rather than replace each other</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot muted"></span><h3>Query — Trino + StarRocks</h3></div>
|
||||
<ul>
|
||||
<li>Trino: federated ad-hoc SQL across any file format, any catalog</li>
|
||||
<li>StarRocks: sub-second MPP for hot partitions and dashboard queries</li>
|
||||
<li>Different latency budgets, same underlying lake</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot soft"></span><h3>Consume</h3></div>
|
||||
<p>Superset for dashboards, JupyterLab for exploration, Python for batch ML. All three can write processed results back to MinIO via the dashed write-back path.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>open data lake · end-to-end stack</span>
|
||||
<span>example · diagram design</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,267 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Open data lake · Architecture</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #f5f5f5;
|
||||
--color-ink: #2d3142;
|
||||
--color-muted: #4f5d75;
|
||||
--color-accent: #eb6c36;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture · Diagram Design</p>
|
||||
<h1>Open data lake · End-to-end stack</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 488" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/>
|
||||
</marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Zone rects (drawn before labels, arrows, nodes) -->
|
||||
<rect x="4" y="32" width="160" height="200" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="176" y="32" width="160" height="200" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="380" y="32" width="184" height="160" rx="8" fill="rgba(235,108,54,0.04)" stroke="rgba(235,108,54,0.20)" stroke-width="0.8"/>
|
||||
<rect x="600" y="32" width="168" height="200" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="804" y="32" width="164" height="280" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Zone labels -->
|
||||
<text x="84" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">SOURCES</text>
|
||||
<text x="256" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">INGEST</text>
|
||||
<text x="472" y="44" fill="#eb6c36" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">DATA LAKE</text>
|
||||
<text x="688" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">QUERY</text>
|
||||
<text x="876" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.16em" text-anchor="middle">CONSUME</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
ARROWS — drawn before boxes (z-order rule)
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<!-- AppServer → NiFi -->
|
||||
<line x1="152" y1="88" x2="187" y2="88" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<!-- DBSource → Airflow -->
|
||||
<line x1="152" y1="192" x2="187" y2="192" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- NiFi → MinIO -->
|
||||
<path d="M 324,80 H 350 Q 358,80 358,88 V 112 Q 358,120 366,120 H 391" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<!-- Airflow → MinIO -->
|
||||
<path d="M 324,200 H 350 Q 358,200 358,192 V 168 Q 358,160 366,160 H 391" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- MinIO → Trino (accent — primary query path) -->
|
||||
<path d="M 553,128 H 576 Q 584,128 584,120 V 96 Q 584,88 592,88 H 615" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<!-- MinIO → StarRocks -->
|
||||
<path d="M 553,152 H 576 Q 584,152 584,160 V 180 Q 584,188 592,188 H 615" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Arrow label on MinIO→Trino (on vertical segment at x=584) -->
|
||||
<rect x="586" y="102" width="28" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="600" y="111" fill="#eb6c36" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">SQL</text>
|
||||
|
||||
<!-- Trino → Superset (same row → left/right port) -->
|
||||
<line x1="760" y1="88" x2="815" y2="88" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<!-- Trino → Jupyter (vertical-primary → exit Trino bottom, enter Jupyter top) -->
|
||||
<path d="M 688,116 V 132 Q 688,140 696,140 H 876 Q 884,140 884,148 V 152" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<!-- StarRocks → Jupyter (nearly horizontal → left/right port) -->
|
||||
<line x1="760" y1="180" x2="815" y2="180" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<!-- StarRocks → Python (vertical-primary → exit StarRocks bottom, enter Python top) -->
|
||||
<path d="M 688,220 V 232 Q 688,240 696,240 H 876 Q 884,240 884,244" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Python → MinIO (write-back, dashed — orthogonal via bottom gutter at y=320) -->
|
||||
<path d="M 884,300 V 312 Q 884,320 876,320 H 480 Q 472,320 472,312 V 180" fill="none" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<rect x="660" y="314" width="36" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="678" y="323" fill="#7a8399" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">WRITE</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
NODES
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<!-- ── App servers ── -->
|
||||
<rect x="16" y="60" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="16" y="60" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="24" y="66" width="26" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="37" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="72" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/>
|
||||
<path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/>
|
||||
<path d="M7 8l0 .01"/><path d="M7 16l0 .01"/>
|
||||
</svg>
|
||||
<text x="84" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">App servers</text>
|
||||
<text x="84" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Events · logs</text>
|
||||
|
||||
<!-- ── Databases ── -->
|
||||
<rect x="16" y="164" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="16" y="164" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="24" y="170" width="26" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="37" y="179" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="72" y="170" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
|
||||
</svg>
|
||||
<text x="84" y="206" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Databases</text>
|
||||
<text x="84" y="218" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · exports</text>
|
||||
|
||||
<!-- ── Apache NiFi ── -->
|
||||
<rect x="188" y="60" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="188" y="60" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="196" y="66" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="210" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">FLOW</text>
|
||||
<svg x="244" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0zm-1.632 3.867c.05 0 .08.034.037.112-.11.197-.218.397-.328.593-.396.702-.819 1.389-1.23 2.08-.196-.032-.39-.06-.585-.088.495-.651 1-1.296 1.48-1.959.153-.209.302-.423.454-.634a.24.24 0 0 1 .172-.104zM7.44 7.186c.221.035.444.076.666.119-.073.129-.15.256-.223.383a29.073 29.073 0 0 0-1.625 3.261c-.874 2.123-1.383 4.444-.77 6.707a8.222 8.222 0 0 0 2.217 3.74c.083.083-.02.216-.119.155a7.568 7.568 0 0 1-.93-.686A7.674 7.674 0 0 1 4.1 16.248c-.329-2.156.387-4.246 1.418-6.115a27.44 27.44 0 0 1 1.92-2.947zm7.931 8.435a.193.193 0 0 0-.191.191V18.3h2.677V15.62zm3.299 0V18.3h1.348a7.975 7.975 0 0 0 .515-2.678zm-6.303 3.004a.193.193 0 0 0-.191.193v2.485h2.678v-2.678Zm3.295.484v2.68h2.115a.562.562 0 0 0 .399-.162v-.004a.562.562 0 0 0 .16-.397V19.11zm3.674.182v1.98a7.999 7.999 0 0 0 1.217-1.98zm-6.969 2.824a.193.193 0 0 0-.191.192v1.672a7.997 7.997 0 0 0 2.678-.516v-1.348zm3.48.668V24a8.008 8.008 0 0 0 1.98-1.217z"/>
|
||||
</svg>
|
||||
<text x="256" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="256" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Route · transform</text>
|
||||
|
||||
<!-- ── Airflow ── -->
|
||||
<rect x="188" y="164" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="188" y="164" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="196" y="170" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="210" y="179" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ORCH</text>
|
||||
<svg x="244" y="170" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/>
|
||||
</svg>
|
||||
<text x="256" y="206" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Airflow</text>
|
||||
<text x="256" y="218" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">DAG scheduling</text>
|
||||
|
||||
<!-- ── MinIO (FOCAL) ── -->
|
||||
<rect x="392" y="100" width="160" height="80" rx="8" fill="#f5f5f5"/>
|
||||
<rect x="392" y="100" width="160" height="80" rx="8" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="400" y="106" width="36" height="12" rx="2" fill="transparent" stroke="rgba(235,108,54,0.50)" stroke-width="0.8"/>
|
||||
<text x="418" y="115" fill="rgba(235,108,54,0.80)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="460" y="108" width="24" height="24" viewBox="0 0 24 24" fill="#eb6c36">
|
||||
<path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Zm-.9024 9.8652v2.9941l-4.1523 2.1484a13.9787 13.9787 0 0 1 2.7676-3.9277 14.1784 14.1784 0 0 1 1.3847-1.2148z"/>
|
||||
</svg>
|
||||
<text x="472" y="148" fill="#eb6c36" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO</text>
|
||||
<text x="472" y="164" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.75">Object store · S3-API</text>
|
||||
|
||||
<!-- ── Trino ── -->
|
||||
<rect x="616" y="60" width="144" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="616" y="60" width="144" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="624" y="66" width="36" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="642" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">QUERY</text>
|
||||
<svg x="676" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075c.2614-1.2682.8762-3.574 2.0292-5.1958 1.6717-2.352 3.4357-4.7808 4.6116-4.1006 1.176.6802-.3074 3.1398-1.3297 4.4272-1.0222 1.2874-2.7862 3.2089-3.3742 4.2274-.2114.3843-.4304.8032-.5956 1.1529a5.7375 5.7375 0 0 1 2.9169 3.6125h.073v-2.3058a.3075.3075 0 0 0-.1806-.2844.9148.9148 0 0 1-.5573-.8148 1.0184 1.0184 0 0 1 .9045-.9044c.5593-.0598 1.061.3452 1.1208.9044a.9187.9187 0 0 1-.5534.8148.3074.3074 0 0 0-.1691.2844v2.1522a.3113.3113 0 0 0 .1691.2805.9724.9724 0 0 1 .5648.857z"/>
|
||||
</svg>
|
||||
<text x="688" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="688" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · any format</text>
|
||||
|
||||
<!-- ── StarRocks ── -->
|
||||
<rect x="616" y="164" width="144" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="616" y="164" width="144" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="624" y="170" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="638" y="179" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">OLAP</text>
|
||||
<svg x="676" y="170" width="24" height="24" viewBox="0 0 100 100" fill="#2d3142">
|
||||
<path d="M11.8,26.4c-0.1,2.2,0.9,3.4,2.3,4.5c9,7.4,18,14.8,27,22.2c2.5,2.1,2.7,3.5,1,6.2c-4.4,6.7-8.8,13.4-13.2,20.1c-1.7,2.5-3.2,2.9-5.9,1.4c-2.8-1.6-5.6-3.2-8.4-4.8c-3.2-1.8-4.8-4.6-4.8-8.3c0-11.8,0-23.6,0-35.5C9.8,30.2,10.3,28.4,11.8,26.4z"/>
|
||||
<path d="M87.9,73.8c0.6-2.3-0.5-3.5-1.9-4.6c-9-7.4-17.9-14.7-26.8-22.1c-2.9-2.4-3.1-3.6-1.1-6.7c4.3-6.6,8.7-13.2,13-19.8c1.7-2.6,3.3-2.9,6-1.4c2.8,1.6,5.6,3.2,8.3,4.8c3.2,1.8,4.7,4.5,4.7,8.2c0,11.9,0,23.7,0,35.6C90.2,69.9,89.6,71.9,87.9,73.8z"/>
|
||||
<path d="M67.1,56.8c0.6,0.4,17.3,14.1,17.5,14.3c2.4,2.2,2.2,4.1-0.6,5.8C76.8,81,57.3,92.1,54.8,93.6c-3.2,1.9-6.4,1.9-9.6,0c-4.6-2.7-9.2-5.3-13.8-8c-2.2-1.3-2.2-2.7,0-3.9C42.3,75.5,53.1,69.2,64,63C66.5,61.6,68.2,60.1,67.1,56.8z"/>
|
||||
<path d="M32.9,43.2c-0.6-0.4-17.3-14.1-17.5-14.3c-2.4-2.2-2.2-4.1,0.6-5.8C23.2,19,42.7,7.9,45.2,6.4c3.2-1.9,6.4-1.9,9.6,0c4.6,2.7,9.2,5.3,13.8,8c2.2,1.3,2.2,2.7,0,3.9C57.7,24.5,46.9,30.8,36,37C33.5,38.4,31.8,39.9,32.9,43.2z"/>
|
||||
</svg>
|
||||
<text x="688" y="206" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">StarRocks</text>
|
||||
<text x="688" y="218" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">MPP · hot layer</text>
|
||||
|
||||
<!-- ── Superset ── -->
|
||||
<rect x="816" y="60" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="816" y="60" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="824" y="66" width="18" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="833" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">BI</text>
|
||||
<svg x="872" y="66" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/>
|
||||
</svg>
|
||||
<text x="884" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Superset</text>
|
||||
<text x="884" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Dashboards</text>
|
||||
|
||||
<!-- ── JupyterLab ── -->
|
||||
<rect x="816" y="152" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="816" y="152" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="824" y="158" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="835" y="167" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">NB</text>
|
||||
<svg x="872" y="158" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424zm.001 13.295c-3.768 0-7.06-1.381-8.768-3.425a9.363 9.363 0 0 0 3.394 4.547A9.238 9.238 0 0 0 12.33 21a9.238 9.238 0 0 0 5.377-1.729 9.363 9.363 0 0 0 3.393-4.547c-1.712 2.044-5.003 3.425-8.772 3.425z"/>
|
||||
</svg>
|
||||
<text x="884" y="194" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">JupyterLab</text>
|
||||
<text x="884" y="206" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Exploration</text>
|
||||
|
||||
<!-- ── Python ── -->
|
||||
<rect x="816" y="244" width="136" height="56" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="816" y="244" width="136" height="56" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="824" y="250" width="32" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="840" y="259" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">PROC</text>
|
||||
<svg x="872" y="250" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M14.25.18l.9.2.73.26.59.3.45.32.34.34.25.34.16.33.1.3.04.26.02.2-.01.13V8.5l-.05.63-.13.55-.21.46-.26.38-.3.31-.33.25-.35.19-.35.14-.33.1-.3.07-.26.04-.21.02H8.77l-.69.05-.59.14-.5.22-.41.27-.33.32-.27.35-.2.36-.15.37-.1.35-.07.32-.04.27-.02.21v3.06H3.17l-.21-.03-.28-.07-.32-.12-.35-.18-.36-.26-.36-.36-.35-.46-.32-.59-.28-.73-.21-.88-.14-1.05-.05-1.23.06-1.22.16-1.04.24-.87.32-.71.36-.57.4-.44.42-.33.42-.24.4-.16.36-.1.32-.05.24-.01h.16l.06.01h8.16v-.83H6.18l-.01-2.75-.02-.37.05-.34.11-.31.17-.28.25-.26.31-.23.38-.2.44-.18.51-.15.58-.12.64-.1.71-.06.77-.04.84-.02 1.27.05zm-6.3 1.98l-.23.33-.08.41.08.41.23.34.33.22.41.09.41-.09.33-.22.23-.34.08-.41-.08-.41-.23-.33-.33-.22-.41-.09-.41.09zm13.09 3.95l.28.06.32.12.35.18.36.27.36.35.35.47.32.59.28.73.21.88.14 1.04.05 1.23-.06 1.23-.16 1.04-.24.86-.32.71-.36.57-.4.45-.42.33-.42.24-.4.16-.36.09-.32.05-.24.02-.16-.01h-8.22v.82h5.84l.01 2.76.02.36-.05.34-.11.31-.17.29-.25.25-.31.24-.38.2-.44.17-.51.15-.58.13-.64.09-.71.07-.77.04-.84.01-1.27-.04-1.07-.14-.9-.2-.73-.25-.59-.3-.45-.33-.34-.34-.25-.34-.16-.33-.1-.3-.04-.25-.02-.2.01-.13v-5.34l.05-.64.13-.54.21-.46.26-.38.3-.32.33-.24.35-.2.35-.14.33-.1.3-.06.26-.04.21-.02.13-.01h5.84l.69-.05.59-.14.5-.21.41-.28.33-.32.27-.35.2-.36.15-.36.1-.35.07-.32.04-.28.02-.21V6.07h2.09l.14.01zm-6.47 14.25l-.23.33-.08.41.08.41.23.33.33.23.41.08.41-.08.33-.23.23-.33.08-.41-.08-.41-.23-.33-.33-.23-.41-.08-.41.08z"/>
|
||||
</svg>
|
||||
<text x="884" y="286" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Python</text>
|
||||
<text x="884" y="298" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Batch · ML</text>
|
||||
|
||||
<!-- ═══════════════════════════════════
|
||||
LEGEND
|
||||
═══════════════════════════════════ -->
|
||||
<line x1="40" y1="432" x2="960" y2="432" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="448" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="460" width="20" height="12" rx="3" fill="rgba(235,108,54,0.10)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="68" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">MinIO data lake (focal)</text>
|
||||
|
||||
<rect x="220" y="460" width="20" height="12" rx="3" fill="white" stroke="rgba(45,49,66,0.30)" stroke-width="1"/>
|
||||
<text x="248" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Ingest · query tools</text>
|
||||
|
||||
<rect x="400" y="460" width="20" height="12" rx="3" fill="white" stroke="rgba(45,49,66,0.30)" stroke-width="1"/>
|
||||
<text x="428" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">BI · notebooks · pipelines</text>
|
||||
|
||||
<line x1="648" y1="462" x2="668" y2="462" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="676" y="468" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Write-back path (batch)</text>
|
||||
|
||||
<line x1="820" y1="462" x2="840" y2="462" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="848" y="468" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Primary query path</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Generic data platform · Integration topology (dark)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#b85450;--custom-red-fill:rgba(184,84,80,.06);--custom-red-stroke:rgba(184,84,80,.35);--custom-blue:#5a7d9a;--custom-blue-fill:rgba(90,125,154,.06);--custom-blue-stroke:rgba(90,125,154,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:3rem 2rem}.frame{max-width:1200px;width:100%}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1100px;display:block}svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1.2}.connector.primary{stroke:var(--accent);stroke-width:1.4}.connector.trigger{stroke:var(--muted);stroke-width:1;stroke-dasharray:4 3}.connector.auth{stroke:var(--accent);stroke-width:1.2;stroke-dasharray:5 4}.label-mask{fill:var(--paper)}.edge-label{fill:var(--accent);font:8px var(--mono);letter-spacing:.06em}.zone{fill:var(--zone-fill);stroke:var(--zone-stroke)}.zone-mask,.node-mask{fill:var(--paper)}.zone-label{fill:var(--soft);font:500 8px var(--mono);letter-spacing:.18em}.side-node{fill:var(--side-fill);stroke:var(--side-stroke)}.core-node{fill:var(--accent-tint);stroke:var(--accent)}.bar{fill:var(--bar-fill);stroke:var(--zone-stroke)}.role-box{fill:transparent;stroke:var(--side-stroke);stroke-opacity:.5}.role-box.focal{stroke:var(--accent)}.role-text{fill:var(--side-stroke);font:500 7px var(--mono);letter-spacing:.08em}.role-text.focal{fill:var(--accent)}.node-name{fill:var(--ink);font:600 12px var(--sans)}.node-name.focal{fill:var(--ink)}.node-sub{fill:var(--muted);font:9px var(--mono)}.footer-red{fill:var(--custom-red-fill);stroke:var(--custom-red-stroke)}.footer-blue{fill:var(--custom-blue-fill);stroke:var(--custom-blue-stroke)}.footer-red-text{fill:var(--custom-red)}.footer-blue-text{fill:var(--custom-blue)}.legend-key{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.14em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">DP Integration · Diagram Design</p>
|
||||
<h1>Generic data platform integration topology</h1>
|
||||
<svg viewBox="0 0 1200 664" xmlns="http://www.w3.org/2000/svg" aria-label="Sources connect to a core data platform and three consumer surfaces">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="var(--dot)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--accent)"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--link)"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0,6 2.5,0 5" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-dim" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--soft)"/></marker>
|
||||
<g id="ico-db" fill="none" stroke="currentColor" stroke-width="1.2"><ellipse cx="0" cy="-5" rx="7" ry="3"/><path d="M-7-5V5c0 2 14 2 14 0V-5M-7 0c0 2 14 2 14 0"/></g>
|
||||
<g id="ico-file" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-6-8H2l4 4V8H-6ZM2-8v4h4"/></g>
|
||||
<g id="ico-stream" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8-5H2M-4 0H8M-8 5H2"/><path d="m0-8 3 3-3 3M6-3l3 3-3 3M0 2l3 3-3 3"/></g>
|
||||
<g id="ico-chart" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8 8H8M-6 6V0H-2V6M0 6V-7H4V6M6 6V-3H9"/></g>
|
||||
<g id="ico-notebook" fill="none" stroke="currentColor" stroke-width="1.2"><rect x="-7" y="-8" width="14" height="16" rx="2"/><path d="M-3-8V8M0-3h4M0 1h4"/></g>
|
||||
<g id="ico-api" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-3-8c-3 0-3 2-3 4v2c0 2-1 2-3 2 2 0 3 0 3 2v2c0 2 0 4 3 4M3-8c3 0 3 2 3 4v2c0 2 1 2 3 2-2 0-3 0-3 2v2c0 2 0 4-3 4"/></g>
|
||||
<g id="ico-key" fill="none" stroke="currentColor" stroke-width="1.2"><circle cx="-4" cy="0" r="4"/><path d="M0 0H9M6 0v3M3 0v2"/></g>
|
||||
<g id="ico-log" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8 6h16M-6 3l3-4 3 2 4-7 3 2"/></g>
|
||||
</defs>
|
||||
<rect width="1200" height="664" fill="var(--paper)"/><rect width="1200" height="664" fill="url(#dots)" opacity=".55"/>
|
||||
<rect class="zone" x="260" y="72" width="696" height="336" rx="8" stroke-width="1"/>
|
||||
<!-- Connectors are emitted before all node rectangles. -->
|
||||
<path class="connector primary" d="M200 124 H360 Q368 124 368 132 V180 Q368 188 376 188 H400" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="200" y1="212" x2="400" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M200 300 H360 Q368 300 368 292 V244 Q368 236 376 236 H400" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="560" y1="212" x2="656" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M816 188 H960 Q968 188 968 180 V132 Q968 124 976 124 H1000" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="816" y1="212" x2="1000" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M816 236 H960 Q968 236 968 244 V292 Q968 300 976 300 H1000" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector trigger" x1="480" y1="160" x2="480" y2="176" marker-end="url(#arrow)"/><line class="connector trigger" x1="736" y1="160" x2="736" y2="176" marker-end="url(#arrow)"/>
|
||||
<line class="connector auth" x1="592" y1="460" x2="592" y2="408" marker-end="url(#arrow-accent)"/>
|
||||
<!-- Dashed transit crosses the identity footer without treating it as an endpoint. -->
|
||||
<line class="connector auth" x1="624" y1="524" x2="624" y2="408" marker-end="url(#arrow-accent)"/>
|
||||
<!-- Protocol labels retain an 8px gap from their connector segment. -->
|
||||
<g text-anchor="middle"><rect class="label-mask" x="252" y="104" width="44" height="12" rx="2"/><text class="edge-label" x="274" y="113">REST</text><rect class="label-mask" x="272" y="192" width="48" height="12" rx="2"/><text class="edge-label" x="296" y="201">CSV</text><rect class="label-mask" x="248" y="280" width="56" height="12" rx="2"/><text class="edge-label" x="276" y="289">EVENTS</text><rect class="label-mask" x="584" y="192" width="48" height="12" rx="2"/><text class="edge-label" x="608" y="201">READ</text><rect class="label-mask" x="856" y="168" width="48" height="12" rx="2"/><text class="edge-label" x="880" y="177">JDBC</text><rect class="label-mask" x="872" y="192" width="56" height="12" rx="2"/><text class="edge-label" x="900" y="201">KERNEL</text><rect class="label-mask" x="856" y="216" width="56" height="12" rx="2"/><text class="edge-label" x="884" y="225">HTTPS</text></g>
|
||||
<g><rect class="label-mask" x="548" y="420" width="32" height="12" rx="2"/><text class="edge-label" x="564" y="429" text-anchor="middle">AUTH</text><rect class="label-mask" x="636" y="420" width="32" height="12" rx="2"/><text class="edge-label" x="652" y="429" text-anchor="middle">AUTH</text></g>
|
||||
<!-- Zone label -->
|
||||
<rect class="zone-mask" x="548" y="68" width="120" height="16" rx="2"/><text class="zone-label" x="608" y="79" text-anchor="middle">DATA PLATFORM</text>
|
||||
<!-- Sources -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="40" y="92" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="92" width="160" height="64" rx="6"/><use href="#ico-db" transform="translate(64 124)" style="color:var(--muted)"/><text class="node-name" x="120" y="120">CRM</text><text class="node-sub" x="120" y="140">customer records</text></g>
|
||||
<g><rect class="node-mask" x="40" y="180" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="180" width="160" height="64" rx="6"/><use href="#ico-file" transform="translate(64 212)" style="color:var(--muted)"/><text class="node-name" x="120" y="208">POS exports</text><text class="node-sub" x="120" y="228">daily CSV batches</text></g>
|
||||
<g><rect class="node-mask" x="40" y="268" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="268" width="160" height="64" rx="6"/><use href="#ico-stream" transform="translate(64 300)" style="color:var(--muted)"/><text class="node-name" x="120" y="296">Event stream</text><text class="node-sub" x="120" y="316">near-real-time events</text></g>
|
||||
</g>
|
||||
<!-- Core: fixed 160px nodes keep clean connector corridors; centers remain evenly spaced. -->
|
||||
<g text-anchor="middle"><rect class="node-mask" x="276" y="116" width="664" height="44" rx="6"/><rect class="bar" x="276" y="116" width="664" height="44" rx="6"/><rect class="role-box" x="288" y="128" width="32" height="12" rx="2"/><text class="role-text" x="304" y="137">DAG</text><text class="node-name" x="608" y="136">Orchestrator</text><text class="node-sub" x="608" y="151">schedules · retries · lineage</text>
|
||||
<rect class="node-mask" x="400" y="176" width="160" height="72" rx="6"/><rect class="core-node" x="400" y="176" width="160" height="72" rx="6"/><rect class="role-box focal" x="408" y="184" width="44" height="12" rx="2"/><text class="role-text focal" x="430" y="193">STORE</text><text class="node-name focal" x="480" y="214">Object storage</text><text class="node-sub" x="480" y="232">versioned data objects</text>
|
||||
<rect class="node-mask" x="656" y="176" width="160" height="72" rx="6"/><rect class="core-node" x="656" y="176" width="160" height="72" rx="6"/><rect class="role-box focal" x="664" y="184" width="32" height="12" rx="2"/><text class="role-text focal" x="680" y="193">SQL</text><text class="node-name focal" x="736" y="214">Query engine</text><text class="node-sub" x="736" y="232">federated SQL access</text></g>
|
||||
<!-- Consumers -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="1000" y="92" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="92" width="160" height="64" rx="6"/><use href="#ico-chart" transform="translate(1024 124)" style="color:var(--muted)"/><text class="node-name" x="1080" y="120">BI tool</text><text class="node-sub" x="1080" y="140">dashboards · reports</text></g>
|
||||
<g><rect class="node-mask" x="1000" y="180" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="180" width="160" height="64" rx="6"/><use href="#ico-notebook" transform="translate(1024 212)" style="color:var(--muted)"/><text class="node-name" x="1080" y="208">Notebooks</text><text class="node-sub" x="1080" y="228">Python · exploration</text></g>
|
||||
<g><rect class="node-mask" x="1000" y="268" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="268" width="160" height="64" rx="6"/><use href="#ico-api" transform="translate(1024 300)" style="color:var(--muted)"/><text class="node-name" x="1080" y="296">Partner API</text><text class="node-sub" x="1080" y="316">scoped data products</text></g>
|
||||
</g>
|
||||
<!-- Layer-wide footer services -->
|
||||
<g><rect class="node-mask" x="40" y="460" width="1120" height="56" rx="6"/><rect class="footer-red" x="40" y="460" width="1120" height="56" rx="6"/><use href="#ico-key" transform="translate(72 488)" style="color:var(--custom-red)"/><text class="node-name footer-red-text" x="96" y="486">Identity provider</text><text class="node-sub" x="96" y="502">SSO · service identities · policy groups</text>
|
||||
<rect class="node-mask" x="40" y="524" width="1120" height="56" rx="6"/><rect class="footer-blue" x="40" y="524" width="1120" height="56" rx="6"/><use href="#ico-log" transform="translate(72 552)" style="color:var(--custom-blue)"/><text class="node-name footer-blue-text" x="96" y="550">Centralized logging</text><text class="node-sub" x="96" y="566">platform events · audit trail · retention</text></g>
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="608" x2="1160" y2="608" stroke="var(--rule)" stroke-width=".8"/><text class="legend-key" x="40" y="628">TYPE KEY</text><rect x="128" y="620" width="16" height="12" rx="2" fill="var(--side-fill)" stroke="var(--side-stroke)"/><text class="legend-text" x="152" y="630">Source / consumer</text><rect x="296" y="620" width="16" height="12" rx="2" fill="var(--accent-tint)" stroke="var(--accent)"/><text class="legend-text" x="320" y="630">Focal platform surface</text><rect x="492" y="620" width="16" height="12" rx="2" fill="var(--bar-fill)" stroke="var(--zone-stroke)"/><text class="legend-text" x="516" y="630">Orchestration</text><line x1="640" y1="626" x2="672" y2="626" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#arrow-accent)"/><text class="legend-text" x="684" y="630">Primary data path</text><line x1="824" y1="626" x2="856" y2="626" stroke="var(--accent)" stroke-dasharray="5 4" marker-end="url(#arrow-accent)"/><text class="legend-text" x="868" y="630">Layer-wide service</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Generic data platform · Integration overview</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#b85450;--custom-red-fill:rgba(184,84,80,.06);--custom-red-stroke:rgba(184,84,80,.35);--custom-blue:#5a7d9a;--custom-blue-fill:rgba(90,125,154,.06);--custom-blue-stroke:rgba(90,125,154,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;padding:3rem 2rem}.frame{max-width:1200px;width:100%;margin:0 auto}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1100px;display:block}svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1.2}.connector.primary{stroke:var(--accent);stroke-width:1.4}.connector.trigger{stroke:var(--muted);stroke-width:1;stroke-dasharray:4 3}.connector.auth{stroke:var(--accent);stroke-width:1.2;stroke-dasharray:5 4}.label-mask{fill:var(--paper)}.edge-label{fill:var(--accent);font:8px var(--mono);letter-spacing:.06em}.zone{fill:var(--zone-fill);stroke:var(--zone-stroke)}.zone-mask,.node-mask{fill:var(--paper)}.zone-label{fill:var(--soft);font:500 8px var(--mono);letter-spacing:.18em}.side-node{fill:var(--side-fill);stroke:var(--side-stroke)}.core-node{fill:var(--accent-tint);stroke:var(--accent)}.bar{fill:var(--bar-fill);stroke:var(--zone-stroke)}.role-box{fill:transparent;stroke:var(--side-stroke);stroke-opacity:.5}.role-box.focal{stroke:var(--accent)}.role-text{fill:var(--side-stroke);font:500 7px var(--mono);letter-spacing:.08em}.role-text.focal{fill:var(--accent)}.node-name{fill:var(--ink);font:600 12px var(--sans)}.node-name.focal{fill:var(--ink)}.node-sub{fill:var(--muted);font:9px var(--mono)}.footer-red{fill:var(--custom-red-fill);stroke:var(--custom-red-stroke)}.footer-blue{fill:var(--custom-blue-fill);stroke:var(--custom-blue-stroke)}.footer-red-text{fill:var(--custom-red)}.footer-blue-text{fill:var(--custom-blue)}.legend-key{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.14em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
.subtitle{max-width:62ch;color:var(--muted);font-size:1rem;line-height:1.55;margin:-.75rem 0 2rem}.diagram-container{background:var(--paper-2);border:1px solid var(--rule);border-radius:8px;padding:1.5rem;overflow-x:auto}.cards{display:grid;grid-template-columns:1.1fr 1fr .9fr;gap:1rem;margin-top:1.5rem}.card{background:#fff;border:1px solid var(--rule);border-radius:6px;padding:1.25rem}.card .card-label{font:500 .5rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}.card-header{display:flex;align-items:center;gap:.6rem;margin-bottom:.875rem;padding-bottom:.875rem;border-bottom:1px solid rgba(45,49,66,.08)}.card-dot{width:7px;height:7px;border-radius:50%;background:var(--muted)}.card-dot.coral{background:var(--accent)}.card-dot.ink{background:var(--ink)}.card h3{font-size:.875rem;font-weight:600}.card p,.card li{color:var(--muted);font-size:.8125rem;line-height:1.55}.card ul{list-style:none}.card li{padding-left:.875rem;position:relative}.card li::before{content:'—';position:absolute;left:0;color:rgba(45,49,66,.25)}.footer{display:flex;justify-content:space-between;gap:.5rem;flex-wrap:wrap;margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--rule);color:var(--soft);font:.72rem var(--mono);letter-spacing:.06em}@media(max-width:820px){.cards{grid-template-columns:1fr}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">DP Integration · Diagram Design</p>
|
||||
<h1>Generic data platform integration topology</h1>
|
||||
<p class="subtitle">Three source patterns land in shared object storage, a query surface serves three consumer modes, and platform-wide services remain visibly cross-cutting.</p>
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1200 664" xmlns="http://www.w3.org/2000/svg" aria-label="Sources connect to a core data platform and three consumer surfaces">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="var(--dot)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--accent)"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--link)"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0,6 2.5,0 5" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-dim" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--soft)"/></marker>
|
||||
<g id="ico-db" fill="none" stroke="currentColor" stroke-width="1.2"><ellipse cx="0" cy="-5" rx="7" ry="3"/><path d="M-7-5V5c0 2 14 2 14 0V-5M-7 0c0 2 14 2 14 0"/></g>
|
||||
<g id="ico-file" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-6-8H2l4 4V8H-6ZM2-8v4h4"/></g>
|
||||
<g id="ico-stream" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8-5H2M-4 0H8M-8 5H2"/><path d="m0-8 3 3-3 3M6-3l3 3-3 3M0 2l3 3-3 3"/></g>
|
||||
<g id="ico-chart" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8 8H8M-6 6V0H-2V6M0 6V-7H4V6M6 6V-3H9"/></g>
|
||||
<g id="ico-notebook" fill="none" stroke="currentColor" stroke-width="1.2"><rect x="-7" y="-8" width="14" height="16" rx="2"/><path d="M-3-8V8M0-3h4M0 1h4"/></g>
|
||||
<g id="ico-api" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-3-8c-3 0-3 2-3 4v2c0 2-1 2-3 2 2 0 3 0 3 2v2c0 2 0 4 3 4M3-8c3 0 3 2 3 4v2c0 2 1 2 3 2-2 0-3 0-3 2v2c0 2 0 4-3 4"/></g>
|
||||
<g id="ico-key" fill="none" stroke="currentColor" stroke-width="1.2"><circle cx="-4" cy="0" r="4"/><path d="M0 0H9M6 0v3M3 0v2"/></g>
|
||||
<g id="ico-log" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8 6h16M-6 3l3-4 3 2 4-7 3 2"/></g>
|
||||
</defs>
|
||||
<rect width="1200" height="664" fill="var(--paper)"/><rect width="1200" height="664" fill="url(#dots)" opacity=".55"/>
|
||||
<rect class="zone" x="260" y="72" width="696" height="336" rx="8" stroke-width="1"/>
|
||||
<!-- Connectors are emitted before all node rectangles. -->
|
||||
<path class="connector primary" d="M200 124 H360 Q368 124 368 132 V180 Q368 188 376 188 H400" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="200" y1="212" x2="400" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M200 300 H360 Q368 300 368 292 V244 Q368 236 376 236 H400" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="560" y1="212" x2="656" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M816 188 H960 Q968 188 968 180 V132 Q968 124 976 124 H1000" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="816" y1="212" x2="1000" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M816 236 H960 Q968 236 968 244 V292 Q968 300 976 300 H1000" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector trigger" x1="480" y1="160" x2="480" y2="176" marker-end="url(#arrow)"/><line class="connector trigger" x1="736" y1="160" x2="736" y2="176" marker-end="url(#arrow)"/>
|
||||
<line class="connector auth" x1="592" y1="460" x2="592" y2="408" marker-end="url(#arrow-accent)"/>
|
||||
<!-- Dashed transit crosses the identity footer without treating it as an endpoint. -->
|
||||
<line class="connector auth" x1="624" y1="524" x2="624" y2="408" marker-end="url(#arrow-accent)"/>
|
||||
<!-- Protocol labels retain an 8px gap from their connector segment. -->
|
||||
<g text-anchor="middle"><rect class="label-mask" x="252" y="104" width="44" height="12" rx="2"/><text class="edge-label" x="274" y="113">REST</text><rect class="label-mask" x="272" y="192" width="48" height="12" rx="2"/><text class="edge-label" x="296" y="201">CSV</text><rect class="label-mask" x="248" y="280" width="56" height="12" rx="2"/><text class="edge-label" x="276" y="289">EVENTS</text><rect class="label-mask" x="584" y="192" width="48" height="12" rx="2"/><text class="edge-label" x="608" y="201">READ</text><rect class="label-mask" x="856" y="168" width="48" height="12" rx="2"/><text class="edge-label" x="880" y="177">JDBC</text><rect class="label-mask" x="872" y="192" width="56" height="12" rx="2"/><text class="edge-label" x="900" y="201">KERNEL</text><rect class="label-mask" x="856" y="216" width="56" height="12" rx="2"/><text class="edge-label" x="884" y="225">HTTPS</text></g>
|
||||
<g><rect class="label-mask" x="548" y="420" width="32" height="12" rx="2"/><text class="edge-label" x="564" y="429" text-anchor="middle">AUTH</text><rect class="label-mask" x="636" y="420" width="32" height="12" rx="2"/><text class="edge-label" x="652" y="429" text-anchor="middle">AUTH</text></g>
|
||||
<!-- Zone label -->
|
||||
<rect class="zone-mask" x="548" y="68" width="120" height="16" rx="2"/><text class="zone-label" x="608" y="79" text-anchor="middle">DATA PLATFORM</text>
|
||||
<!-- Sources -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="40" y="92" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="92" width="160" height="64" rx="6"/><use href="#ico-db" transform="translate(64 124)" style="color:var(--muted)"/><text class="node-name" x="120" y="120">CRM</text><text class="node-sub" x="120" y="140">customer records</text></g>
|
||||
<g><rect class="node-mask" x="40" y="180" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="180" width="160" height="64" rx="6"/><use href="#ico-file" transform="translate(64 212)" style="color:var(--muted)"/><text class="node-name" x="120" y="208">POS exports</text><text class="node-sub" x="120" y="228">daily CSV batches</text></g>
|
||||
<g><rect class="node-mask" x="40" y="268" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="268" width="160" height="64" rx="6"/><use href="#ico-stream" transform="translate(64 300)" style="color:var(--muted)"/><text class="node-name" x="120" y="296">Event stream</text><text class="node-sub" x="120" y="316">near-real-time events</text></g>
|
||||
</g>
|
||||
<!-- Core: fixed 160px nodes keep clean connector corridors; centers remain evenly spaced. -->
|
||||
<g text-anchor="middle"><rect class="node-mask" x="276" y="116" width="664" height="44" rx="6"/><rect class="bar" x="276" y="116" width="664" height="44" rx="6"/><rect class="role-box" x="288" y="128" width="32" height="12" rx="2"/><text class="role-text" x="304" y="137">DAG</text><text class="node-name" x="608" y="136">Orchestrator</text><text class="node-sub" x="608" y="151">schedules · retries · lineage</text>
|
||||
<rect class="node-mask" x="400" y="176" width="160" height="72" rx="6"/><rect class="core-node" x="400" y="176" width="160" height="72" rx="6"/><rect class="role-box focal" x="408" y="184" width="44" height="12" rx="2"/><text class="role-text focal" x="430" y="193">STORE</text><text class="node-name focal" x="480" y="214">Object storage</text><text class="node-sub" x="480" y="232">versioned data objects</text>
|
||||
<rect class="node-mask" x="656" y="176" width="160" height="72" rx="6"/><rect class="core-node" x="656" y="176" width="160" height="72" rx="6"/><rect class="role-box focal" x="664" y="184" width="32" height="12" rx="2"/><text class="role-text focal" x="680" y="193">SQL</text><text class="node-name focal" x="736" y="214">Query engine</text><text class="node-sub" x="736" y="232">federated SQL access</text></g>
|
||||
<!-- Consumers -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="1000" y="92" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="92" width="160" height="64" rx="6"/><use href="#ico-chart" transform="translate(1024 124)" style="color:var(--muted)"/><text class="node-name" x="1080" y="120">BI tool</text><text class="node-sub" x="1080" y="140">dashboards · reports</text></g>
|
||||
<g><rect class="node-mask" x="1000" y="180" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="180" width="160" height="64" rx="6"/><use href="#ico-notebook" transform="translate(1024 212)" style="color:var(--muted)"/><text class="node-name" x="1080" y="208">Notebooks</text><text class="node-sub" x="1080" y="228">Python · exploration</text></g>
|
||||
<g><rect class="node-mask" x="1000" y="268" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="268" width="160" height="64" rx="6"/><use href="#ico-api" transform="translate(1024 300)" style="color:var(--muted)"/><text class="node-name" x="1080" y="296">Partner API</text><text class="node-sub" x="1080" y="316">scoped data products</text></g>
|
||||
</g>
|
||||
<!-- Layer-wide footer services -->
|
||||
<g><rect class="node-mask" x="40" y="460" width="1120" height="56" rx="6"/><rect class="footer-red" x="40" y="460" width="1120" height="56" rx="6"/><use href="#ico-key" transform="translate(72 488)" style="color:var(--custom-red)"/><text class="node-name footer-red-text" x="96" y="486">Identity provider</text><text class="node-sub" x="96" y="502">SSO · service identities · policy groups</text>
|
||||
<rect class="node-mask" x="40" y="524" width="1120" height="56" rx="6"/><rect class="footer-blue" x="40" y="524" width="1120" height="56" rx="6"/><use href="#ico-log" transform="translate(72 552)" style="color:var(--custom-blue)"/><text class="node-name footer-blue-text" x="96" y="550">Centralized logging</text><text class="node-sub" x="96" y="566">platform events · audit trail · retention</text></g>
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="608" x2="1160" y2="608" stroke="var(--rule)" stroke-width=".8"/><text class="legend-key" x="40" y="628">TYPE KEY</text><rect x="128" y="620" width="16" height="12" rx="2" fill="var(--side-fill)" stroke="var(--side-stroke)"/><text class="legend-text" x="152" y="630">Source / consumer</text><rect x="296" y="620" width="16" height="12" rx="2" fill="var(--accent-tint)" stroke="var(--accent)"/><text class="legend-text" x="320" y="630">Focal platform surface</text><rect x="492" y="620" width="16" height="12" rx="2" fill="var(--bar-fill)" stroke="var(--zone-stroke)"/><text class="legend-text" x="516" y="630">Orchestration</text><line x1="640" y1="626" x2="672" y2="626" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#arrow-accent)"/><text class="legend-text" x="684" y="630">Primary data path</text><line x1="824" y1="626" x2="856" y2="626" stroke="var(--accent)" stroke-dasharray="5 4" marker-end="url(#arrow-accent)"/><text class="legend-text" x="868" y="630">Layer-wide service</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<article class="card"><p class="card-label">THE HEADLINE</p><div class="card-header"><span class="card-dot coral"></span><h3>Storage and query are the platform core</h3></div><p>The two focal surfaces separate durable ingestion from governed access without hiding the wire protocols.</p></article>
|
||||
<article class="card"><div class="card-header"><span class="card-dot ink"></span><h3>Every integration stays explicit</h3></div><ul><li>CRM arrives over REST</li><li>Point-of-sale data arrives as CSV</li><li>Consumers use JDBC, kernels, or HTTPS</li></ul></article>
|
||||
<article class="card"><div class="card-header"><span class="card-dot"></span><h3>Services apply to the whole layer</h3></div><p>Identity and centralized logging terminate at the platform boundary, not at an individual component.</p></article>
|
||||
</div>
|
||||
<footer class="footer"><span>data platform · integration topology</span><span>example · diagram design</span></footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Generic data platform · Integration topology</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--link:#2e5aa8;--rule:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--side-fill:rgba(79,93,117,.06);--side-stroke:#7a8399;--zone-fill:rgba(45,49,66,.025);--zone-stroke:rgba(45,49,66,.32);--bar-fill:rgba(45,49,66,.05);--node-fill:#fff;--custom-red:#b85450;--custom-red-fill:rgba(184,84,80,.06);--custom-red-stroke:rgba(184,84,80,.35);--custom-blue:#5a7d9a;--custom-blue-fill:rgba(90,125,154,.06);--custom-blue-stroke:rgba(90,125,154,.35);--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:3rem 2rem}.frame{max-width:1200px;width:100%}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1100px;display:block}svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1.2}.connector.primary{stroke:var(--accent);stroke-width:1.4}.connector.trigger{stroke:var(--muted);stroke-width:1;stroke-dasharray:4 3}.connector.auth{stroke:var(--accent);stroke-width:1.2;stroke-dasharray:5 4}.label-mask{fill:var(--paper)}.edge-label{fill:var(--accent);font:8px var(--mono);letter-spacing:.06em}.zone{fill:var(--zone-fill);stroke:var(--zone-stroke)}.zone-mask,.node-mask{fill:var(--paper)}.zone-label{fill:var(--soft);font:500 8px var(--mono);letter-spacing:.18em}.side-node{fill:var(--side-fill);stroke:var(--side-stroke)}.core-node{fill:var(--accent-tint);stroke:var(--accent)}.bar{fill:var(--bar-fill);stroke:var(--zone-stroke)}.role-box{fill:transparent;stroke:var(--side-stroke);stroke-opacity:.5}.role-box.focal{stroke:var(--accent)}.role-text{fill:var(--side-stroke);font:500 7px var(--mono);letter-spacing:.08em}.role-text.focal{fill:var(--accent)}.node-name{fill:var(--ink);font:600 12px var(--sans)}.node-name.focal{fill:var(--ink)}.node-sub{fill:var(--muted);font:9px var(--mono)}.footer-red{fill:var(--custom-red-fill);stroke:var(--custom-red-stroke)}.footer-blue{fill:var(--custom-blue-fill);stroke:var(--custom-blue-stroke)}.footer-red-text{fill:var(--custom-red)}.footer-blue-text{fill:var(--custom-blue)}.legend-key{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.14em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">DP Integration · Diagram Design</p>
|
||||
<h1>Generic data platform integration topology</h1>
|
||||
<svg viewBox="0 0 1200 664" xmlns="http://www.w3.org/2000/svg" aria-label="Sources connect to a core data platform and three consumer surfaces">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="var(--dot)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--accent)"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--link)"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0,6 2.5,0 5" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-dim" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--soft)"/></marker>
|
||||
<g id="ico-db" fill="none" stroke="currentColor" stroke-width="1.2"><ellipse cx="0" cy="-5" rx="7" ry="3"/><path d="M-7-5V5c0 2 14 2 14 0V-5M-7 0c0 2 14 2 14 0"/></g>
|
||||
<g id="ico-file" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-6-8H2l4 4V8H-6ZM2-8v4h4"/></g>
|
||||
<g id="ico-stream" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8-5H2M-4 0H8M-8 5H2"/><path d="m0-8 3 3-3 3M6-3l3 3-3 3M0 2l3 3-3 3"/></g>
|
||||
<g id="ico-chart" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8 8H8M-6 6V0H-2V6M0 6V-7H4V6M6 6V-3H9"/></g>
|
||||
<g id="ico-notebook" fill="none" stroke="currentColor" stroke-width="1.2"><rect x="-7" y="-8" width="14" height="16" rx="2"/><path d="M-3-8V8M0-3h4M0 1h4"/></g>
|
||||
<g id="ico-api" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-3-8c-3 0-3 2-3 4v2c0 2-1 2-3 2 2 0 3 0 3 2v2c0 2 0 4 3 4M3-8c3 0 3 2 3 4v2c0 2 1 2 3 2-2 0-3 0-3 2v2c0 2 0 4-3 4"/></g>
|
||||
<g id="ico-key" fill="none" stroke="currentColor" stroke-width="1.2"><circle cx="-4" cy="0" r="4"/><path d="M0 0H9M6 0v3M3 0v2"/></g>
|
||||
<g id="ico-log" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M-8 6h16M-6 3l3-4 3 2 4-7 3 2"/></g>
|
||||
</defs>
|
||||
<rect width="1200" height="664" fill="var(--paper)"/><rect width="1200" height="664" fill="url(#dots)" opacity=".55"/>
|
||||
<rect class="zone" x="260" y="72" width="696" height="336" rx="8" stroke-width="1"/>
|
||||
<!-- Connectors are emitted before all node rectangles. -->
|
||||
<path class="connector primary" d="M200 124 H360 Q368 124 368 132 V180 Q368 188 376 188 H400" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="200" y1="212" x2="400" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M200 300 H360 Q368 300 368 292 V244 Q368 236 376 236 H400" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="560" y1="212" x2="656" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M816 188 H960 Q968 188 968 180 V132 Q968 124 976 124 H1000" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector primary" x1="816" y1="212" x2="1000" y2="212" marker-end="url(#arrow-accent)"/>
|
||||
<path class="connector primary" d="M816 236 H960 Q968 236 968 244 V292 Q968 300 976 300 H1000" marker-end="url(#arrow-accent)"/>
|
||||
<line class="connector trigger" x1="480" y1="160" x2="480" y2="176" marker-end="url(#arrow)"/><line class="connector trigger" x1="736" y1="160" x2="736" y2="176" marker-end="url(#arrow)"/>
|
||||
<line class="connector auth" x1="592" y1="460" x2="592" y2="408" marker-end="url(#arrow-accent)"/>
|
||||
<!-- Dashed transit crosses the identity footer without treating it as an endpoint. -->
|
||||
<line class="connector auth" x1="624" y1="524" x2="624" y2="408" marker-end="url(#arrow-accent)"/>
|
||||
<!-- Protocol labels retain an 8px gap from their connector segment. -->
|
||||
<g text-anchor="middle"><rect class="label-mask" x="252" y="104" width="44" height="12" rx="2"/><text class="edge-label" x="274" y="113">REST</text><rect class="label-mask" x="272" y="192" width="48" height="12" rx="2"/><text class="edge-label" x="296" y="201">CSV</text><rect class="label-mask" x="248" y="280" width="56" height="12" rx="2"/><text class="edge-label" x="276" y="289">EVENTS</text><rect class="label-mask" x="584" y="192" width="48" height="12" rx="2"/><text class="edge-label" x="608" y="201">READ</text><rect class="label-mask" x="856" y="168" width="48" height="12" rx="2"/><text class="edge-label" x="880" y="177">JDBC</text><rect class="label-mask" x="872" y="192" width="56" height="12" rx="2"/><text class="edge-label" x="900" y="201">KERNEL</text><rect class="label-mask" x="856" y="216" width="56" height="12" rx="2"/><text class="edge-label" x="884" y="225">HTTPS</text></g>
|
||||
<g><rect class="label-mask" x="548" y="420" width="32" height="12" rx="2"/><text class="edge-label" x="564" y="429" text-anchor="middle">AUTH</text><rect class="label-mask" x="636" y="420" width="32" height="12" rx="2"/><text class="edge-label" x="652" y="429" text-anchor="middle">AUTH</text></g>
|
||||
<!-- Zone label -->
|
||||
<rect class="zone-mask" x="548" y="68" width="120" height="16" rx="2"/><text class="zone-label" x="608" y="79" text-anchor="middle">DATA PLATFORM</text>
|
||||
<!-- Sources -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="40" y="92" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="92" width="160" height="64" rx="6"/><use href="#ico-db" transform="translate(64 124)" style="color:var(--muted)"/><text class="node-name" x="120" y="120">CRM</text><text class="node-sub" x="120" y="140">customer records</text></g>
|
||||
<g><rect class="node-mask" x="40" y="180" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="180" width="160" height="64" rx="6"/><use href="#ico-file" transform="translate(64 212)" style="color:var(--muted)"/><text class="node-name" x="120" y="208">POS exports</text><text class="node-sub" x="120" y="228">daily CSV batches</text></g>
|
||||
<g><rect class="node-mask" x="40" y="268" width="160" height="64" rx="6"/><rect class="side-node" x="40" y="268" width="160" height="64" rx="6"/><use href="#ico-stream" transform="translate(64 300)" style="color:var(--muted)"/><text class="node-name" x="120" y="296">Event stream</text><text class="node-sub" x="120" y="316">near-real-time events</text></g>
|
||||
</g>
|
||||
<!-- Core: fixed 160px nodes keep clean connector corridors; centers remain evenly spaced. -->
|
||||
<g text-anchor="middle"><rect class="node-mask" x="276" y="116" width="664" height="44" rx="6"/><rect class="bar" x="276" y="116" width="664" height="44" rx="6"/><rect class="role-box" x="288" y="128" width="32" height="12" rx="2"/><text class="role-text" x="304" y="137">DAG</text><text class="node-name" x="608" y="136">Orchestrator</text><text class="node-sub" x="608" y="151">schedules · retries · lineage</text>
|
||||
<rect class="node-mask" x="400" y="176" width="160" height="72" rx="6"/><rect class="core-node" x="400" y="176" width="160" height="72" rx="6"/><rect class="role-box focal" x="408" y="184" width="44" height="12" rx="2"/><text class="role-text focal" x="430" y="193">STORE</text><text class="node-name focal" x="480" y="214">Object storage</text><text class="node-sub" x="480" y="232">versioned data objects</text>
|
||||
<rect class="node-mask" x="656" y="176" width="160" height="72" rx="6"/><rect class="core-node" x="656" y="176" width="160" height="72" rx="6"/><rect class="role-box focal" x="664" y="184" width="32" height="12" rx="2"/><text class="role-text focal" x="680" y="193">SQL</text><text class="node-name focal" x="736" y="214">Query engine</text><text class="node-sub" x="736" y="232">federated SQL access</text></g>
|
||||
<!-- Consumers -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="1000" y="92" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="92" width="160" height="64" rx="6"/><use href="#ico-chart" transform="translate(1024 124)" style="color:var(--muted)"/><text class="node-name" x="1080" y="120">BI tool</text><text class="node-sub" x="1080" y="140">dashboards · reports</text></g>
|
||||
<g><rect class="node-mask" x="1000" y="180" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="180" width="160" height="64" rx="6"/><use href="#ico-notebook" transform="translate(1024 212)" style="color:var(--muted)"/><text class="node-name" x="1080" y="208">Notebooks</text><text class="node-sub" x="1080" y="228">Python · exploration</text></g>
|
||||
<g><rect class="node-mask" x="1000" y="268" width="160" height="64" rx="6"/><rect class="side-node" x="1000" y="268" width="160" height="64" rx="6"/><use href="#ico-api" transform="translate(1024 300)" style="color:var(--muted)"/><text class="node-name" x="1080" y="296">Partner API</text><text class="node-sub" x="1080" y="316">scoped data products</text></g>
|
||||
</g>
|
||||
<!-- Layer-wide footer services -->
|
||||
<g><rect class="node-mask" x="40" y="460" width="1120" height="56" rx="6"/><rect class="footer-red" x="40" y="460" width="1120" height="56" rx="6"/><use href="#ico-key" transform="translate(72 488)" style="color:var(--custom-red)"/><text class="node-name footer-red-text" x="96" y="486">Identity provider</text><text class="node-sub" x="96" y="502">SSO · service identities · policy groups</text>
|
||||
<rect class="node-mask" x="40" y="524" width="1120" height="56" rx="6"/><rect class="footer-blue" x="40" y="524" width="1120" height="56" rx="6"/><use href="#ico-log" transform="translate(72 552)" style="color:var(--custom-blue)"/><text class="node-name footer-blue-text" x="96" y="550">Centralized logging</text><text class="node-sub" x="96" y="566">platform events · audit trail · retention</text></g>
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="608" x2="1160" y2="608" stroke="var(--rule)" stroke-width=".8"/><text class="legend-key" x="40" y="628">TYPE KEY</text><rect x="128" y="620" width="16" height="12" rx="2" fill="var(--side-fill)" stroke="var(--side-stroke)"/><text class="legend-text" x="152" y="630">Source / consumer</text><rect x="296" y="620" width="16" height="12" rx="2" fill="var(--accent-tint)" stroke="var(--accent)"/><text class="legend-text" x="320" y="630">Focal platform surface</text><rect x="492" y="620" width="16" height="12" rx="2" fill="var(--bar-fill)" stroke="var(--zone-stroke)"/><text class="legend-text" x="516" y="630">Orchestration</text><line x1="640" y1="626" x2="672" y2="626" stroke="var(--accent)" stroke-width="1.4" marker-end="url(#arrow-accent)"/><text class="legend-text" x="684" y="630">Primary data path</text><line x1="824" y1="626" x2="856" y2="626" stroke="var(--accent)" stroke-dasharray="5 4" marker-end="url(#arrow-accent)"/><text class="legend-text" x="868" y="630">Layer-wide service</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Data Platform · Access Matrix · Dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}:root{--paper:#2d3142;--paper-2:#393e53;--ink:#f5f5f5;--muted:#bfc0c0;--soft:#8e98ac;--rule:rgba(245,245,245,.12);--accent:#f08a59;--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:3rem 2rem;background:var(--paper);color:var(--ink);font-family:var(--sans)}.frame{width:100%;max-width:1200px}
|
||||
.eyebrow{margin-bottom:.5rem;color:var(--muted);font:500 .66rem/1 var(--mono);letter-spacing:.18em;text-transform:uppercase}h1{margin-bottom:.35rem;font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em}.subtitle{margin-bottom:1.5rem;color:var(--muted);font-size:.9rem;line-height:1.5}.diagram{overflow-x:auto}svg{display:block;width:100%;min-width:1040px}
|
||||
svg .component-head,svg .component{fill:rgba(245,245,245,.06);stroke:rgba(245,245,245,.18);stroke-width:.8}svg .role{fill:#f5f5f5}
|
||||
svg .head-name,svg .component-name,svg .role-name,svg .value{font-family:'Geist',sans-serif}svg .head-name,svg .component-name,svg .role-name{font-size:11px;font-weight:600}svg .head-name,svg .component-name{fill:#f5f5f5}svg .role-name,svg .role-code{fill:#2d3142}
|
||||
svg .head-sub,svg .role-code,svg .hint,svg .legend-text,svg .focal-sub{font-family:'Geist Mono',monospace}svg .head-sub,svg .hint,svg .legend-text{fill:#8e98ac}svg .head-sub,svg .role-code,svg .hint{font-size:9px}svg .role-code{opacity:.85}
|
||||
svg .cell{stroke:rgba(245,245,245,.18);stroke-width:.6}svg .full{fill:rgba(245,245,245,.10)}svg .write{fill:rgba(245,245,245,.06)}svg .read{fill:rgba(142,152,172,.12)}svg .none{fill:rgba(245,245,245,.02)}svg .value{font-size:10px}svg .value.full-text{fill:#f5f5f5;font-weight:600}svg .value.write-text{fill:#f5f5f5}svg .value.read-text{fill:#8e98ac}svg .value.none-text{fill:#bfc0c0}
|
||||
svg .focal{fill:rgba(240,138,89,.12);stroke:#f08a59;stroke-width:1.4}svg .focal-text{fill:#f08a59;font-weight:600}svg .focal-sub{fill:#f08a59;font-size:8px;opacity:.85}svg .legend-text{font-size:9px}
|
||||
</style>
|
||||
</head>
|
||||
<body><main class="frame"><p class="eyebrow">DP security matrix · Diagram Design</p><h1>Data platform access matrix</h1><p class="subtitle">Five operating roles × five platform components, expressed as admin, write, read, or none.</p><div class="diagram">
|
||||
<svg viewBox="0 0 1084 400" role="img" aria-labelledby="matrix-dark-title matrix-dark-desc" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="matrix-dark-title">Data platform access matrix</title><desc id="matrix-dark-desc">A five by five permission matrix for platform roles and components.</desc><rect width="1084" height="400" fill="#2d3142"/>
|
||||
<rect class="component-head" x="12" y="72" width="208" height="52" rx="6"/><text class="head-name" x="116" y="96" text-anchor="middle">Component</text><text class="head-sub" x="116" y="112" text-anchor="middle">vs. AD group</text>
|
||||
<rect class="role" x="232" y="72" width="148" height="52" rx="6"/><text class="role-name" x="306" y="92" text-anchor="middle">Data Engineer</text><text class="role-code" x="306" y="108" text-anchor="middle">GRP-DATA-ENG</text><rect class="role" x="396" y="72" width="148" height="52" rx="6"/><text class="role-name" x="470" y="92" text-anchor="middle">Data Scientist</text><text class="role-code" x="470" y="108" text-anchor="middle">GRP-DATA-SCI</text><rect class="role" x="560" y="72" width="148" height="52" rx="6"/><text class="role-name" x="634" y="92" text-anchor="middle">Analyst</text><text class="role-code" x="634" y="108" text-anchor="middle">GRP-ANALYST</text><rect class="role" x="724" y="72" width="148" height="52" rx="6"/><text class="role-name" x="798" y="92" text-anchor="middle">Administrator</text><text class="role-code" x="798" y="108" text-anchor="middle">GRP-ADMIN</text><rect class="role" x="888" y="72" width="148" height="52" rx="6"/><text class="role-name" x="962" y="92" text-anchor="middle">External Partner</text><text class="role-code" x="962" y="108" text-anchor="middle">GRP-PARTNER</text>
|
||||
<rect class="component" x="12" y="140" width="208" height="36" rx="4"/><text class="component-name" x="24" y="162">Object storage</text><text class="hint" x="208" y="162" text-anchor="end">S3</text><rect class="cell write" x="232" y="140" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="162" text-anchor="middle">Write</text><rect class="cell read" x="396" y="140" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="162" text-anchor="middle">Read</text><rect class="cell none" x="560" y="140" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="162" text-anchor="middle">None</text><rect class="cell full" x="724" y="140" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="162" text-anchor="middle">Admin</text><rect class="cell none" x="888" y="140" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="162" text-anchor="middle">None</text>
|
||||
<rect class="component" x="12" y="180" width="208" height="36" rx="4"/><text class="component-name" x="24" y="202">Query engine</text><text class="hint" x="208" y="202" text-anchor="end">SQL</text><rect class="cell write" x="232" y="180" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="202" text-anchor="middle">Write</text><rect class="cell read" x="396" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="202" text-anchor="middle">Read</text><rect class="cell read" x="560" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="634" y="202" text-anchor="middle">Read</text><rect class="cell full" x="724" y="180" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="202" text-anchor="middle">Admin</text><rect class="cell read" x="888" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="962" y="202" text-anchor="middle">Read</text>
|
||||
<rect class="component" x="12" y="220" width="208" height="36" rx="4"/><text class="component-name" x="24" y="242">Notebooks</text><text class="hint" x="208" y="242" text-anchor="end">PY</text><rect class="cell write" x="232" y="220" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="242" text-anchor="middle">Write</text><rect class="cell write" x="396" y="220" width="148" height="36" rx="4"/><text class="value write-text" x="470" y="242" text-anchor="middle">Write</text><rect class="cell none" x="560" y="220" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="242" text-anchor="middle">None</text><rect class="cell full" x="724" y="220" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="242" text-anchor="middle">Admin</text><rect class="cell none" x="888" y="220" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="242" text-anchor="middle">None</text>
|
||||
<rect class="component" x="12" y="260" width="208" height="36" rx="4"/><text class="component-name" x="24" y="282">BI tool</text><text class="hint" x="208" y="282" text-anchor="end">DASH</text><rect class="cell write" x="232" y="260" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="282" text-anchor="middle">Write</text><rect class="cell read" x="396" y="260" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="282" text-anchor="middle">Read</text><rect class="cell write" x="560" y="260" width="148" height="36" rx="4"/><text class="value write-text" x="634" y="282" text-anchor="middle">Write</text><rect class="cell full" x="724" y="260" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="282" text-anchor="middle">Admin</text><rect class="focal" x="888" y="260" width="148" height="36" rx="4"/><text class="value focal-text" x="962" y="278" text-anchor="middle">Read</text><text class="focal-sub" x="962" y="290" text-anchor="middle">shared dashboards</text>
|
||||
<rect class="component" x="12" y="300" width="208" height="36" rx="4"/><text class="component-name" x="24" y="322">Orchestrator</text><text class="hint" x="208" y="322" text-anchor="end">DAG</text><rect class="cell write" x="232" y="300" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="322" text-anchor="middle">Write</text><rect class="cell read" x="396" y="300" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="322" text-anchor="middle">Read</text><rect class="cell none" x="560" y="300" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="322" text-anchor="middle">None</text><rect class="cell full" x="724" y="300" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="322" text-anchor="middle">Admin</text><rect class="cell none" x="888" y="300" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="322" text-anchor="middle">None</text>
|
||||
<line x1="12" y1="356" x2="1072" y2="356" stroke="rgba(245,245,245,.12)" stroke-width=".8"/><text class="legend-text" x="12" y="380" letter-spacing=".14em">LEGEND</text><rect class="cell full" x="96" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="120" y="380">Admin</text><rect class="cell write" x="200" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="224" y="380">Write</text><rect class="cell read" x="304" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="328" y="380">Read</text><rect class="cell none" x="408" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="432" y="380">None</text><rect class="focal" x="504" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="528" y="380">partner read boundary</text>
|
||||
</svg>
|
||||
</div></main></body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Data Platform · Access Matrix · Editorial</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}:root{--paper:#f5f5f5;--paper-2:#ececec;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--rule:rgba(45,49,66,.12);--accent:#eb6c36;--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{min-height:100vh;padding:3rem 2rem;background:var(--paper);color:var(--ink);font-family:var(--sans)}.container{max-width:1200px;margin:0 auto}.header{margin-bottom:2.5rem}.header-eyebrow,.card .eyebrow{color:var(--muted);font-family:var(--mono);font-weight:500;letter-spacing:.18em;text-transform:uppercase}.header-eyebrow{margin-bottom:.75rem;font-size:.66rem}h1{margin-bottom:.5rem;font:400 clamp(1.75rem,3vw + 1rem,2.5rem)/1.1 var(--serif);letter-spacing:-.02em}.subtitle{max-width:68ch;color:var(--muted);font-size:1rem;line-height:1.55}
|
||||
.diagram-container{overflow-x:auto;padding:1.5rem;background:var(--paper-2);border:1px solid var(--rule);border-radius:8px}svg{display:block;width:100%;min-width:1040px}
|
||||
svg .component-head,svg .component{fill:#fff;stroke:rgba(45,49,66,.12);stroke-width:.8}svg .role{fill:#2d3142}svg .head-name,svg .component-name,svg .role-name,svg .value{font-family:'Geist',sans-serif}svg .head-name,svg .component-name,svg .role-name{font-size:11px;font-weight:600}svg .head-name,svg .component-name{fill:#2d3142}svg .role-name,svg .role-code{fill:#f5f5f5}svg .head-sub,svg .role-code,svg .hint,svg .legend-text,svg .focal-sub{font-family:'Geist Mono',monospace}svg .head-sub,svg .hint,svg .legend-text{fill:#4f5d75}svg .head-sub,svg .role-code,svg .hint{font-size:9px}svg .role-code{opacity:.85}
|
||||
svg .cell{stroke:rgba(45,49,66,.12);stroke-width:.6}svg .full{fill:rgba(45,49,66,.08)}svg .write{fill:#fff}svg .read{fill:rgba(79,93,117,.08)}svg .none{fill:#f5f5f5}svg .value{font-size:10px}svg .value.full-text{fill:#2d3142;font-weight:600}svg .value.write-text{fill:#2d3142}svg .value.read-text{fill:#4f5d75}svg .value.none-text{fill:#7a8399}svg .focal{fill:rgba(235,108,54,.07);stroke:#eb6c36;stroke-width:1.4}svg .focal-text{fill:#eb6c36;font-weight:600}svg .focal-sub{fill:#eb6c36;font-size:8px;opacity:.85}svg .legend-text{font-size:9px}
|
||||
.cards{display:grid;grid-template-columns:1.1fr 1fr .9fr;gap:1rem;margin-top:1.5rem}@media(max-width:820px){.cards{grid-template-columns:1fr}}.card{padding:1.25rem;background:#fff;border:1px solid var(--rule);border-radius:6px}.card .eyebrow{margin-bottom:.5rem;font-size:.5rem}.card-header{display:flex;align-items:center;gap:.6rem;margin-bottom:.875rem;padding-bottom:.875rem;border-bottom:1px solid rgba(45,49,66,.08)}.card-dot{width:7px;height:7px;flex:0 0 auto;border-radius:50%}.card-dot.coral{background:var(--accent)}.card-dot.ink{background:var(--ink)}.card-dot.muted{background:var(--muted)}.card h2{font-size:.875rem;font-weight:600}.card p,.card ul{color:var(--muted);font-size:.8125rem;line-height:1.55;list-style:none}.card li{position:relative;margin-bottom:.3rem;padding-left:.875rem}.card li::before{content:'—';position:absolute;left:0;color:rgba(45,49,66,.25)}.footer{display:flex;justify-content:space-between;flex-wrap:wrap;gap:.5rem;margin-top:2rem;padding-top:1.5rem;color:var(--soft);border-top:1px solid rgba(45,49,66,.10);font:400 .72rem/1.4 var(--mono);letter-spacing:.06em}
|
||||
</style>
|
||||
</head>
|
||||
<body><main class="container"><header class="header"><p class="header-eyebrow">DP security matrix · Diagram Design</p><h1>Data platform access matrix</h1><p class="subtitle">A compact policy view for five human roles across storage, query, analysis, reporting, and orchestration. Each intersection resolves to one explicit permission level.</p></header><div class="diagram-container">
|
||||
<svg viewBox="0 0 1084 400" role="img" aria-labelledby="matrix-full-title matrix-full-desc" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="matrix-full-title">Data platform access matrix</title><desc id="matrix-full-desc">A five by five permission matrix for platform roles and components.</desc><rect width="1084" height="400" fill="#f5f5f5"/>
|
||||
<rect class="component-head" x="12" y="72" width="208" height="52" rx="6"/><text class="head-name" x="116" y="96" text-anchor="middle">Component</text><text class="head-sub" x="116" y="112" text-anchor="middle">vs. AD group</text>
|
||||
<rect class="role" x="232" y="72" width="148" height="52" rx="6"/><text class="role-name" x="306" y="92" text-anchor="middle">Data Engineer</text><text class="role-code" x="306" y="108" text-anchor="middle">GRP-DATA-ENG</text><rect class="role" x="396" y="72" width="148" height="52" rx="6"/><text class="role-name" x="470" y="92" text-anchor="middle">Data Scientist</text><text class="role-code" x="470" y="108" text-anchor="middle">GRP-DATA-SCI</text><rect class="role" x="560" y="72" width="148" height="52" rx="6"/><text class="role-name" x="634" y="92" text-anchor="middle">Analyst</text><text class="role-code" x="634" y="108" text-anchor="middle">GRP-ANALYST</text><rect class="role" x="724" y="72" width="148" height="52" rx="6"/><text class="role-name" x="798" y="92" text-anchor="middle">Administrator</text><text class="role-code" x="798" y="108" text-anchor="middle">GRP-ADMIN</text><rect class="role" x="888" y="72" width="148" height="52" rx="6"/><text class="role-name" x="962" y="92" text-anchor="middle">External Partner</text><text class="role-code" x="962" y="108" text-anchor="middle">GRP-PARTNER</text>
|
||||
<rect class="component" x="12" y="140" width="208" height="36" rx="4"/><text class="component-name" x="24" y="162">Object storage</text><text class="hint" x="208" y="162" text-anchor="end">S3</text><rect class="cell write" x="232" y="140" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="162" text-anchor="middle">Write</text><rect class="cell read" x="396" y="140" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="162" text-anchor="middle">Read</text><rect class="cell none" x="560" y="140" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="162" text-anchor="middle">None</text><rect class="cell full" x="724" y="140" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="162" text-anchor="middle">Admin</text><rect class="cell none" x="888" y="140" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="162" text-anchor="middle">None</text>
|
||||
<rect class="component" x="12" y="180" width="208" height="36" rx="4"/><text class="component-name" x="24" y="202">Query engine</text><text class="hint" x="208" y="202" text-anchor="end">SQL</text><rect class="cell write" x="232" y="180" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="202" text-anchor="middle">Write</text><rect class="cell read" x="396" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="202" text-anchor="middle">Read</text><rect class="cell read" x="560" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="634" y="202" text-anchor="middle">Read</text><rect class="cell full" x="724" y="180" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="202" text-anchor="middle">Admin</text><rect class="cell read" x="888" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="962" y="202" text-anchor="middle">Read</text>
|
||||
<rect class="component" x="12" y="220" width="208" height="36" rx="4"/><text class="component-name" x="24" y="242">Notebooks</text><text class="hint" x="208" y="242" text-anchor="end">PY</text><rect class="cell write" x="232" y="220" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="242" text-anchor="middle">Write</text><rect class="cell write" x="396" y="220" width="148" height="36" rx="4"/><text class="value write-text" x="470" y="242" text-anchor="middle">Write</text><rect class="cell none" x="560" y="220" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="242" text-anchor="middle">None</text><rect class="cell full" x="724" y="220" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="242" text-anchor="middle">Admin</text><rect class="cell none" x="888" y="220" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="242" text-anchor="middle">None</text>
|
||||
<rect class="component" x="12" y="260" width="208" height="36" rx="4"/><text class="component-name" x="24" y="282">BI tool</text><text class="hint" x="208" y="282" text-anchor="end">DASH</text><rect class="cell write" x="232" y="260" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="282" text-anchor="middle">Write</text><rect class="cell read" x="396" y="260" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="282" text-anchor="middle">Read</text><rect class="cell write" x="560" y="260" width="148" height="36" rx="4"/><text class="value write-text" x="634" y="282" text-anchor="middle">Write</text><rect class="cell full" x="724" y="260" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="282" text-anchor="middle">Admin</text><rect class="focal" x="888" y="260" width="148" height="36" rx="4"/><text class="value focal-text" x="962" y="278" text-anchor="middle">Read</text><text class="focal-sub" x="962" y="290" text-anchor="middle">shared dashboards</text>
|
||||
<rect class="component" x="12" y="300" width="208" height="36" rx="4"/><text class="component-name" x="24" y="322">Orchestrator</text><text class="hint" x="208" y="322" text-anchor="end">DAG</text><rect class="cell write" x="232" y="300" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="322" text-anchor="middle">Write</text><rect class="cell read" x="396" y="300" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="322" text-anchor="middle">Read</text><rect class="cell none" x="560" y="300" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="322" text-anchor="middle">None</text><rect class="cell full" x="724" y="300" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="322" text-anchor="middle">Admin</text><rect class="cell none" x="888" y="300" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="322" text-anchor="middle">None</text>
|
||||
<line x1="12" y1="356" x2="1072" y2="356" stroke="rgba(45,49,66,.12)" stroke-width=".8"/><text class="legend-text" x="12" y="380" letter-spacing=".14em">LEGEND</text><rect class="cell full" x="96" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="120" y="380">Admin</text><rect class="cell write" x="200" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="224" y="380">Write</text><rect class="cell read" x="304" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="328" y="380">Read</text><rect class="cell none" x="408" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="432" y="380">None</text><rect class="focal" x="504" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="528" y="380">partner read boundary</text>
|
||||
</svg>
|
||||
</div>
|
||||
<section class="cards" aria-label="Access policy summary"><article class="card"><p class="eyebrow">THE HEADLINE</p><div class="card-header"><span class="card-dot coral"></span><h2>Partner access stops at BI</h2></div><p>External partners receive read-only shared dashboards. They cannot enter storage, notebooks, or orchestration.</p></article><article class="card"><div class="card-header"><span class="card-dot ink"></span><h2>Administration stays separate</h2></div><ul><li>Administrators hold admin rights everywhere.</li><li>Engineers write across the platform.</li><li>Scientists write only in notebooks.</li></ul></article><article class="card"><div class="card-header"><span class="card-dot muted"></span><h2>Analyst scope is narrow</h2></div><p>Analysts query governed data and author BI content, while object storage, notebooks, and workflow controls remain unavailable.</p></article></section>
|
||||
<footer class="footer"><span>data platform · access policy</span><span>example · diagram design</span></footer>
|
||||
</main></body>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Data Platform · Access Matrix</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--rule:rgba(45,49,66,.12);--accent:#eb6c36;--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:3rem 2rem;background:var(--paper);color:var(--ink);font-family:var(--sans)}
|
||||
.frame{width:100%;max-width:1200px}.eyebrow{margin-bottom:.5rem;color:var(--muted);font:500 .66rem/1 var(--mono);letter-spacing:.18em;text-transform:uppercase}
|
||||
h1{margin-bottom:.35rem;font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em}.subtitle{margin-bottom:1.5rem;color:var(--muted);font-size:.9rem;line-height:1.5}
|
||||
.diagram{overflow-x:auto}svg{display:block;width:100%;min-width:1040px}
|
||||
svg .component-head,svg .component{fill:#fff;stroke:rgba(45,49,66,.12);stroke-width:.8}svg .role{fill:#2d3142}
|
||||
svg .head-name,svg .component-name,svg .role-name,svg .value{font-family:'Geist',sans-serif}svg .head-name,svg .component-name,svg .role-name{font-size:11px;font-weight:600}
|
||||
svg .head-name,svg .component-name{fill:#2d3142}svg .role-name,svg .role-code{fill:#f5f5f5}svg .head-sub,svg .role-code,svg .hint,svg .legend-text,svg .focal-sub{font-family:'Geist Mono',monospace}
|
||||
svg .head-sub,svg .hint,svg .legend-text{fill:#4f5d75}svg .head-sub,svg .role-code,svg .hint{font-size:9px}svg .role-code{opacity:.85}
|
||||
svg .cell{stroke:rgba(45,49,66,.12);stroke-width:.6}svg .full{fill:rgba(45,49,66,.08)}svg .write{fill:#fff}svg .read{fill:rgba(79,93,117,.08)}svg .none{fill:#f5f5f5}
|
||||
svg .value{font-size:10px}svg .value.full-text{fill:#2d3142;font-weight:600}svg .value.write-text{fill:#2d3142}svg .value.read-text{fill:#4f5d75}svg .value.none-text{fill:#7a8399}
|
||||
svg .focal{fill:rgba(235,108,54,.07);stroke:#eb6c36;stroke-width:1.4}svg .focal-text{fill:#eb6c36;font-weight:600}svg .focal-sub{fill:#eb6c36;font-size:8px;opacity:.85}
|
||||
svg .legend-text{font-size:9px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">DP security matrix · Diagram Design</p><h1>Data platform access matrix</h1><p class="subtitle">Five operating roles × five platform components, expressed as admin, write, read, or none.</p>
|
||||
<div class="diagram">
|
||||
<svg viewBox="0 0 1084 400" role="img" aria-labelledby="matrix-title matrix-desc" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="matrix-title">Data platform access matrix</title><desc id="matrix-desc">A five by five permission matrix for platform roles and components.</desc>
|
||||
<rect width="1084" height="400" fill="#f5f5f5"/>
|
||||
|
||||
<!-- Header: y=72, h=52; role x positions derive from the 148px column formula -->
|
||||
<rect class="component-head" x="12" y="72" width="208" height="52" rx="6"/><text class="head-name" x="116" y="96" text-anchor="middle">Component</text><text class="head-sub" x="116" y="112" text-anchor="middle">vs. AD group</text>
|
||||
<rect class="role" x="232" y="72" width="148" height="52" rx="6"/><text class="role-name" x="306" y="92" text-anchor="middle">Data Engineer</text><text class="role-code" x="306" y="108" text-anchor="middle">GRP-DATA-ENG</text>
|
||||
<rect class="role" x="396" y="72" width="148" height="52" rx="6"/><text class="role-name" x="470" y="92" text-anchor="middle">Data Scientist</text><text class="role-code" x="470" y="108" text-anchor="middle">GRP-DATA-SCI</text>
|
||||
<rect class="role" x="560" y="72" width="148" height="52" rx="6"/><text class="role-name" x="634" y="92" text-anchor="middle">Analyst</text><text class="role-code" x="634" y="108" text-anchor="middle">GRP-ANALYST</text>
|
||||
<rect class="role" x="724" y="72" width="148" height="52" rx="6"/><text class="role-name" x="798" y="92" text-anchor="middle">Administrator</text><text class="role-code" x="798" y="108" text-anchor="middle">GRP-ADMIN</text>
|
||||
<rect class="role" x="888" y="72" width="148" height="52" rx="6"/><text class="role-name" x="962" y="92" text-anchor="middle">External Partner</text><text class="role-code" x="962" y="108" text-anchor="middle">GRP-PARTNER</text>
|
||||
|
||||
<!-- Object storage, y=140 -->
|
||||
<rect class="component" x="12" y="140" width="208" height="36" rx="4"/><text class="component-name" x="24" y="162">Object storage</text><text class="hint" x="208" y="162" text-anchor="end">S3</text>
|
||||
<rect class="cell write" x="232" y="140" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="162" text-anchor="middle">Write</text>
|
||||
<rect class="cell read" x="396" y="140" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="162" text-anchor="middle">Read</text>
|
||||
<rect class="cell none" x="560" y="140" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="162" text-anchor="middle">None</text>
|
||||
<rect class="cell full" x="724" y="140" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="162" text-anchor="middle">Admin</text>
|
||||
<rect class="cell none" x="888" y="140" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="162" text-anchor="middle">None</text>
|
||||
|
||||
<!-- Query engine, y=180 -->
|
||||
<rect class="component" x="12" y="180" width="208" height="36" rx="4"/><text class="component-name" x="24" y="202">Query engine</text><text class="hint" x="208" y="202" text-anchor="end">SQL</text>
|
||||
<rect class="cell write" x="232" y="180" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="202" text-anchor="middle">Write</text>
|
||||
<rect class="cell read" x="396" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="202" text-anchor="middle">Read</text>
|
||||
<rect class="cell read" x="560" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="634" y="202" text-anchor="middle">Read</text>
|
||||
<rect class="cell full" x="724" y="180" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="202" text-anchor="middle">Admin</text>
|
||||
<rect class="cell read" x="888" y="180" width="148" height="36" rx="4"/><text class="value read-text" x="962" y="202" text-anchor="middle">Read</text>
|
||||
|
||||
<!-- Notebooks, y=220 -->
|
||||
<rect class="component" x="12" y="220" width="208" height="36" rx="4"/><text class="component-name" x="24" y="242">Notebooks</text><text class="hint" x="208" y="242" text-anchor="end">PY</text>
|
||||
<rect class="cell write" x="232" y="220" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="242" text-anchor="middle">Write</text>
|
||||
<rect class="cell write" x="396" y="220" width="148" height="36" rx="4"/><text class="value write-text" x="470" y="242" text-anchor="middle">Write</text>
|
||||
<rect class="cell none" x="560" y="220" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="242" text-anchor="middle">None</text>
|
||||
<rect class="cell full" x="724" y="220" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="242" text-anchor="middle">Admin</text>
|
||||
<rect class="cell none" x="888" y="220" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="242" text-anchor="middle">None</text>
|
||||
|
||||
<!-- BI tool, y=260; one focal access boundary -->
|
||||
<rect class="component" x="12" y="260" width="208" height="36" rx="4"/><text class="component-name" x="24" y="282">BI tool</text><text class="hint" x="208" y="282" text-anchor="end">DASH</text>
|
||||
<rect class="cell write" x="232" y="260" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="282" text-anchor="middle">Write</text>
|
||||
<rect class="cell read" x="396" y="260" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="282" text-anchor="middle">Read</text>
|
||||
<rect class="cell write" x="560" y="260" width="148" height="36" rx="4"/><text class="value write-text" x="634" y="282" text-anchor="middle">Write</text>
|
||||
<rect class="cell full" x="724" y="260" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="282" text-anchor="middle">Admin</text>
|
||||
<rect class="focal" x="888" y="260" width="148" height="36" rx="4"/><text class="value focal-text" x="962" y="278" text-anchor="middle">Read</text><text class="focal-sub" x="962" y="290" text-anchor="middle">shared dashboards</text>
|
||||
|
||||
<!-- Orchestrator, y=300 -->
|
||||
<rect class="component" x="12" y="300" width="208" height="36" rx="4"/><text class="component-name" x="24" y="322">Orchestrator</text><text class="hint" x="208" y="322" text-anchor="end">DAG</text>
|
||||
<rect class="cell write" x="232" y="300" width="148" height="36" rx="4"/><text class="value write-text" x="306" y="322" text-anchor="middle">Write</text>
|
||||
<rect class="cell read" x="396" y="300" width="148" height="36" rx="4"/><text class="value read-text" x="470" y="322" text-anchor="middle">Read</text>
|
||||
<rect class="cell none" x="560" y="300" width="148" height="36" rx="4"/><text class="value none-text" x="634" y="322" text-anchor="middle">None</text>
|
||||
<rect class="cell full" x="724" y="300" width="148" height="36" rx="4"/><text class="value full-text" x="798" y="322" text-anchor="middle">Admin</text>
|
||||
<rect class="cell none" x="888" y="300" width="148" height="36" rx="4"/><text class="value none-text" x="962" y="322" text-anchor="middle">None</text>
|
||||
|
||||
<!-- Legend: rows_bottom=336, legend_y_top=356 -->
|
||||
<line x1="12" y1="356" x2="1072" y2="356" stroke="rgba(45,49,66,.12)" stroke-width=".8"/><text class="legend-text" x="12" y="380" letter-spacing=".14em">LEGEND</text>
|
||||
<rect class="cell full" x="96" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="120" y="380">Admin</text><rect class="cell write" x="200" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="224" y="380">Write</text><rect class="cell read" x="304" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="328" y="380">Read</text><rect class="cell none" x="408" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="432" y="380">None</text><rect class="focal" x="504" y="368" width="16" height="12" rx="2"/><text class="legend-text" x="528" y="380">partner read boundary</text>
|
||||
</svg>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -73,7 +73,7 @@
|
||||
<!-- 1. Author — Article (1:N "writes") -->
|
||||
<line x1="260" y1="240" x2="400" y2="240" stroke="#bfc0c0" stroke-width="1" />
|
||||
<!-- 2. Article — ArticleTag (1:N) -->
|
||||
<line x1="640" y1="320" x2="780" y2="328" stroke="#bfc0c0" stroke-width="1" />
|
||||
<path d="M 640,320 H 702 Q 710,320 710,328 H 780" fill="none" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<!-- 3. Tag — ArticleTag (1:N, vertical) -->
|
||||
<line x1="880" y1="248" x2="880" y2="280" stroke="#bfc0c0" stroke-width="1" />
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<!-- 1. Author — Article (1:N "writes") -->
|
||||
<line x1="260" y1="240" x2="400" y2="240" stroke="#4f5d75" stroke-width="1" />
|
||||
<!-- 2. Article — ArticleTag (1:N) -->
|
||||
<line x1="640" y1="320" x2="780" y2="328" stroke="#4f5d75" stroke-width="1" />
|
||||
<path d="M 640,320 H 702 Q 710,320 710,328 H 780" fill="none" stroke="#4f5d75" stroke-width="1"/>
|
||||
<!-- 3. Tag — ArticleTag (1:N, vertical) -->
|
||||
<line x1="880" y1="248" x2="880" y2="280" stroke="#4f5d75" stroke-width="1" />
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<!-- 1. Author — Article (1:N "writes") -->
|
||||
<line x1="260" y1="240" x2="400" y2="240" stroke="#4f5d75" stroke-width="1" />
|
||||
<!-- 2. Article — ArticleTag (1:N) -->
|
||||
<line x1="640" y1="320" x2="780" y2="328" stroke="#4f5d75" stroke-width="1" />
|
||||
<path d="M 640,320 H 702 Q 710,320 710,328 H 780" fill="none" stroke="#4f5d75" stroke-width="1"/>
|
||||
<!-- 3. Tag — ArticleTag (1:N, vertical) -->
|
||||
<line x1="880" y1="248" x2="880" y2="280" stroke="#4f5d75" stroke-width="1" />
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Q2 product launch · Gantt · dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-ink:#2d3142; --color-muted:#4f5d75; --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Gantt · Diagram Design</p>
|
||||
<h1>Q2 product launch · 12-week plan</h1>
|
||||
|
||||
<!-- viewBox 0 0 1000 500. Timeline x=200-956 = 756px / 12 weeks = 63px per week -->
|
||||
<!-- Rows: label col x=20-196, timeline x=200-956 -->
|
||||
<!-- Row height=40, bar h=24, bar top pad=8 -->
|
||||
<!-- Week x = 200 + i*63, i=0..12 -->
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Phase zones (drawn before everything) -->
|
||||
<!-- Phase 1 DISCOVERY: rows y=84,124 → zone y=72, h=84 -->
|
||||
<rect x="12" y="72" width="976" height="84" rx="6" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<!-- Phase 2 DESIGN: rows y=184,224,264 → zone y=172, h=120 -->
|
||||
<rect x="12" y="172" width="976" height="120" rx="6" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<!-- Phase 3 LAUNCH: rows y=324,364 → zone y=312, h=88 -->
|
||||
<rect x="12" y="312" width="976" height="88" rx="6" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Month headers at y=40-56 -->
|
||||
<text x="326" y="52" fill="#f5f5f5" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">April</text>
|
||||
<text x="578" y="52" fill="#f5f5f5" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">May</text>
|
||||
<text x="830" y="52" fill="#f5f5f5" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">June</text>
|
||||
|
||||
<!-- Month separator lines -->
|
||||
<line x1="452" y1="36" x2="452" y2="416" stroke="rgba(245,245,245,0.08)" stroke-width="0.8" stroke-dasharray="3,3"/>
|
||||
<line x1="704" y1="36" x2="704" y2="416" stroke="rgba(245,245,245,0.08)" stroke-width="0.8" stroke-dasharray="3,3"/>
|
||||
|
||||
<!-- Week gridlines (vertical, hairline) -->
|
||||
<line x1="263" y1="60" x2="263" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
<line x1="326" y1="60" x2="326" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
<line x1="389" y1="60" x2="389" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
<line x1="515" y1="60" x2="515" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
<line x1="578" y1="60" x2="578" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
<line x1="641" y1="60" x2="641" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
<line x1="767" y1="60" x2="767" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
<line x1="830" y1="60" x2="830" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
<line x1="893" y1="60" x2="893" y2="416" stroke="rgba(245,245,245,0.05)" stroke-width="0.6"/>
|
||||
|
||||
<!-- Timeline header line -->
|
||||
<line x1="200" y1="60" x2="956" y2="60" stroke="rgba(245,245,245,0.15)" stroke-width="0.8"/>
|
||||
<!-- Left divider -->
|
||||
<line x1="200" y1="36" x2="200" y2="416" stroke="rgba(245,245,245,0.15)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Phase labels (eyebrow style) -->
|
||||
<text x="20" y="85" fill="rgba(245,245,245,0.35)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">DISCOVERY</text>
|
||||
<text x="20" y="185" fill="rgba(245,245,245,0.35)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">DESIGN</text>
|
||||
<text x="20" y="325" fill="rgba(245,245,245,0.35)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">LAUNCH</text>
|
||||
|
||||
<!-- ─── TASK BARS ─── -->
|
||||
<!-- W_x = 200 + week_index * 63 -->
|
||||
<!-- Row 1 y=84: Market research W1-W3 (i=0..2, x=200, dur=2*63=126) -->
|
||||
<rect x="20" y="92" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="108" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Market research</text>
|
||||
<rect x="200" y="92" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="200" y="92" width="126" height="24" rx="4" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<!-- Row 2 y=124: User interviews W2-W4 (i=1..3, x=263, dur=2*63=126) -->
|
||||
<rect x="20" y="132" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="148" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif">User interviews</text>
|
||||
<rect x="263" y="132" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="263" y="132" width="126" height="24" rx="4" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<!-- Row 3 y=184: Wireframes W4-W6 (i=3..5, x=389, dur=2*63=126) -->
|
||||
<rect x="20" y="192" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="208" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Wireframes</text>
|
||||
<rect x="389" y="192" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="389" y="192" width="126" height="24" rx="4" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<!-- Row 4 y=224: Prototype W5-W8 (i=4..7, x=452, dur=3*63=189) -->
|
||||
<rect x="20" y="232" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="248" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Prototype</text>
|
||||
<rect x="452" y="232" width="189" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="452" y="232" width="189" height="24" rx="4" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<!-- Row 5 y=264 FOCAL: Design review W8-W9 (i=7..8, x=641, dur=63) -->
|
||||
<rect x="20" y="272" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="288" fill="#f08a59" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Design review</text>
|
||||
<rect x="641" y="272" width="63" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="641" y="272" width="63" height="24" rx="4" fill="rgba(240,138,89,0.14)" stroke="#f08a59" stroke-width="1"/>
|
||||
<text x="672" y="289" fill="#f08a59" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">GATE</text>
|
||||
|
||||
<!-- Row 6 y=324: Development W7-W11 (i=6..10, x=578, dur=4*63=252) -->
|
||||
<rect x="20" y="332" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="348" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Development</text>
|
||||
<rect x="578" y="332" width="252" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="578" y="332" width="252" height="24" rx="4" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<!-- Row 7 y=364: Beta testing W10-W12 (i=9..11, x=767, dur=2*63=126) -->
|
||||
<rect x="20" y="372" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="388" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Beta testing</text>
|
||||
<rect x="767" y="372" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="767" y="372" width="126" height="24" rx="4" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="430" x2="960" y2="430" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="446" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="456" width="20" height="12" rx="3" fill="rgba(240,138,89,0.14)" stroke="#f08a59" stroke-width="1"/>
|
||||
<text x="68" y="466" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Design review · critical gate</text>
|
||||
|
||||
<rect x="280" y="456" width="20" height="12" rx="3" fill="rgba(191,192,192,0.15)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="308" y="466" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Task</text>
|
||||
|
||||
<rect x="368" y="456" width="20" height="12" rx="3" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<text x="396" y="466" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Phase</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Q2 product launch · Gantt</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
|
||||
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 58ch; }
|
||||
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
@media (max-width: 720px) { .cards { grid-template-columns: 1fr; } }
|
||||
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
|
||||
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--color-accent); } .card-dot.ink { background: var(--color-ink); } .card-dot.muted { background: var(--color-muted); }
|
||||
.card h3 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; }
|
||||
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-soft); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<p class="header-eyebrow">Gantt · Diagram Design</p>
|
||||
<h1>Q2 product launch · 12-week plan</h1>
|
||||
<p class="subtitle">Three phases from research through launch. Design review in week 8 is the critical gate — development doesn't continue until sign-off.</p>
|
||||
</div>
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs><pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/></pattern></defs>
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
<rect x="12" y="72" width="976" height="84" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="12" y="172" width="976" height="120" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="12" y="312" width="976" height="88" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="326" y="52" fill="#2d3142" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">April</text>
|
||||
<text x="578" y="52" fill="#2d3142" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">May</text>
|
||||
<text x="830" y="52" fill="#2d3142" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">June</text>
|
||||
<line x1="452" y1="36" x2="452" y2="416" stroke="rgba(45,49,66,0.08)" stroke-width="0.8" stroke-dasharray="3,3"/>
|
||||
<line x1="704" y1="36" x2="704" y2="416" stroke="rgba(45,49,66,0.08)" stroke-width="0.8" stroke-dasharray="3,3"/>
|
||||
<line x1="263" y1="60" x2="263" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="326" y1="60" x2="326" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="389" y1="60" x2="389" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="515" y1="60" x2="515" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="578" y1="60" x2="578" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="641" y1="60" x2="641" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="767" y1="60" x2="767" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="830" y1="60" x2="830" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="893" y1="60" x2="893" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="200" y1="60" x2="956" y2="60" stroke="rgba(45,49,66,0.20)" stroke-width="0.8"/>
|
||||
<line x1="200" y1="36" x2="200" y2="416" stroke="rgba(45,49,66,0.20)" stroke-width="0.8"/>
|
||||
<text x="20" y="85" fill="rgba(45,49,66,0.40)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">DISCOVERY</text>
|
||||
<text x="20" y="185" fill="rgba(45,49,66,0.40)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">DESIGN</text>
|
||||
<text x="20" y="325" fill="rgba(45,49,66,0.40)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">LAUNCH</text>
|
||||
<rect x="20" y="92" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="108" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Market research</text>
|
||||
<rect x="200" y="92" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="200" y="92" width="126" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<rect x="20" y="132" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="148" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">User interviews</text>
|
||||
<rect x="263" y="132" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="263" y="132" width="126" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<rect x="20" y="192" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="208" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Wireframes</text>
|
||||
<rect x="389" y="192" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="389" y="192" width="126" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<rect x="20" y="232" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="248" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Prototype</text>
|
||||
<rect x="452" y="232" width="189" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="452" y="232" width="189" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<rect x="20" y="272" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="288" fill="#eb6c36" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Design review</text>
|
||||
<rect x="641" y="272" width="63" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="641" y="272" width="63" height="24" rx="4" fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/>
|
||||
<text x="672" y="289" fill="#eb6c36" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">GATE</text>
|
||||
<rect x="20" y="332" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="348" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Development</text>
|
||||
<rect x="578" y="332" width="252" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="578" y="332" width="252" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<rect x="20" y="372" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="388" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Beta testing</text>
|
||||
<rect x="767" y="372" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="767" y="372" width="126" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<line x1="40" y1="430" x2="960" y2="430" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="446" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
<rect x="40" y="456" width="20" height="12" rx="3" fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/>
|
||||
<text x="68" y="466" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Design review · critical gate</text>
|
||||
<rect x="280" y="456" width="20" height="12" rx="3" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="308" y="466" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Task</text>
|
||||
<rect x="368" y="456" width="20" height="12" rx="3" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="396" y="466" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Phase</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="eyebrow">CRITICAL GATE</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h3>Design review — week 8</h3></div>
|
||||
<p>Development begins in week 7 but the full build is contingent on design sign-off. Starting before the gate means dev can fix implementation issues early, not rebuild from scratch post-review.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot ink"></span><h3>Discovery ↔ Design overlap</h3></div>
|
||||
<p>User interviews (W2–W4) overlap with wireframes (W4–W6) by one week. This is intentional — the first round of interview findings feeds directly into iteration without a formal handoff ceremony.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot muted"></span><h3>Float in the schedule</h3></div>
|
||||
<p>Beta testing (W10–W12) has two weeks of overlap with dev end (W11). If dev slips by one week, beta absorbs it. If dev slips two weeks, the launch date moves — there is no buffer beyond that.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span>Q2 product launch · 12-week plan</span>
|
||||
<span>example · diagram design</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Q2 product launch · Gantt</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-ink:#2d3142; --color-muted:#4f5d75; --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Gantt · Diagram Design</p>
|
||||
<h1>Q2 product launch · 12-week plan</h1>
|
||||
|
||||
<!-- viewBox 0 0 1000 500. Timeline x=200-956 = 756px / 12 weeks = 63px per week -->
|
||||
<!-- Rows: label col x=20-196, timeline x=200-956 -->
|
||||
<!-- Row height=40, bar h=24, bar top pad=8 -->
|
||||
<!-- Week x = 200 + i*63, i=0..12 -->
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Phase zones (drawn before everything) -->
|
||||
<!-- Phase 1 DISCOVERY: rows y=84,124 → zone y=72, h=84 -->
|
||||
<rect x="12" y="72" width="976" height="84" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<!-- Phase 2 DESIGN: rows y=184,224,264 → zone y=172, h=120 -->
|
||||
<rect x="12" y="172" width="976" height="120" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<!-- Phase 3 LAUNCH: rows y=324,364 → zone y=312, h=88 -->
|
||||
<rect x="12" y="312" width="976" height="88" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Month headers at y=40-56 -->
|
||||
<text x="326" y="52" fill="#2d3142" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">April</text>
|
||||
<text x="578" y="52" fill="#2d3142" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">May</text>
|
||||
<text x="830" y="52" fill="#2d3142" font-size="9" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">June</text>
|
||||
|
||||
<!-- Month separator lines -->
|
||||
<line x1="452" y1="36" x2="452" y2="416" stroke="rgba(45,49,66,0.08)" stroke-width="0.8" stroke-dasharray="3,3"/>
|
||||
<line x1="704" y1="36" x2="704" y2="416" stroke="rgba(45,49,66,0.08)" stroke-width="0.8" stroke-dasharray="3,3"/>
|
||||
|
||||
<!-- Week gridlines (vertical, hairline) -->
|
||||
<line x1="263" y1="60" x2="263" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="326" y1="60" x2="326" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="389" y1="60" x2="389" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="515" y1="60" x2="515" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="578" y1="60" x2="578" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="641" y1="60" x2="641" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="767" y1="60" x2="767" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="830" y1="60" x2="830" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
<line x1="893" y1="60" x2="893" y2="416" stroke="rgba(45,49,66,0.05)" stroke-width="0.6"/>
|
||||
|
||||
<!-- Timeline header line -->
|
||||
<line x1="200" y1="60" x2="956" y2="60" stroke="rgba(45,49,66,0.20)" stroke-width="0.8"/>
|
||||
<!-- Left divider -->
|
||||
<line x1="200" y1="36" x2="200" y2="416" stroke="rgba(45,49,66,0.20)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Phase labels (eyebrow style) -->
|
||||
<text x="20" y="85" fill="rgba(45,49,66,0.40)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">DISCOVERY</text>
|
||||
<text x="20" y="185" fill="rgba(45,49,66,0.40)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">DESIGN</text>
|
||||
<text x="20" y="325" fill="rgba(45,49,66,0.40)" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em">LAUNCH</text>
|
||||
|
||||
<!-- ─── TASK BARS ─── -->
|
||||
<!-- W_x = 200 + week_index * 63 -->
|
||||
<!-- Row 1 y=84: Market research W1-W3 (i=0..2, x=200, dur=2*63=126) -->
|
||||
<rect x="20" y="92" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="108" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Market research</text>
|
||||
<rect x="200" y="92" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="200" y="92" width="126" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<!-- Row 2 y=124: User interviews W2-W4 (i=1..3, x=263, dur=2*63=126) -->
|
||||
<rect x="20" y="132" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="148" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">User interviews</text>
|
||||
<rect x="263" y="132" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="263" y="132" width="126" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<!-- Row 3 y=184: Wireframes W4-W6 (i=3..5, x=389, dur=2*63=126) -->
|
||||
<rect x="20" y="192" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="208" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Wireframes</text>
|
||||
<rect x="389" y="192" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="389" y="192" width="126" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<!-- Row 4 y=224: Prototype W5-W8 (i=4..7, x=452, dur=3*63=189) -->
|
||||
<rect x="20" y="232" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="248" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Prototype</text>
|
||||
<rect x="452" y="232" width="189" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="452" y="232" width="189" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<!-- Row 5 y=264 FOCAL: Design review W8-W9 (i=7..8, x=641, dur=63) -->
|
||||
<rect x="20" y="272" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="288" fill="#eb6c36" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Design review</text>
|
||||
<rect x="641" y="272" width="63" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="641" y="272" width="63" height="24" rx="4" fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/>
|
||||
<text x="672" y="289" fill="#eb6c36" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">GATE</text>
|
||||
|
||||
<!-- Row 6 y=324: Development W7-W11 (i=6..10, x=578, dur=4*63=252) -->
|
||||
<rect x="20" y="332" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="348" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Development</text>
|
||||
<rect x="578" y="332" width="252" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="578" y="332" width="252" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<!-- Row 7 y=364: Beta testing W10-W12 (i=9..11, x=767, dur=2*63=126) -->
|
||||
<rect x="20" y="372" width="176" height="24" fill="#f5f5f5"/>
|
||||
<text x="20" y="388" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif">Beta testing</text>
|
||||
<rect x="767" y="372" width="126" height="24" rx="4" fill="#f5f5f5"/>
|
||||
<rect x="767" y="372" width="126" height="24" rx="4" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="430" x2="960" y2="430" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="446" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="456" width="20" height="12" rx="3" fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/>
|
||||
<text x="68" y="466" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Design review · critical gate</text>
|
||||
|
||||
<rect x="280" y="456" width="20" height="12" rx="3" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="308" y="466" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Task</text>
|
||||
|
||||
<rect x="368" y="456" width="20" height="12" rx="3" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="396" y="466" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Phase</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,249 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>High-level architecture · End-to-end stack</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #2d3142;
|
||||
--color-ink: #f5f5f5;
|
||||
--color-muted: #bfc0c0;
|
||||
--color-accent: #f08a59;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture · Diagram Design</p>
|
||||
<h1>High-level architecture · End-to-end stack</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 540" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#bfc0c0"/>
|
||||
</marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#f08a59"/>
|
||||
</marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto">
|
||||
<polygon points="0 0, 6 2.5, 0 5" fill="#bfc0c0"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#2d3142"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Phase chevron banner -->
|
||||
<polygon points="0,4 188,4 200,18 188,32 0,32" fill="#3d4460"/>
|
||||
<polygon points="200,4 388,4 400,18 388,32 200,32 212,18" fill="#4a5270"/>
|
||||
<polygon points="400,4 588,4 600,18 588,32 400,32 412,18" fill="#3d4460"/>
|
||||
<polygon points="600,4 788,4 800,18 788,32 600,32 612,18" fill="#4a5270"/>
|
||||
<polygon points="800,4 1000,4 1000,32 800,32 812,18" fill="#3d4460"/>
|
||||
<text x="100" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DATA SOURCES</text>
|
||||
<text x="300" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">INGESTION</text>
|
||||
<text x="500" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORAGE</text>
|
||||
<text x="700" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.13em" text-anchor="middle">TRANSFORMATION & ANALYSIS</text>
|
||||
<text x="900" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">VISUALIZATION</text>
|
||||
|
||||
<!-- Structural zones -->
|
||||
<rect x="4" y="40" width="152" height="336" rx="6" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.22)" stroke-width="0.8" stroke-dasharray="6,3"/>
|
||||
<text x="80" y="52" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SOURCES</text>
|
||||
<rect x="164" y="40" width="832" height="336" rx="8" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.18)" stroke-width="1.2"/>
|
||||
<rect x="4" y="388" width="992" height="40" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.18)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Arrows -->
|
||||
<path d="M 144,92 H 156 Q 164,92 164,100 V 112 Q 164,120 176,120" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,172 H 156 Q 164,172 164,164 V 144 Q 164,136 176,136" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,252 H 156 Q 164,252 164,244 V 160 Q 164,152 176,152" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,336 H 156 Q 164,336 164,328 V 176 Q 164,168 176,168" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<line x1="452" y1="96" x2="452" y2="104" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="682" y1="96" x2="682" y2="144" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="896" y1="96" x2="896" y2="144" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<path d="M 328,152 H 344 Q 348,152 348,148 V 144 Q 348,140 352,140 H 372" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 328,152 H 344 Q 352,152 352,160 V 240 Q 352,248 372,248" fill="none" stroke="#f08a59" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="448" y1="176" x2="448" y2="208" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<line x1="456" y1="208" x2="456" y2="176" stroke="rgba(245,245,245,0.25)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<path d="M 532,140 H 556 Q 564,140 564,148 V 176 Q 564,184 600,184" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 532,248 H 556 Q 564,248 564,240 V 192 Q 564,184 600,184" fill="none" stroke="#f08a59" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="764" y1="184" x2="820" y2="184" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Nodes -->
|
||||
<!-- WEB -->
|
||||
<rect x="12" y="60" width="132" height="64" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="20" y="66" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="31" y="75" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="9"/>
|
||||
<path d="M3.6 9h16.8M3.6 15h16.8"/>
|
||||
<path d="M11.5 3a17 17 0 0 0 0 18M12.5 3a17 17 0 0 1 0 18"/>
|
||||
</svg>
|
||||
<text x="78" y="102" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">WEB</text>
|
||||
<text x="78" y="114" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Census · Survey · Admin</text>
|
||||
|
||||
<!-- FTP -->
|
||||
<rect x="12" y="140" width="132" height="64" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="20" y="146" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="31" y="155" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="146" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/>
|
||||
<path d="M7 9l5 -5l5 5"/><path d="M12 4l0 12"/>
|
||||
</svg>
|
||||
<text x="78" y="182" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">FTP</text>
|
||||
<text x="78" y="194" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Census · Business</text>
|
||||
|
||||
<!-- DB Connection -->
|
||||
<rect x="12" y="220" width="132" height="64" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="20" y="226" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="31" y="235" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="226" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
|
||||
</svg>
|
||||
<text x="78" y="262" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">DB Connection</text>
|
||||
<text x="78" y="274" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · SQL · API</text>
|
||||
|
||||
<!-- Legacy System -->
|
||||
<rect x="12" y="304" width="132" height="64" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="20" y="310" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="31" y="319" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="310" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/>
|
||||
<path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/>
|
||||
<path d="M7 8l0 .01"/><path d="M7 16l0 .01"/>
|
||||
</svg>
|
||||
<text x="78" y="346" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Legacy System</text>
|
||||
<text x="78" y="358" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Trade data</text>
|
||||
|
||||
<!-- Airflow bar -->
|
||||
<rect x="176" y="52" width="808" height="44" rx="4" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.18)" stroke-width="0.8"/>
|
||||
<svg x="950" y="58" width="24" height="24" viewBox="0 0 24 24" fill="#bfc0c0">
|
||||
<path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/>
|
||||
</svg>
|
||||
<text x="564" y="71" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Orchestration / Workflow Automation</text>
|
||||
<text x="564" y="84" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Apache Airflow)</text>
|
||||
|
||||
<!-- NiFi -->
|
||||
<rect x="176" y="112" width="152" height="80" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="184" y="118" width="30" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="199" y="127" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">COLL</text>
|
||||
<svg x="240" y="118" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0zm-1.632 3.867c.05 0 .08.034.037.112-.11.197-.218.397-.328.593-.396.702-.819 1.389-1.23 2.08-.196-.032-.39-.06-.585-.088.495-.651 1-1.296 1.48-1.959.153-.209.302-.423.454-.634a.24.24 0 0 1 .172-.104zM7.44 7.186c.221.035.444.076.666.119-.073.129-.15.256-.223.383a29.073 29.073 0 0 0-1.625 3.261c-.874 2.123-1.383 4.444-.77 6.707a8.222 8.222 0 0 0 2.217 3.74c.083.083-.02.216-.119.155a7.568 7.568 0 0 1-.93-.686A7.674 7.674 0 0 1 4.1 16.248c-.329-2.156.387-4.246 1.418-6.115a27.44 27.44 0 0 1 1.92-2.947zm7.931 8.435a.193.193 0 0 0-.191.191V18.3h2.677V15.62zm3.299 0V18.3h1.348a7.975 7.975 0 0 0 .515-2.678zm-6.303 3.004a.193.193 0 0 0-.191.193v2.485h2.678v-2.678Zm3.295.484v2.68h2.115a.562.562 0 0 0 .399-.162v-.004a.562.562 0 0 0 .16-.397V19.11zm3.674.182v1.98a7.999 7.999 0 0 0 1.217-1.98zm-6.969 2.824a.193.193 0 0 0-.191.192v1.672a7.997 7.997 0 0 0 2.678-.516v-1.348zm3.48.668V24a8.008 8.008 0 0 0 1.98-1.217z"/>
|
||||
</svg>
|
||||
<text x="252" y="154" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="252" y="166" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Collect · route · transform</text>
|
||||
|
||||
<!-- Trino -->
|
||||
<rect x="372" y="104" width="160" height="72" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="380" y="110" width="28" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="394" y="119" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">VIRT</text>
|
||||
<svg x="440" y="110" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075c.2614-1.2682.8762-3.574 2.0292-5.1958 1.6717-2.352 3.4357-4.7808 4.6116-4.1006 1.176.6802-.3074 3.1398-1.3297 4.4272-1.0222 1.2874-2.7862 3.2089-3.3742 4.2274-.2114.3843-.4304.8032-.5956 1.1529a5.7375 5.7375 0 0 1 2.9169 3.6125h.073v-2.3058a.3075.3075 0 0 0-.1806-.2844.9148.9148 0 0 1-.5573-.8148 1.0184 1.0184 0 0 1 .9045-.9044c.5593-.0598 1.061.3452 1.1208.9044a.9187.9187 0 0 1-.5534.8148.3074.3074 0 0 0-.1691.2844v2.1522a.3113.3113 0 0 0 .1691.2805.9724.9724 0 0 1 .5648.857z"/>
|
||||
</svg>
|
||||
<text x="452" y="146" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="452" y="158" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · data virtualization</text>
|
||||
|
||||
<!-- MinIO (FOCAL) -->
|
||||
<rect x="372" y="208" width="160" height="80" rx="8" fill="rgba(240,138,89,0.12)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<rect x="380" y="214" width="36" height="12" rx="2" fill="transparent" stroke="rgba(240,138,89,0.55)" stroke-width="0.8"/>
|
||||
<text x="398" y="223" fill="rgba(240,138,89,0.85)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="440" y="214" width="24" height="24" viewBox="0 0 24 24" fill="#f08a59">
|
||||
<path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Zm-.9024 9.8652v2.9941l-4.1523 2.1484a13.9787 13.9787 0 0 1 2.7676-3.9277 14.1784 14.1784 0 0 1 1.3847-1.2148z"/>
|
||||
</svg>
|
||||
<text x="452" y="252" fill="#f08a59" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO / S3</text>
|
||||
<text x="452" y="268" fill="#f08a59" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.75">Object store · S3-API</text>
|
||||
|
||||
<!-- Data Modeling -->
|
||||
<rect x="600" y="144" width="164" height="80" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="608" y="150" width="32" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="624" y="159" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ANLZ</text>
|
||||
<svg x="670" y="150" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424zm.001 13.295c-3.768 0-7.06-1.381-8.768-3.425a9.363 9.363 0 0 0 3.394 4.547A9.238 9.238 0 0 0 12.33 21a9.238 9.238 0 0 0 5.377-1.729 9.363 9.363 0 0 0 3.393-4.547c-1.712 2.044-5.003 3.425-8.772 3.425z"/>
|
||||
</svg>
|
||||
<text x="682" y="186" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Data Modeling</text>
|
||||
<text x="682" y="198" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Notebook · Python · R</text>
|
||||
|
||||
<!-- Dashboards -->
|
||||
<rect x="820" y="144" width="152" height="80" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="828" y="150" width="28" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.25)" stroke-width="0.8"/>
|
||||
<text x="842" y="159" fill="rgba(245,245,245,0.45)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">DASH</text>
|
||||
<svg x="884" y="150" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/>
|
||||
</svg>
|
||||
<text x="896" y="186" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Dashboards</text>
|
||||
<text x="896" y="198" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Superset · reports</text>
|
||||
|
||||
<!-- Kubernetes label -->
|
||||
<svg x="180" y="348" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#bfc0c0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19.875 6.27a2.225 2.225 0 0 1 1.125 1.948v7.284c0 .809-.443 1.555-1.158 1.948l-6.75 3.98a2.5 2.5 0 0 1-2.184 0l-6.75-3.98a2.225 2.225 0 0 1-1.158-1.948v-7.284c0-.809.443-1.555 1.158-1.948l6.75-3.98a2.5 2.5 0 0 1 2.184 0z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/>
|
||||
<line x1="20.2" y1="7.5" x2="14.9" y2="10.5"/><line x1="9.1" y1="13.5" x2="3.8" y2="16.5"/>
|
||||
<line x1="20.2" y1="16.5" x2="14.9" y2="13.5"/><line x1="9.1" y1="10.5" x2="3.8" y2="7.5"/>
|
||||
</svg>
|
||||
<text x="204" y="362" fill="#bfc0c0" font-size="8" font-weight="500" font-family="'Geist', sans-serif">Kubernetes</text>
|
||||
|
||||
<!-- Identity Manager -->
|
||||
<svg x="16" y="398" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#bfc0c0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1-8.5 15a12 12 0 0 1-8.5-15a12 12 0 0 0 8.5-3"/>
|
||||
<circle cx="12" cy="11" r="1" fill="#bfc0c0"/>
|
||||
<line x1="12" y1="12" x2="12" y2="14.5"/>
|
||||
</svg>
|
||||
<text x="500" y="410" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Identity Manager</text>
|
||||
<text x="500" y="422" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Active Directory)</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="440" x2="960" y2="440" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="456" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
<rect x="40" y="464" width="20" height="12" rx="3" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="68" y="474" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">External source · outside cluster</text>
|
||||
<rect x="252" y="464" width="20" height="12" rx="3" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.25)" stroke-width="1"/>
|
||||
<text x="280" y="474" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Kubernetes boundary</text>
|
||||
<rect x="436" y="464" width="20" height="12" rx="3" fill="rgba(240,138,89,0.18)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<text x="464" y="474" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Focal component (MinIO)</text>
|
||||
<line x1="648" y1="466" x2="668" y2="466" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<text x="676" y="471" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Primary data path</text>
|
||||
<line x1="808" y1="466" x2="828" y2="466" stroke="rgba(245,245,245,0.35)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="836" y="471" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Orchestration trigger</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,245 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>High-level architecture · End-to-end stack</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
|
||||
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 72ch; }
|
||||
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
@media (max-width: 900px) { .cards { grid-template-columns: 1fr; } }
|
||||
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
|
||||
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--color-accent); }
|
||||
.card-dot.ink { background: var(--color-ink); }
|
||||
.card-dot.muted { background: var(--color-muted); }
|
||||
.card h3 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card ul { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; list-style: none; }
|
||||
.card li { margin-bottom: 0.3rem; padding-left: 0.875rem; position: relative; }
|
||||
.card li::before { content: '—'; position: absolute; left: 0; color: rgba(45,49,66,0.25); font-size: 0.75rem; }
|
||||
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-soft); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<p class="header-eyebrow">Architecture · Diagram Design</p>
|
||||
<h1>High-level architecture · End-to-end stack</h1>
|
||||
<p class="subtitle">Five functional phases from raw source ingestion to business visualization. Apache NiFi collects, MinIO stores, Trino virtualizes, notebooks model — all orchestrated by Airflow and secured by Active Directory, running on Kubernetes.</p>
|
||||
</div>
|
||||
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 540" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/>
|
||||
</marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/>
|
||||
</marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto">
|
||||
<polygon points="0 0, 6 2.5, 0 5" fill="#4f5d75"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Phase chevrons -->
|
||||
<polygon points="0,4 188,4 200,18 188,32 0,32" fill="#2d3142"/>
|
||||
<polygon points="200,4 388,4 400,18 388,32 200,32 212,18" fill="#3d4460"/>
|
||||
<polygon points="400,4 588,4 600,18 588,32 400,32 412,18" fill="#2d3142"/>
|
||||
<polygon points="600,4 788,4 800,18 788,32 600,32 612,18" fill="#3d4460"/>
|
||||
<polygon points="800,4 1000,4 1000,32 800,32 812,18" fill="#2d3142"/>
|
||||
<text x="100" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DATA SOURCES</text>
|
||||
<text x="300" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">INGESTION</text>
|
||||
<text x="500" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORAGE</text>
|
||||
<text x="700" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.13em" text-anchor="middle">TRANSFORMATION & ANALYSIS</text>
|
||||
<text x="900" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">VISUALIZATION</text>
|
||||
|
||||
<!-- Zones -->
|
||||
<rect x="4" y="40" width="152" height="336" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.20)" stroke-width="0.8" stroke-dasharray="6,3"/>
|
||||
<text x="80" y="52" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SOURCES</text>
|
||||
<rect x="164" y="40" width="832" height="336" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.18)" stroke-width="1.2"/>
|
||||
<rect x="4" y="388" width="992" height="40" rx="6" fill="rgba(45,49,66,0.05)" stroke="rgba(45,49,66,0.20)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Arrows -->
|
||||
<path d="M 144,92 H 156 Q 164,92 164,100 V 112 Q 164,120 176,120" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,172 H 156 Q 164,172 164,164 V 144 Q 164,136 176,136" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,252 H 156 Q 164,252 164,244 V 160 Q 164,152 176,152" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,336 H 156 Q 164,336 164,328 V 176 Q 164,168 176,168" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<line x1="452" y1="96" x2="452" y2="104" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="682" y1="96" x2="682" y2="144" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="896" y1="96" x2="896" y2="144" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<path d="M 328,152 H 344 Q 348,152 348,148 V 144 Q 348,140 352,140 H 372" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 328,152 H 344 Q 352,152 352,160 V 240 Q 352,248 372,248" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="448" y1="176" x2="448" y2="208" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<line x1="456" y1="208" x2="456" y2="176" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<path d="M 532,140 H 556 Q 564,140 564,148 V 176 Q 564,184 600,184" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 532,248 H 556 Q 564,248 564,240 V 192 Q 564,184 600,184" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="764" y1="184" x2="820" y2="184" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Source nodes -->
|
||||
<rect x="12" y="60" width="132" height="64" rx="6" fill="#f5f5f5"/><rect x="12" y="60" width="132" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="20" y="66" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="31" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3.6 9h16.8M3.6 15h16.8"/><path d="M11.5 3a17 17 0 0 0 0 18M12.5 3a17 17 0 0 1 0 18"/></svg>
|
||||
<text x="78" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">WEB</text>
|
||||
<text x="78" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Census · Survey · Admin</text>
|
||||
|
||||
<rect x="12" y="140" width="132" height="64" rx="6" fill="#f5f5f5"/><rect x="12" y="140" width="132" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="20" y="146" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="31" y="155" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="146" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/><path d="M7 9l5 -5l5 5"/><path d="M12 4l0 12"/></svg>
|
||||
<text x="78" y="182" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">FTP</text>
|
||||
<text x="78" y="194" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Census · Business</text>
|
||||
|
||||
<rect x="12" y="220" width="132" height="64" rx="6" fill="#f5f5f5"/><rect x="12" y="220" width="132" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="20" y="226" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="31" y="235" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="226" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/><path d="M4 6v6a8 3 0 0 0 16 0v-6"/><path d="M4 12v6a8 3 0 0 0 16 0v-6"/></svg>
|
||||
<text x="78" y="262" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">DB Connection</text>
|
||||
<text x="78" y="274" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · SQL · API</text>
|
||||
|
||||
<rect x="12" y="304" width="132" height="64" rx="6" fill="#f5f5f5"/><rect x="12" y="304" width="132" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="20" y="310" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="31" y="319" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="310" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/><path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/><path d="M7 8l0 .01"/><path d="M7 16l0 .01"/></svg>
|
||||
<text x="78" y="346" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Legacy System</text>
|
||||
<text x="78" y="358" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Trade data</text>
|
||||
|
||||
<!-- Airflow bar -->
|
||||
<rect x="176" y="52" width="808" height="44" rx="4" fill="rgba(45,49,66,0.05)" stroke="rgba(45,49,66,0.18)" stroke-width="0.8"/>
|
||||
<svg x="950" y="58" width="24" height="24" viewBox="0 0 24 24" fill="#4f5d75"><path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/></svg>
|
||||
<text x="564" y="71" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Orchestration / Workflow Automation</text>
|
||||
<text x="564" y="84" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Apache Airflow)</text>
|
||||
|
||||
<!-- NiFi -->
|
||||
<rect x="176" y="112" width="152" height="80" rx="6" fill="#f5f5f5"/><rect x="176" y="112" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="184" y="118" width="30" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="199" y="127" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">COLL</text>
|
||||
<svg x="240" y="118" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142"><path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0zm-1.632 3.867c.05 0 .08.034.037.112-.11.197-.218.397-.328.593-.396.702-.819 1.389-1.23 2.08-.196-.032-.39-.06-.585-.088.495-.651 1-1.296 1.48-1.959.153-.209.302-.423.454-.634a.24.24 0 0 1 .172-.104zM7.44 7.186c.221.035.444.076.666.119-.073.129-.15.256-.223.383a29.073 29.073 0 0 0-1.625 3.261c-.874 2.123-1.383 4.444-.77 6.707a8.222 8.222 0 0 0 2.217 3.74c.083.083-.02.216-.119.155a7.568 7.568 0 0 1-.93-.686A7.674 7.674 0 0 1 4.1 16.248c-.329-2.156.387-4.246 1.418-6.115a27.44 27.44 0 0 1 1.92-2.947zm7.931 8.435a.193.193 0 0 0-.191.191V18.3h2.677V15.62zm3.299 0V18.3h1.348a7.975 7.975 0 0 0 .515-2.678zm-6.303 3.004a.193.193 0 0 0-.191.193v2.485h2.678v-2.678Zm3.295.484v2.68h2.115a.562.562 0 0 0 .399-.162v-.004a.562.562 0 0 0 .16-.397V19.11zm3.674.182v1.98a7.999 7.999 0 0 0 1.217-1.98zm-6.969 2.824a.193.193 0 0 0-.191.192v1.672a7.997 7.997 0 0 0 2.678-.516v-1.348zm3.48.668V24a8.008 8.008 0 0 0 1.98-1.217z"/></svg>
|
||||
<text x="252" y="154" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="252" y="166" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Collect · route · transform</text>
|
||||
|
||||
<!-- Trino -->
|
||||
<rect x="372" y="104" width="160" height="72" rx="6" fill="#f5f5f5"/><rect x="372" y="104" width="160" height="72" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="380" y="110" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="394" y="119" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">VIRT</text>
|
||||
<svg x="440" y="110" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142"><path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075c.2614-1.2682.8762-3.574 2.0292-5.1958 1.6717-2.352 3.4357-4.7808 4.6116-4.1006 1.176.6802-.3074 3.1398-1.3297 4.4272-1.0222 1.2874-2.7862 3.2089-3.3742 4.2274-.2114.3843-.4304.8032-.5956 1.1529a5.7375 5.7375 0 0 1 2.9169 3.6125h.073v-2.3058a.3075.3075 0 0 0-.1806-.2844.9148.9148 0 0 1-.5573-.8148 1.0184 1.0184 0 0 1 .9045-.9044c.5593-.0598 1.061.3452 1.1208.9044a.9187.9187 0 0 1-.5534.8148.3074.3074 0 0 0-.1691.2844v2.1522a.3113.3113 0 0 0 .1691.2805.9724.9724 0 0 1 .5648.857z"/></svg>
|
||||
<text x="452" y="146" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="452" y="158" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · data virtualization</text>
|
||||
|
||||
<!-- MinIO FOCAL -->
|
||||
<rect x="372" y="208" width="160" height="80" rx="8" fill="#f5f5f5"/><rect x="372" y="208" width="160" height="80" rx="8" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="380" y="214" width="36" height="12" rx="2" fill="transparent" stroke="rgba(235,108,54,0.50)" stroke-width="0.8"/>
|
||||
<text x="398" y="223" fill="rgba(235,108,54,0.80)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="440" y="214" width="24" height="24" viewBox="0 0 24 24" fill="#eb6c36"><path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Zm-.9024 9.8652v2.9941l-4.1523 2.1484a13.9787 13.9787 0 0 1 2.7676-3.9277 14.1784 14.1784 0 0 1 1.3847-1.2148z"/></svg>
|
||||
<text x="452" y="252" fill="#eb6c36" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO / S3</text>
|
||||
<text x="452" y="268" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.75">Object store · S3-API</text>
|
||||
|
||||
<!-- Data Modeling -->
|
||||
<rect x="600" y="144" width="164" height="80" rx="6" fill="#f5f5f5"/><rect x="600" y="144" width="164" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="608" y="150" width="32" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="624" y="159" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ANLZ</text>
|
||||
<svg x="670" y="150" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142"><path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424zm.001 13.295c-3.768 0-7.06-1.381-8.768-3.425a9.363 9.363 0 0 0 3.394 4.547A9.238 9.238 0 0 0 12.33 21a9.238 9.238 0 0 0 5.377-1.729 9.363 9.363 0 0 0 3.393-4.547c-1.712 2.044-5.003 3.425-8.772 3.425z"/></svg>
|
||||
<text x="682" y="186" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Data Modeling</text>
|
||||
<text x="682" y="198" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Notebook · Python · R</text>
|
||||
|
||||
<!-- Dashboards -->
|
||||
<rect x="820" y="144" width="152" height="80" rx="6" fill="#f5f5f5"/><rect x="820" y="144" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="828" y="150" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="842" y="159" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">DASH</text>
|
||||
<svg x="884" y="150" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142"><path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/></svg>
|
||||
<text x="896" y="186" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Dashboards</text>
|
||||
<text x="896" y="198" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Superset · reports</text>
|
||||
|
||||
<!-- Kubernetes label -->
|
||||
<svg x="180" y="348" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4f5d75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19.875 6.27a2.225 2.225 0 0 1 1.125 1.948v7.284c0 .809-.443 1.555-1.158 1.948l-6.75 3.98a2.5 2.5 0 0 1-2.184 0l-6.75-3.98a2.225 2.225 0 0 1-1.158-1.948v-7.284c0-.809.443-1.555 1.158-1.948l6.75-3.98a2.5 2.5 0 0 1 2.184 0z"/><circle cx="12" cy="12" r="3"/><line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/><line x1="20.2" y1="7.5" x2="14.9" y2="10.5"/><line x1="9.1" y1="13.5" x2="3.8" y2="16.5"/><line x1="20.2" y1="16.5" x2="14.9" y2="13.5"/><line x1="9.1" y1="10.5" x2="3.8" y2="7.5"/></svg>
|
||||
<text x="204" y="362" fill="#4f5d75" font-size="8" font-weight="500" font-family="'Geist', sans-serif">Kubernetes</text>
|
||||
|
||||
<!-- Identity Manager -->
|
||||
<svg x="16" y="398" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4f5d75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1-8.5 15a12 12 0 0 1-8.5-15a12 12 0 0 0 8.5-3"/><circle cx="12" cy="11" r="1" fill="#4f5d75"/><line x1="12" y1="12" x2="12" y2="14.5"/></svg>
|
||||
<text x="500" y="410" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Identity Manager</text>
|
||||
<text x="500" y="422" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Active Directory)</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="440" x2="960" y2="440" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="456" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
<rect x="40" y="464" width="20" height="12" rx="3" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="68" y="474" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">External source · outside cluster</text>
|
||||
<rect x="252" y="464" width="20" height="12" rx="3" fill="rgba(45,49,66,0.04)" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<text x="280" y="474" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Kubernetes boundary</text>
|
||||
<rect x="436" y="464" width="20" height="12" rx="3" fill="rgba(235,108,54,0.10)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="464" y="474" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Focal component (MinIO)</text>
|
||||
<line x1="648" y1="466" x2="668" y2="466" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="676" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Primary data path</text>
|
||||
<line x1="808" y1="466" x2="828" y2="466" stroke="rgba(45,49,66,0.35)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="836" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Orchestration trigger</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="eyebrow">Ingestion · Apache NiFi</p>
|
||||
<div class="card-header">
|
||||
<div class="card-dot ink"></div>
|
||||
<h3>Data collection</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Collects from WEB (HTTP/REST), FTP, database CDC, and legacy file exports</li>
|
||||
<li>No-code dataflow routing with back-pressure and guaranteed delivery</li>
|
||||
<li>Lands raw data into MinIO and exposes it to Trino simultaneously</li>
|
||||
<li>Orchestrated by Airflow DAGs for scheduled batch and event-driven runs</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="eyebrow">Storage · MinIO / S3</p>
|
||||
<div class="card-header">
|
||||
<div class="card-dot coral"></div>
|
||||
<h3>Object store (focal)</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>S3-compatible API — single storage surface for all tools in the stack</li>
|
||||
<li>Holds raw, cleansed, and curated layers (bronze / silver / gold paths)</li>
|
||||
<li>Trino virtualizes directly over MinIO buckets — no ETL copy step needed</li>
|
||||
<li>Python notebooks write model outputs back as Parquet for further querying</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="eyebrow">Query & Serve · Trino + Superset</p>
|
||||
<div class="card-header">
|
||||
<div class="card-dot muted"></div>
|
||||
<h3>Analysis & visualization</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Trino federates SQL queries across MinIO, databases, and APIs in one query</li>
|
||||
<li>JupyterLab notebooks run Python and R for statistical modeling and ML</li>
|
||||
<li>Superset dashboards connect to Trino for live, governed report delivery</li>
|
||||
<li>Active Directory enforces row- and column-level access across all surfaces</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>High-level architecture · Kubernetes-deployed open-source stack</span>
|
||||
<span>Sources → NiFi → MinIO → Trino → Notebooks → Superset</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,272 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>High-level architecture · Vertical chevrons (dark)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #2d3142;
|
||||
--color-ink: #f5f5f5;
|
||||
--color-muted: #bfc0c0;
|
||||
--color-accent: #f08a59;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture · Diagram Design · Parametric</p>
|
||||
<h1>High-level architecture · Vertical Orchestration + Security</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 600" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#bfc0c0"/>
|
||||
</marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#f08a59"/>
|
||||
</marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto">
|
||||
<polygon points="0 0, 6 2.5, 0 5" fill="#bfc0c0"/>
|
||||
</marker>
|
||||
<marker id="arrow-dim" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="rgba(245,245,245,0.45)"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#2d3142"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Horizontal chevron banner (effective_w=964 with 8-px margin to right strip) -->
|
||||
<polygon points="0,4 180,4 192,18 180,32 0,32" fill="#3d4460"/>
|
||||
<polygon points="192,4 372,4 384,18 372,32 192,32 204,18" fill="#4a5270"/>
|
||||
<polygon points="384,4 564,4 576,18 564,32 384,32 396,18" fill="#3d4460"/>
|
||||
<polygon points="576,4 756,4 768,18 756,32 576,32 588,18" fill="#4a5270"/>
|
||||
<polygon points="768,4 964,4 964,32 768,32 780,18" fill="#3d4460"/>
|
||||
<text x="96" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DATA SOURCES</text>
|
||||
<text x="288" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">INGESTION</text>
|
||||
<text x="480" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORAGE</text>
|
||||
<text x="672" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">TRANSFORM</text>
|
||||
<text x="866" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">VISUALIZATION</text>
|
||||
|
||||
<!-- Right strip — 3 vertical chevrons (Orchestration / Security / Observability) -->
|
||||
<polygon points="972,40 1000,40 1000,172 986,184 972,172" fill="#3d4460"/>
|
||||
<text x="986" y="112" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 112)">ORCHESTRATION</text>
|
||||
<polygon points="972,184 986,196 1000,184 1000,316 986,328 972,316" fill="#b85450"/>
|
||||
<text x="986" y="256" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 256)">SECURITY</text>
|
||||
<polygon points="972,328 986,340 1000,328 1000,472 972,472" fill="#3d4460"/>
|
||||
<text x="986" y="400" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 400)">OBSERVABILITY</text>
|
||||
|
||||
<!-- Structural zones -->
|
||||
<rect x="4" y="40" width="184" height="336" rx="6" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.22)" stroke-width="0.8" stroke-dasharray="6,3"/>
|
||||
<text x="96" y="52" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SOURCES</text>
|
||||
<rect x="196" y="40" width="768" height="336" rx="8" fill="rgba(245,245,245,0.03)" stroke="rgba(245,245,245,0.18)" stroke-width="1.2"/>
|
||||
<rect x="4" y="388" width="960" height="40" rx="6" fill="rgba(217,122,120,0.12)" stroke="rgba(217,122,120,0.45)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Arrows (drawn before nodes) -->
|
||||
<path d="M 184,92 H 196 Q 200,92 200,96 V 128 Q 200,132 204,132 H 212" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,172 H 196 Q 200,172 200,168 V 156 Q 200,152 204,152 H 212" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,252 H 196 Q 200,252 200,248 V 176 Q 200,172 204,172 H 212" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,332 H 196 Q 200,332 200,328 V 192 Q 200,188 204,188 H 212" fill="none" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<line x1="288" y1="96" x2="288" y2="120" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="480" y1="96" x2="480" y2="120" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="672" y1="96" x2="672" y2="120" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
|
||||
<line x1="364" y1="164" x2="404" y2="164" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 364,156 H 384 Q 388,156 388,164 V 248 Q 388,256 396,256 H 404" fill="none" stroke="#f08a59" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
|
||||
<line x1="480" y1="200" x2="480" y2="216" stroke="rgba(245,245,245,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-dim)"/>
|
||||
|
||||
<path d="M 556,256 H 576 Q 580,256 580,248 V 168 Q 580,160 588,160 H 596" fill="none" stroke="#f08a59" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="748" y1="160" x2="790" y2="160" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Source nodes -->
|
||||
<rect x="8" y="60" width="176" height="64" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="16" y="66" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="27" y="75" fill="rgba(245,245,245,0.65)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
|
||||
</svg>
|
||||
<text x="96" y="102" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">PostgreSQL</text>
|
||||
<text x="96" y="114" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · SQL · API</text>
|
||||
|
||||
<rect x="8" y="140" width="176" height="64" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="16" y="146" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="27" y="155" fill="rgba(245,245,245,0.65)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="146" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/>
|
||||
<path d="M7 9l5 -5l5 5"/>
|
||||
<path d="M12 4l0 12"/>
|
||||
</svg>
|
||||
<text x="96" y="182" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">SFTP drop</text>
|
||||
<text x="96" y="194" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Batch · landing</text>
|
||||
|
||||
<rect x="8" y="220" width="176" height="64" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="16" y="226" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="27" y="235" fill="rgba(245,245,245,0.65)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="226" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="9"/>
|
||||
<path d="M3.6 9h16.8M3.6 15h16.8"/>
|
||||
<path d="M11.5 3a17 17 0 0 0 0 18M12.5 3a17 17 0 0 1 0 18"/>
|
||||
</svg>
|
||||
<text x="96" y="262" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Web forms</text>
|
||||
<text x="96" y="274" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Survey · admin</text>
|
||||
|
||||
<rect x="8" y="300" width="176" height="64" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="16" y="306" width="22" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="27" y="315" fill="rgba(245,245,245,0.65)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="306" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#f5f5f5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/>
|
||||
<path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/>
|
||||
<path d="M7 8l0 .01"/><path d="M7 16l0 .01"/>
|
||||
</svg>
|
||||
<text x="96" y="342" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Legacy system</text>
|
||||
<text x="96" y="354" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Trade · mainframe</text>
|
||||
|
||||
<!-- Airflow bar -->
|
||||
<rect x="208" y="52" width="744" height="44" rx="4" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.18)" stroke-width="0.8"/>
|
||||
<svg x="914" y="58" width="24" height="24" viewBox="0 0 24 24" fill="#bfc0c0">
|
||||
<path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/>
|
||||
</svg>
|
||||
<text x="580" y="71" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Orchestration / Workflow Automation</text>
|
||||
<text x="580" y="84" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Apache Airflow)</text>
|
||||
|
||||
<!-- Cluster nodes -->
|
||||
<rect x="212" y="120" width="152" height="80" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="220" y="126" width="30" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="235" y="135" fill="rgba(245,245,245,0.65)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">COLL</text>
|
||||
<svg x="332" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0z"/>
|
||||
</svg>
|
||||
<text x="288" y="166" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="288" y="178" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Collect · route · transform</text>
|
||||
|
||||
<rect x="404" y="120" width="152" height="80" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="412" y="126" width="28" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="426" y="135" fill="rgba(245,245,245,0.65)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">VIRT</text>
|
||||
<svg x="524" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075z"/>
|
||||
</svg>
|
||||
<text x="480" y="166" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="480" y="178" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · data virtualization</text>
|
||||
|
||||
<!-- MinIO focal -->
|
||||
<rect x="404" y="216" width="152" height="80" rx="8" fill="rgba(240,138,89,0.12)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<rect x="412" y="222" width="36" height="12" rx="2" fill="transparent" stroke="rgba(240,138,89,0.55)" stroke-width="0.8"/>
|
||||
<text x="430" y="231" fill="rgba(240,138,89,0.95)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="524" y="222" width="24" height="24" viewBox="0 0 24 24" fill="#f08a59">
|
||||
<path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Z"/>
|
||||
</svg>
|
||||
<text x="480" y="262" fill="#f08a59" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO / S3</text>
|
||||
<text x="480" y="276" fill="#f08a59" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.78">Object store · S3-API</text>
|
||||
|
||||
<rect x="596" y="120" width="152" height="80" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="604" y="126" width="32" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="620" y="135" fill="rgba(245,245,245,0.65)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ANLZ</text>
|
||||
<svg x="716" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424zm.001 13.295c-3.768 0-7.06-1.381-8.768-3.425a9.363 9.363 0 0 0 3.394 4.547A9.238 9.238 0 0 0 12.33 21a9.238 9.238 0 0 0 5.377-1.729 9.363 9.363 0 0 0 3.393-4.547c-1.712 2.044-5.003 3.425-8.772 3.425z"/>
|
||||
</svg>
|
||||
<text x="672" y="166" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Notebooks</text>
|
||||
<text x="672" y="178" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Jupyter · Python · R</text>
|
||||
|
||||
<rect x="790" y="120" width="152" height="80" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<rect x="798" y="126" width="28" height="12" rx="2" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="812" y="135" fill="rgba(245,245,245,0.65)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">DASH</text>
|
||||
<svg x="910" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#f5f5f5">
|
||||
<path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/>
|
||||
</svg>
|
||||
<text x="866" y="166" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Superset</text>
|
||||
<text x="866" y="178" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Dashboards · reports</text>
|
||||
|
||||
<!-- Kubernetes label -->
|
||||
<svg x="212" y="348" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#bfc0c0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19.875 6.27a2.225 2.225 0 0 1 1.125 1.948v7.284c0 .809-.443 1.555-1.158 1.948l-6.75 3.98a2.5 2.5 0 0 1-2.184 0l-6.75-3.98a2.225 2.225 0 0 1-1.158-1.948v-7.284c0-.809.443-1.555 1.158-1.948l6.75-3.98a2.5 2.5 0 0 1 2.184 0z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/>
|
||||
<line x1="20.2" y1="7.5" x2="14.9" y2="10.5"/><line x1="9.1" y1="13.5" x2="3.8" y2="16.5"/>
|
||||
<line x1="20.2" y1="16.5" x2="14.9" y2="13.5"/><line x1="9.1" y1="10.5" x2="3.8" y2="7.5"/>
|
||||
</svg>
|
||||
<text x="236" y="362" fill="#bfc0c0" font-size="8" font-weight="500" font-family="'Geist', sans-serif">Kubernetes</text>
|
||||
|
||||
<!-- Cross-cutting bar #1 — Identity (Security row, y=388..428, color=#d97a78 lightened red for dark mode) -->
|
||||
<svg x="16" y="398" width="20" height="20" viewBox="0 0 24 24" fill="#d97a78">
|
||||
<path d="M12.0001.0036C5.376.0036 0 5.3796 0 12.0036c0 6.6276 5.376 12 12.0001 12 6.6275 0 12-5.3724 12-12 0-6.624-5.3725-12-12-12zm5.532 17.0496c-.612 0-3.972-3.144-5.532-5.046C10.452 13.9356 7.092 17.0796 6.48 17.0796c-1.788 0-2.052-3.564-2.052-5.076 0-1.512.264-5.076 2.052-5.076.612 0 3.972 3.144 5.5201 5.046 1.548-1.932 4.908-5.046 5.532-5.046 1.788 0 2.052 3.564 2.052 5.076 0 1.512-.264 5.076-2.052 5.076z"/>
|
||||
</svg>
|
||||
<text x="484" y="410" fill="#d97a78" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Identity Manager</text>
|
||||
<text x="484" y="422" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Keycloak · LDAP · OIDC)</text>
|
||||
|
||||
<!-- Cross-cutting bar #2 — Monitoring (Observability row, y=432..472) -->
|
||||
<rect x="4" y="432" width="960" height="40" rx="6" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.18)" stroke-width="0.8"/>
|
||||
<svg x="16" y="442" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#bfc0c0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 3v18h18"/>
|
||||
<path d="M7 14l3-3 2 2 5-5"/>
|
||||
<path d="M14 8h3v3"/>
|
||||
</svg>
|
||||
<text x="484" y="454" fill="#f5f5f5" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Monitoring & Observability</text>
|
||||
<text x="484" y="466" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Prometheus · Grafana · Loki)</text>
|
||||
|
||||
<!-- Legend (pushed down past 2nd crosscut) -->
|
||||
<line x1="40" y1="496" x2="952" y2="496" stroke="rgba(245,245,245,0.18)" stroke-width="0.8"/>
|
||||
<text x="40" y="512" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="520" width="20" height="12" rx="3" fill="transparent" stroke="rgba(245,245,245,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="68" y="530" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">External · outside cluster</text>
|
||||
|
||||
<rect x="232" y="520" width="20" height="12" rx="3" fill="rgba(245,245,245,0.06)" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<text x="260" y="530" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Kubernetes boundary</text>
|
||||
|
||||
<rect x="404" y="520" width="20" height="12" rx="3" fill="rgba(240,138,89,0.14)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<text x="432" y="530" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Focal (storage hub)</text>
|
||||
|
||||
<line x1="552" y1="522" x2="572" y2="522" stroke="#f08a59" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<text x="580" y="527" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Primary path</text>
|
||||
|
||||
<line x1="680" y1="522" x2="700" y2="522" stroke="#bfc0c0" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<text x="708" y="527" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Orchestration trigger</text>
|
||||
|
||||
<line x1="836" y1="522" x2="856" y2="522" stroke="rgba(245,245,245,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-dim)"/>
|
||||
<text x="864" y="527" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Query read-back</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,250 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>High-level architecture · Vertical chevrons (editorial)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
|
||||
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 72ch; }
|
||||
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
@media (max-width: 900px) { .cards { grid-template-columns: 1fr; } }
|
||||
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
|
||||
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--color-accent); }
|
||||
.card-dot.ink { background: var(--color-ink); }
|
||||
.card-dot.muted { background: var(--color-muted); }
|
||||
.card h3 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card ul { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; list-style: none; }
|
||||
.card li { margin-bottom: 0.3rem; padding-left: 0.875rem; position: relative; }
|
||||
.card li::before { content: '—'; position: absolute; left: 0; color: rgba(45,49,66,0.25); font-size: 0.75rem; }
|
||||
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-soft); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<p class="header-eyebrow">Architecture · Diagram Design · Parametric</p>
|
||||
<h1>High-level architecture · Vertical Orchestration + Security</h1>
|
||||
<p class="subtitle">Five horizontal phases (Sources → Ingestion → Storage → Transformation → Visualization) plus three cross-cutting verticals (Orchestration, Security, Observability) rendered as a reserved right strip. Two cross-cutting bars stack below the cluster — Identity and Monitoring — each paired 1:1 with a vertical chevron. The diagram is fully parametric: chevrons, sources, components, and connections drive every coordinate.</p>
|
||||
</div>
|
||||
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 600" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0, 6 2.5, 0 5" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-dim" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="rgba(45,49,66,0.45)"/></marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Horizontal chevron banner -->
|
||||
<polygon points="0,4 180,4 192,18 180,32 0,32" fill="#2d3142"/>
|
||||
<polygon points="192,4 372,4 384,18 372,32 192,32 204,18" fill="#3d4460"/>
|
||||
<polygon points="384,4 564,4 576,18 564,32 384,32 396,18" fill="#2d3142"/>
|
||||
<polygon points="576,4 756,4 768,18 756,32 576,32 588,18" fill="#3d4460"/>
|
||||
<polygon points="768,4 964,4 964,32 768,32 780,18" fill="#2d3142"/>
|
||||
<text x="96" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DATA SOURCES</text>
|
||||
<text x="288" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">INGESTION</text>
|
||||
<text x="480" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORAGE</text>
|
||||
<text x="672" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">TRANSFORM</text>
|
||||
<text x="866" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">VISUALIZATION</text>
|
||||
|
||||
<!-- Right strip — 3 vertical chevrons (Orchestration / Security / Observability) -->
|
||||
<polygon points="972,40 1000,40 1000,172 986,184 972,172" fill="#2d3142"/>
|
||||
<text x="986" y="112" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 112)">ORCHESTRATION</text>
|
||||
<polygon points="972,184 986,196 1000,184 1000,316 986,328 972,316" fill="#b85450"/>
|
||||
<text x="986" y="256" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 256)">SECURITY</text>
|
||||
<polygon points="972,328 986,340 1000,328 1000,472 972,472" fill="#2d3142"/>
|
||||
<text x="986" y="400" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 400)">OBSERVABILITY</text>
|
||||
|
||||
<!-- Zones -->
|
||||
<rect x="4" y="40" width="184" height="336" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.20)" stroke-width="0.8" stroke-dasharray="6,3"/>
|
||||
<text x="96" y="52" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SOURCES</text>
|
||||
<rect x="196" y="40" width="768" height="336" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.18)" stroke-width="1.2"/>
|
||||
<rect x="4" y="388" width="960" height="40" rx="6" fill="rgba(184,84,80,0.06)" stroke="rgba(184,84,80,0.35)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Arrows -->
|
||||
<path d="M 184,92 H 196 Q 200,92 200,96 V 128 Q 200,132 204,132 H 212" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,172 H 196 Q 200,172 200,168 V 156 Q 200,152 204,152 H 212" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,252 H 196 Q 200,252 200,248 V 176 Q 200,172 204,172 H 212" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,332 H 196 Q 200,332 200,328 V 192 Q 200,188 204,188 H 212" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<line x1="288" y1="96" x2="288" y2="120" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="480" y1="96" x2="480" y2="120" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="672" y1="96" x2="672" y2="120" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="364" y1="164" x2="404" y2="164" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 364,156 H 384 Q 388,156 388,164 V 248 Q 388,256 396,256 H 404" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="480" y1="200" x2="480" y2="216" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-dim)"/>
|
||||
<path d="M 556,256 H 576 Q 580,256 580,248 V 168 Q 580,160 588,160 H 596" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="748" y1="160" x2="790" y2="160" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Source nodes -->
|
||||
<rect x="8" y="60" width="176" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="16" y="66" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="27" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/><path d="M4 6v6a8 3 0 0 0 16 0v-6"/><path d="M4 12v6a8 3 0 0 0 16 0v-6"/></svg>
|
||||
<text x="96" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">PostgreSQL</text>
|
||||
<text x="96" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · SQL · API</text>
|
||||
|
||||
<rect x="8" y="140" width="176" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="16" y="146" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="27" y="155" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="146" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/><path d="M7 9l5 -5l5 5"/><path d="M12 4l0 12"/></svg>
|
||||
<text x="96" y="182" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">SFTP drop</text>
|
||||
<text x="96" y="194" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Batch · landing</text>
|
||||
|
||||
<rect x="8" y="220" width="176" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="16" y="226" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="27" y="235" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="226" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3.6 9h16.8M3.6 15h16.8"/><path d="M11.5 3a17 17 0 0 0 0 18M12.5 3a17 17 0 0 1 0 18"/></svg>
|
||||
<text x="96" y="262" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Web forms</text>
|
||||
<text x="96" y="274" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Survey · admin</text>
|
||||
|
||||
<rect x="8" y="300" width="176" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="16" y="306" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="27" y="315" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="306" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/><path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/><path d="M7 8l0 .01"/><path d="M7 16l0 .01"/></svg>
|
||||
<text x="96" y="342" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Legacy system</text>
|
||||
<text x="96" y="354" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Trade · mainframe</text>
|
||||
|
||||
<!-- Airflow bar -->
|
||||
<rect x="208" y="52" width="744" height="44" rx="4" fill="rgba(45,49,66,0.05)" stroke="rgba(45,49,66,0.18)" stroke-width="0.8"/>
|
||||
<svg x="914" y="58" width="24" height="24" viewBox="0 0 24 24" fill="#4f5d75"><path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/></svg>
|
||||
<text x="580" y="71" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Orchestration / Workflow Automation</text>
|
||||
<text x="580" y="84" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Apache Airflow)</text>
|
||||
|
||||
<!-- Cluster nodes -->
|
||||
<rect x="212" y="120" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="220" y="126" width="30" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="235" y="135" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">COLL</text>
|
||||
<svg x="332" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142"><path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0z"/></svg>
|
||||
<text x="288" y="166" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="288" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Collect · route · transform</text>
|
||||
|
||||
<rect x="404" y="120" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="412" y="126" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="426" y="135" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">VIRT</text>
|
||||
<svg x="524" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142"><path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075z"/></svg>
|
||||
<text x="480" y="166" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="480" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · data virtualization</text>
|
||||
|
||||
<rect x="404" y="216" width="152" height="80" rx="8" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="412" y="222" width="36" height="12" rx="2" fill="transparent" stroke="rgba(235,108,54,0.50)" stroke-width="0.8"/>
|
||||
<text x="430" y="231" fill="rgba(235,108,54,0.80)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="524" y="222" width="24" height="24" viewBox="0 0 24 24" fill="#eb6c36"><path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Z"/></svg>
|
||||
<text x="480" y="262" fill="#eb6c36" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO / S3</text>
|
||||
<text x="480" y="276" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.75">Object store · S3-API</text>
|
||||
|
||||
<rect x="596" y="120" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="604" y="126" width="32" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="620" y="135" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ANLZ</text>
|
||||
<svg x="716" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142"><path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424z"/></svg>
|
||||
<text x="672" y="166" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Notebooks</text>
|
||||
<text x="672" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Jupyter · Python · R</text>
|
||||
|
||||
<rect x="790" y="120" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="798" y="126" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="812" y="135" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">DASH</text>
|
||||
<svg x="910" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142"><path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/></svg>
|
||||
<text x="866" y="166" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Superset</text>
|
||||
<text x="866" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Dashboards · reports</text>
|
||||
|
||||
<!-- Kubernetes label -->
|
||||
<svg x="212" y="348" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4f5d75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19.875 6.27a2.225 2.225 0 0 1 1.125 1.948v7.284c0 .809-.443 1.555-1.158 1.948l-6.75 3.98a2.5 2.5 0 0 1-2.184 0l-6.75-3.98a2.225 2.225 0 0 1-1.158-1.948v-7.284c0-.809.443-1.555 1.158-1.948l6.75-3.98a2.5 2.5 0 0 1 2.184 0z"/><circle cx="12" cy="12" r="3"/><line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/><line x1="20.2" y1="7.5" x2="14.9" y2="10.5"/><line x1="9.1" y1="13.5" x2="3.8" y2="16.5"/><line x1="20.2" y1="16.5" x2="14.9" y2="13.5"/><line x1="9.1" y1="10.5" x2="3.8" y2="7.5"/></svg>
|
||||
<text x="236" y="362" fill="#4f5d75" font-size="8" font-weight="500" font-family="'Geist', sans-serif">Kubernetes</text>
|
||||
|
||||
<!-- Cross-cutting bar #1 — Identity (color=#b85450 rust-red) -->
|
||||
<svg x="16" y="398" width="20" height="20" viewBox="0 0 24 24" fill="#b85450"><path d="M12.0001.0036C5.376.0036 0 5.3796 0 12.0036c0 6.6276 5.376 12 12.0001 12 6.6275 0 12-5.3724 12-12 0-6.624-5.3725-12-12-12zm5.532 17.0496c-.612 0-3.972-3.144-5.532-5.046C10.452 13.9356 7.092 17.0796 6.48 17.0796c-1.788 0-2.052-3.564-2.052-5.076 0-1.512.264-5.076 2.052-5.076.612 0 3.972 3.144 5.5201 5.046 1.548-1.932 4.908-5.046 5.532-5.046 1.788 0 2.052 3.564 2.052 5.076 0 1.512-.264 5.076-2.052 5.076z"/></svg>
|
||||
<text x="484" y="410" fill="#b85450" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Identity Manager</text>
|
||||
<text x="484" y="422" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Keycloak · LDAP · OIDC)</text>
|
||||
|
||||
<!-- Cross-cutting bar #2 — Monitoring -->
|
||||
<rect x="4" y="432" width="960" height="40" rx="6" fill="rgba(45,49,66,0.05)" stroke="rgba(45,49,66,0.20)" stroke-width="0.8"/>
|
||||
<svg x="16" y="442" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4f5d75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M7 14l3-3 2 2 5-5"/><path d="M14 8h3v3"/></svg>
|
||||
<text x="484" y="454" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Monitoring & Observability</text>
|
||||
<text x="484" y="466" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Prometheus · Grafana · Loki)</text>
|
||||
|
||||
<!-- Legend (pushed down past 2nd crosscut) -->
|
||||
<line x1="40" y1="496" x2="952" y2="496" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="512" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
<rect x="40" y="520" width="20" height="12" rx="3" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="68" y="530" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">External · outside cluster</text>
|
||||
<rect x="232" y="520" width="20" height="12" rx="3" fill="rgba(45,49,66,0.04)" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<text x="260" y="530" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Kubernetes boundary</text>
|
||||
<rect x="404" y="520" width="20" height="12" rx="3" fill="rgba(235,108,54,0.10)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="432" y="530" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Focal (storage hub)</text>
|
||||
<line x1="552" y1="522" x2="572" y2="522" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<text x="580" y="527" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Primary path</text>
|
||||
<line x1="680" y1="522" x2="700" y2="522" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<text x="708" y="527" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Orchestration trigger</text>
|
||||
<line x1="836" y1="522" x2="856" y2="522" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-dim)"/>
|
||||
<text x="864" y="527" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Query read-back</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="eyebrow">Vertical · Orchestration</p>
|
||||
<div class="card-header">
|
||||
<div class="card-dot ink"></div>
|
||||
<h3>Workflow automation</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Apache Airflow runs as a full-width <em>bar</em> at the top of the cluster — visible to every cluster node</li>
|
||||
<li>Trigger drops are always dashed (style: trigger), originating from the bar's bottom edge</li>
|
||||
<li>The vertical Orchestration chevron in the right strip labels the cross-cutting column</li>
|
||||
<li>One bar component per vertical chevron — enforced by the pairing rule</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="eyebrow">Storage · MinIO / S3 (focal)</p>
|
||||
<div class="card-header">
|
||||
<div class="card-dot coral"></div>
|
||||
<h3>Object store hub</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Single focal node — accent border, accent ink, accent connectors</li>
|
||||
<li>Every edge touching the focal node is automatically <em>style: primary</em> (accent stroke)</li>
|
||||
<li>Trino virtualizes MinIO buckets — read-back drawn as <em>style: query</em> (dashed dim)</li>
|
||||
<li>Notebooks consume curated data; their output writes back as Parquet</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<p class="eyebrow">Verticals · Security + Observability</p>
|
||||
<div class="card-header">
|
||||
<div class="card-dot muted"></div>
|
||||
<h3>Cross-cutting concerns</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Multiple <em>cross-cutting</em> bars stack below the cluster, one row per concern (44 px each)</li>
|
||||
<li>Each crosscut pairs 1:1 with a vertical chevron in the right strip (count constraint)</li>
|
||||
<li>Bars emit no individual connectors — semantic is "applies to everything above"</li>
|
||||
<li>Components carry an optional <code>color</code> override (Identity Manager rendered in rust-red here) — applies to border, icon, and name; connectors stay topology-driven</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>High-level architecture · Parametric (chevrons + sources + components → SVG)</span>
|
||||
<span>5 horizontal phases × 3 vertical chevrons · 2 cross-cutting bars · reserved right strip</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,323 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>High-level architecture · Vertical chevrons (Orchestration + Security)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #f5f5f5;
|
||||
--color-ink: #2d3142;
|
||||
--color-muted: #4f5d75;
|
||||
--color-accent: #eb6c36;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture · Diagram Design · Parametric</p>
|
||||
<h1>High-level architecture · Vertical Orchestration + Security</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 600" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/>
|
||||
</marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/>
|
||||
</marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto">
|
||||
<polygon points="0 0, 6 2.5, 0 5" fill="#4f5d75"/>
|
||||
</marker>
|
||||
<marker id="arrow-dim" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="rgba(45,49,66,0.45)"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
PHASE CHEVRON BANNER (horizontal, 5 phases)
|
||||
effective_w=964 (8-px margin to right strip), widths=[192,192,192,192,196], cx=[96,288,480,672,866]
|
||||
═══════════════════════════════════════════ -->
|
||||
<polygon points="0,4 180,4 192,18 180,32 0,32" fill="#2d3142"/>
|
||||
<polygon points="192,4 372,4 384,18 372,32 192,32 204,18" fill="#3d4460"/>
|
||||
<polygon points="384,4 564,4 576,18 564,32 384,32 396,18" fill="#2d3142"/>
|
||||
<polygon points="576,4 756,4 768,18 756,32 576,32 588,18" fill="#3d4460"/>
|
||||
<polygon points="768,4 964,4 964,32 768,32 780,18" fill="#2d3142"/>
|
||||
|
||||
<text x="96" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DATA SOURCES</text>
|
||||
<text x="288" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">INGESTION</text>
|
||||
<text x="480" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORAGE</text>
|
||||
<text x="672" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">TRANSFORM</text>
|
||||
<text x="866" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">VISUALIZATION</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
RIGHT STRIP — vertical chevrons (Orchestration / Security / Observability)
|
||||
strip_x=972 w=28, n_cross=2 → strip_y_bot=472, heights=[144,144,144]
|
||||
layout: [40..184, 184..328, 328..472]
|
||||
═══════════════════════════════════════════ -->
|
||||
<!-- Orchestration (first/topmost): flat top, point at bottom -->
|
||||
<polygon points="972,40 1000,40 1000,172 986,184 972,172" fill="#2d3142"/>
|
||||
<text x="986" y="112" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 112)">ORCHESTRATION</text>
|
||||
<!-- Security (middle): notch on top, point on bottom — chevron color override #b85450 to pair with Identity bar -->
|
||||
<polygon points="972,184 986,196 1000,184 1000,316 986,328 972,316" fill="#b85450"/>
|
||||
<text x="986" y="256" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 256)">SECURITY</text>
|
||||
<!-- Observability (last/bottommost): notch on top, flat bottom -->
|
||||
<polygon points="972,328 986,340 1000,328 1000,472 972,472" fill="#2d3142"/>
|
||||
<text x="986" y="400" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle" transform="rotate(-90 986 400)">OBSERVABILITY</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
STRUCTURAL ZONES
|
||||
═══════════════════════════════════════════ -->
|
||||
<!-- Sources zone (dashed external) -->
|
||||
<rect x="4" y="40" width="184" height="336" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.20)" stroke-width="0.8" stroke-dasharray="6,3"/>
|
||||
<text x="96" y="52" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SOURCES</text>
|
||||
|
||||
<!-- Cluster boundary (solid) — right edge at 964 (8-px gap to strip at 972) -->
|
||||
<rect x="196" y="40" width="768" height="336" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.18)" stroke-width="1.2"/>
|
||||
|
||||
<!-- Identity / cross-cutting bar (color override: #b85450 rust-red) -->
|
||||
<rect x="4" y="388" width="960" height="40" rx="6" fill="rgba(184,84,80,0.06)" stroke="rgba(184,84,80,0.35)" stroke-width="0.8"/>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
ARROWS (drawn before nodes — z-order rule)
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<!-- Sources → NiFi (staggered entry on NiFi left edge x=212; entry_y = 132,152,172,188) -->
|
||||
<path d="M 184,92 H 196 Q 200,92 200,96 V 128 Q 200,132 204,132 H 212" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,172 H 196 Q 200,172 200,168 V 156 Q 200,152 204,152 H 212" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,252 H 196 Q 200,252 200,248 V 176 Q 200,172 204,172 H 212" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 184,332 H 196 Q 200,332 200,328 V 192 Q 200,188 204,188 H 212" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Airflow bar → NiFi, Trino, Notebooks (trigger drops at each node's cx, from bar.bottom=96 to node.top=120) -->
|
||||
<line x1="288" y1="96" x2="288" y2="120" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="480" y1="96" x2="480" y2="120" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="672" y1="96" x2="672" y2="120" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
|
||||
<!-- NiFi → Trino (secondary, straight at cy=164) -->
|
||||
<line x1="364" y1="164" x2="404" y2="164" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<!-- NiFi → MinIO (PRIMARY accent — trunk at boundary x=388, drop to MinIO cy=256) -->
|
||||
<path d="M 364,156 H 384 Q 388,156 388,164 V 248 Q 388,256 396,256 H 404" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
|
||||
<!-- Trino ↔ MinIO (query read-back, dashed) -->
|
||||
<line x1="480" y1="200" x2="480" y2="216" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-dim)"/>
|
||||
|
||||
<!-- MinIO → Notebooks (PRIMARY accent — trunk at boundary x=580, rise to Notebooks cy=160) -->
|
||||
<path d="M 556,256 H 576 Q 580,256 580,248 V 168 Q 580,160 588,160 H 596" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
|
||||
<!-- Notebooks → Superset (secondary, straight at cy=160) -->
|
||||
<line x1="748" y1="160" x2="790" y2="160" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
SOURCE NODES (w=176, h=64, x=8)
|
||||
═══════════════════════════════════════════ -->
|
||||
<!-- PostgreSQL (db) -->
|
||||
<rect x="8" y="60" width="176" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="16" y="66" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="27" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
|
||||
</svg>
|
||||
<text x="96" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">PostgreSQL</text>
|
||||
<text x="96" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · SQL · API</text>
|
||||
|
||||
<!-- SFTP (ftp) -->
|
||||
<rect x="8" y="140" width="176" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="16" y="146" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="27" y="155" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="146" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/>
|
||||
<path d="M7 9l5 -5l5 5"/>
|
||||
<path d="M12 4l0 12"/>
|
||||
</svg>
|
||||
<text x="96" y="182" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">SFTP drop</text>
|
||||
<text x="96" y="194" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Batch · landing</text>
|
||||
|
||||
<!-- Web forms (web) -->
|
||||
<rect x="8" y="220" width="176" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="16" y="226" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="27" y="235" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="226" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="9"/>
|
||||
<path d="M3.6 9h16.8M3.6 15h16.8"/>
|
||||
<path d="M11.5 3a17 17 0 0 0 0 18M12.5 3a17 17 0 0 1 0 18"/>
|
||||
</svg>
|
||||
<text x="96" y="262" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Web forms</text>
|
||||
<text x="96" y="274" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Survey · admin</text>
|
||||
|
||||
<!-- Legacy (legacy) -->
|
||||
<rect x="8" y="300" width="176" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="16" y="306" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="27" y="315" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="62" y="306" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/>
|
||||
<path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/>
|
||||
<path d="M7 8l0 .01"/><path d="M7 16l0 .01"/>
|
||||
</svg>
|
||||
<text x="96" y="342" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Legacy system</text>
|
||||
<text x="96" y="354" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Trade · mainframe</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
AIRFLOW BAR (Orchestration, x=208 y=52 w=744 h=44 — 12-px inside cluster on both sides)
|
||||
═══════════════════════════════════════════ -->
|
||||
<rect x="208" y="52" width="744" height="44" rx="4" fill="rgba(45,49,66,0.05)" stroke="rgba(45,49,66,0.18)" stroke-width="0.8"/>
|
||||
<svg x="914" y="58" width="24" height="24" viewBox="0 0 24 24" fill="#4f5d75">
|
||||
<path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/>
|
||||
</svg>
|
||||
<text x="580" y="71" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Orchestration / Workflow Automation</text>
|
||||
<text x="580" y="84" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Apache Airflow)</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
CLUSTER NODES (w=152, h=80; first_top_y=120)
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<!-- NiFi (Ingestion, cx=288) -->
|
||||
<rect x="212" y="120" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="220" y="126" width="30" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="235" y="135" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">COLL</text>
|
||||
<svg x="332" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0zm-1.632 3.867c.05 0 .08.034.037.112-.11.197-.218.397-.328.593-.396.702-.819 1.389-1.23 2.08-.196-.032-.39-.06-.585-.088.495-.651 1-1.296 1.48-1.959.153-.209.302-.423.454-.634a.24.24 0 0 1 .172-.104zM7.44 7.186c.221.035.444.076.666.119-.073.129-.15.256-.223.383a29.073 29.073 0 0 0-1.625 3.261c-.874 2.123-1.383 4.444-.77 6.707a8.222 8.222 0 0 0 2.217 3.74c.083.083-.02.216-.119.155a7.568 7.568 0 0 1-.93-.686A7.674 7.674 0 0 1 4.1 16.248c-.329-2.156.387-4.246 1.418-6.115a27.44 27.44 0 0 1 1.92-2.947z"/>
|
||||
</svg>
|
||||
<text x="288" y="166" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="288" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Collect · route · transform</text>
|
||||
|
||||
<!-- Trino (Storage col, top row, cx=480, y=120) -->
|
||||
<rect x="404" y="120" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="412" y="126" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="426" y="135" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">VIRT</text>
|
||||
<svg x="524" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075c.2614-1.2682.8762-3.574 2.0292-5.1958 1.6717-2.352 3.4357-4.7808 4.6116-4.1006 1.176.6802-.3074 3.1398-1.3297 4.4272-1.0222 1.2874-2.7862 3.2089-3.3742 4.2274-.2114.3843-.4304.8032-.5956 1.1529a5.7375 5.7375 0 0 1 2.9169 3.6125h.073v-2.3058a.3075.3075 0 0 0-.1806-.2844.9148.9148 0 0 1-.5573-.8148 1.0184 1.0184 0 0 1 .9045-.9044c.5593-.0598 1.061.3452 1.1208.9044a.9187.9187 0 0 1-.5534.8148.3074.3074 0 0 0-.1691.2844v2.1522a.3113.3113 0 0 0 .1691.2805.9724.9724 0 0 1 .5648.857z"/>
|
||||
</svg>
|
||||
<text x="480" y="166" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="480" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · data virtualization</text>
|
||||
|
||||
<!-- MinIO / S3 (Storage col, bottom row, FOCAL, cx=480, y=216) -->
|
||||
<rect x="404" y="216" width="152" height="80" rx="8" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="412" y="222" width="36" height="12" rx="2" fill="transparent" stroke="rgba(235,108,54,0.50)" stroke-width="0.8"/>
|
||||
<text x="430" y="231" fill="rgba(235,108,54,0.80)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="524" y="222" width="24" height="24" viewBox="0 0 24 24" fill="#eb6c36">
|
||||
<path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Zm-.9024 9.8652v2.9941l-4.1523 2.1484a13.9787 13.9787 0 0 1 2.7676-3.9277 14.1784 14.1784 0 0 1 1.3847-1.2148z"/>
|
||||
</svg>
|
||||
<text x="480" y="262" fill="#eb6c36" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO / S3</text>
|
||||
<text x="480" y="276" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.75">Object store · S3-API</text>
|
||||
|
||||
<!-- Notebooks (Transformation, cx=672) -->
|
||||
<rect x="596" y="120" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="604" y="126" width="32" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="620" y="135" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ANLZ</text>
|
||||
<svg x="716" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424zm.001 13.295c-3.768 0-7.06-1.381-8.768-3.425a9.363 9.363 0 0 0 3.394 4.547A9.238 9.238 0 0 0 12.33 21a9.238 9.238 0 0 0 5.377-1.729 9.363 9.363 0 0 0 3.393-4.547c-1.712 2.044-5.003 3.425-8.772 3.425z"/>
|
||||
</svg>
|
||||
<text x="672" y="166" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Notebooks</text>
|
||||
<text x="672" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Jupyter · Python · R</text>
|
||||
|
||||
<!-- Superset (Visualization, cx=866) -->
|
||||
<rect x="790" y="120" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="798" y="126" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="812" y="135" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">DASH</text>
|
||||
<svg x="910" y="126" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/>
|
||||
</svg>
|
||||
<text x="866" y="166" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Superset</text>
|
||||
<text x="866" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Dashboards · reports</text>
|
||||
|
||||
<!-- ── Kubernetes label (bottom-left of cluster) ── -->
|
||||
<svg x="212" y="348" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4f5d75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19.875 6.27a2.225 2.225 0 0 1 1.125 1.948v7.284c0 .809-.443 1.555-1.158 1.948l-6.75 3.98a2.5 2.5 0 0 1-2.184 0l-6.75-3.98a2.225 2.225 0 0 1-1.158-1.948v-7.284c0-.809.443-1.555 1.158-1.948l6.75-3.98a2.5 2.5 0 0 1 2.184 0z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/>
|
||||
<line x1="20.2" y1="7.5" x2="14.9" y2="10.5"/><line x1="9.1" y1="13.5" x2="3.8" y2="16.5"/>
|
||||
<line x1="20.2" y1="16.5" x2="14.9" y2="13.5"/><line x1="9.1" y1="10.5" x2="3.8" y2="7.5"/>
|
||||
</svg>
|
||||
<text x="236" y="362" fill="#4f5d75" font-size="8" font-weight="500" font-family="'Geist', sans-serif">Kubernetes</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
CROSS-CUTTING BAR #1 — Identity (Security, y=388..428, color=#b85450)
|
||||
═══════════════════════════════════════════ -->
|
||||
<svg x="16" y="398" width="20" height="20" viewBox="0 0 24 24" fill="#b85450">
|
||||
<path d="M12.0001.0036C5.376.0036 0 5.3796 0 12.0036c0 6.6276 5.376 12 12.0001 12 6.6275 0 12-5.3724 12-12 0-6.624-5.3725-12-12-12zm5.532 17.0496c-.612 0-3.972-3.144-5.532-5.046C10.452 13.9356 7.092 17.0796 6.48 17.0796c-1.788 0-2.052-3.564-2.052-5.076 0-1.512.264-5.076 2.052-5.076.612 0 3.972 3.144 5.5201 5.046 1.548-1.932 4.908-5.046 5.532-5.046 1.788 0 2.052 3.564 2.052 5.076 0 1.512-.264 5.076-2.052 5.076z"/>
|
||||
</svg>
|
||||
<text x="484" y="410" fill="#b85450" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Identity Manager</text>
|
||||
<text x="484" y="422" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Keycloak · LDAP · OIDC)</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
CROSS-CUTTING BAR #2 — Monitoring (Observability, y=432..472)
|
||||
═══════════════════════════════════════════ -->
|
||||
<rect x="4" y="432" width="960" height="40" rx="6" fill="rgba(45,49,66,0.05)" stroke="rgba(45,49,66,0.20)" stroke-width="0.8"/>
|
||||
<svg x="16" y="442" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4f5d75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 3v18h18"/>
|
||||
<path d="M7 14l3-3 2 2 5-5"/>
|
||||
<path d="M14 8h3v3"/>
|
||||
</svg>
|
||||
<text x="484" y="454" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Monitoring & Observability</text>
|
||||
<text x="484" y="466" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Prometheus · Grafana · Loki)</text>
|
||||
|
||||
<!-- ═══════════════════════════════════
|
||||
LEGEND (pushed down to clear 2nd crosscut at y=472)
|
||||
═══════════════════════════════════ -->
|
||||
<line x1="40" y1="496" x2="952" y2="496" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="512" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="520" width="20" height="12" rx="3" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="68" y="530" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">External · outside cluster</text>
|
||||
|
||||
<rect x="232" y="520" width="20" height="12" rx="3" fill="rgba(45,49,66,0.04)" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<text x="260" y="530" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Kubernetes boundary</text>
|
||||
|
||||
<rect x="404" y="520" width="20" height="12" rx="3" fill="rgba(235,108,54,0.10)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="432" y="530" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Focal (storage hub)</text>
|
||||
|
||||
<line x1="552" y1="522" x2="572" y2="522" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
<text x="580" y="527" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Primary path</text>
|
||||
|
||||
<line x1="680" y1="522" x2="700" y2="522" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<text x="708" y="527" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Orchestration trigger</text>
|
||||
|
||||
<line x1="836" y1="522" x2="856" y2="522" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-dim)"/>
|
||||
<text x="864" y="527" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Query read-back</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,299 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>High-level architecture · End-to-end stack</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #f5f5f5;
|
||||
--color-ink: #2d3142;
|
||||
--color-muted: #4f5d75;
|
||||
--color-accent: #eb6c36;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
svg { width: 100%; min-width: 960px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture · Diagram Design</p>
|
||||
<h1>High-level architecture · End-to-end stack</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 540" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/>
|
||||
</marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/>
|
||||
</marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto">
|
||||
<polygon points="0 0, 6 2.5, 0 5" fill="#4f5d75"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
PHASE CHEVRON BANNER
|
||||
═══════════════════════════════════════════ -->
|
||||
<polygon points="0,4 188,4 200,18 188,32 0,32" fill="#2d3142"/>
|
||||
<polygon points="200,4 388,4 400,18 388,32 200,32 212,18" fill="#3d4460"/>
|
||||
<polygon points="400,4 588,4 600,18 588,32 400,32 412,18" fill="#2d3142"/>
|
||||
<polygon points="600,4 788,4 800,18 788,32 600,32 612,18" fill="#3d4460"/>
|
||||
<polygon points="800,4 1000,4 1000,32 800,32 812,18" fill="#2d3142"/>
|
||||
|
||||
<text x="100" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DATA SOURCES</text>
|
||||
<text x="300" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">INGESTION</text>
|
||||
<text x="500" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">STORAGE</text>
|
||||
<text x="700" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.13em" text-anchor="middle">TRANSFORMATION & ANALYSIS</text>
|
||||
<text x="900" y="21" fill="#f5f5f5" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">VISUALIZATION</text>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
STRUCTURAL ZONES
|
||||
═══════════════════════════════════════════ -->
|
||||
<!-- Data sources zone (external, dashed border) -->
|
||||
<rect x="4" y="40" width="152" height="336" rx="6" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.20)" stroke-width="0.8" stroke-dasharray="6,3"/>
|
||||
<text x="80" y="52" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SOURCES</text>
|
||||
|
||||
<!-- Kubernetes boundary (solid) -->
|
||||
<rect x="164" y="40" width="832" height="336" rx="8" fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.18)" stroke-width="1.2"/>
|
||||
|
||||
<!-- Identity Manager bar -->
|
||||
<rect x="4" y="388" width="992" height="40" rx="6" fill="rgba(45,49,66,0.05)" stroke="rgba(45,49,66,0.20)" stroke-width="0.8"/>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
ARROWS — drawn before boxes (z-order rule)
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<!-- Data sources → NiFi (staggered entry points) -->
|
||||
<path d="M 144,92 H 156 Q 164,92 164,100 V 112 Q 164,120 176,120" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,172 H 156 Q 164,172 164,164 V 144 Q 164,136 176,136" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,252 H 156 Q 164,252 164,244 V 160 Q 164,152 176,152" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 144,336 H 156 Q 164,336 164,328 V 176 Q 164,168 176,168" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Airflow → tools (dashed orchestration drops) -->
|
||||
<line x1="452" y1="96" x2="452" y2="104" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="682" y1="96" x2="682" y2="144" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
<line x1="896" y1="96" x2="896" y2="144" stroke="#4f5d75" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow-sm)"/>
|
||||
|
||||
<!-- NiFi → Trino (bend up) -->
|
||||
<path d="M 328,152 H 344 Q 348,152 348,148 V 144 Q 348,140 352,140 H 372" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- NiFi → MinIO (bend down — ACCENT primary path) -->
|
||||
<path d="M 328,152 H 344 Q 352,152 352,160 V 240 Q 352,248 372,248" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
|
||||
<!-- Trino → MinIO (vertical, Trino queries storage) -->
|
||||
<line x1="448" y1="176" x2="448" y2="208" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<!-- MinIO → Trino (write-back, dashed) -->
|
||||
<line x1="456" y1="208" x2="456" y2="176" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Trino → Data Modeling (bend down) -->
|
||||
<path d="M 532,140 H 556 Q 564,140 564,148 V 176 Q 564,184 600,184" fill="none" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- MinIO → Data Modeling (bend up — ACCENT primary path) -->
|
||||
<path d="M 532,248 H 556 Q 564,248 564,240 V 192 Q 564,184 600,184" fill="none" stroke="#eb6c36" stroke-width="1.2" marker-end="url(#arrow-accent)"/>
|
||||
|
||||
<!-- Data Modeling → Dashboards (straight) -->
|
||||
<line x1="764" y1="184" x2="820" y2="184" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ═══════════════════════════════════════════
|
||||
NODES
|
||||
═══════════════════════════════════════════ -->
|
||||
|
||||
<!-- ── WEB ── -->
|
||||
<rect x="12" y="60" width="132" height="64" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="12" y="60" width="132" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="20" y="66" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="31" y="75" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="66" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="9"/>
|
||||
<path d="M3.6 9h16.8M3.6 15h16.8"/>
|
||||
<path d="M11.5 3a17 17 0 0 0 0 18M12.5 3a17 17 0 0 1 0 18"/>
|
||||
</svg>
|
||||
<text x="78" y="102" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">WEB</text>
|
||||
<text x="78" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Census · Survey · Admin</text>
|
||||
|
||||
<!-- ── FTP ── -->
|
||||
<rect x="12" y="140" width="132" height="64" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="12" y="140" width="132" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="20" y="146" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="31" y="155" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="146" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/>
|
||||
<path d="M7 9l5 -5l5 5"/>
|
||||
<path d="M12 4l0 12"/>
|
||||
</svg>
|
||||
<text x="78" y="182" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">FTP</text>
|
||||
<text x="78" y="194" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Census · Business</text>
|
||||
|
||||
<!-- ── DB Connection ── -->
|
||||
<rect x="12" y="220" width="132" height="64" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="12" y="220" width="132" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="20" y="226" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="31" y="235" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="226" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0"/>
|
||||
<path d="M4 6v6a8 3 0 0 0 16 0v-6"/>
|
||||
<path d="M4 12v6a8 3 0 0 0 16 0v-6"/>
|
||||
</svg>
|
||||
<text x="78" y="262" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">DB Connection</text>
|
||||
<text x="78" y="274" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">CDC · SQL · API</text>
|
||||
|
||||
<!-- ── Legacy System ── -->
|
||||
<rect x="12" y="304" width="132" height="64" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="12" y="304" width="132" height="64" rx="6" fill="white" stroke="rgba(45,49,66,0.22)" stroke-width="1"/>
|
||||
<rect x="20" y="310" width="22" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="31" y="319" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">EXT</text>
|
||||
<svg x="66" y="310" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#2d3142" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3"/>
|
||||
<path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2"/>
|
||||
<path d="M7 8l0 .01"/><path d="M7 16l0 .01"/>
|
||||
</svg>
|
||||
<text x="78" y="346" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Legacy System</text>
|
||||
<text x="78" y="358" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Trade data</text>
|
||||
|
||||
<!-- ── Airflow orchestration bar ── -->
|
||||
<rect x="176" y="52" width="808" height="44" rx="4" fill="rgba(45,49,66,0.05)" stroke="rgba(45,49,66,0.18)" stroke-width="0.8"/>
|
||||
<svg x="950" y="58" width="24" height="24" viewBox="0 0 24 24" fill="#4f5d75">
|
||||
<path d="M17.195 16.822l4.002-4.102C23.55 10.308 23.934 5.154 24 .43a.396.396 0 0 0-.246-.373.392.392 0 0 0-.437.09l-6.495 6.658-4.102-4.003C10.309.45 5.154.066.43 0H.423a.397.397 0 0 0-.277.683l6.658 6.494-4.003 4.103C.45 13.692.065 18.846 0 23.57a.398.398 0 0 0 .683.282l6.494-6.657 3.934 3.837.17.165c2.41 2.353 7.565 2.737 12.288 2.803h.006a.397.397 0 0 0 .277-.683l-6.657-6.495z"/>
|
||||
</svg>
|
||||
<text x="564" y="71" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Orchestration / Workflow Automation</text>
|
||||
<text x="564" y="84" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Apache Airflow)</text>
|
||||
|
||||
<!-- ── Apache NiFi ── -->
|
||||
<rect x="176" y="112" width="152" height="80" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="176" y="112" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="184" y="118" width="30" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="199" y="127" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">COLL</text>
|
||||
<svg x="240" y="118" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M11.648 0a.093.093 0 0 0-.084.053A30.71 30.71 0 0 1 8.592 4.73c-2.09 2.728-5.145 6.466-5.145 10.364a8.201 8.201 0 0 0 8.201 8.2v-5.003c0-.106.087-.193.194-.193h2.81v-2.813c0-.106.087-.191.194-.191h5.004c0-3.9-3.056-7.636-5.145-10.364A30.712 30.712 0 0 1 11.732.053.094.094 0 0 0 11.648 0zm-1.632 3.867c.05 0 .08.034.037.112-.11.197-.218.397-.328.593-.396.702-.819 1.389-1.23 2.08-.196-.032-.39-.06-.585-.088.495-.651 1-1.296 1.48-1.959.153-.209.302-.423.454-.634a.24.24 0 0 1 .172-.104zM7.44 7.186c.221.035.444.076.666.119-.073.129-.15.256-.223.383a29.073 29.073 0 0 0-1.625 3.261c-.874 2.123-1.383 4.444-.77 6.707a8.222 8.222 0 0 0 2.217 3.74c.083.083-.02.216-.119.155a7.568 7.568 0 0 1-.93-.686A7.674 7.674 0 0 1 4.1 16.248c-.329-2.156.387-4.246 1.418-6.115a27.44 27.44 0 0 1 1.92-2.947zm7.931 8.435a.193.193 0 0 0-.191.191V18.3h2.677V15.62zm3.299 0V18.3h1.348a7.975 7.975 0 0 0 .515-2.678zm-6.303 3.004a.193.193 0 0 0-.191.193v2.485h2.678v-2.678Zm3.295.484v2.68h2.115a.562.562 0 0 0 .399-.162v-.004a.562.562 0 0 0 .16-.397V19.11zm3.674.182v1.98a7.999 7.999 0 0 0 1.217-1.98zm-6.969 2.824a.193.193 0 0 0-.191.192v1.672a7.997 7.997 0 0 0 2.678-.516v-1.348zm3.48.668V24a8.008 8.008 0 0 0 1.98-1.217z"/>
|
||||
</svg>
|
||||
<text x="252" y="154" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Apache NiFi</text>
|
||||
<text x="252" y="166" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Collect · route · transform</text>
|
||||
|
||||
<!-- ── Trino ── -->
|
||||
<rect x="372" y="104" width="160" height="72" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="372" y="104" width="160" height="72" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="380" y="110" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="394" y="119" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">VIRT</text>
|
||||
<svg x="440" y="110" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M14.124 16.8529a.1615.1615 0 1 1 .1576.1614.1577.1577 0 0 1-.1576-.1614zm-5.607-.1576a.1614.1614 0 1 0 0 .3228.1614.1614 0 0 0 0-.3228zm10.1341-.6648v1.9869c-.031.5788-.524 1.0237-1.1029.9954h-.3843a5.0596 5.0596 0 0 1-1.1298 1.7178.3192.3192 0 0 0 0 .465l.2382.2191a.3036.3036 0 0 1 .0385.4304c-1.126 1.3835-2.9669 2.1521-5.0498 2.1521a6.575 6.575 0 0 1-4.8192-1.8985c-.0029-.0032-.0059-.0063-.0087-.0096a.6302.6302 0 0 1 .0548-.8896c.137-.1265.1371-.3462 0-.4727a4.944 4.944 0 0 1-1.126-1.714h-.3497c-.5797.0284-1.0737-.416-1.1068-.9954v-1.9869c.0351-.5779.5286-1.02 1.1068-.9915h.2728a5.7648 5.7648 0 0 1 2.0791-3.0936c-.4227-1.0991-1.1529-3.2551-1.226-5.0075C6.0229 4.4705 6.2189.078 7.8253.001c1.6064-.0768 1.3719 4.0275 1.0991 6.6946a32.732 32.732 0 0 0-.123 4.4503 6.994 6.994 0 0 1 2.4826-.4304 7.2414 7.2414 0 0 1 1.7371.2075c.2614-1.2682.8762-3.574 2.0292-5.1958 1.6717-2.352 3.4357-4.7808 4.6116-4.1006 1.176.6802-.3074 3.1398-1.3297 4.4272-1.0222 1.2874-2.7862 3.2089-3.3742 4.2274-.2114.3843-.4304.8032-.5956 1.1529a5.7375 5.7375 0 0 1 2.9169 3.6125h.073v-2.3058a.3075.3075 0 0 0-.1806-.2844.9148.9148 0 0 1-.5573-.8148 1.0184 1.0184 0 0 1 .9045-.9044c.5593-.0598 1.061.3452 1.1208.9044a.9187.9187 0 0 1-.5534.8148.3074.3074 0 0 0-.1691.2844v2.1522a.3113.3113 0 0 0 .1691.2805.9724.9724 0 0 1 .5648.857z"/>
|
||||
</svg>
|
||||
<text x="452" y="146" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Trino</text>
|
||||
<text x="452" y="158" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">SQL · data virtualization</text>
|
||||
|
||||
<!-- ── MinIO / S3 (FOCAL) ── -->
|
||||
<rect x="372" y="208" width="160" height="80" rx="8" fill="#f5f5f5"/>
|
||||
<rect x="372" y="208" width="160" height="80" rx="8" fill="rgba(235,108,54,0.08)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="380" y="214" width="36" height="12" rx="2" fill="transparent" stroke="rgba(235,108,54,0.50)" stroke-width="0.8"/>
|
||||
<text x="398" y="223" fill="rgba(235,108,54,0.80)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">STORE</text>
|
||||
<svg x="440" y="214" width="24" height="24" viewBox="0 0 24 24" fill="#eb6c36">
|
||||
<path d="M13.2072.006c-.6216-.0478-1.2.1943-1.6211.582a2.15 2.15 0 0 0-.0938 3.0352l3.4082 3.5507a3.042 3.042 0 0 1-.664 4.6875l-.463.2383V7.2853a15.4198 15.4198 0 0 0-8.0174 10.4862v.0176l6.5487-3.3281v7.621L13.7794 24V13.6817l.8965-.4629a4.4432 4.4432 0 0 0 1.2207-7.0292l-3.371-3.5254a.7489.7489 0 0 1 .037-1.0547.7522.7522 0 0 1 1.0567.0371l.4668.4863-.006.0059 4.0704 4.2441a.0566.0566 0 0 0 .082 0 .06.06 0 0 0 0-.0703l-3.1406-5.1425-.1484.1425.1484-.1445C14.4945.3926 13.8287.0538 13.2072.006Zm-.9024 9.8652v2.9941l-4.1523 2.1484a13.9787 13.9787 0 0 1 2.7676-3.9277 14.1784 14.1784 0 0 1 1.3847-1.2148z"/>
|
||||
</svg>
|
||||
<text x="452" y="252" fill="#eb6c36" font-size="12" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">MinIO / S3</text>
|
||||
<text x="452" y="268" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" opacity="0.75">Object store · S3-API</text>
|
||||
|
||||
<!-- ── Data Modeling & Analysis ── -->
|
||||
<rect x="600" y="144" width="164" height="80" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="600" y="144" width="164" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="608" y="150" width="32" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="624" y="159" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">ANLZ</text>
|
||||
<svg x="670" y="150" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M7.157 22.201A1.784 1.799 0 0 1 5.374 24a1.784 1.799 0 0 1-1.784-1.799 1.784 1.799 0 0 1 1.784-1.799 1.784 1.799 0 0 1 1.783 1.799zM20.582 1.427a1.415 1.427 0 0 1-1.415 1.428 1.415 1.427 0 0 1-1.416-1.428A1.415 1.427 0 0 1 19.167 0a1.415 1.427 0 0 1 1.415 1.427zM4.992 3.336A1.047 1.056 0 0 1 3.946 4.39a1.047 1.056 0 0 1-1.047-1.055A1.047 1.056 0 0 1 3.946 2.28a1.047 1.056 0 0 1 1.046 1.056zm7.336 1.517c3.769 0 7.06 1.38 8.768 3.424a9.363 9.363 0 0 0-3.393-4.547 9.238 9.238 0 0 0-5.377-1.728A9.238 9.238 0 0 0 6.95 3.73a9.363 9.363 0 0 0-3.394 4.547c1.713-2.04 5.004-3.424 8.772-3.424zm.001 13.295c-3.768 0-7.06-1.381-8.768-3.425a9.363 9.363 0 0 0 3.394 4.547A9.238 9.238 0 0 0 12.33 21a9.238 9.238 0 0 0 5.377-1.729 9.363 9.363 0 0 0 3.393-4.547c-1.712 2.044-5.003 3.425-8.772 3.425z"/>
|
||||
</svg>
|
||||
<text x="682" y="186" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Data Modeling</text>
|
||||
<text x="682" y="198" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Notebook · Python · R</text>
|
||||
|
||||
<!-- ── Dashboards & Report ── -->
|
||||
<rect x="820" y="144" width="152" height="80" rx="6" fill="#f5f5f5"/>
|
||||
<rect x="820" y="144" width="152" height="80" rx="6" fill="white" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<rect x="828" y="150" width="28" height="12" rx="2" fill="transparent" stroke="rgba(45,49,66,0.28)" stroke-width="0.8"/>
|
||||
<text x="842" y="159" fill="rgba(45,49,66,0.55)" font-size="7" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.08em">DASH</text>
|
||||
<svg x="884" y="150" width="24" height="24" viewBox="0 0 24 24" fill="#2d3142">
|
||||
<path d="M6.168 6.045C2.603 6.045 0 8.579 0 12.014c0 3.434 2.603 5.941 6.168 5.941 2.184 0 3.888-1.026 5.775-3.078 1.53 2.033 4.037 3.136 5.89 3.078 3.566 0 6.167-2.503 6.167-5.941 0-3.438-2.601-5.97-6.168-5.97-2.864 0-5.138 2.425-5.771 3.173-.76-.9-1.674-1.665-2.682-2.274-1.019-.588-2.084-.898-3.211-.898z"/>
|
||||
</svg>
|
||||
<text x="896" y="186" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Dashboards</text>
|
||||
<text x="896" y="198" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">Superset · reports</text>
|
||||
|
||||
<!-- ── Kubernetes label (bottom-left of boundary) ── -->
|
||||
<svg x="180" y="348" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4f5d75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19.875 6.27a2.225 2.225 0 0 1 1.125 1.948v7.284c0 .809-.443 1.555-1.158 1.948l-6.75 3.98a2.5 2.5 0 0 1-2.184 0l-6.75-3.98a2.225 2.225 0 0 1-1.158-1.948v-7.284c0-.809.443-1.555 1.158-1.948l6.75-3.98a2.5 2.5 0 0 1 2.184 0z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<line x1="12" y1="3" x2="12" y2="9"/><line x1="12" y1="15" x2="12" y2="21"/>
|
||||
<line x1="20.2" y1="7.5" x2="14.9" y2="10.5"/><line x1="9.1" y1="13.5" x2="3.8" y2="16.5"/>
|
||||
<line x1="20.2" y1="16.5" x2="14.9" y2="13.5"/><line x1="9.1" y1="10.5" x2="3.8" y2="7.5"/>
|
||||
</svg>
|
||||
<text x="204" y="362" fill="#4f5d75" font-size="8" font-weight="500" font-family="'Geist', sans-serif">Kubernetes</text>
|
||||
|
||||
<!-- ── Identity Manager bar ── -->
|
||||
<svg x="16" y="398" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#4f5d75" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1-8.5 15a12 12 0 0 1-8.5-15a12 12 0 0 0 8.5-3"/>
|
||||
<circle cx="12" cy="11" r="1" fill="#4f5d75"/>
|
||||
<line x1="12" y1="12" x2="12" y2="14.5"/>
|
||||
</svg>
|
||||
<text x="500" y="410" fill="#2d3142" font-size="10" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Identity Manager</text>
|
||||
<text x="500" y="422" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">(Active Directory)</text>
|
||||
|
||||
<!-- ═══════════════════════════════════
|
||||
LEGEND
|
||||
═══════════════════════════════════ -->
|
||||
<line x1="40" y1="440" x2="960" y2="440" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="456" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="464" width="20" height="12" rx="3" fill="transparent" stroke="rgba(45,49,66,0.30)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="68" y="474" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">External source · outside cluster</text>
|
||||
|
||||
<rect x="252" y="464" width="20" height="12" rx="3" fill="rgba(45,49,66,0.04)" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<text x="280" y="474" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Kubernetes boundary</text>
|
||||
|
||||
<rect x="436" y="464" width="20" height="12" rx="3" fill="rgba(235,108,54,0.10)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="464" y="474" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Focal component (MinIO)</text>
|
||||
|
||||
<line x1="648" y1="466" x2="668" y2="466" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="676" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Primary data path</text>
|
||||
|
||||
<line x1="808" y1="466" x2="828" y2="466" stroke="rgba(45,49,66,0.35)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="836" y="471" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Orchestration trigger</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Northwind Retail · Current IT Landscape · Dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --paper:#2d3142; --paper-2:#393e53; --ink:#f5f5f5; --muted:#bfc0c0; --soft:#8e98ac; --rule:rgba(245,245,245,0.12); --accent:#f08a59; --link:#6a95d8; --sans:'Geist',system-ui,sans-serif; --serif:'Instrument Serif',serif; --mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { min-height:100vh; display:flex; align-items:center; justify-content:center; padding:3rem 2rem; background:var(--paper); color:var(--ink); font-family:var(--sans); }
|
||||
.frame { width:100%; max-width:1200px; }
|
||||
.eyebrow { margin-bottom:.5rem; color:var(--muted); font:500 .66rem/1 var(--mono); letter-spacing:.18em; text-transform:uppercase; }
|
||||
h1 { margin-bottom:.35rem; color:var(--ink); font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif); letter-spacing:-.02em; }
|
||||
.subtitle { margin-bottom:1.5rem; color:var(--muted); font-size:.9rem; line-height:1.5; }
|
||||
.diagram { overflow-x:auto; } svg { display:block; width:100%; min-width:920px; }
|
||||
svg .zone { fill:rgba(245,245,245,.04); stroke:rgba(245,245,245,.14); stroke-width:.8; }
|
||||
svg .zone-mask, svg .node-mask, svg .label-mask { fill:#2d3142; }
|
||||
svg .zone-label, svg .arrow-label, svg .legend-label { font-family:'Geist Mono',monospace; }
|
||||
svg .zone-label { fill:rgba(245,245,245,.40); font-size:8px; letter-spacing:.14em; }
|
||||
svg .node { fill:rgba(245,245,245,.04); stroke:rgba(245,245,245,.32); stroke-width:1; }
|
||||
svg .node.external { stroke:#bfc0c0; stroke-dasharray:4 3; }
|
||||
svg .node.focal { fill:rgba(240,138,89,.12); stroke:#f08a59; stroke-width:1.4; }
|
||||
svg .node.survivor { fill:rgba(154,172,103,.10); stroke:rgba(154,172,103,.55); }
|
||||
svg .node-name { fill:#f5f5f5; font:600 12px 'Geist',sans-serif; }
|
||||
svg .node-name.survivor { fill:#9aac67; }
|
||||
svg .sub { fill:#bfc0c0; font:400 10px 'Geist Mono',monospace; } svg .sub.focal { fill:#f08a59; }
|
||||
svg .arrow-label { font-size:8px; font-weight:600; letter-spacing:.08em; } svg .legend-label { fill:#bfc0c0; font-size:8px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">IT current-state · Northwind Retail</p>
|
||||
<h1>Legacy retail reporting landscape</h1>
|
||||
<p class="subtitle">File exports, desktop spreadsheets, and manual publication before a shared data platform.</p>
|
||||
<div class="diagram">
|
||||
<svg viewBox="0 0 960 500" role="img" aria-labelledby="it-dark-title it-dark-desc" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="it-dark-title">Northwind Retail current IT landscape</title><desc id="it-dark-desc">Three zones show collection, processing, and dissemination through manual file handoffs.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#bfc0c0"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#6a95d8"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#f08a59"/></marker>
|
||||
<symbol id="icon-file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2"/></symbol>
|
||||
<symbol id="icon-screen" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="1"/><path d="M7 20h10M9 16v4M15 16v4"/></symbol>
|
||||
<symbol id="icon-db" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/></symbol>
|
||||
<symbol id="icon-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="9" cy="8" r="4"/><path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2M16 4.5a4 4 0 0 1 0 7M18 15a4 4 0 0 1 3 4v2"/></symbol>
|
||||
</defs>
|
||||
<rect width="960" height="500" fill="#2d3142"/>
|
||||
<rect class="zone" x="16" y="52" width="256" height="360" rx="8"/><rect class="zone-mask" x="36" y="44" width="92" height="16"/><text class="zone-label" x="40" y="56">COLLECTION</text>
|
||||
<rect class="zone" x="292" y="52" width="360" height="360" rx="8"/><rect class="zone-mask" x="312" y="44" width="92" height="16"/><text class="zone-label" x="316" y="56">PROCESSING</text>
|
||||
<rect class="zone" x="672" y="52" width="272" height="360" rx="8"/><rect class="zone-mask" x="692" y="44" width="116" height="16"/><text class="zone-label" x="696" y="56">DISSEMINATION</text>
|
||||
|
||||
<path d="M 252,108 H 340" fill="none" stroke="#6a95d8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<path d="M 252,196 H 308 Q 316,196 316,188 V 132 Q 316,124 324,124 H 340" fill="none" stroke="#6a95d8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<path d="M 252,284 H 324 Q 332,284 332,276 V 148 Q 332,140 340,140" fill="none" stroke="#6a95d8" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#arrow-link)"/>
|
||||
<line x1="472" y1="148" x2="472" y2="232" stroke="#f08a59" stroke-width="1.4" stroke-dasharray="4 3" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="472" y1="304" x2="472" y2="344" stroke="#bfc0c0" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 604,268 H 654 Q 662,268 662,260 V 72 Q 662,64 670,64 H 800 Q 808,64 808,72 V 80" fill="none" stroke="#f08a59" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="808" y1="140" x2="808" y2="216" stroke="#6a95d8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<line x1="808" y1="272" x2="808" y2="344" stroke="#6a95d8" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#arrow-link)"/>
|
||||
|
||||
<rect class="node-mask" x="36" y="80" width="216" height="56" rx="6"/><rect class="node" x="36" y="80" width="216" height="56" rx="6"/><use href="#icon-file" x="48" y="96" width="24" height="24" color="#f5f5f5"/><text class="node-name" x="80" y="106">Point-of-Sale Exports</text><text class="sub" x="80" y="122">nightly · CSV</text>
|
||||
<rect class="node-mask" x="36" y="168" width="216" height="56" rx="6"/><rect class="node" x="36" y="168" width="216" height="56" rx="6"/><use href="#icon-file" x="48" y="184" width="24" height="24" color="#f5f5f5"/><text class="node-name" x="80" y="194">Online Store Export</text><text class="sub" x="80" y="210">orders · flat file</text>
|
||||
<rect class="node-mask" x="36" y="256" width="216" height="56" rx="6"/><rect class="node external" x="36" y="256" width="216" height="56" rx="6"/><use href="#icon-file" x="48" y="272" width="24" height="24" color="#bfc0c0"/><text class="node-name" x="80" y="282">Supplier Price Lists</text><text class="sub" x="80" y="298">external · XLSX</text>
|
||||
<rect class="node-mask" x="340" y="80" width="264" height="68" rx="6"/><rect class="node focal" x="340" y="80" width="264" height="68" rx="6"/><use href="#icon-file" x="352" y="100" width="24" height="24" color="#f08a59"/><text class="node-name" x="384" y="108">Shared Drive</text><text class="sub focal" x="384" y="124">no version control</text><text class="sub" x="384" y="140">department folders</text>
|
||||
<rect class="node-mask" x="340" y="232" width="264" height="72" rx="6"/><rect class="node" x="340" y="232" width="264" height="72" rx="6"/><use href="#icon-screen" x="352" y="256" width="24" height="24" color="#f5f5f5"/><text class="node-name" x="384" y="262">Spreadsheet Handoffs</text><text class="sub" x="384" y="278">local workbooks · macros</text><text class="sub" x="384" y="292">manual reconciliation</text>
|
||||
<rect class="node-mask" x="340" y="344" width="264" height="56" rx="6"/><rect class="node survivor" x="340" y="344" width="264" height="56" rx="6"/><use href="#icon-db" x="352" y="360" width="24" height="24" color="#9aac67"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
<rect class="node-mask" x="704" y="80" width="208" height="60" rx="6"/><rect class="node focal" x="704" y="80" width="208" height="60" rx="6"/><use href="#icon-screen" x="716" y="100" width="24" height="24" color="#f08a59"/><text class="node-name" x="748" y="108">Reporting Portal</text><text class="sub focal" x="748" y="124">manual refresh</text>
|
||||
<rect class="node-mask" x="704" y="216" width="208" height="56" rx="6"/><rect class="node" x="704" y="216" width="208" height="56" rx="6"/><use href="#icon-file" x="716" y="232" width="24" height="24" color="#f5f5f5"/><text class="node-name" x="748" y="242">Email Report Packs</text><text class="sub" x="748" y="258">weekly · PDF</text>
|
||||
<rect class="node-mask" x="704" y="344" width="208" height="56" rx="6"/><rect class="node external" x="704" y="344" width="208" height="56" rx="6"/><use href="#icon-users" x="716" y="360" width="24" height="24" color="#bfc0c0"/><text class="node-name" x="748" y="370">Regional Managers</text><text class="sub" x="748" y="386">12 store regions</text>
|
||||
|
||||
<rect class="label-mask" x="260" y="86" width="40" height="16" rx="2"/><text class="arrow-label" x="280" y="98" text-anchor="middle" fill="#6a95d8">CSV</text>
|
||||
<rect class="label-mask" x="260" y="174" width="52" height="16" rx="2"/><text class="arrow-label" x="286" y="186" text-anchor="middle" fill="#6a95d8">EXPORT</text>
|
||||
<rect class="label-mask" x="260" y="262" width="44" height="16" rx="2"/><text class="arrow-label" x="282" y="274" text-anchor="middle" fill="#6a95d8">XLSX</text>
|
||||
<rect class="label-mask" x="478" y="156" width="40" height="16" rx="2"/><text class="arrow-label" x="498" y="168" text-anchor="middle" fill="#f08a59">COPY</text>
|
||||
<rect class="label-mask" x="478" y="312" width="40" height="16" rx="2"/><text class="arrow-label" x="498" y="324" text-anchor="middle" fill="#bfc0c0">LOAD</text>
|
||||
<rect class="label-mask" x="612" y="246" width="44" height="16" rx="2"/><text class="arrow-label" x="634" y="258" text-anchor="middle" fill="#f08a59">XLSX</text>
|
||||
<rect class="label-mask" x="814" y="148" width="36" height="16" rx="2"/><text class="arrow-label" x="832" y="160" text-anchor="middle" fill="#6a95d8">PDF</text>
|
||||
<rect class="label-mask" x="814" y="280" width="48" height="16" rx="2"/><text class="arrow-label" x="838" y="292" text-anchor="middle" fill="#6a95d8">EMAIL</text>
|
||||
|
||||
<line x1="16" y1="440" x2="944" y2="440" stroke="rgba(245,245,245,.12)" stroke-width="0.8"/><text class="legend-label" x="16" y="460" letter-spacing=".14em">LEGEND</text>
|
||||
<line x1="80" y1="456" x2="104" y2="456" stroke="#6a95d8" stroke-width="1.2"/><text class="legend-label" x="112" y="460">data flow</text>
|
||||
<line x1="208" y1="456" x2="232" y2="456" stroke="#f08a59" stroke-width="1.4"/><text class="legend-label" x="240" y="460">pain-point</text>
|
||||
<rect x="372" y="450" width="16" height="12" rx="2" fill="rgba(245,245,245,.04)" stroke="#bfc0c0" stroke-width=".8" stroke-dasharray="4 3"/><text class="legend-label" x="396" y="460">external</text>
|
||||
<rect x="492" y="450" width="16" height="12" rx="2" fill="rgba(240,138,89,.12)" stroke="#f08a59"/><text class="legend-label" x="516" y="460">bottleneck</text>
|
||||
</svg>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Northwind Retail · Current IT Landscape · Editorial</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing:border-box; margin:0; padding:0; }
|
||||
:root { --paper:#f5f5f5; --paper-2:#ececec; --ink:#2d3142; --muted:#4f5d75; --soft:#7a8399; --rule:rgba(45,49,66,.12); --accent:#eb6c36; --link:#2e5aa8; --sans:'Geist',system-ui,sans-serif; --serif:'Instrument Serif',serif; --mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { min-height:100vh; padding:3rem 2rem; background:var(--paper); color:var(--ink); font-family:var(--sans); }
|
||||
.container { max-width:1200px; margin:0 auto; } .header { margin-bottom:2.5rem; }
|
||||
.header-eyebrow,.card .eyebrow { color:var(--muted); font-family:var(--mono); font-weight:500; letter-spacing:.18em; text-transform:uppercase; }
|
||||
.header-eyebrow { margin-bottom:.75rem; font-size:.66rem; }
|
||||
h1 { margin-bottom:.5rem; font:400 clamp(1.75rem,3vw + 1rem,2.5rem)/1.1 var(--serif); letter-spacing:-.02em; }
|
||||
.subtitle { max-width:68ch; color:var(--muted); font-size:1rem; line-height:1.55; }
|
||||
.diagram-container { overflow-x:auto; padding:1.5rem; background:var(--paper-2); border:1px solid var(--rule); border-radius:8px; }
|
||||
svg { display:block; width:100%; min-width:920px; }
|
||||
svg .zone { fill:rgba(45,49,66,.02); stroke:rgba(45,49,66,.10); stroke-width:.8; } svg .zone-mask,svg .node-mask,svg .label-mask { fill:#f5f5f5; }
|
||||
svg .zone-label,svg .arrow-label,svg .legend-label { font-family:'Geist Mono',monospace; } svg .zone-label { fill:rgba(45,49,66,.40); font-size:8px; letter-spacing:.14em; }
|
||||
svg .node { fill:#fff; stroke:#2d3142; stroke-width:1; } svg .node.external { stroke:#4f5d75; stroke-dasharray:4 3; } svg .node.focal { fill:rgba(235,108,54,.07); stroke:#eb6c36; stroke-width:1.4; } svg .node.survivor { fill:rgba(122,140,71,.06); stroke:rgba(122,140,71,.45); }
|
||||
svg .node-name { fill:#2d3142; font:600 12px 'Geist',sans-serif; } svg .node-name.survivor { fill:#7a8c47; } svg .sub { fill:#4f5d75; font:400 10px 'Geist Mono',monospace; } svg .sub.focal { fill:#eb6c36; }
|
||||
svg .arrow-label { font-size:8px; font-weight:600; letter-spacing:.08em; } svg .legend-label { fill:#4f5d75; font-size:8px; }
|
||||
.cards { display:grid; grid-template-columns:1.1fr 1fr .9fr; gap:1rem; margin-top:1.5rem; } @media(max-width:820px){.cards{grid-template-columns:1fr;}}
|
||||
.card { padding:1.25rem; background:#fff; border:1px solid var(--rule); border-radius:6px; } .card .eyebrow { margin-bottom:.5rem; font-size:.5rem; }
|
||||
.card-header { display:flex; align-items:center; gap:.6rem; margin-bottom:.875rem; padding-bottom:.875rem; border-bottom:1px solid rgba(45,49,66,.08); }
|
||||
.card-dot { width:7px; height:7px; flex:0 0 auto; border-radius:50%; } .card-dot.coral{background:var(--accent)} .card-dot.link{background:var(--link)} .card-dot.muted{background:var(--muted)}
|
||||
.card h2 { font-size:.875rem; font-weight:600; } .card p,.card ul { color:var(--muted); font-size:.8125rem; line-height:1.55; list-style:none; }
|
||||
.card li { position:relative; margin-bottom:.3rem; padding-left:.875rem; } .card li::before { content:'—'; position:absolute; left:0; color:rgba(45,49,66,.25); }
|
||||
.footer { display:flex; justify-content:space-between; flex-wrap:wrap; gap:.5rem; margin-top:2rem; padding-top:1.5rem; color:var(--soft); border-top:1px solid rgba(45,49,66,.10); font:400 .72rem/1.4 var(--mono); letter-spacing:.06em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<header class="header"><p class="header-eyebrow">IT current-state · Northwind Retail</p><h1>Legacy retail reporting landscape</h1><p class="subtitle">Sales and supplier files converge on a shared drive, move through desktop workbooks, and reach regional teams through a manually refreshed portal and emailed report packs.</p></header>
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 960 500" role="img" aria-labelledby="it-full-title it-full-desc" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="it-full-title">Northwind Retail current IT landscape</title><desc id="it-full-desc">Three zones show collection, processing, and dissemination through manual file handoffs.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker><marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/></marker><marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<symbol id="icon-file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2"/></symbol><symbol id="icon-screen" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="1"/><path d="M7 20h10M9 16v4M15 16v4"/></symbol><symbol id="icon-db" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/></symbol><symbol id="icon-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="9" cy="8" r="4"/><path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2M16 4.5a4 4 0 0 1 0 7M18 15a4 4 0 0 1 3 4v2"/></symbol>
|
||||
</defs>
|
||||
<rect width="960" height="500" fill="#f5f5f5"/>
|
||||
<rect class="zone" x="16" y="52" width="256" height="360" rx="8"/><rect class="zone-mask" x="36" y="44" width="92" height="16"/><text class="zone-label" x="40" y="56">COLLECTION</text><rect class="zone" x="292" y="52" width="360" height="360" rx="8"/><rect class="zone-mask" x="312" y="44" width="92" height="16"/><text class="zone-label" x="316" y="56">PROCESSING</text><rect class="zone" x="672" y="52" width="272" height="360" rx="8"/><rect class="zone-mask" x="692" y="44" width="116" height="16"/><text class="zone-label" x="696" y="56">DISSEMINATION</text>
|
||||
<path d="M 252,108 H 340" fill="none" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/><path d="M 252,196 H 308 Q 316,196 316,188 V 132 Q 316,124 324,124 H 340" fill="none" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/><path d="M 252,284 H 324 Q 332,284 332,276 V 148 Q 332,140 340,140" fill="none" stroke="#2e5aa8" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#arrow-link)"/>
|
||||
<line x1="472" y1="148" x2="472" y2="232" stroke="#eb6c36" stroke-width="1.4" stroke-dasharray="4 3" marker-end="url(#arrow-accent)"/><line x1="472" y1="304" x2="472" y2="344" stroke="#4f5d75" marker-end="url(#arrow)"/><path d="M 604,268 H 654 Q 662,268 662,260 V 72 Q 662,64 670,64 H 800 Q 808,64 808,72 V 80" fill="none" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/><line x1="808" y1="140" x2="808" y2="216" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/><line x1="808" y1="272" x2="808" y2="344" stroke="#2e5aa8" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#arrow-link)"/>
|
||||
<rect class="node-mask" x="36" y="80" width="216" height="56" rx="6"/><rect class="node" x="36" y="80" width="216" height="56" rx="6"/><use href="#icon-file" x="48" y="96" width="24" height="24" color="#2d3142"/><text class="node-name" x="80" y="106">Point-of-Sale Exports</text><text class="sub" x="80" y="122">nightly · CSV</text>
|
||||
<rect class="node-mask" x="36" y="168" width="216" height="56" rx="6"/><rect class="node" x="36" y="168" width="216" height="56" rx="6"/><use href="#icon-file" x="48" y="184" width="24" height="24" color="#2d3142"/><text class="node-name" x="80" y="194">Online Store Export</text><text class="sub" x="80" y="210">orders · flat file</text>
|
||||
<rect class="node-mask" x="36" y="256" width="216" height="56" rx="6"/><rect class="node external" x="36" y="256" width="216" height="56" rx="6"/><use href="#icon-file" x="48" y="272" width="24" height="24" color="#4f5d75"/><text class="node-name" x="80" y="282">Supplier Price Lists</text><text class="sub" x="80" y="298">external · XLSX</text>
|
||||
<rect class="node-mask" x="340" y="80" width="264" height="68" rx="6"/><rect class="node focal" x="340" y="80" width="264" height="68" rx="6"/><use href="#icon-file" x="352" y="100" width="24" height="24" color="#eb6c36"/><text class="node-name" x="384" y="108">Shared Drive</text><text class="sub focal" x="384" y="124">no version control</text><text class="sub" x="384" y="140">department folders</text>
|
||||
<rect class="node-mask" x="340" y="232" width="264" height="72" rx="6"/><rect class="node" x="340" y="232" width="264" height="72" rx="6"/><use href="#icon-screen" x="352" y="256" width="24" height="24" color="#2d3142"/><text class="node-name" x="384" y="262">Spreadsheet Handoffs</text><text class="sub" x="384" y="278">local workbooks · macros</text><text class="sub" x="384" y="292">manual reconciliation</text>
|
||||
<rect class="node-mask" x="340" y="344" width="264" height="56" rx="6"/><rect class="node survivor" x="340" y="344" width="264" height="56" rx="6"/><use href="#icon-db" x="352" y="360" width="24" height="24" color="#7a8c47"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
<rect class="node-mask" x="704" y="80" width="208" height="60" rx="6"/><rect class="node focal" x="704" y="80" width="208" height="60" rx="6"/><use href="#icon-screen" x="716" y="100" width="24" height="24" color="#eb6c36"/><text class="node-name" x="748" y="108">Reporting Portal</text><text class="sub focal" x="748" y="124">manual refresh</text>
|
||||
<rect class="node-mask" x="704" y="216" width="208" height="56" rx="6"/><rect class="node" x="704" y="216" width="208" height="56" rx="6"/><use href="#icon-file" x="716" y="232" width="24" height="24" color="#2d3142"/><text class="node-name" x="748" y="242">Email Report Packs</text><text class="sub" x="748" y="258">weekly · PDF</text>
|
||||
<rect class="node-mask" x="704" y="344" width="208" height="56" rx="6"/><rect class="node external" x="704" y="344" width="208" height="56" rx="6"/><use href="#icon-users" x="716" y="360" width="24" height="24" color="#4f5d75"/><text class="node-name" x="748" y="370">Regional Managers</text><text class="sub" x="748" y="386">12 store regions</text>
|
||||
<rect class="label-mask" x="260" y="86" width="40" height="16" rx="2"/><text class="arrow-label" x="280" y="98" text-anchor="middle" fill="#2e5aa8">CSV</text><rect class="label-mask" x="260" y="174" width="52" height="16" rx="2"/><text class="arrow-label" x="286" y="186" text-anchor="middle" fill="#2e5aa8">EXPORT</text><rect class="label-mask" x="260" y="262" width="44" height="16" rx="2"/><text class="arrow-label" x="282" y="274" text-anchor="middle" fill="#2e5aa8">XLSX</text>
|
||||
<rect class="label-mask" x="478" y="156" width="40" height="16" rx="2"/><text class="arrow-label" x="498" y="168" text-anchor="middle" fill="#eb6c36">COPY</text><rect class="label-mask" x="478" y="312" width="40" height="16" rx="2"/><text class="arrow-label" x="498" y="324" text-anchor="middle" fill="#4f5d75">LOAD</text><rect class="label-mask" x="612" y="246" width="44" height="16" rx="2"/><text class="arrow-label" x="634" y="258" text-anchor="middle" fill="#eb6c36">XLSX</text><rect class="label-mask" x="814" y="148" width="36" height="16" rx="2"/><text class="arrow-label" x="832" y="160" text-anchor="middle" fill="#2e5aa8">PDF</text><rect class="label-mask" x="814" y="280" width="48" height="16" rx="2"/><text class="arrow-label" x="838" y="292" text-anchor="middle" fill="#2e5aa8">EMAIL</text>
|
||||
<line x1="16" y1="440" x2="944" y2="440" stroke="rgba(45,49,66,.12)" stroke-width=".8"/><text class="legend-label" x="16" y="460" letter-spacing=".14em">LEGEND</text><line x1="80" y1="456" x2="104" y2="456" stroke="#2e5aa8" stroke-width="1.2"/><text class="legend-label" x="112" y="460">data flow</text><line x1="208" y1="456" x2="232" y2="456" stroke="#eb6c36" stroke-width="1.4"/><text class="legend-label" x="240" y="460">pain-point</text><rect x="372" y="450" width="16" height="12" rx="2" fill="#fff" stroke="#4f5d75" stroke-width=".8" stroke-dasharray="4 3"/><text class="legend-label" x="396" y="460">external</text><rect x="492" y="450" width="16" height="12" rx="2" fill="rgba(235,108,54,.07)" stroke="#eb6c36"/><text class="legend-label" x="516" y="460">bottleneck</text>
|
||||
</svg>
|
||||
</div>
|
||||
<section class="cards" aria-label="Current-state summary">
|
||||
<article class="card"><p class="eyebrow">THE HEADLINE</p><div class="card-header"><span class="card-dot coral"></span><h2>Two manual bottlenecks</h2></div><ul><li>The shared drive has no version history.</li><li>The portal depends on a person-led refresh.</li><li>Spreadsheet copies break traceability.</li></ul></article>
|
||||
<article class="card"><div class="card-header"><span class="card-dot link"></span><h2>File-based exchanges</h2></div><ul><li>Stores export nightly CSV files.</li><li>Suppliers email changing workbooks.</li><li>Regional packs leave as PDF attachments.</li></ul></article>
|
||||
<article class="card"><div class="card-header"><span class="card-dot muted"></span><h2>System that remains</h2></div><p>The on-premises relational database remains the inventory and finance source of record while the surrounding handoffs are modernized.</p></article>
|
||||
</section>
|
||||
<footer class="footer"><span>Northwind Retail · IT current-state</span><span>example · diagram design</span></footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Northwind Retail · Current IT Landscape</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #f5f5f5; --paper-2: #ececec; --ink: #2d3142;
|
||||
--muted: #4f5d75; --soft: #7a8399; --rule: rgba(45,49,66,0.12);
|
||||
--accent: #eb6c36; --link: #2e5aa8;
|
||||
--sans: 'Geist', system-ui, sans-serif;
|
||||
--serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.frame { width: 100%; max-width: 1200px; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem/1 var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 0.35rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
.subtitle { margin-bottom: 1.5rem; color: var(--muted); font-size: 0.9rem; line-height: 1.5; }
|
||||
.diagram { overflow-x: auto; }
|
||||
svg { display: block; width: 100%; min-width: 920px; }
|
||||
svg .zone { fill: rgba(45,49,66,0.02); stroke: rgba(45,49,66,0.10); stroke-width: 0.8; }
|
||||
svg .zone-mask, svg .node-mask, svg .label-mask { fill: #f5f5f5; }
|
||||
svg .zone-label, svg .arrow-label, svg .legend-label { font-family: 'Geist Mono', monospace; }
|
||||
svg .zone-label { fill: rgba(45,49,66,0.40); font-size: 8px; letter-spacing: 0.14em; }
|
||||
svg .node { fill: #fff; stroke: #2d3142; stroke-width: 1; }
|
||||
svg .node.external { stroke: #4f5d75; stroke-dasharray: 4 3; }
|
||||
svg .node.focal { fill: rgba(235,108,54,0.07); stroke: #eb6c36; stroke-width: 1.4; }
|
||||
svg .node.survivor { fill: rgba(122,140,71,0.06); stroke: rgba(122,140,71,0.45); }
|
||||
svg .node-name { fill: #2d3142; font: 600 12px 'Geist', sans-serif; }
|
||||
svg .node-name.survivor { fill: #7a8c47; }
|
||||
svg .sub { fill: #4f5d75; font: 400 10px 'Geist Mono', monospace; }
|
||||
svg .sub.focal { fill: #eb6c36; }
|
||||
svg .arrow-label { font-size: 8px; font-weight: 600; letter-spacing: 0.08em; }
|
||||
svg .legend-label { fill: #4f5d75; font-size: 8px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">IT current-state · Northwind Retail</p>
|
||||
<h1>Legacy retail reporting landscape</h1>
|
||||
<p class="subtitle">File exports, desktop spreadsheets, and manual publication before a shared data platform.</p>
|
||||
<div class="diagram">
|
||||
<svg viewBox="0 0 960 500" role="img" aria-labelledby="it-title it-desc" xmlns="http://www.w3.org/2000/svg">
|
||||
<title id="it-title">Northwind Retail current IT landscape</title>
|
||||
<desc id="it-desc">Three zones show collection, processing, and dissemination through manual file handoffs.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<symbol id="icon-file" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2"/></symbol>
|
||||
<symbol id="icon-screen" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="1"/><path d="M7 20h10M9 16v4M15 16v4"/></symbol>
|
||||
<symbol id="icon-db" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/></symbol>
|
||||
<symbol id="icon-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="9" cy="8" r="4"/><path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2M16 4.5a4 4 0 0 1 0 7M18 15a4 4 0 0 1 3 4v2"/></symbol>
|
||||
</defs>
|
||||
|
||||
<rect width="960" height="500" fill="#f5f5f5"/>
|
||||
|
||||
<!-- Phase zones -->
|
||||
<rect class="zone" x="16" y="52" width="256" height="360" rx="8"/>
|
||||
<rect class="zone-mask" x="36" y="44" width="92" height="16"/>
|
||||
<text class="zone-label" x="40" y="56">COLLECTION</text>
|
||||
<rect class="zone" x="292" y="52" width="360" height="360" rx="8"/>
|
||||
<rect class="zone-mask" x="312" y="44" width="92" height="16"/>
|
||||
<text class="zone-label" x="316" y="56">PROCESSING</text>
|
||||
<rect class="zone" x="672" y="52" width="272" height="360" rx="8"/>
|
||||
<rect class="zone-mask" x="692" y="44" width="116" height="16"/>
|
||||
<text class="zone-label" x="696" y="56">DISSEMINATION</text>
|
||||
|
||||
<!-- Connectors first; Shared Drive entries fan to y=108,124,140 -->
|
||||
<path d="M 252,108 H 340" fill="none" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<path d="M 252,196 H 308 Q 316,196 316,188 V 132 Q 316,124 324,124 H 340" fill="none" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<path d="M 252,284 H 324 Q 332,284 332,276 V 148 Q 332,140 340,140" fill="none" stroke="#2e5aa8" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#arrow-link)"/>
|
||||
<line x1="472" y1="148" x2="472" y2="232" stroke="#eb6c36" stroke-width="1.4" stroke-dasharray="4 3" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="472" y1="304" x2="472" y2="344" stroke="#4f5d75" stroke-width="1" marker-end="url(#arrow)"/>
|
||||
<path d="M 604,268 H 654 Q 662,268 662,260 V 72 Q 662,64 670,64 H 800 Q 808,64 808,72 V 80" fill="none" stroke="#eb6c36" stroke-width="1.4" marker-end="url(#arrow-accent)"/>
|
||||
<line x1="808" y1="140" x2="808" y2="216" stroke="#2e5aa8" stroke-width="1.2" marker-end="url(#arrow-link)"/>
|
||||
<line x1="808" y1="272" x2="808" y2="344" stroke="#2e5aa8" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#arrow-link)"/>
|
||||
|
||||
<!-- Collection -->
|
||||
<rect class="node-mask" x="36" y="80" width="216" height="56" rx="6"/><rect class="node" x="36" y="80" width="216" height="56" rx="6"/>
|
||||
<use href="#icon-file" x="48" y="96" width="24" height="24" color="#2d3142"/><text class="node-name" x="80" y="106">Point-of-Sale Exports</text><text class="sub" x="80" y="122">nightly · CSV</text>
|
||||
<rect class="node-mask" x="36" y="168" width="216" height="56" rx="6"/><rect class="node" x="36" y="168" width="216" height="56" rx="6"/>
|
||||
<use href="#icon-file" x="48" y="184" width="24" height="24" color="#2d3142"/><text class="node-name" x="80" y="194">Online Store Export</text><text class="sub" x="80" y="210">orders · flat file</text>
|
||||
<rect class="node-mask" x="36" y="256" width="216" height="56" rx="6"/><rect class="node external" x="36" y="256" width="216" height="56" rx="6"/>
|
||||
<use href="#icon-file" x="48" y="272" width="24" height="24" color="#4f5d75"/><text class="node-name" x="80" y="282">Supplier Price Lists</text><text class="sub" x="80" y="298">external · XLSX</text>
|
||||
|
||||
<!-- Processing -->
|
||||
<rect class="node-mask" x="340" y="80" width="264" height="68" rx="6"/><rect class="node focal" x="340" y="80" width="264" height="68" rx="6"/>
|
||||
<use href="#icon-file" x="352" y="100" width="24" height="24" color="#eb6c36"/><text class="node-name" x="384" y="108">Shared Drive</text><text class="sub focal" x="384" y="124">no version control</text><text class="sub" x="384" y="140">department folders</text>
|
||||
<rect class="node-mask" x="340" y="232" width="264" height="72" rx="6"/><rect class="node" x="340" y="232" width="264" height="72" rx="6"/>
|
||||
<use href="#icon-screen" x="352" y="256" width="24" height="24" color="#2d3142"/><text class="node-name" x="384" y="262">Spreadsheet Handoffs</text><text class="sub" x="384" y="278">local workbooks · macros</text><text class="sub" x="384" y="292">manual reconciliation</text>
|
||||
<rect class="node-mask" x="340" y="344" width="264" height="56" rx="6"/><rect class="node survivor" x="340" y="344" width="264" height="56" rx="6"/>
|
||||
<use href="#icon-db" x="352" y="360" width="24" height="24" color="#7a8c47"/><text class="node-name survivor" x="384" y="370">On-prem RDBMS</text><text class="sub" x="384" y="386">inventory · finance</text>
|
||||
|
||||
<!-- Dissemination -->
|
||||
<rect class="node-mask" x="704" y="80" width="208" height="60" rx="6"/><rect class="node focal" x="704" y="80" width="208" height="60" rx="6"/>
|
||||
<use href="#icon-screen" x="716" y="100" width="24" height="24" color="#eb6c36"/><text class="node-name" x="748" y="108">Reporting Portal</text><text class="sub focal" x="748" y="124">manual refresh</text>
|
||||
<rect class="node-mask" x="704" y="216" width="208" height="56" rx="6"/><rect class="node" x="704" y="216" width="208" height="56" rx="6"/>
|
||||
<use href="#icon-file" x="716" y="232" width="24" height="24" color="#2d3142"/><text class="node-name" x="748" y="242">Email Report Packs</text><text class="sub" x="748" y="258">weekly · PDF</text>
|
||||
<rect class="node-mask" x="704" y="344" width="208" height="56" rx="6"/><rect class="node external" x="704" y="344" width="208" height="56" rx="6"/>
|
||||
<use href="#icon-users" x="716" y="360" width="24" height="24" color="#4f5d75"/><text class="node-name" x="748" y="370">Regional Managers</text><text class="sub" x="748" y="386">12 store regions</text>
|
||||
|
||||
<!-- Labels are source-bound with a 6px perpendicular gap -->
|
||||
<rect class="label-mask" x="260" y="86" width="40" height="16" rx="2"/><text class="arrow-label" x="280" y="98" text-anchor="middle" fill="#2e5aa8">CSV</text>
|
||||
<rect class="label-mask" x="260" y="174" width="52" height="16" rx="2"/><text class="arrow-label" x="286" y="186" text-anchor="middle" fill="#2e5aa8">EXPORT</text>
|
||||
<rect class="label-mask" x="260" y="262" width="44" height="16" rx="2"/><text class="arrow-label" x="282" y="274" text-anchor="middle" fill="#2e5aa8">XLSX</text>
|
||||
<rect class="label-mask" x="478" y="156" width="40" height="16" rx="2"/><text class="arrow-label" x="498" y="168" text-anchor="middle" fill="#eb6c36">COPY</text>
|
||||
<rect class="label-mask" x="478" y="312" width="40" height="16" rx="2"/><text class="arrow-label" x="498" y="324" text-anchor="middle" fill="#4f5d75">LOAD</text>
|
||||
<rect class="label-mask" x="612" y="246" width="44" height="16" rx="2"/><text class="arrow-label" x="634" y="258" text-anchor="middle" fill="#eb6c36">XLSX</text>
|
||||
<rect class="label-mask" x="814" y="148" width="36" height="16" rx="2"/><text class="arrow-label" x="832" y="160" text-anchor="middle" fill="#2e5aa8">PDF</text>
|
||||
<rect class="label-mask" x="814" y="280" width="48" height="16" rx="2"/><text class="arrow-label" x="838" y="292" text-anchor="middle" fill="#2e5aa8">EMAIL</text>
|
||||
|
||||
<!-- Legend strip -->
|
||||
<line x1="16" y1="440" x2="944" y2="440" stroke="rgba(45,49,66,0.12)" stroke-width="0.8"/>
|
||||
<text class="legend-label" x="16" y="460" letter-spacing="0.14em">LEGEND</text>
|
||||
<line x1="80" y1="456" x2="104" y2="456" stroke="#2e5aa8" stroke-width="1.2"/><text class="legend-label" x="112" y="460">data flow</text>
|
||||
<line x1="208" y1="456" x2="232" y2="456" stroke="#eb6c36" stroke-width="1.4"/><text class="legend-label" x="240" y="460">pain-point</text>
|
||||
<rect x="372" y="450" width="16" height="12" rx="2" fill="#fff" stroke="#4f5d75" stroke-width="0.8" stroke-dasharray="4 3"/><text class="legend-label" x="396" y="460">external</text>
|
||||
<rect x="492" y="450" width="16" height="12" rx="2" fill="rgba(235,108,54,0.07)" stroke="#eb6c36"/><text class="legend-label" x="516" y="460">bottleneck</text>
|
||||
</svg>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Weekly signups · Line chart · dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-ink:#2d3142; --color-muted:#4f5d75; --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Line · Diagram Design</p>
|
||||
<h1>Weekly signups · Organic leads the growth</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Y-axis rotated label -->
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SIGNUPS / WEEK</text>
|
||||
|
||||
<!-- Gridlines -->
|
||||
<line x1="80" y1="357" x2="960" y2="357" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="293" x2="960" y2="293" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="167" x2="960" y2="167" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="103" x2="960" y2="103" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(245,245,245,0.06)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Axes -->
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="72" y="361" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">40</text>
|
||||
<text x="72" y="297" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">80</text>
|
||||
<text x="72" y="234" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">120</text>
|
||||
<text x="72" y="171" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">160</text>
|
||||
<text x="72" y="107" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">200</text>
|
||||
<text x="72" y="44" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">240</text>
|
||||
|
||||
<!-- ─── SERIES (non-focal drawn first) ─── -->
|
||||
<!-- Referral: [45,52,48,60,55,68,62,75] series-2 dusty-blue -->
|
||||
<polyline points="80,349 200,338 320,344 440,325 560,333 680,312 800,322 920,301"
|
||||
fill="none" stroke="#82a0c0" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Direct: [80,82,88,90,86,92,95,98] series-1 sage -->
|
||||
<polyline points="80,293 200,290 320,281 440,278 560,284 680,274 800,270 920,265"
|
||||
fill="none" stroke="#9caf8f" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Organic FOCAL area fill (subtle) -->
|
||||
<polygon points="80,230 200,206 320,186 440,164 560,175 680,138 800,111 920,88 920,420 80,420"
|
||||
fill="rgba(240,138,89,0.08)"/>
|
||||
|
||||
<!-- Organic FOCAL line: [120,135,148,162,155,178,195,210] accent -->
|
||||
<polyline points="80,230 200,206 320,186 440,164 560,175 680,138 800,111 920,88"
|
||||
fill="none" stroke="#f08a59" stroke-width="1.8" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Focal vertex dots -->
|
||||
<circle cx="80" cy="230" r="4" fill="#f08a59"/>
|
||||
<circle cx="200" cy="206" r="4" fill="#f08a59"/>
|
||||
<circle cx="320" cy="186" r="4" fill="#f08a59"/>
|
||||
<circle cx="440" cy="164" r="4" fill="#f08a59"/>
|
||||
<circle cx="560" cy="175" r="4" fill="#f08a59"/>
|
||||
<circle cx="680" cy="138" r="4" fill="#f08a59"/>
|
||||
<circle cx="800" cy="111" r="4" fill="#f08a59"/>
|
||||
<circle cx="920" cy="88" r="4" fill="#f08a59"/>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
<text x="80" y="440" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W1</text>
|
||||
<text x="200" y="440" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W2</text>
|
||||
<text x="320" y="440" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W3</text>
|
||||
<text x="440" y="440" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W4</text>
|
||||
<text x="560" y="440" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W5</text>
|
||||
<text x="680" y="440" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W6</text>
|
||||
<text x="800" y="440" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W7</text>
|
||||
<text x="920" y="440" fill="#bfc0c0" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W8</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<line x1="40" y1="492" x2="64" y2="492" stroke="#f08a59" stroke-width="1.8"/>
|
||||
<circle cx="52" cy="492" r="4" fill="#f08a59"/>
|
||||
<text x="72" y="496" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Organic · focal</text>
|
||||
|
||||
<line x1="200" y1="492" x2="224" y2="492" stroke="#9caf8f" stroke-width="1.2"/>
|
||||
<text x="232" y="496" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Direct</text>
|
||||
|
||||
<line x1="316" y1="492" x2="340" y2="492" stroke="#82a0c0" stroke-width="1.2"/>
|
||||
<text x="348" y="496" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Referral</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Weekly signups · Line chart</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --series-1:#7c8f6f; --series-2:#5e7a9b; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
|
||||
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 58ch; }
|
||||
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
@media (max-width: 720px) { .cards { grid-template-columns: 1fr; } }
|
||||
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
|
||||
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--color-accent); } .card-dot.sage { background: var(--series-1); } .card-dot.dusty { background: var(--series-2); }
|
||||
.card h3 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; }
|
||||
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-soft); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<p class="header-eyebrow">Line · Diagram Design</p>
|
||||
<h1>Weekly signups · Organic leads the growth</h1>
|
||||
<p class="subtitle">Signup volume by acquisition channel over eight weeks. Organic search is both the largest and fastest-growing channel; Direct is the stable baseline.</p>
|
||||
</div>
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs><pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/></pattern></defs>
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SIGNUPS / WEEK</text>
|
||||
<line x1="80" y1="357" x2="960" y2="357" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="293" x2="960" y2="293" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="167" x2="960" y2="167" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="103" x2="960" y2="103" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(45,49,66,0.06)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<text x="72" y="361" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">40</text>
|
||||
<text x="72" y="297" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">80</text>
|
||||
<text x="72" y="234" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">120</text>
|
||||
<text x="72" y="171" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">160</text>
|
||||
<text x="72" y="107" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">200</text>
|
||||
<text x="72" y="44" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">240</text>
|
||||
<polyline points="80,349 200,338 320,344 440,325 560,333 680,312 800,322 920,301" fill="none" stroke="#5e7a9b" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
<polyline points="80,293 200,290 320,281 440,278 560,284 680,274 800,270 920,265" fill="none" stroke="#7c8f6f" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
<polygon points="80,230 200,206 320,186 440,164 560,175 680,138 800,111 920,88 920,420 80,420" fill="rgba(235,108,54,0.06)"/>
|
||||
<polyline points="80,230 200,206 320,186 440,164 560,175 680,138 800,111 920,88" fill="none" stroke="#eb6c36" stroke-width="1.8" stroke-linejoin="round"/>
|
||||
<circle cx="80" cy="230" r="4" fill="#eb6c36"/><circle cx="200" cy="206" r="4" fill="#eb6c36"/><circle cx="320" cy="186" r="4" fill="#eb6c36"/><circle cx="440" cy="164" r="4" fill="#eb6c36"/>
|
||||
<circle cx="560" cy="175" r="4" fill="#eb6c36"/><circle cx="680" cy="138" r="4" fill="#eb6c36"/><circle cx="800" cy="111" r="4" fill="#eb6c36"/><circle cx="920" cy="88" r="4" fill="#eb6c36"/>
|
||||
<text x="80" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W1</text>
|
||||
<text x="200" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W2</text>
|
||||
<text x="320" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W3</text>
|
||||
<text x="440" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W4</text>
|
||||
<text x="560" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W5</text>
|
||||
<text x="680" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W6</text>
|
||||
<text x="800" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W7</text>
|
||||
<text x="920" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W8</text>
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
<line x1="40" y1="492" x2="64" y2="492" stroke="#eb6c36" stroke-width="1.8"/><circle cx="52" cy="492" r="4" fill="#eb6c36"/>
|
||||
<text x="72" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Organic · focal</text>
|
||||
<line x1="200" y1="492" x2="224" y2="492" stroke="#7c8f6f" stroke-width="1.2"/>
|
||||
<text x="232" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Direct</text>
|
||||
<line x1="316" y1="492" x2="340" y2="492" stroke="#5e7a9b" stroke-width="1.2"/>
|
||||
<text x="348" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Referral</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="eyebrow">FOCAL · ORGANIC</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h3>Organic search driving 55% of new signups</h3></div>
|
||||
<p>75% week-over-week growth over 8 weeks — from 120 to 210 signups. Two long-form guides published in W4 account for the step change. The W5 dip is a weekday holiday effect.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot sage"></span><h3>Direct: stable core</h3></div>
|
||||
<p>22% growth (80→98) — this is mostly returning users and brand-aware discovery. The near-flat slope is a healthy baseline, not stagnation. It means the product retains its direct audience while organic expands.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot dusty"></span><h3>Referral: early signal</h3></div>
|
||||
<p>67% growth (45→75), driven by one integration partner. Volatile week-to-week but directionally positive. Worth a dedicated channel review before investing further in partner programmes.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span>weekly signups · acquisition channels</span>
|
||||
<span>example · diagram design</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Weekly signups · Line chart</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-ink:#2d3142; --color-muted:#4f5d75; --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Line · Diagram Design</p>
|
||||
<h1>Weekly signups · Organic leads the growth</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Y-axis rotated label -->
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">SIGNUPS / WEEK</text>
|
||||
|
||||
<!-- Gridlines -->
|
||||
<line x1="80" y1="357" x2="960" y2="357" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="293" x2="960" y2="293" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="167" x2="960" y2="167" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="103" x2="960" y2="103" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(45,49,66,0.06)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Axes -->
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="72" y="361" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">40</text>
|
||||
<text x="72" y="297" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">80</text>
|
||||
<text x="72" y="234" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">120</text>
|
||||
<text x="72" y="171" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">160</text>
|
||||
<text x="72" y="107" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">200</text>
|
||||
<text x="72" y="44" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">240</text>
|
||||
|
||||
<!-- ─── SERIES (non-focal drawn first) ─── -->
|
||||
<!-- Referral: [45,52,48,60,55,68,62,75] series-2 dusty-blue -->
|
||||
<polyline points="80,349 200,338 320,344 440,325 560,333 680,312 800,322 920,301"
|
||||
fill="none" stroke="#5e7a9b" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Direct: [80,82,88,90,86,92,95,98] series-1 sage -->
|
||||
<polyline points="80,293 200,290 320,281 440,278 560,284 680,274 800,270 920,265"
|
||||
fill="none" stroke="#7c8f6f" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Organic FOCAL area fill (subtle) -->
|
||||
<polygon points="80,230 200,206 320,186 440,164 560,175 680,138 800,111 920,88 920,420 80,420"
|
||||
fill="rgba(235,108,54,0.06)"/>
|
||||
|
||||
<!-- Organic FOCAL line: [120,135,148,162,155,178,195,210] accent -->
|
||||
<polyline points="80,230 200,206 320,186 440,164 560,175 680,138 800,111 920,88"
|
||||
fill="none" stroke="#eb6c36" stroke-width="1.8" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Focal vertex dots -->
|
||||
<circle cx="80" cy="230" r="4" fill="#eb6c36"/>
|
||||
<circle cx="200" cy="206" r="4" fill="#eb6c36"/>
|
||||
<circle cx="320" cy="186" r="4" fill="#eb6c36"/>
|
||||
<circle cx="440" cy="164" r="4" fill="#eb6c36"/>
|
||||
<circle cx="560" cy="175" r="4" fill="#eb6c36"/>
|
||||
<circle cx="680" cy="138" r="4" fill="#eb6c36"/>
|
||||
<circle cx="800" cy="111" r="4" fill="#eb6c36"/>
|
||||
<circle cx="920" cy="88" r="4" fill="#eb6c36"/>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
<text x="80" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W1</text>
|
||||
<text x="200" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W2</text>
|
||||
<text x="320" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W3</text>
|
||||
<text x="440" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W4</text>
|
||||
<text x="560" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W5</text>
|
||||
<text x="680" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W6</text>
|
||||
<text x="800" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W7</text>
|
||||
<text x="920" y="440" fill="#4f5d75" font-size="9" font-family="'Geist Mono', monospace" text-anchor="middle">W8</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<line x1="40" y1="492" x2="64" y2="492" stroke="#eb6c36" stroke-width="1.8"/>
|
||||
<circle cx="52" cy="492" r="4" fill="#eb6c36"/>
|
||||
<text x="72" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Organic · focal</text>
|
||||
|
||||
<line x1="200" y1="492" x2="224" y2="492" stroke="#7c8f6f" stroke-width="1.2"/>
|
||||
<text x="232" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Direct</text>
|
||||
|
||||
<line x1="316" y1="492" x2="340" y2="492" stroke="#5e7a9b" stroke-width="1.2"/>
|
||||
<text x="348" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Referral</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,159 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>E-commerce analytics · Medallion · Dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #2d3142; --paper-2: #393e53; --ink: #f5f5f5; --muted: #bfc0c0;
|
||||
--soft: #8e98ac; --rule: rgba(245,245,245,0.12); --accent: #f08a59;
|
||||
--accent-tint: rgba(240,138,89,0.10); --link: #6a95d8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.frame { width: 100%; max-width: 1200px; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 1.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
svg { display: block; width: 100%; min-width: 1040px; }
|
||||
.tier-title { fill: var(--ink); font: 600 13px var(--sans); text-anchor: middle; }
|
||||
.bucket, .field-value, .example-value { fill: var(--muted); font: 400 9px var(--mono); }
|
||||
.bucket { text-anchor: middle; }
|
||||
.field-value { color: var(--muted); }
|
||||
.focal-text { fill: var(--accent); }
|
||||
.field-label { fill: var(--ink); font: 600 11px var(--sans); }
|
||||
.example-label, .arc-label, .path-tag { font-family: var(--mono); font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; }
|
||||
.example-label { fill: var(--soft); font-size: 8px; }
|
||||
.arc-label { font-size: 10px; text-anchor: middle; }
|
||||
.path-title { fill: var(--ink); font: 600 11px var(--sans); }
|
||||
.path-sub { fill: var(--muted); font: 400 9px var(--mono); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">Medallion · Diagram Design</p>
|
||||
<h1>Five-tier e-commerce analytics storage</h1>
|
||||
|
||||
<svg viewBox="0 0 1040 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="medallion-title medallion-desc">
|
||||
<title id="medallion-title">Five-tier e-commerce analytics medallion</title>
|
||||
<desc id="medallion-desc">Clickstream events and order records move from raw storage through anonymized, staging, and aggregated tiers before lifecycle archiving.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#bfc0c0"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#f08a59"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#6a95d8"/></marker>
|
||||
<clipPath id="clip-raw"><rect x="16" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-anon"><rect x="204" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-stage"><rect x="392" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-aggregate"><rect x="580" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-archive"><rect x="768" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
</defs>
|
||||
<rect width="1040" height="548" fill="#2d3142"/>
|
||||
|
||||
<!-- Promotion arcs are the medallion type's required connector grammar. -->
|
||||
<path d="M102 80 C102 0 290 0 290 80" fill="none" stroke="#bfc0c0" stroke-width="1.4" marker-end="url(#arrow)"/>
|
||||
<path d="M290 80 C290 0 478 0 478 80" fill="none" stroke="#bfc0c0" stroke-width="1.4" marker-end="url(#arrow)"/>
|
||||
<path d="M478 80 C478 0 666 0 666 80" fill="none" stroke="#f08a59" stroke-width="1.6" marker-end="url(#arrow-accent)"/>
|
||||
<path d="M666 80 C666 0 854 0 854 80" fill="none" stroke="#bfc0c0" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<text x="196" y="50" class="arc-label" fill="#bfc0c0">MASK IDS</text>
|
||||
<text x="384" y="50" class="arc-label" fill="#bfc0c0">CLEAN + JOIN</text>
|
||||
<text x="572" y="50" class="arc-label" fill="#f08a59">AGGREGATE</text>
|
||||
<text x="760" y="50" class="arc-label" fill="#bfc0c0">LIFECYCLE</text>
|
||||
|
||||
<!-- Raw -->
|
||||
<rect x="16" y="80" width="172" height="380" rx="6" fill="rgba(245,245,245,0.04)"/>
|
||||
<rect x="16" y="80" width="172" height="50" fill="rgba(142,152,172,0.16)" clip-path="url(#clip-raw)"/>
|
||||
<rect x="16" y="80" width="172" height="380" rx="6" fill="none" stroke="#bfc0c0"/>
|
||||
<text x="102" y="106" class="tier-title">Raw</text>
|
||||
<text x="102" y="144" class="bucket">raw-commerce</text>
|
||||
<text x="32" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="32" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">NiFi ingest</div></foreignObject>
|
||||
<text x="32" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="32" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">JSON · Parquet</div></foreignObject>
|
||||
<text x="32" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="32" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="32" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="32" y="374" class="example-value">clickstream events</text>
|
||||
<text x="32" y="388" class="example-value">order records</text>
|
||||
|
||||
<!-- Anonymized -->
|
||||
<rect x="204" y="80" width="172" height="380" rx="6" fill="rgba(245,245,245,0.04)"/>
|
||||
<rect x="204" y="80" width="172" height="50" fill="rgba(245,245,245,0.08)" clip-path="url(#clip-anon)"/>
|
||||
<rect x="204" y="80" width="172" height="380" rx="6" fill="none" stroke="rgba(245,245,245,0.30)"/>
|
||||
<text x="290" y="106" class="tier-title">Anonymized</text>
|
||||
<text x="290" y="144" class="bucket">anon-commerce</text>
|
||||
<text x="220" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="220" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino SQL</div></foreignObject>
|
||||
<text x="220" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="220" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · partitioned</div></foreignObject>
|
||||
<text x="220" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="220" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="220" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="220" y="374" class="example-value">session pseudonyms</text>
|
||||
<text x="220" y="388" class="example-value">masked customer IDs</text>
|
||||
|
||||
<!-- Staging -->
|
||||
<rect x="392" y="80" width="172" height="380" rx="6" fill="rgba(245,245,245,0.04)"/>
|
||||
<rect x="392" y="80" width="172" height="50" fill="rgba(245,245,245,0.08)" clip-path="url(#clip-stage)"/>
|
||||
<rect x="392" y="80" width="172" height="380" rx="6" fill="none" stroke="rgba(245,245,245,0.30)"/>
|
||||
<text x="478" y="106" class="tier-title">Staging</text>
|
||||
<text x="478" y="144" class="bucket">staging-commerce</text>
|
||||
<text x="408" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="408" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino · notebooks</div></foreignObject>
|
||||
<text x="408" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="408" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · cleaned</div></foreignObject>
|
||||
<text x="408" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="408" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Scientist</div></foreignObject>
|
||||
<text x="408" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="408" y="374" class="example-value">joined order facts</text>
|
||||
<text x="408" y="388" class="example-value">validated event rows</text>
|
||||
|
||||
<!-- Aggregated focal tier -->
|
||||
<rect x="580" y="80" width="172" height="380" rx="6" fill="rgba(240,138,89,0.12)"/>
|
||||
<rect x="580" y="80" width="172" height="50" fill="rgba(240,138,89,0.20)" clip-path="url(#clip-aggregate)"/>
|
||||
<rect x="580" y="80" width="172" height="380" rx="6" fill="none" stroke="#f08a59" stroke-width="1.6"/>
|
||||
<text x="666" y="106" class="tier-title">Aggregated</text>
|
||||
<text x="666" y="144" class="bucket focal-text">analytics-marts</text>
|
||||
<text x="596" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="596" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino INSERT</div></foreignObject>
|
||||
<text x="596" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="596" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · metrics</div></foreignObject>
|
||||
<text x="596" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="596" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Scientist</div></foreignObject>
|
||||
<text x="596" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="596" y="374" class="example-value focal-text">daily conversion rate</text>
|
||||
<text x="596" y="388" class="example-value focal-text">revenue by channel</text>
|
||||
|
||||
<!-- Archive -->
|
||||
<rect x="768" y="80" width="172" height="380" rx="6" fill="rgba(245,245,245,0.06)"/>
|
||||
<rect x="768" y="80" width="172" height="50" fill="rgba(142,152,172,0.24)" clip-path="url(#clip-archive)"/>
|
||||
<rect x="768" y="80" width="172" height="380" rx="6" fill="none" stroke="#bfc0c0" stroke-dasharray="5,3"/>
|
||||
<text x="854" y="106" class="tier-title">Archive</text>
|
||||
<text x="854" y="144" class="bucket">archive-commerce</text>
|
||||
<text x="784" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="784" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Object storage lifecycle</div></foreignObject>
|
||||
<text x="784" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="784" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Cold tier · immutable</div></foreignObject>
|
||||
<text x="784" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="784" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="784" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="784" y="374" class="example-value">retained clickstream</text>
|
||||
<text x="784" y="388" class="example-value">closed order snapshots</text>
|
||||
|
||||
<!-- Write-method paths -->
|
||||
<rect x="16" y="476" width="460" height="56" rx="6" fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.20)"/>
|
||||
<rect x="24" y="482" width="64" height="12" rx="2" fill="none" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="56" y="491" class="path-tag" fill="#f5f5f5" font-size="7" text-anchor="middle">SQL PATH</text>
|
||||
<text x="96" y="506" class="path-title">Trino INSERT INTO … SELECT</text>
|
||||
<text x="96" y="522" class="path-sub">filter · join · aggregate — repeatable transforms</text>
|
||||
<rect x="492" y="476" width="460" height="56" rx="6" fill="rgba(245,245,245,0.04)" stroke="rgba(245,245,245,0.20)"/>
|
||||
<rect x="500" y="482" width="96" height="12" rx="2" fill="none" stroke="rgba(245,245,245,0.30)" stroke-width="0.8"/>
|
||||
<text x="548" y="491" class="path-tag" fill="#f5f5f5" font-size="7" text-anchor="middle">NOTEBOOK PATH</text>
|
||||
<text x="604" y="506" class="path-title">Python notebooks on object storage</text>
|
||||
<text x="604" y="522" class="path-sub">explore · validate · model — interactive analysis</text>
|
||||
</svg>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,201 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>E-commerce analytics · Medallion · Full</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #f5f5f5; --paper-2: #ececec; --ink: #2d3142; --muted: #4f5d75;
|
||||
--soft: #7a8399; --rule: rgba(45,49,66,0.12); --accent: #eb6c36;
|
||||
--accent-tint: rgba(235,108,54,0.08); --link: #2e5aa8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.container { width: 100%; max-width: 1200px; margin: 0 auto; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 1.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
svg { display: block; width: 100%; min-width: 1040px; }
|
||||
.tier-title { fill: var(--ink); font: 600 13px var(--sans); text-anchor: middle; }
|
||||
.bucket, .field-value, .example-value { fill: var(--muted); font: 400 9px var(--mono); }
|
||||
.bucket { text-anchor: middle; }
|
||||
.field-value { color: var(--muted); }
|
||||
.focal-text { fill: var(--accent); }
|
||||
.field-label { fill: var(--ink); font: 600 11px var(--sans); }
|
||||
.example-label, .arc-label, .path-tag { font-family: var(--mono); font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; }
|
||||
.example-label { fill: var(--soft); font-size: 8px; }
|
||||
.arc-label { font-size: 10px; text-anchor: middle; }
|
||||
.path-title { fill: var(--ink); font: 600 11px var(--sans); }
|
||||
.path-sub { fill: var(--muted); font: 400 9px var(--mono); }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header h1 { margin-bottom: 0.5rem; }
|
||||
.subtitle { max-width: 62ch; color: var(--muted); font-size: 1rem; line-height: 1.55; }
|
||||
.diagram-container { overflow-x: auto; padding: 1.5rem; background: var(--paper-2); border: 1px solid var(--rule); border-radius: 8px; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
.card { padding: 1.25rem; background: #fff; border: 1px solid var(--rule); border-radius: 6px; }
|
||||
.card .card-eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.5rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
.card-header { display: flex; gap: 0.6rem; align-items: center; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--accent); } .card-dot.ink { background: var(--ink); } .card-dot.muted { background: var(--muted); }
|
||||
.card h2 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p, .card ul { color: var(--muted); font-size: 0.8125rem; line-height: 1.55; list-style: none; }
|
||||
.card li { position: relative; margin-bottom: 0.3rem; padding-left: 0.875rem; }
|
||||
.card li::before { position: absolute; left: 0; color: rgba(45,49,66,0.25); content: '—'; }
|
||||
.footer { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.5rem; margin-top: 2rem; padding-top: 1.5rem; color: var(--soft); border-top: 1px solid rgba(45,49,66,0.10); font: 400 0.72rem var(--mono); letter-spacing: 0.06em; }
|
||||
@media (max-width: 820px) { .cards { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<header class="header">
|
||||
<p class="eyebrow">Medallion · Diagram Design</p>
|
||||
<h1>Five-tier e-commerce analytics storage</h1>
|
||||
<p class="subtitle">A quality ladder for clickstream events and order records, from immutable landing data to dashboard-ready metrics and cold retention.</p>
|
||||
</header>
|
||||
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1040 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="medallion-title medallion-desc">
|
||||
<title id="medallion-title">Five-tier e-commerce analytics medallion</title>
|
||||
<desc id="medallion-desc">Clickstream events and order records move from raw storage through anonymized, staging, and aggregated tiers before lifecycle archiving.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/></marker>
|
||||
<clipPath id="clip-raw"><rect x="16" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-anon"><rect x="204" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-stage"><rect x="392" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-aggregate"><rect x="580" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-archive"><rect x="768" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
</defs>
|
||||
<rect width="1040" height="548" fill="#f5f5f5"/>
|
||||
|
||||
<!-- Promotion arcs are the medallion type's required connector grammar. -->
|
||||
<path d="M102 80 C102 0 290 0 290 80" fill="none" stroke="#4f5d75" stroke-width="1.4" marker-end="url(#arrow)"/>
|
||||
<path d="M290 80 C290 0 478 0 478 80" fill="none" stroke="#4f5d75" stroke-width="1.4" marker-end="url(#arrow)"/>
|
||||
<path d="M478 80 C478 0 666 0 666 80" fill="none" stroke="#eb6c36" stroke-width="1.6" marker-end="url(#arrow-accent)"/>
|
||||
<path d="M666 80 C666 0 854 0 854 80" fill="none" stroke="#4f5d75" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<text x="196" y="50" class="arc-label" fill="#4f5d75">MASK IDS</text>
|
||||
<text x="384" y="50" class="arc-label" fill="#4f5d75">CLEAN + JOIN</text>
|
||||
<text x="572" y="50" class="arc-label" fill="#eb6c36">AGGREGATE</text>
|
||||
<text x="760" y="50" class="arc-label" fill="#4f5d75">LIFECYCLE</text>
|
||||
|
||||
<!-- Raw -->
|
||||
<rect x="16" y="80" width="172" height="380" rx="6" fill="#fff"/>
|
||||
<rect x="16" y="80" width="172" height="50" fill="rgba(79,93,117,0.10)" clip-path="url(#clip-raw)"/>
|
||||
<rect x="16" y="80" width="172" height="380" rx="6" fill="none" stroke="#4f5d75"/>
|
||||
<text x="102" y="106" class="tier-title">Raw</text>
|
||||
<text x="102" y="144" class="bucket">raw-commerce</text>
|
||||
<text x="32" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="32" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">NiFi ingest</div></foreignObject>
|
||||
<text x="32" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="32" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">JSON · Parquet</div></foreignObject>
|
||||
<text x="32" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="32" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="32" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="32" y="374" class="example-value">clickstream events</text>
|
||||
<text x="32" y="388" class="example-value">order records</text>
|
||||
|
||||
<!-- Anonymized -->
|
||||
<rect x="204" y="80" width="172" height="380" rx="6" fill="#fff"/>
|
||||
<rect x="204" y="80" width="172" height="50" fill="rgba(45,49,66,0.06)" clip-path="url(#clip-anon)"/>
|
||||
<rect x="204" y="80" width="172" height="380" rx="6" fill="none" stroke="#2d3142"/>
|
||||
<text x="290" y="106" class="tier-title">Anonymized</text>
|
||||
<text x="290" y="144" class="bucket">anon-commerce</text>
|
||||
<text x="220" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="220" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino SQL</div></foreignObject>
|
||||
<text x="220" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="220" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · partitioned</div></foreignObject>
|
||||
<text x="220" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="220" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="220" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="220" y="374" class="example-value">session pseudonyms</text>
|
||||
<text x="220" y="388" class="example-value">masked customer IDs</text>
|
||||
|
||||
<!-- Staging -->
|
||||
<rect x="392" y="80" width="172" height="380" rx="6" fill="#fff"/>
|
||||
<rect x="392" y="80" width="172" height="50" fill="rgba(45,49,66,0.06)" clip-path="url(#clip-stage)"/>
|
||||
<rect x="392" y="80" width="172" height="380" rx="6" fill="none" stroke="#2d3142"/>
|
||||
<text x="478" y="106" class="tier-title">Staging</text>
|
||||
<text x="478" y="144" class="bucket">staging-commerce</text>
|
||||
<text x="408" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="408" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino · notebooks</div></foreignObject>
|
||||
<text x="408" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="408" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · cleaned</div></foreignObject>
|
||||
<text x="408" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="408" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Scientist</div></foreignObject>
|
||||
<text x="408" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="408" y="374" class="example-value">joined order facts</text>
|
||||
<text x="408" y="388" class="example-value">validated event rows</text>
|
||||
|
||||
<!-- Aggregated focal tier -->
|
||||
<rect x="580" y="80" width="172" height="380" rx="6" fill="rgba(235,108,54,0.07)"/>
|
||||
<rect x="580" y="80" width="172" height="50" fill="rgba(235,108,54,0.14)" clip-path="url(#clip-aggregate)"/>
|
||||
<rect x="580" y="80" width="172" height="380" rx="6" fill="none" stroke="#eb6c36" stroke-width="1.6"/>
|
||||
<text x="666" y="106" class="tier-title">Aggregated</text>
|
||||
<text x="666" y="144" class="bucket focal-text">analytics-marts</text>
|
||||
<text x="596" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="596" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino INSERT</div></foreignObject>
|
||||
<text x="596" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="596" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · metrics</div></foreignObject>
|
||||
<text x="596" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="596" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Scientist</div></foreignObject>
|
||||
<text x="596" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="596" y="374" class="example-value focal-text">daily conversion rate</text>
|
||||
<text x="596" y="388" class="example-value focal-text">revenue by channel</text>
|
||||
|
||||
<!-- Archive -->
|
||||
<rect x="768" y="80" width="172" height="380" rx="6" fill="#ececec"/>
|
||||
<rect x="768" y="80" width="172" height="50" fill="rgba(79,93,117,0.18)" clip-path="url(#clip-archive)"/>
|
||||
<rect x="768" y="80" width="172" height="380" rx="6" fill="none" stroke="#4f5d75" stroke-dasharray="5,3"/>
|
||||
<text x="854" y="106" class="tier-title">Archive</text>
|
||||
<text x="854" y="144" class="bucket">archive-commerce</text>
|
||||
<text x="784" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="784" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Object storage lifecycle</div></foreignObject>
|
||||
<text x="784" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="784" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Cold tier · immutable</div></foreignObject>
|
||||
<text x="784" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="784" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="784" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="784" y="374" class="example-value">retained clickstream</text>
|
||||
<text x="784" y="388" class="example-value">closed order snapshots</text>
|
||||
|
||||
<!-- Write-method paths -->
|
||||
<rect x="16" y="476" width="460" height="56" rx="6" fill="#fff" stroke="rgba(45,49,66,0.20)"/>
|
||||
<rect x="24" y="482" width="64" height="12" rx="2" fill="none" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="56" y="491" class="path-tag" fill="#2d3142" font-size="7" text-anchor="middle">SQL PATH</text>
|
||||
<text x="96" y="506" class="path-title">Trino INSERT INTO … SELECT</text>
|
||||
<text x="96" y="522" class="path-sub">filter · join · aggregate — repeatable transforms</text>
|
||||
<rect x="492" y="476" width="460" height="56" rx="6" fill="#fff" stroke="rgba(45,49,66,0.20)"/>
|
||||
<rect x="500" y="482" width="96" height="12" rx="2" fill="none" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="548" y="491" class="path-tag" fill="#2d3142" font-size="7" text-anchor="middle">NOTEBOOK PATH</text>
|
||||
<text x="604" y="506" class="path-title">Python notebooks on object storage</text>
|
||||
<text x="604" y="522" class="path-sub">explore · validate · model — interactive analysis</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<section class="cards" aria-label="Diagram summary">
|
||||
<article class="card">
|
||||
<p class="card-eyebrow">THE HEADLINE</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h2>Aggregated is the query surface</h2></div>
|
||||
<p>Dashboards read stable metrics instead of recomputing joins across raw commerce records.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-header"><span class="card-dot ink"></span><h2>Quality rises by tier</h2></div>
|
||||
<ul><li>Identifiers are masked first</li><li>Events and orders are validated</li><li>Metrics are versioned for reuse</li></ul>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-header"><span class="card-dot muted"></span><h2>Two write modes</h2></div>
|
||||
<p>Trino handles repeatable promotion jobs; notebooks support exploration before logic is operationalized.</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<span>e-commerce analytics · medallion</span>
|
||||
<span>example · schematic skill</span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,159 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>E-commerce analytics · Medallion</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #f5f5f5; --paper-2: #ececec; --ink: #2d3142; --muted: #4f5d75;
|
||||
--soft: #7a8399; --rule: rgba(45,49,66,0.12); --accent: #eb6c36;
|
||||
--accent-tint: rgba(235,108,54,0.08); --link: #2e5aa8;
|
||||
--sans: 'Geist', system-ui, sans-serif; --serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
body { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; background: var(--paper); color: var(--ink); font-family: var(--sans); }
|
||||
.frame { width: 100%; max-width: 1200px; }
|
||||
.eyebrow { margin-bottom: 0.5rem; color: var(--muted); font: 500 0.66rem var(--mono); letter-spacing: 0.18em; text-transform: uppercase; }
|
||||
h1 { margin-bottom: 1.5rem; color: var(--ink); font: 400 clamp(1.5rem, 2.4vw + 0.75rem, 2rem)/1.15 var(--serif); letter-spacing: -0.02em; }
|
||||
svg { display: block; width: 100%; min-width: 1040px; }
|
||||
.tier-title { fill: var(--ink); font: 600 13px var(--sans); text-anchor: middle; }
|
||||
.bucket, .field-value, .example-value { fill: var(--muted); font: 400 9px var(--mono); }
|
||||
.bucket { text-anchor: middle; }
|
||||
.field-value { color: var(--muted); }
|
||||
.focal-text { fill: var(--accent); }
|
||||
.field-label { fill: var(--ink); font: 600 11px var(--sans); }
|
||||
.example-label, .arc-label, .path-tag { font-family: var(--mono); font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; }
|
||||
.example-label { fill: var(--soft); font-size: 8px; }
|
||||
.arc-label { font-size: 10px; text-anchor: middle; }
|
||||
.path-title { fill: var(--ink); font: 600 11px var(--sans); }
|
||||
.path-sub { fill: var(--muted); font: 400 9px var(--mono); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="frame">
|
||||
<p class="eyebrow">Medallion · Diagram Design</p>
|
||||
<h1>Five-tier e-commerce analytics storage</h1>
|
||||
|
||||
<svg viewBox="0 0 1040 548" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="medallion-title medallion-desc">
|
||||
<title id="medallion-title">Five-tier e-commerce analytics medallion</title>
|
||||
<desc id="medallion-desc">Clickstream events and order records move from raw storage through anonymized, staging, and aggregated tiers before lifecycle archiving.</desc>
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/></marker>
|
||||
<clipPath id="clip-raw"><rect x="16" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-anon"><rect x="204" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-stage"><rect x="392" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-aggregate"><rect x="580" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
<clipPath id="clip-archive"><rect x="768" y="80" width="172" height="380" rx="6"/></clipPath>
|
||||
</defs>
|
||||
<rect width="1040" height="548" fill="#f5f5f5"/>
|
||||
|
||||
<!-- Promotion arcs are the medallion type's required connector grammar. -->
|
||||
<path d="M102 80 C102 0 290 0 290 80" fill="none" stroke="#4f5d75" stroke-width="1.4" marker-end="url(#arrow)"/>
|
||||
<path d="M290 80 C290 0 478 0 478 80" fill="none" stroke="#4f5d75" stroke-width="1.4" marker-end="url(#arrow)"/>
|
||||
<path d="M478 80 C478 0 666 0 666 80" fill="none" stroke="#eb6c36" stroke-width="1.6" marker-end="url(#arrow-accent)"/>
|
||||
<path d="M666 80 C666 0 854 0 854 80" fill="none" stroke="#4f5d75" stroke-width="1.4" stroke-dasharray="4,3" marker-end="url(#arrow)"/>
|
||||
<text x="196" y="50" class="arc-label" fill="#4f5d75">MASK IDS</text>
|
||||
<text x="384" y="50" class="arc-label" fill="#4f5d75">CLEAN + JOIN</text>
|
||||
<text x="572" y="50" class="arc-label" fill="#eb6c36">AGGREGATE</text>
|
||||
<text x="760" y="50" class="arc-label" fill="#4f5d75">LIFECYCLE</text>
|
||||
|
||||
<!-- Raw -->
|
||||
<rect x="16" y="80" width="172" height="380" rx="6" fill="#fff"/>
|
||||
<rect x="16" y="80" width="172" height="50" fill="rgba(79,93,117,0.10)" clip-path="url(#clip-raw)"/>
|
||||
<rect x="16" y="80" width="172" height="380" rx="6" fill="none" stroke="#4f5d75"/>
|
||||
<text x="102" y="106" class="tier-title">Raw</text>
|
||||
<text x="102" y="144" class="bucket">raw-commerce</text>
|
||||
<text x="32" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="32" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">NiFi ingest</div></foreignObject>
|
||||
<text x="32" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="32" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">JSON · Parquet</div></foreignObject>
|
||||
<text x="32" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="32" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="32" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="32" y="374" class="example-value">clickstream events</text>
|
||||
<text x="32" y="388" class="example-value">order records</text>
|
||||
|
||||
<!-- Anonymized -->
|
||||
<rect x="204" y="80" width="172" height="380" rx="6" fill="#fff"/>
|
||||
<rect x="204" y="80" width="172" height="50" fill="rgba(45,49,66,0.06)" clip-path="url(#clip-anon)"/>
|
||||
<rect x="204" y="80" width="172" height="380" rx="6" fill="none" stroke="#2d3142"/>
|
||||
<text x="290" y="106" class="tier-title">Anonymized</text>
|
||||
<text x="290" y="144" class="bucket">anon-commerce</text>
|
||||
<text x="220" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="220" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino SQL</div></foreignObject>
|
||||
<text x="220" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="220" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · partitioned</div></foreignObject>
|
||||
<text x="220" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="220" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="220" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="220" y="374" class="example-value">session pseudonyms</text>
|
||||
<text x="220" y="388" class="example-value">masked customer IDs</text>
|
||||
|
||||
<!-- Staging -->
|
||||
<rect x="392" y="80" width="172" height="380" rx="6" fill="#fff"/>
|
||||
<rect x="392" y="80" width="172" height="50" fill="rgba(45,49,66,0.06)" clip-path="url(#clip-stage)"/>
|
||||
<rect x="392" y="80" width="172" height="380" rx="6" fill="none" stroke="#2d3142"/>
|
||||
<text x="478" y="106" class="tier-title">Staging</text>
|
||||
<text x="478" y="144" class="bucket">staging-commerce</text>
|
||||
<text x="408" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="408" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino · notebooks</div></foreignObject>
|
||||
<text x="408" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="408" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · cleaned</div></foreignObject>
|
||||
<text x="408" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="408" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Scientist</div></foreignObject>
|
||||
<text x="408" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="408" y="374" class="example-value">joined order facts</text>
|
||||
<text x="408" y="388" class="example-value">validated event rows</text>
|
||||
|
||||
<!-- Aggregated focal tier -->
|
||||
<rect x="580" y="80" width="172" height="380" rx="6" fill="rgba(235,108,54,0.07)"/>
|
||||
<rect x="580" y="80" width="172" height="50" fill="rgba(235,108,54,0.14)" clip-path="url(#clip-aggregate)"/>
|
||||
<rect x="580" y="80" width="172" height="380" rx="6" fill="none" stroke="#eb6c36" stroke-width="1.6"/>
|
||||
<text x="666" y="106" class="tier-title">Aggregated</text>
|
||||
<text x="666" y="144" class="bucket focal-text">analytics-marts</text>
|
||||
<text x="596" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="596" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Trino INSERT</div></foreignObject>
|
||||
<text x="596" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="596" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Iceberg · metrics</div></foreignObject>
|
||||
<text x="596" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="596" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Scientist</div></foreignObject>
|
||||
<text x="596" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="596" y="374" class="example-value focal-text">daily conversion rate</text>
|
||||
<text x="596" y="388" class="example-value focal-text">revenue by channel</text>
|
||||
|
||||
<!-- Archive -->
|
||||
<rect x="768" y="80" width="172" height="380" rx="6" fill="#ececec"/>
|
||||
<rect x="768" y="80" width="172" height="50" fill="rgba(79,93,117,0.18)" clip-path="url(#clip-archive)"/>
|
||||
<rect x="768" y="80" width="172" height="380" rx="6" fill="none" stroke="#4f5d75" stroke-dasharray="5,3"/>
|
||||
<text x="854" y="106" class="tier-title">Archive</text>
|
||||
<text x="854" y="144" class="bucket">archive-commerce</text>
|
||||
<text x="784" y="180" class="field-label">Tool</text>
|
||||
<foreignObject x="784" y="186" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Object storage lifecycle</div></foreignObject>
|
||||
<text x="784" y="220" class="field-label">Format</text>
|
||||
<foreignObject x="784" y="226" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Cold tier · immutable</div></foreignObject>
|
||||
<text x="784" y="260" class="field-label">Writer</text>
|
||||
<foreignObject x="784" y="266" width="140" height="24"><div xmlns="http://www.w3.org/1999/xhtml" class="field-value" style="line-height:1.25">Data Engineer</div></foreignObject>
|
||||
<text x="784" y="360" class="example-label">E-COMMERCE EXAMPLE</text>
|
||||
<text x="784" y="374" class="example-value">retained clickstream</text>
|
||||
<text x="784" y="388" class="example-value">closed order snapshots</text>
|
||||
|
||||
<!-- Write-method paths -->
|
||||
<rect x="16" y="476" width="460" height="56" rx="6" fill="#fff" stroke="rgba(45,49,66,0.20)"/>
|
||||
<rect x="24" y="482" width="64" height="12" rx="2" fill="none" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="56" y="491" class="path-tag" fill="#2d3142" font-size="7" text-anchor="middle">SQL PATH</text>
|
||||
<text x="96" y="506" class="path-title">Trino INSERT INTO … SELECT</text>
|
||||
<text x="96" y="522" class="path-sub">filter · join · aggregate — repeatable transforms</text>
|
||||
<rect x="492" y="476" width="460" height="56" rx="6" fill="#fff" stroke="rgba(45,49,66,0.20)"/>
|
||||
<rect x="500" y="482" width="96" height="12" rx="2" fill="none" stroke="rgba(45,49,66,0.30)" stroke-width="0.8"/>
|
||||
<text x="548" y="491" class="path-tag" fill="#2d3142" font-size="7" text-anchor="middle">NOTEBOOK PATH</text>
|
||||
<text x="604" y="506" class="path-title">Python notebooks on object storage</text>
|
||||
<text x="604" y="522" class="path-sub">explore · validate · model — interactive analysis</text>
|
||||
</svg>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Order fulfillment · Process (dark)</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--rule:rgba(45,49,66,.12);--label-rule:rgba(45,49,66,.20);--lane:rgba(45,49,66,.018);--node:#fff;--node-stroke:rgba(45,49,66,.25);--chip:rgba(45,49,66,.12);--dot:rgba(245,245,245,.10);--db:#5e7a9b;--ls:#7c8f6f;--fl:#9c6b50;--tb:#b8915a;--wb:#6e6479;--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:3rem 2rem}
|
||||
.frame{max-width:1120px;width:100%}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1000px;display:block}
|
||||
svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.lane{fill:var(--lane)}.rule{stroke:var(--rule)}.label-rule{stroke:var(--label-rule)}.actor{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.08em}.step-label{fill:var(--muted);font:500 6px var(--mono);letter-spacing:.12em}.step-chip{fill:var(--chip)}.step-num{fill:var(--ink);font:600 7px var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1}.connector.focal{stroke:var(--accent);stroke-width:1.2}.node-mask{fill:var(--paper)}.node{fill:var(--node);stroke:var(--node-stroke)}.node.focal{fill:var(--accent-tint);stroke:var(--accent);stroke-width:1.2}.role{fill:rgba(245,245,245,.10)}.role.focal{fill:rgba(240,138,89,.26)}.role-text{fill:var(--ink);font:600 6px var(--mono)}.role-text.focal{fill:var(--accent)}.node-title{fill:var(--ink);font:600 9px var(--sans)}.node-sub{fill:var(--muted);font:6.5px var(--mono)}.node-tool{fill:var(--soft);font:6.5px var(--mono)}.data-text{fill:white;font:700 5px var(--mono)}.legend-key{fill:var(--muted);font:500 7px var(--mono);letter-spacing:.12em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Process · Diagram Design</p>
|
||||
<h1>Order fulfillment across four teams</h1>
|
||||
<svg viewBox="0 0 1064 436" xmlns="http://www.w3.org/2000/svg" aria-label="Order fulfillment process from customer order to support closure">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="11" cy="11" r="0.8" fill="var(--dot)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--accent)"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0,6 2.5,0 5" fill="var(--muted)"/></marker>
|
||||
</defs>
|
||||
<rect width="1064" height="436" fill="var(--paper)"/><rect width="1064" height="436" fill="url(#dots)" opacity=".55"/>
|
||||
<rect class="lane" x="140" y="36" width="924" height="80"/><rect class="lane" x="140" y="196" width="924" height="80"/>
|
||||
<g class="rule" stroke-width=".8"><line x1="0" y1="36" x2="1064" y2="36"/><line x1="0" y1="116" x2="1064" y2="116"/><line x1="0" y1="196" x2="1064" y2="196"/><line x1="0" y1="276" x2="1064" y2="276"/><line x1="0" y1="356" x2="1064" y2="356"/></g>
|
||||
<line class="label-rule" x1="140" y1="36" x2="140" y2="356"/>
|
||||
<g class="actor" text-anchor="middle"><text x="70" y="80">CUSTOMER</text><text x="70" y="160">SUPPORT</text><text x="70" y="240">WAREHOUSE</text><text x="70" y="320">FINANCE</text></g>
|
||||
<g text-anchor="middle">
|
||||
<rect class="step-chip" x="190" y="8" width="16" height="16" rx="8"/><text class="step-num" x="198" y="19">1</text><text class="step-label" x="198" y="32">ORDER</text>
|
||||
<rect class="step-chip" x="302" y="8" width="16" height="16" rx="8"/><text class="step-num" x="310" y="19">2</text><text class="step-label" x="310" y="32">VERIFY</text>
|
||||
<rect x="414" y="8" width="16" height="16" rx="8" fill="rgba(240,138,89,.22)"/><text x="422" y="19" fill="var(--accent)" font-size="7" font-weight="600" class="mono">3</text><text x="422" y="32" fill="var(--accent)" font-size="6" font-weight="500" letter-spacing=".12em" class="mono">ALLOCATE</text>
|
||||
<rect class="step-chip" x="526" y="8" width="16" height="16" rx="8"/><text class="step-num" x="534" y="19">4</text><text class="step-label" x="534" y="32">PICK</text>
|
||||
<rect class="step-chip" x="638" y="8" width="16" height="16" rx="8"/><text class="step-num" x="646" y="19">5</text><text class="step-label" x="646" y="32">PACK</text>
|
||||
<rect class="step-chip" x="750" y="8" width="16" height="16" rx="8"/><text class="step-num" x="758" y="19">6</text><text class="step-label" x="758" y="32">PAY</text>
|
||||
<rect class="step-chip" x="862" y="8" width="16" height="16" rx="8"/><text class="step-num" x="870" y="19">7</text><text class="step-label" x="870" y="32">RECEIVE</text>
|
||||
<rect class="step-chip" x="974" y="8" width="16" height="16" rx="8"/><text class="step-num" x="982" y="19">8</text><text class="step-label" x="982" y="32">CLOSE</text>
|
||||
</g>
|
||||
<!-- Connectors: right exit, r=8 orthogonal bend, top/bottom entry. -->
|
||||
<path class="connector" d="M248 76 H302 Q310 76 310 84 V124" marker-end="url(#arrow)"/><path class="connector focal" d="M360 156 H414 Q422 156 422 164 V204" marker-end="url(#arrow-accent)"/><line class="connector focal" x1="472" y1="236" x2="484" y2="236" marker-end="url(#arrow-accent)"/><line class="connector" x1="584" y1="236" x2="596" y2="236" marker-end="url(#arrow)"/><path class="connector" d="M696 236 H750 Q758 236 758 244 V284" marker-end="url(#arrow)"/><path class="connector" d="M808 316 H862 Q870 316 870 308 V108" marker-end="url(#arrow)"/><path class="connector" d="M920 76 H974 Q982 76 982 84 V124" marker-end="url(#arrow)"/>
|
||||
<!-- Nodes -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="148" y="44" width="100" height="64" rx="6"/><rect class="node" x="148" y="44" width="100" height="64" rx="6"/><rect class="role" x="152" y="48" width="20" height="10" rx="2"/><text class="role-text" x="162" y="56">CUS</text><text class="node-title" x="198" y="70">Place order</text><text class="node-sub" x="198" y="84">cart → order</text><text class="node-tool" x="198" y="96">storefront</text><rect x="228" y="98" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="236" y="104">DB</text></g>
|
||||
<g><rect class="node-mask" x="260" y="124" width="100" height="64" rx="6"/><rect class="node" x="260" y="124" width="100" height="64" rx="6"/><rect class="role" x="264" y="128" width="20" height="10" rx="2"/><text class="role-text" x="274" y="136">SUP</text><text class="node-title" x="310" y="150">Verify order</text><text class="node-sub" x="310" y="164">order → cleared</text><text class="node-tool" x="310" y="176">service desk</text><rect x="264" y="178" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="272" y="184">DB</text><rect x="340" y="178" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="348" y="184">DB</text></g>
|
||||
<g><rect class="node-mask" x="372" y="204" width="100" height="64" rx="6"/><rect class="node focal" x="372" y="204" width="100" height="64" rx="6"/><rect class="role focal" x="376" y="208" width="20" height="10" rx="2"/><text class="role-text focal" x="386" y="216">WHS</text><text class="node-title" x="422" y="230">Allocate stock</text><text class="node-sub" x="422" y="244">order → pick list</text><text class="node-tool" x="422" y="256">inventory system</text><rect x="376" y="258" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="384" y="264">DB</text><rect x="452" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="460" y="264">LS</text></g>
|
||||
<g><rect class="node-mask" x="484" y="204" width="100" height="64" rx="6"/><rect class="node" x="484" y="204" width="100" height="64" rx="6"/><rect class="role" x="488" y="208" width="20" height="10" rx="2"/><text class="role-text" x="498" y="216">WHS</text><text class="node-title" x="534" y="230">Pick items</text><text class="node-sub" x="534" y="244">list → picked</text><text class="node-tool" x="534" y="256">handheld scanner</text><rect x="488" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="496" y="264">LS</text><rect x="564" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="572" y="264">LS</text></g>
|
||||
<g><rect class="node-mask" x="596" y="204" width="100" height="64" rx="6"/><rect class="node" x="596" y="204" width="100" height="64" rx="6"/><rect class="role" x="600" y="208" width="20" height="10" rx="2"/><text class="role-text" x="610" y="216">WHS</text><text class="node-title" x="646" y="230">Pack order</text><text class="node-sub" x="646" y="244">picked → shipment</text><text class="node-tool" x="646" y="256">packing station</text><rect x="600" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="608" y="264">LS</text><rect x="676" y="258" width="16" height="8" rx="2" fill="var(--fl)"/><text class="data-text" x="684" y="264">FL</text></g>
|
||||
<g><rect class="node-mask" x="708" y="284" width="100" height="64" rx="6"/><rect class="node" x="708" y="284" width="100" height="64" rx="6"/><rect class="role" x="712" y="288" width="20" height="10" rx="2"/><text class="role-text" x="722" y="296">FIN</text><text class="node-title" x="758" y="310">Capture payment</text><text class="node-sub" x="758" y="324">shipment → receipt</text><text class="node-tool" x="758" y="336">payment gateway</text><rect x="712" y="338" width="16" height="8" rx="2" fill="var(--fl)"/><text class="data-text" x="720" y="344">FL</text><rect x="788" y="338" width="16" height="8" rx="2" fill="var(--tb)"/><text class="data-text" x="796" y="344">TB</text></g>
|
||||
<g><rect class="node-mask" x="820" y="44" width="100" height="64" rx="6"/><rect class="node" x="820" y="44" width="100" height="64" rx="6"/><rect class="role" x="824" y="48" width="20" height="10" rx="2"/><text class="role-text" x="834" y="56">CUS</text><text class="node-title" x="870" y="70">Receive shipment</text><text class="node-sub" x="870" y="84">receipt → confirmed</text><text class="node-tool" x="870" y="96">delivery portal</text><rect x="824" y="98" width="16" height="8" rx="2" fill="var(--tb)"/><text class="data-text" x="832" y="104">TB</text><rect x="900" y="98" width="16" height="8" rx="2" fill="var(--wb)"/><text class="data-text" x="908" y="104">WB</text></g>
|
||||
<g><rect class="node-mask" x="932" y="124" width="100" height="64" rx="6"/><rect class="node" x="932" y="124" width="100" height="64" rx="6"/><rect class="role" x="936" y="128" width="20" height="10" rx="2"/><text class="role-text" x="946" y="136">SUP</text><text class="node-title" x="982" y="150">Close order</text><text class="node-sub" x="982" y="164">confirmed → closed</text><text class="node-tool" x="982" y="176">service desk</text><rect x="936" y="178" width="16" height="8" rx="2" fill="var(--wb)"/><text class="data-text" x="944" y="184">WB</text></g>
|
||||
</g>
|
||||
<!-- Three-row type key -->
|
||||
<g><text class="legend-key" x="144" y="372">STEPS</text><g text-anchor="middle"><rect class="step-chip" x="208" y="364" width="16" height="16" rx="8"/><text class="step-num" x="216" y="375">1</text><text class="legend-text" x="248" y="375">ORDER</text><rect class="step-chip" x="300" y="364" width="16" height="16" rx="8"/><text class="step-num" x="308" y="375">2</text><text class="legend-text" x="340" y="375">VERIFY</text><rect x="392" y="364" width="16" height="16" rx="8" fill="rgba(240,138,89,.22)"/><text x="400" y="375" fill="var(--accent)" font-size="7" class="mono">3</text><text x="432" y="375" fill="var(--accent)" font-size="8">ALLOCATE</text><rect class="step-chip" x="496" y="364" width="16" height="16" rx="8"/><text class="step-num" x="504" y="375">4</text><text class="legend-text" x="532" y="375">PICK</text><rect class="step-chip" x="576" y="364" width="16" height="16" rx="8"/><text class="step-num" x="584" y="375">5</text><text class="legend-text" x="612" y="375">PACK</text><rect class="step-chip" x="656" y="364" width="16" height="16" rx="8"/><text class="step-num" x="664" y="375">6</text><text class="legend-text" x="692" y="375">PAY</text><rect class="step-chip" x="736" y="364" width="16" height="16" rx="8"/><text class="step-num" x="744" y="375">7</text><text class="legend-text" x="780" y="375">RECEIVE</text><rect class="step-chip" x="832" y="364" width="16" height="16" rx="8"/><text class="step-num" x="840" y="375">8</text><text class="legend-text" x="872" y="375">CLOSE</text></g>
|
||||
<text class="legend-key" x="144" y="393">DATA TYPE</text><rect x="220" y="386" width="16" height="8" rx="2" fill="var(--db)"/><text class="legend-text" x="244" y="393">DB · records</text><rect x="328" y="386" width="16" height="8" rx="2" fill="var(--ls)"/><text class="legend-text" x="352" y="393">LS · task</text><rect x="428" y="386" width="16" height="8" rx="2" fill="var(--fl)"/><text class="legend-text" x="452" y="393">FL · document</text><rect x="552" y="386" width="16" height="8" rx="2" fill="var(--tb)"/><text class="legend-text" x="576" y="393">TB · transaction</text><rect x="688" y="386" width="16" height="8" rx="2" fill="var(--wb)"/><text class="legend-text" x="712" y="393">WB · web status</text><text class="legend-key" x="856" y="393">LEFT IN · RIGHT OUT</text>
|
||||
<text class="legend-key" x="144" y="414">FLOW</text><line x1="200" y1="410" x2="228" y2="410" stroke="var(--accent)" stroke-width="1.2" marker-end="url(#arrow-accent)"/><text class="legend-text" x="240" y="414">Critical handoff</text><line x1="360" y1="410" x2="388" y2="410" stroke="var(--muted)" marker-end="url(#arrow)"/><text class="legend-text" x="400" y="414">Sequential handoff</text></g>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Order fulfillment · Process overview</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--rule:rgba(45,49,66,.12);--label-rule:rgba(45,49,66,.20);--lane:rgba(45,49,66,.018);--node:#fff;--node-stroke:rgba(45,49,66,.25);--chip:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--db:#5e7a9b;--ls:#7c8f6f;--fl:#9c6b50;--tb:#b8915a;--wb:#6e6479;--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;padding:3rem 2rem}
|
||||
.frame{max-width:1120px;width:100%;margin:0 auto}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1000px;display:block}
|
||||
svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.lane{fill:var(--lane)}.rule{stroke:var(--rule)}.label-rule{stroke:var(--label-rule)}.actor{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.08em}.step-label{fill:var(--muted);font:500 6px var(--mono);letter-spacing:.12em}.step-chip{fill:var(--chip)}.step-num{fill:var(--ink);font:600 7px var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1}.connector.focal{stroke:var(--accent);stroke-width:1.2}.node-mask{fill:var(--paper)}.node{fill:var(--node);stroke:var(--node-stroke)}.node.focal{fill:var(--accent-tint);stroke:var(--accent);stroke-width:1.2}.role{fill:rgba(45,49,66,.10)}.role.focal{fill:rgba(235,108,54,.25)}.role-text{fill:var(--ink);font:600 6px var(--mono)}.role-text.focal{fill:var(--accent)}.node-title{fill:var(--ink);font:600 9px var(--sans)}.node-sub{fill:var(--muted);font:6.5px var(--mono)}.node-tool{fill:var(--soft);font:6.5px var(--mono)}.data-text{fill:white;font:700 5px var(--mono)}.legend-key{fill:var(--muted);font:500 7px var(--mono);letter-spacing:.12em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
.subtitle{max-width:62ch;color:var(--muted);font-size:1rem;line-height:1.55;margin:-.75rem 0 2rem}.diagram-container{background:#ececec;border:1px solid var(--rule);border-radius:8px;padding:1.5rem;overflow-x:auto}.cards{display:grid;grid-template-columns:1.1fr 1fr .9fr;gap:1rem;margin-top:1.5rem}.card{background:#fff;border:1px solid var(--rule);border-radius:6px;padding:1.25rem}.card .card-label{font:500 .5rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}.card-header{display:flex;align-items:center;gap:.6rem;margin-bottom:.875rem;padding-bottom:.875rem;border-bottom:1px solid rgba(45,49,66,.08)}.card-dot{width:7px;height:7px;border-radius:50%;background:var(--muted)}.card-dot.coral{background:var(--accent)}.card-dot.ink{background:var(--ink)}.card h3{font-size:.875rem;font-weight:600}.card p,.card li{color:var(--muted);font-size:.8125rem;line-height:1.55}.card ul{list-style:none}.card li{padding-left:.875rem;position:relative}.card li::before{content:'—';position:absolute;left:0;color:rgba(45,49,66,.25)}.footer{display:flex;justify-content:space-between;gap:.5rem;flex-wrap:wrap;margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--rule);color:var(--soft);font:.72rem var(--mono);letter-spacing:.06em}@media(max-width:820px){.cards{grid-template-columns:1fr}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Process · Diagram Design</p>
|
||||
<h1>Order fulfillment across four teams</h1>
|
||||
<p class="subtitle">One order moves through customer, support, warehouse, and finance ownership, while the payload chips show what each handoff carries.</p>
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1064 436" xmlns="http://www.w3.org/2000/svg" aria-label="Order fulfillment process from customer order to support closure">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="11" cy="11" r="0.8" fill="var(--dot)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--accent)"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0,6 2.5,0 5" fill="var(--muted)"/></marker>
|
||||
</defs>
|
||||
<rect width="1064" height="436" fill="var(--paper)"/><rect width="1064" height="436" fill="url(#dots)" opacity=".55"/>
|
||||
<rect class="lane" x="140" y="36" width="924" height="80"/><rect class="lane" x="140" y="196" width="924" height="80"/>
|
||||
<g class="rule" stroke-width=".8"><line x1="0" y1="36" x2="1064" y2="36"/><line x1="0" y1="116" x2="1064" y2="116"/><line x1="0" y1="196" x2="1064" y2="196"/><line x1="0" y1="276" x2="1064" y2="276"/><line x1="0" y1="356" x2="1064" y2="356"/></g>
|
||||
<line class="label-rule" x1="140" y1="36" x2="140" y2="356"/>
|
||||
<g class="actor" text-anchor="middle"><text x="70" y="80">CUSTOMER</text><text x="70" y="160">SUPPORT</text><text x="70" y="240">WAREHOUSE</text><text x="70" y="320">FINANCE</text></g>
|
||||
<g text-anchor="middle">
|
||||
<rect class="step-chip" x="190" y="8" width="16" height="16" rx="8"/><text class="step-num" x="198" y="19">1</text><text class="step-label" x="198" y="32">ORDER</text>
|
||||
<rect class="step-chip" x="302" y="8" width="16" height="16" rx="8"/><text class="step-num" x="310" y="19">2</text><text class="step-label" x="310" y="32">VERIFY</text>
|
||||
<rect x="414" y="8" width="16" height="16" rx="8" fill="rgba(235,108,54,.20)"/><text x="422" y="19" fill="var(--accent)" font-size="7" font-weight="600" class="mono">3</text><text x="422" y="32" fill="var(--accent)" font-size="6" font-weight="500" letter-spacing=".12em" class="mono">ALLOCATE</text>
|
||||
<rect class="step-chip" x="526" y="8" width="16" height="16" rx="8"/><text class="step-num" x="534" y="19">4</text><text class="step-label" x="534" y="32">PICK</text>
|
||||
<rect class="step-chip" x="638" y="8" width="16" height="16" rx="8"/><text class="step-num" x="646" y="19">5</text><text class="step-label" x="646" y="32">PACK</text>
|
||||
<rect class="step-chip" x="750" y="8" width="16" height="16" rx="8"/><text class="step-num" x="758" y="19">6</text><text class="step-label" x="758" y="32">PAY</text>
|
||||
<rect class="step-chip" x="862" y="8" width="16" height="16" rx="8"/><text class="step-num" x="870" y="19">7</text><text class="step-label" x="870" y="32">RECEIVE</text>
|
||||
<rect class="step-chip" x="974" y="8" width="16" height="16" rx="8"/><text class="step-num" x="982" y="19">8</text><text class="step-label" x="982" y="32">CLOSE</text>
|
||||
</g>
|
||||
<!-- Connectors: right exit, r=8 orthogonal bend, top/bottom entry. -->
|
||||
<path class="connector" d="M248 76 H302 Q310 76 310 84 V124" marker-end="url(#arrow)"/><path class="connector focal" d="M360 156 H414 Q422 156 422 164 V204" marker-end="url(#arrow-accent)"/><line class="connector focal" x1="472" y1="236" x2="484" y2="236" marker-end="url(#arrow-accent)"/><line class="connector" x1="584" y1="236" x2="596" y2="236" marker-end="url(#arrow)"/><path class="connector" d="M696 236 H750 Q758 236 758 244 V284" marker-end="url(#arrow)"/><path class="connector" d="M808 316 H862 Q870 316 870 308 V108" marker-end="url(#arrow)"/><path class="connector" d="M920 76 H974 Q982 76 982 84 V124" marker-end="url(#arrow)"/>
|
||||
<!-- Nodes -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="148" y="44" width="100" height="64" rx="6"/><rect class="node" x="148" y="44" width="100" height="64" rx="6"/><rect class="role" x="152" y="48" width="20" height="10" rx="2"/><text class="role-text" x="162" y="56">CUS</text><text class="node-title" x="198" y="70">Place order</text><text class="node-sub" x="198" y="84">cart → order</text><text class="node-tool" x="198" y="96">storefront</text><rect x="228" y="98" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="236" y="104">DB</text></g>
|
||||
<g><rect class="node-mask" x="260" y="124" width="100" height="64" rx="6"/><rect class="node" x="260" y="124" width="100" height="64" rx="6"/><rect class="role" x="264" y="128" width="20" height="10" rx="2"/><text class="role-text" x="274" y="136">SUP</text><text class="node-title" x="310" y="150">Verify order</text><text class="node-sub" x="310" y="164">order → cleared</text><text class="node-tool" x="310" y="176">service desk</text><rect x="264" y="178" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="272" y="184">DB</text><rect x="340" y="178" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="348" y="184">DB</text></g>
|
||||
<g><rect class="node-mask" x="372" y="204" width="100" height="64" rx="6"/><rect class="node focal" x="372" y="204" width="100" height="64" rx="6"/><rect class="role focal" x="376" y="208" width="20" height="10" rx="2"/><text class="role-text focal" x="386" y="216">WHS</text><text class="node-title" x="422" y="230">Allocate stock</text><text class="node-sub" x="422" y="244">order → pick list</text><text class="node-tool" x="422" y="256">inventory system</text><rect x="376" y="258" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="384" y="264">DB</text><rect x="452" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="460" y="264">LS</text></g>
|
||||
<g><rect class="node-mask" x="484" y="204" width="100" height="64" rx="6"/><rect class="node" x="484" y="204" width="100" height="64" rx="6"/><rect class="role" x="488" y="208" width="20" height="10" rx="2"/><text class="role-text" x="498" y="216">WHS</text><text class="node-title" x="534" y="230">Pick items</text><text class="node-sub" x="534" y="244">list → picked</text><text class="node-tool" x="534" y="256">handheld scanner</text><rect x="488" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="496" y="264">LS</text><rect x="564" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="572" y="264">LS</text></g>
|
||||
<g><rect class="node-mask" x="596" y="204" width="100" height="64" rx="6"/><rect class="node" x="596" y="204" width="100" height="64" rx="6"/><rect class="role" x="600" y="208" width="20" height="10" rx="2"/><text class="role-text" x="610" y="216">WHS</text><text class="node-title" x="646" y="230">Pack order</text><text class="node-sub" x="646" y="244">picked → shipment</text><text class="node-tool" x="646" y="256">packing station</text><rect x="600" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="608" y="264">LS</text><rect x="676" y="258" width="16" height="8" rx="2" fill="var(--fl)"/><text class="data-text" x="684" y="264">FL</text></g>
|
||||
<g><rect class="node-mask" x="708" y="284" width="100" height="64" rx="6"/><rect class="node" x="708" y="284" width="100" height="64" rx="6"/><rect class="role" x="712" y="288" width="20" height="10" rx="2"/><text class="role-text" x="722" y="296">FIN</text><text class="node-title" x="758" y="310">Capture payment</text><text class="node-sub" x="758" y="324">shipment → receipt</text><text class="node-tool" x="758" y="336">payment gateway</text><rect x="712" y="338" width="16" height="8" rx="2" fill="var(--fl)"/><text class="data-text" x="720" y="344">FL</text><rect x="788" y="338" width="16" height="8" rx="2" fill="var(--tb)"/><text class="data-text" x="796" y="344">TB</text></g>
|
||||
<g><rect class="node-mask" x="820" y="44" width="100" height="64" rx="6"/><rect class="node" x="820" y="44" width="100" height="64" rx="6"/><rect class="role" x="824" y="48" width="20" height="10" rx="2"/><text class="role-text" x="834" y="56">CUS</text><text class="node-title" x="870" y="70">Receive shipment</text><text class="node-sub" x="870" y="84">receipt → confirmed</text><text class="node-tool" x="870" y="96">delivery portal</text><rect x="824" y="98" width="16" height="8" rx="2" fill="var(--tb)"/><text class="data-text" x="832" y="104">TB</text><rect x="900" y="98" width="16" height="8" rx="2" fill="var(--wb)"/><text class="data-text" x="908" y="104">WB</text></g>
|
||||
<g><rect class="node-mask" x="932" y="124" width="100" height="64" rx="6"/><rect class="node" x="932" y="124" width="100" height="64" rx="6"/><rect class="role" x="936" y="128" width="20" height="10" rx="2"/><text class="role-text" x="946" y="136">SUP</text><text class="node-title" x="982" y="150">Close order</text><text class="node-sub" x="982" y="164">confirmed → closed</text><text class="node-tool" x="982" y="176">service desk</text><rect x="936" y="178" width="16" height="8" rx="2" fill="var(--wb)"/><text class="data-text" x="944" y="184">WB</text></g>
|
||||
</g>
|
||||
<!-- Three-row type key -->
|
||||
<g><text class="legend-key" x="144" y="372">STEPS</text><g text-anchor="middle"><rect class="step-chip" x="208" y="364" width="16" height="16" rx="8"/><text class="step-num" x="216" y="375">1</text><text class="legend-text" x="248" y="375">ORDER</text><rect class="step-chip" x="300" y="364" width="16" height="16" rx="8"/><text class="step-num" x="308" y="375">2</text><text class="legend-text" x="340" y="375">VERIFY</text><rect x="392" y="364" width="16" height="16" rx="8" fill="rgba(235,108,54,.20)"/><text x="400" y="375" fill="var(--accent)" font-size="7" class="mono">3</text><text x="432" y="375" fill="var(--accent)" font-size="8">ALLOCATE</text><rect class="step-chip" x="496" y="364" width="16" height="16" rx="8"/><text class="step-num" x="504" y="375">4</text><text class="legend-text" x="532" y="375">PICK</text><rect class="step-chip" x="576" y="364" width="16" height="16" rx="8"/><text class="step-num" x="584" y="375">5</text><text class="legend-text" x="612" y="375">PACK</text><rect class="step-chip" x="656" y="364" width="16" height="16" rx="8"/><text class="step-num" x="664" y="375">6</text><text class="legend-text" x="692" y="375">PAY</text><rect class="step-chip" x="736" y="364" width="16" height="16" rx="8"/><text class="step-num" x="744" y="375">7</text><text class="legend-text" x="780" y="375">RECEIVE</text><rect class="step-chip" x="832" y="364" width="16" height="16" rx="8"/><text class="step-num" x="840" y="375">8</text><text class="legend-text" x="872" y="375">CLOSE</text></g>
|
||||
<text class="legend-key" x="144" y="393">DATA TYPE</text><rect x="220" y="386" width="16" height="8" rx="2" fill="var(--db)"/><text class="legend-text" x="244" y="393">DB · records</text><rect x="328" y="386" width="16" height="8" rx="2" fill="var(--ls)"/><text class="legend-text" x="352" y="393">LS · task</text><rect x="428" y="386" width="16" height="8" rx="2" fill="var(--fl)"/><text class="legend-text" x="452" y="393">FL · document</text><rect x="552" y="386" width="16" height="8" rx="2" fill="var(--tb)"/><text class="legend-text" x="576" y="393">TB · transaction</text><rect x="688" y="386" width="16" height="8" rx="2" fill="var(--wb)"/><text class="legend-text" x="712" y="393">WB · web status</text><text class="legend-key" x="856" y="393">LEFT IN · RIGHT OUT</text>
|
||||
<text class="legend-key" x="144" y="414">FLOW</text><line x1="200" y1="410" x2="228" y2="410" stroke="var(--accent)" stroke-width="1.2" marker-end="url(#arrow-accent)"/><text class="legend-text" x="240" y="414">Critical handoff</text><line x1="360" y1="410" x2="388" y2="410" stroke="var(--muted)" marker-end="url(#arrow)"/><text class="legend-text" x="400" y="414">Sequential handoff</text></g>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<article class="card"><p class="card-label">THE PIVOT</p><div class="card-header"><span class="card-dot coral"></span><h3>Stock allocation commits the order</h3></div><p>The focal handoff turns a verified order record into an actionable warehouse pick list.</p></article>
|
||||
<article class="card"><div class="card-header"><span class="card-dot ink"></span><h3>Ownership stays explicit</h3></div><ul><li>Support verifies and closes</li><li>Warehouse allocates, picks, and packs</li><li>Finance captures the final payment</li></ul></article>
|
||||
<article class="card"><div class="card-header"><span class="card-dot"></span><h3>Payloads change with the work</h3></div><p>Records become tasks, then documents, transactions, and a customer-visible status.</p></article>
|
||||
</div>
|
||||
<footer class="footer"><span>order fulfillment · process</span><span>example · diagram design</span></footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Order fulfillment · Process</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--paper:#f5f5f5;--ink:#2d3142;--muted:#4f5d75;--soft:#7a8399;--accent:#eb6c36;--accent-tint:rgba(235,108,54,.08);--rule:rgba(45,49,66,.12);--label-rule:rgba(45,49,66,.20);--lane:rgba(45,49,66,.018);--node:#fff;--node-stroke:rgba(45,49,66,.25);--chip:rgba(45,49,66,.12);--dot:rgba(45,49,66,.10);--db:#5e7a9b;--ls:#7c8f6f;--fl:#9c6b50;--tb:#b8915a;--wb:#6e6479;--sans:'Geist',system-ui,sans-serif;--serif:'Instrument Serif',serif;--mono:'Geist Mono',ui-monospace,monospace}
|
||||
body{font-family:var(--sans);background:var(--paper);color:var(--ink);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:3rem 2rem}
|
||||
.frame{max-width:1120px;width:100%}.eyebrow{font:500 .66rem var(--mono);letter-spacing:.18em;text-transform:uppercase;color:var(--muted);margin-bottom:.5rem}h1{font:400 clamp(1.5rem,2.4vw + .75rem,2rem)/1.15 var(--serif);letter-spacing:-.02em;margin-bottom:1.5rem}svg{width:100%;min-width:1000px;display:block}
|
||||
svg text{font-family:var(--sans)}.mono{font-family:var(--mono)}.lane{fill:var(--lane)}.rule{stroke:var(--rule)}.label-rule{stroke:var(--label-rule)}.actor{fill:var(--muted);font:500 8px var(--mono);letter-spacing:.08em}.step-label{fill:var(--muted);font:500 6px var(--mono);letter-spacing:.12em}.step-chip{fill:var(--chip)}.step-num{fill:var(--ink);font:600 7px var(--mono)}.connector{fill:none;stroke:var(--muted);stroke-width:1}.connector.focal{stroke:var(--accent);stroke-width:1.2}.node-mask{fill:var(--paper)}.node{fill:var(--node);stroke:var(--node-stroke)}.node.focal{fill:var(--accent-tint);stroke:var(--accent);stroke-width:1.2}.role{fill:rgba(45,49,66,.10)}.role.focal{fill:rgba(235,108,54,.25)}.role-text{fill:var(--ink);font:600 6px var(--mono)}.role-text.focal{fill:var(--accent)}.node-title{fill:var(--ink);font:600 9px var(--sans)}.node-sub{fill:var(--muted);font:6.5px var(--mono)}.node-tool{fill:var(--soft);font:6.5px var(--mono)}.data-text{fill:white;font:700 5px var(--mono)}.legend-key{fill:var(--muted);font:500 7px var(--mono);letter-spacing:.12em}.legend-text{fill:var(--muted);font:8px var(--sans)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Process · Diagram Design</p>
|
||||
<h1>Order fulfillment across four teams</h1>
|
||||
<svg viewBox="0 0 1064 436" xmlns="http://www.w3.org/2000/svg" aria-label="Order fulfillment process from customer order to support closure">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="11" cy="11" r="0.8" fill="var(--dot)"/></pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--muted)"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--accent)"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0,6 2.5,0 5" fill="var(--muted)"/></marker>
|
||||
</defs>
|
||||
<rect width="1064" height="436" fill="var(--paper)"/><rect width="1064" height="436" fill="url(#dots)" opacity=".55"/>
|
||||
<rect class="lane" x="140" y="36" width="924" height="80"/><rect class="lane" x="140" y="196" width="924" height="80"/>
|
||||
<g class="rule" stroke-width=".8"><line x1="0" y1="36" x2="1064" y2="36"/><line x1="0" y1="116" x2="1064" y2="116"/><line x1="0" y1="196" x2="1064" y2="196"/><line x1="0" y1="276" x2="1064" y2="276"/><line x1="0" y1="356" x2="1064" y2="356"/></g>
|
||||
<line class="label-rule" x1="140" y1="36" x2="140" y2="356"/>
|
||||
<g class="actor" text-anchor="middle"><text x="70" y="80">CUSTOMER</text><text x="70" y="160">SUPPORT</text><text x="70" y="240">WAREHOUSE</text><text x="70" y="320">FINANCE</text></g>
|
||||
<g text-anchor="middle">
|
||||
<rect class="step-chip" x="190" y="8" width="16" height="16" rx="8"/><text class="step-num" x="198" y="19">1</text><text class="step-label" x="198" y="32">ORDER</text>
|
||||
<rect class="step-chip" x="302" y="8" width="16" height="16" rx="8"/><text class="step-num" x="310" y="19">2</text><text class="step-label" x="310" y="32">VERIFY</text>
|
||||
<rect x="414" y="8" width="16" height="16" rx="8" fill="rgba(235,108,54,.20)"/><text x="422" y="19" fill="var(--accent)" font-size="7" font-weight="600" class="mono">3</text><text x="422" y="32" fill="var(--accent)" font-size="6" font-weight="500" letter-spacing=".12em" class="mono">ALLOCATE</text>
|
||||
<rect class="step-chip" x="526" y="8" width="16" height="16" rx="8"/><text class="step-num" x="534" y="19">4</text><text class="step-label" x="534" y="32">PICK</text>
|
||||
<rect class="step-chip" x="638" y="8" width="16" height="16" rx="8"/><text class="step-num" x="646" y="19">5</text><text class="step-label" x="646" y="32">PACK</text>
|
||||
<rect class="step-chip" x="750" y="8" width="16" height="16" rx="8"/><text class="step-num" x="758" y="19">6</text><text class="step-label" x="758" y="32">PAY</text>
|
||||
<rect class="step-chip" x="862" y="8" width="16" height="16" rx="8"/><text class="step-num" x="870" y="19">7</text><text class="step-label" x="870" y="32">RECEIVE</text>
|
||||
<rect class="step-chip" x="974" y="8" width="16" height="16" rx="8"/><text class="step-num" x="982" y="19">8</text><text class="step-label" x="982" y="32">CLOSE</text>
|
||||
</g>
|
||||
<!-- Connectors: right exit, r=8 orthogonal bend, top/bottom entry. -->
|
||||
<path class="connector" d="M248 76 H302 Q310 76 310 84 V124" marker-end="url(#arrow)"/><path class="connector focal" d="M360 156 H414 Q422 156 422 164 V204" marker-end="url(#arrow-accent)"/><line class="connector focal" x1="472" y1="236" x2="484" y2="236" marker-end="url(#arrow-accent)"/><line class="connector" x1="584" y1="236" x2="596" y2="236" marker-end="url(#arrow)"/><path class="connector" d="M696 236 H750 Q758 236 758 244 V284" marker-end="url(#arrow)"/><path class="connector" d="M808 316 H862 Q870 316 870 308 V108" marker-end="url(#arrow)"/><path class="connector" d="M920 76 H974 Q982 76 982 84 V124" marker-end="url(#arrow)"/>
|
||||
<!-- Nodes -->
|
||||
<g text-anchor="middle">
|
||||
<g><rect class="node-mask" x="148" y="44" width="100" height="64" rx="6"/><rect class="node" x="148" y="44" width="100" height="64" rx="6"/><rect class="role" x="152" y="48" width="20" height="10" rx="2"/><text class="role-text" x="162" y="56">CUS</text><text class="node-title" x="198" y="70">Place order</text><text class="node-sub" x="198" y="84">cart → order</text><text class="node-tool" x="198" y="96">storefront</text><rect x="228" y="98" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="236" y="104">DB</text></g>
|
||||
<g><rect class="node-mask" x="260" y="124" width="100" height="64" rx="6"/><rect class="node" x="260" y="124" width="100" height="64" rx="6"/><rect class="role" x="264" y="128" width="20" height="10" rx="2"/><text class="role-text" x="274" y="136">SUP</text><text class="node-title" x="310" y="150">Verify order</text><text class="node-sub" x="310" y="164">order → cleared</text><text class="node-tool" x="310" y="176">service desk</text><rect x="264" y="178" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="272" y="184">DB</text><rect x="340" y="178" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="348" y="184">DB</text></g>
|
||||
<g><rect class="node-mask" x="372" y="204" width="100" height="64" rx="6"/><rect class="node focal" x="372" y="204" width="100" height="64" rx="6"/><rect class="role focal" x="376" y="208" width="20" height="10" rx="2"/><text class="role-text focal" x="386" y="216">WHS</text><text class="node-title" x="422" y="230">Allocate stock</text><text class="node-sub" x="422" y="244">order → pick list</text><text class="node-tool" x="422" y="256">inventory system</text><rect x="376" y="258" width="16" height="8" rx="2" fill="var(--db)"/><text class="data-text" x="384" y="264">DB</text><rect x="452" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="460" y="264">LS</text></g>
|
||||
<g><rect class="node-mask" x="484" y="204" width="100" height="64" rx="6"/><rect class="node" x="484" y="204" width="100" height="64" rx="6"/><rect class="role" x="488" y="208" width="20" height="10" rx="2"/><text class="role-text" x="498" y="216">WHS</text><text class="node-title" x="534" y="230">Pick items</text><text class="node-sub" x="534" y="244">list → picked</text><text class="node-tool" x="534" y="256">handheld scanner</text><rect x="488" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="496" y="264">LS</text><rect x="564" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="572" y="264">LS</text></g>
|
||||
<g><rect class="node-mask" x="596" y="204" width="100" height="64" rx="6"/><rect class="node" x="596" y="204" width="100" height="64" rx="6"/><rect class="role" x="600" y="208" width="20" height="10" rx="2"/><text class="role-text" x="610" y="216">WHS</text><text class="node-title" x="646" y="230">Pack order</text><text class="node-sub" x="646" y="244">picked → shipment</text><text class="node-tool" x="646" y="256">packing station</text><rect x="600" y="258" width="16" height="8" rx="2" fill="var(--ls)"/><text class="data-text" x="608" y="264">LS</text><rect x="676" y="258" width="16" height="8" rx="2" fill="var(--fl)"/><text class="data-text" x="684" y="264">FL</text></g>
|
||||
<g><rect class="node-mask" x="708" y="284" width="100" height="64" rx="6"/><rect class="node" x="708" y="284" width="100" height="64" rx="6"/><rect class="role" x="712" y="288" width="20" height="10" rx="2"/><text class="role-text" x="722" y="296">FIN</text><text class="node-title" x="758" y="310">Capture payment</text><text class="node-sub" x="758" y="324">shipment → receipt</text><text class="node-tool" x="758" y="336">payment gateway</text><rect x="712" y="338" width="16" height="8" rx="2" fill="var(--fl)"/><text class="data-text" x="720" y="344">FL</text><rect x="788" y="338" width="16" height="8" rx="2" fill="var(--tb)"/><text class="data-text" x="796" y="344">TB</text></g>
|
||||
<g><rect class="node-mask" x="820" y="44" width="100" height="64" rx="6"/><rect class="node" x="820" y="44" width="100" height="64" rx="6"/><rect class="role" x="824" y="48" width="20" height="10" rx="2"/><text class="role-text" x="834" y="56">CUS</text><text class="node-title" x="870" y="70">Receive shipment</text><text class="node-sub" x="870" y="84">receipt → confirmed</text><text class="node-tool" x="870" y="96">delivery portal</text><rect x="824" y="98" width="16" height="8" rx="2" fill="var(--tb)"/><text class="data-text" x="832" y="104">TB</text><rect x="900" y="98" width="16" height="8" rx="2" fill="var(--wb)"/><text class="data-text" x="908" y="104">WB</text></g>
|
||||
<g><rect class="node-mask" x="932" y="124" width="100" height="64" rx="6"/><rect class="node" x="932" y="124" width="100" height="64" rx="6"/><rect class="role" x="936" y="128" width="20" height="10" rx="2"/><text class="role-text" x="946" y="136">SUP</text><text class="node-title" x="982" y="150">Close order</text><text class="node-sub" x="982" y="164">confirmed → closed</text><text class="node-tool" x="982" y="176">service desk</text><rect x="936" y="178" width="16" height="8" rx="2" fill="var(--wb)"/><text class="data-text" x="944" y="184">WB</text></g>
|
||||
</g>
|
||||
<!-- Three-row type key -->
|
||||
<g><text class="legend-key" x="144" y="372">STEPS</text><g text-anchor="middle"><rect class="step-chip" x="208" y="364" width="16" height="16" rx="8"/><text class="step-num" x="216" y="375">1</text><text class="legend-text" x="248" y="375">ORDER</text><rect class="step-chip" x="300" y="364" width="16" height="16" rx="8"/><text class="step-num" x="308" y="375">2</text><text class="legend-text" x="340" y="375">VERIFY</text><rect x="392" y="364" width="16" height="16" rx="8" fill="rgba(235,108,54,.20)"/><text x="400" y="375" fill="var(--accent)" font-size="7" class="mono">3</text><text x="432" y="375" fill="var(--accent)" font-size="8">ALLOCATE</text><rect class="step-chip" x="496" y="364" width="16" height="16" rx="8"/><text class="step-num" x="504" y="375">4</text><text class="legend-text" x="532" y="375">PICK</text><rect class="step-chip" x="576" y="364" width="16" height="16" rx="8"/><text class="step-num" x="584" y="375">5</text><text class="legend-text" x="612" y="375">PACK</text><rect class="step-chip" x="656" y="364" width="16" height="16" rx="8"/><text class="step-num" x="664" y="375">6</text><text class="legend-text" x="692" y="375">PAY</text><rect class="step-chip" x="736" y="364" width="16" height="16" rx="8"/><text class="step-num" x="744" y="375">7</text><text class="legend-text" x="780" y="375">RECEIVE</text><rect class="step-chip" x="832" y="364" width="16" height="16" rx="8"/><text class="step-num" x="840" y="375">8</text><text class="legend-text" x="872" y="375">CLOSE</text></g>
|
||||
<text class="legend-key" x="144" y="393">DATA TYPE</text><rect x="220" y="386" width="16" height="8" rx="2" fill="var(--db)"/><text class="legend-text" x="244" y="393">DB · records</text><rect x="328" y="386" width="16" height="8" rx="2" fill="var(--ls)"/><text class="legend-text" x="352" y="393">LS · task</text><rect x="428" y="386" width="16" height="8" rx="2" fill="var(--fl)"/><text class="legend-text" x="452" y="393">FL · document</text><rect x="552" y="386" width="16" height="8" rx="2" fill="var(--tb)"/><text class="legend-text" x="576" y="393">TB · transaction</text><rect x="688" y="386" width="16" height="8" rx="2" fill="var(--wb)"/><text class="legend-text" x="712" y="393">WB · web status</text><text class="legend-key" x="856" y="393">LEFT IN · RIGHT OUT</text>
|
||||
<text class="legend-key" x="144" y="414">FLOW</text><line x1="200" y1="410" x2="228" y2="410" stroke="var(--accent)" stroke-width="1.2" marker-end="url(#arrow-accent)"/><text class="legend-text" x="240" y="414">Critical handoff</text><line x1="360" y1="410" x2="388" y2="410" stroke="var(--muted)" marker-end="url(#arrow)"/><text class="legend-text" x="400" y="414">Sequential handoff</text></g>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Storage backends · Capability radar</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #2d3142;
|
||||
--color-ink: #f5f5f5;
|
||||
--color-muted: #bfc0c0;
|
||||
--color-accent: #f08a59;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
color: var(--color-ink);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 900px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Radar · Diagram Design</p>
|
||||
<h1>Storage backends · Capability radar</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#2d3142"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Concentric grid rings -->
|
||||
<polygon points="500,208 530,230 519,266 481,266 470,230" fill="none" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,176 561,220 538,292 462,292 439,220" fill="none" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,144 591,211 556,317 444,317 409,211" fill="none" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,112 622,201 575,343 425,343 378,201" fill="none" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,80 652,191 594,369 406,369 348,191" fill="none" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
|
||||
<!-- Axis spokes -->
|
||||
<line x1="500" y1="240" x2="500" y2="80" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="652" y2="191" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="594" y2="369" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="406" y2="369" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="348" y2="191" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
|
||||
<!-- Axis labels -->
|
||||
<text x="500" y="62" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Small-file handling</text>
|
||||
<text x="669" y="189" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="start">Large-object reads</text>
|
||||
<text x="605" y="400" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="start">Write throughput</text>
|
||||
<text x="395" y="400" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="end">Operational simplicity</text>
|
||||
<text x="331" y="189" fill="#f5f5f5" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="end">Iceberg integration</text>
|
||||
|
||||
<!-- Scale ticks -->
|
||||
<text x="494" y="82" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">10</text>
|
||||
<text x="494" y="114" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">8</text>
|
||||
<text x="494" y="146" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">6</text>
|
||||
<text x="494" y="178" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">4</text>
|
||||
<text x="494" y="210" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">2</text>
|
||||
|
||||
<!-- Non-focal series -->
|
||||
<!-- Ceph · series-2 dusty-blue dark -->
|
||||
<polygon points="500,128 606,206 566,330 462,292 409,211" fill="rgba(130,160,192,0.22)" stroke="#82a0c0" stroke-width="1.5"/>
|
||||
<!-- GCS · series-3 mustard dark -->
|
||||
<polygon points="500,144 637,196 575,343 444,317 394,206" fill="rgba(211,173,122,0.22)" stroke="#d3ad7a" stroke-width="1.5"/>
|
||||
<!-- Amazon S3 · series-1 sage dark -->
|
||||
<polygon points="500,144 652,191 585,356 453,305 378,201" fill="rgba(156,175,143,0.22)" stroke="#9caf8f" stroke-width="1.5"/>
|
||||
|
||||
<!-- Focal series · MinIO -->
|
||||
<polygon points="500,96 622,201 585,356 415,356 363,196" fill="rgba(240,138,89,0.22)" stroke="#f08a59" stroke-width="1.8"/>
|
||||
|
||||
<!-- Focal vertex dots -->
|
||||
<circle cx="500" cy="96" r="4" fill="#f08a59"/>
|
||||
<circle cx="622" cy="201" r="4" fill="#f08a59"/>
|
||||
<circle cx="585" cy="356" r="4" fill="#f08a59"/>
|
||||
<circle cx="415" cy="356" r="4" fill="#f08a59"/>
|
||||
<circle cx="363" cy="196" r="4" fill="#f08a59"/>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="456" x2="960" y2="456" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="472" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="484" width="16" height="8" fill="rgba(240,138,89,0.22)" stroke="#f08a59" stroke-width="1.5"/>
|
||||
<text x="64" y="492" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">MinIO · recommended</text>
|
||||
|
||||
<rect x="220" y="484" width="16" height="8" fill="rgba(156,175,143,0.22)" stroke="#9caf8f" stroke-width="1.5"/>
|
||||
<text x="244" y="492" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Amazon S3</text>
|
||||
|
||||
<rect x="344" y="484" width="16" height="8" fill="rgba(130,160,192,0.22)" stroke="#82a0c0" stroke-width="1.5"/>
|
||||
<text x="368" y="492" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Ceph</text>
|
||||
|
||||
<rect x="424" y="484" width="16" height="8" fill="rgba(211,173,122,0.22)" stroke="#d3ad7a" stroke-width="1.5"/>
|
||||
<text x="448" y="492" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Google Cloud Storage</text>
|
||||
|
||||
<text x="608" y="492" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif" font-style="italic">One coral. Position is the signal — color reserved for the recommended option.</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,133 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Storage backends · Capability radar</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --color-link:#2e5aa8; --series-1:#7c8f6f; --series-2:#5e7a9b; --series-3:#b8915a; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
|
||||
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 58ch; }
|
||||
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
|
||||
svg { width: 100%; min-width: 900px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
@media (max-width: 980px) { .cards { grid-template-columns: 1fr 1fr; } }
|
||||
@media (max-width: 600px) { .cards { grid-template-columns: 1fr; } }
|
||||
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
|
||||
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--color-accent); }
|
||||
.card-dot.sage { background: var(--series-1); }
|
||||
.card-dot.dusty { background: var(--series-2); }
|
||||
.card-dot.mustard { background: var(--series-3); }
|
||||
.card h3 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; }
|
||||
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-soft); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<p class="header-eyebrow">Radar · Diagram Design</p>
|
||||
<h1>Storage backends · Capability radar</h1>
|
||||
<p class="subtitle">Four object-storage candidates scored 0–10 across five workload dimensions. The chart is a decision tool — the recommended option carries the only colored fill; everything else sits in the muted comparison palette.</p>
|
||||
</div>
|
||||
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<polygon points="500,208 530,230 519,266 481,266 470,230" fill="none" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,176 561,220 538,292 462,292 439,220" fill="none" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,144 591,211 556,317 444,317 409,211" fill="none" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,112 622,201 575,343 425,343 378,201" fill="none" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,80 652,191 594,369 406,369 348,191" fill="none" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
|
||||
<line x1="500" y1="240" x2="500" y2="80" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="652" y2="191" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="594" y2="369" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="406" y2="369" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="348" y2="191" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
|
||||
<text x="500" y="62" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Small-file handling</text>
|
||||
<text x="669" y="189" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="start">Large-object reads</text>
|
||||
<text x="605" y="400" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="start">Write throughput</text>
|
||||
<text x="395" y="400" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="end">Operational simplicity</text>
|
||||
<text x="331" y="189" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="end">Iceberg integration</text>
|
||||
|
||||
<text x="494" y="82" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">10</text>
|
||||
<text x="494" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">8</text>
|
||||
<text x="494" y="146" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">6</text>
|
||||
<text x="494" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">4</text>
|
||||
<text x="494" y="210" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">2</text>
|
||||
|
||||
<polygon points="500,128 606,206 566,330 462,292 409,211" fill="rgba(94,122,155,0.18)" stroke="#5e7a9b" stroke-width="1.5"/>
|
||||
<polygon points="500,144 637,196 575,343 444,317 394,206" fill="rgba(184,145,90,0.18)" stroke="#b8915a" stroke-width="1.5"/>
|
||||
<polygon points="500,144 652,191 585,356 453,305 378,201" fill="rgba(124,143,111,0.18)" stroke="#7c8f6f" stroke-width="1.5"/>
|
||||
|
||||
<polygon points="500,96 622,201 585,356 415,356 363,196" fill="rgba(235,108,54,0.18)" stroke="#eb6c36" stroke-width="1.8"/>
|
||||
|
||||
<circle cx="500" cy="96" r="4" fill="#eb6c36"/>
|
||||
<circle cx="622" cy="201" r="4" fill="#eb6c36"/>
|
||||
<circle cx="585" cy="356" r="4" fill="#eb6c36"/>
|
||||
<circle cx="415" cy="356" r="4" fill="#eb6c36"/>
|
||||
<circle cx="363" cy="196" r="4" fill="#eb6c36"/>
|
||||
|
||||
<line x1="40" y1="456" x2="960" y2="456" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="472" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="484" width="16" height="8" fill="rgba(235,108,54,0.18)" stroke="#eb6c36" stroke-width="1.5"/>
|
||||
<text x="64" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">MinIO · recommended</text>
|
||||
|
||||
<rect x="220" y="484" width="16" height="8" fill="rgba(124,143,111,0.18)" stroke="#7c8f6f" stroke-width="1.5"/>
|
||||
<text x="244" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Amazon S3</text>
|
||||
|
||||
<rect x="344" y="484" width="16" height="8" fill="rgba(94,122,155,0.18)" stroke="#5e7a9b" stroke-width="1.5"/>
|
||||
<text x="368" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Ceph</text>
|
||||
|
||||
<rect x="424" y="484" width="16" height="8" fill="rgba(184,145,90,0.18)" stroke="#b8915a" stroke-width="1.5"/>
|
||||
<text x="448" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Google Cloud Storage</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="eyebrow">RECOMMENDED</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h3>MinIO</h3></div>
|
||||
<p>9 across the board, weakest only on large-object reads. The clear pick when small-file performance and Iceberg integration matter and you can run the operator yourself.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot sage"></span><h3>Amazon S3</h3></div>
|
||||
<p>The reference implementation: top-class on large-object reads and write throughput, but a hard step down on operational simplicity once you factor in IAM, lifecycle, and bucket policy sprawl.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot dusty"></span><h3>Ceph</h3></div>
|
||||
<p>Capable across the board, but the lowest score on operational simplicity. Pick it when you already run Ceph or need block + object + file from one cluster — not for a fresh greenfield.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot mustard"></span><h3>Google Cloud Storage</h3></div>
|
||||
<p>Solid middle path. No standout dimension — which is itself an answer when the team optimises for "boring, managed, fine."</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>storage backends · capability radar</span>
|
||||
<span>example · diagram design</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Storage backends · Capability radar</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--color-paper: #f5f5f5;
|
||||
--color-ink: #2d3142;
|
||||
--color-muted: #4f5d75;
|
||||
--color-accent: #eb6c36;
|
||||
--font-sans: 'Geist', system-ui, sans-serif;
|
||||
--font-serif: 'Instrument Serif', serif;
|
||||
--font-mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-serif);
|
||||
font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.15;
|
||||
color: var(--color-ink);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 900px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Radar · Diagram Design</p>
|
||||
<h1>Storage backends · Capability radar</h1>
|
||||
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Concentric grid rings (f = 0.2, 0.4, 0.6, 0.8, 1.0) -->
|
||||
<polygon points="500,208 530,230 519,266 481,266 470,230" fill="none" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,176 561,220 538,292 462,292 439,220" fill="none" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,144 591,211 556,317 444,317 409,211" fill="none" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,112 622,201 575,343 425,343 378,201" fill="none" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<polygon points="500,80 652,191 594,369 406,369 348,191" fill="none" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
|
||||
<!-- Axis spokes (center to outer vertex) -->
|
||||
<line x1="500" y1="240" x2="500" y2="80" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="652" y2="191" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="594" y2="369" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="406" y2="369" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
<line x1="500" y1="240" x2="348" y2="191" stroke="rgba(45,49,66,0.20)" stroke-width="1"/>
|
||||
|
||||
<!-- Axis labels (one word per spoke, Geist sans 11px 600) -->
|
||||
<text x="500" y="62" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="middle">Small-file handling</text>
|
||||
<text x="669" y="189" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="start">Large-object reads</text>
|
||||
<text x="605" y="400" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="start">Write throughput</text>
|
||||
<text x="395" y="400" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="end">Operational simplicity</text>
|
||||
<text x="331" y="189" fill="#2d3142" font-size="11" font-weight="600" font-family="'Geist', sans-serif" text-anchor="end">Iceberg integration</text>
|
||||
|
||||
<!-- Scale ticks on first (top) axis only -->
|
||||
<text x="494" y="82" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">10</text>
|
||||
<text x="494" y="114" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">8</text>
|
||||
<text x="494" y="146" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">6</text>
|
||||
<text x="494" y="178" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">4</text>
|
||||
<text x="494" y="210" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">2</text>
|
||||
|
||||
<!-- Non-focal series (smallest to largest, back to front) -->
|
||||
<!-- Ceph · series-2 dusty-blue · [7,7,7,4,6] -->
|
||||
<polygon points="500,128 606,206 566,330 462,292 409,211" fill="rgba(94,122,155,0.18)" stroke="#5e7a9b" stroke-width="1.5"/>
|
||||
<!-- GCS · series-3 mustard · [6,9,8,6,7] -->
|
||||
<polygon points="500,144 637,196 575,343 444,317 394,206" fill="rgba(184,145,90,0.18)" stroke="#b8915a" stroke-width="1.5"/>
|
||||
<!-- Amazon S3 · series-1 sage · [6,10,9,5,8] -->
|
||||
<polygon points="500,144 652,191 585,356 453,305 378,201" fill="rgba(124,143,111,0.18)" stroke="#7c8f6f" stroke-width="1.5"/>
|
||||
|
||||
<!-- Focal series · MinIO · accent · [9,8,9,9,9] -->
|
||||
<polygon points="500,96 622,201 585,356 415,356 363,196" fill="rgba(235,108,54,0.18)" stroke="#eb6c36" stroke-width="1.8"/>
|
||||
|
||||
<!-- Focal vertex dots (only on focal series) -->
|
||||
<circle cx="500" cy="96" r="4" fill="#eb6c36"/>
|
||||
<circle cx="622" cy="201" r="4" fill="#eb6c36"/>
|
||||
<circle cx="585" cy="356" r="4" fill="#eb6c36"/>
|
||||
<circle cx="415" cy="356" r="4" fill="#eb6c36"/>
|
||||
<circle cx="363" cy="196" r="4" fill="#eb6c36"/>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="456" x2="960" y2="456" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="472" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="484" width="16" height="8" fill="rgba(235,108,54,0.18)" stroke="#eb6c36" stroke-width="1.5"/>
|
||||
<text x="64" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">MinIO · recommended</text>
|
||||
|
||||
<rect x="220" y="484" width="16" height="8" fill="rgba(124,143,111,0.18)" stroke="#7c8f6f" stroke-width="1.5"/>
|
||||
<text x="244" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Amazon S3</text>
|
||||
|
||||
<rect x="344" y="484" width="16" height="8" fill="rgba(94,122,155,0.18)" stroke="#5e7a9b" stroke-width="1.5"/>
|
||||
<text x="368" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Ceph</text>
|
||||
|
||||
<rect x="424" y="484" width="16" height="8" fill="rgba(184,145,90,0.18)" stroke="#b8915a" stroke-width="1.5"/>
|
||||
<text x="448" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Google Cloud Storage</text>
|
||||
|
||||
<text x="608" y="492" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif" font-style="italic">One coral. Position is the signal — color reserved for the recommended option.</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Deploy frequency vs lead time · Scatter · dark</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-ink:#2d3142; --color-muted:#4f5d75; --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Scatter · Diagram Design</p>
|
||||
<h1>Deploy frequency vs. lead time · 12 teams</h1>
|
||||
|
||||
<!-- X: 0-20 deploys/week → 880px. x = 80 + f/20*880 = 80+44*f -->
|
||||
<!-- Y: 0-24 days lead time → 380px. y = 420 - lt/24*380. High lt = high y (worse) -->
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(245,245,245,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Axis labels -->
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">LEAD TIME (DAYS)</text>
|
||||
<text x="520" y="492" fill="#bfc0c0" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DEPLOYS PER WEEK</text>
|
||||
|
||||
<!-- Gridlines -->
|
||||
<!-- Horizontal (constant lead time) -->
|
||||
<line x1="80" y1="325" x2="960" y2="325" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="135" x2="960" y2="135" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(245,245,245,0.06)" stroke-width="0.8"/>
|
||||
<!-- Vertical (constant deploy freq) -->
|
||||
<line x1="256" y1="40" x2="256" y2="420" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="432" y1="40" x2="432" y2="420" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="608" y1="40" x2="608" y2="420" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
<line x1="784" y1="40" x2="784" y2="420" stroke="rgba(245,245,245,0.08)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Axes -->
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(245,245,245,0.20)" stroke-width="1"/>
|
||||
|
||||
<!-- Y-axis labels (lead time: 0 at bottom, 24 at top) -->
|
||||
<text x="72" y="324" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">6</text>
|
||||
<text x="72" y="234" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">12</text>
|
||||
<text x="72" y="139" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">18</text>
|
||||
<text x="72" y="44" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">24</text>
|
||||
<text x="72" y="424" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">0</text>
|
||||
|
||||
<!-- X-axis labels (deploy frequency) -->
|
||||
<text x="80" y="440" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">0</text>
|
||||
<text x="256" y="440" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">4</text>
|
||||
<text x="432" y="440" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">8</text>
|
||||
<text x="608" y="440" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">12</text>
|
||||
<text x="784" y="440" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">16</text>
|
||||
<text x="960" y="440" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">20</text>
|
||||
|
||||
<!-- Trend line (dashed, approximate) -->
|
||||
<line x1="168" y1="103" x2="900" y2="388" stroke="rgba(245,245,245,0.20)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
|
||||
<!-- ─── DATA POINTS (non-focal first) ─── -->
|
||||
<!-- (freq, lt) → (x=80+44*f, y=420-380*lt/24) -->
|
||||
<!-- (2,20)→(168,103), (4,18)→(256,135), (4,16)→(256,167), (6,14)→(344,198) -->
|
||||
<!-- (8,12)→(432,230), (8,10)→(432,262), (10,8)→(520,293), (12,6)→(608,325) -->
|
||||
<!-- (12,10)→(608,262), (16,4)→(784,357), (18,3)→(872,372) FOCAL, (20,2)→(960,388) -->
|
||||
|
||||
<circle cx="168" cy="103" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="168" cy="103" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="256" cy="135" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="256" cy="135" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="256" cy="167" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="256" cy="167" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="344" cy="198" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="344" cy="198" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="432" cy="230" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="432" cy="230" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="432" cy="262" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="432" cy="262" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="520" cy="293" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="520" cy="293" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="608" cy="325" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="608" cy="325" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="608" cy="262" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="608" cy="262" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="784" cy="357" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="784" cy="357" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<circle cx="960" cy="388" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="960" cy="388" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
|
||||
<!-- FOCAL: Platform team (18,3) → (872,372) -->
|
||||
<circle cx="872" cy="372" r="6" fill="#f5f5f5"/>
|
||||
<circle cx="872" cy="372" r="6" fill="rgba(240,138,89,0.18)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
|
||||
<!-- Focal label -->
|
||||
<rect x="798" y="348" width="64" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="830" y="357" fill="#f08a59" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.06em">PLATFORM</text>
|
||||
|
||||
<!-- Quadrant guides (optional) -->
|
||||
<text x="88" y="56" fill="rgba(245,245,245,0.22)" font-size="8" font-family="'Geist Mono', monospace">HIGH LEAD TIME</text>
|
||||
<text x="88" y="412" fill="rgba(245,245,245,0.22)" font-size="8" font-family="'Geist Mono', monospace">LOW LEAD TIME</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(245,245,245,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#bfc0c0" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<circle cx="52" cy="490" r="6" fill="rgba(240,138,89,0.18)" stroke="#f08a59" stroke-width="1.2"/>
|
||||
<text x="68" y="494" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Platform team · best performer</text>
|
||||
|
||||
<circle cx="256" cy="490" r="5" fill="rgba(191,192,192,0.20)" stroke="#bfc0c0" stroke-width="1"/>
|
||||
<text x="272" y="494" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Engineering team</text>
|
||||
|
||||
<line x1="416" y1="490" x2="440" y2="490" stroke="rgba(245,245,245,0.20)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="448" y="494" fill="#bfc0c0" font-size="8.5" font-family="'Geist', sans-serif">Trend</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Deploy frequency vs lead time · Scatter</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
.header { margin-bottom: 2.5rem; }
|
||||
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
|
||||
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 58ch; }
|
||||
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
.cards { display: grid; grid-template-columns: 1.1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
|
||||
@media (max-width: 720px) { .cards { grid-template-columns: 1fr; } }
|
||||
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
|
||||
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
|
||||
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
|
||||
.card-dot.coral { background: var(--color-accent); } .card-dot.ink { background: var(--color-ink); } .card-dot.muted { background: var(--color-muted); }
|
||||
.card h3 { font-size: 0.875rem; font-weight: 600; }
|
||||
.card p { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; }
|
||||
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-soft); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<p class="header-eyebrow">Scatter · Diagram Design</p>
|
||||
<h1>Deploy frequency vs. lead time · 12 teams</h1>
|
||||
<p class="subtitle">Each point is an engineering team. Moving right means deploying more often; moving down means shipping faster. The Platform team occupies the lower-right — the ideal quadrant.</p>
|
||||
</div>
|
||||
<div class="diagram-container">
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs><pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/></pattern></defs>
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">LEAD TIME (DAYS)</text>
|
||||
<text x="520" y="492" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DEPLOYS PER WEEK</text>
|
||||
<line x1="80" y1="325" x2="960" y2="325" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="135" x2="960" y2="135" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(45,49,66,0.06)" stroke-width="0.8"/>
|
||||
<line x1="256" y1="40" x2="256" y2="420" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="432" y1="40" x2="432" y2="420" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="608" y1="40" x2="608" y2="420" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="784" y1="40" x2="784" y2="420" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<text x="72" y="324" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">6</text>
|
||||
<text x="72" y="234" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">12</text>
|
||||
<text x="72" y="139" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">18</text>
|
||||
<text x="72" y="44" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">24</text>
|
||||
<text x="72" y="424" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">0</text>
|
||||
<text x="80" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">0</text>
|
||||
<text x="256" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">4</text>
|
||||
<text x="432" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">8</text>
|
||||
<text x="608" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">12</text>
|
||||
<text x="784" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">16</text>
|
||||
<text x="960" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">20</text>
|
||||
<line x1="168" y1="103" x2="900" y2="388" stroke="rgba(45,49,66,0.18)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<circle cx="168" cy="103" r="5" fill="#f5f5f5"/><circle cx="168" cy="103" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="256" cy="135" r="5" fill="#f5f5f5"/><circle cx="256" cy="135" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="256" cy="167" r="5" fill="#f5f5f5"/><circle cx="256" cy="167" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="344" cy="198" r="5" fill="#f5f5f5"/><circle cx="344" cy="198" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="432" cy="230" r="5" fill="#f5f5f5"/><circle cx="432" cy="230" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="432" cy="262" r="5" fill="#f5f5f5"/><circle cx="432" cy="262" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="520" cy="293" r="5" fill="#f5f5f5"/><circle cx="520" cy="293" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="608" cy="325" r="5" fill="#f5f5f5"/><circle cx="608" cy="325" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="608" cy="262" r="5" fill="#f5f5f5"/><circle cx="608" cy="262" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="784" cy="357" r="5" fill="#f5f5f5"/><circle cx="784" cy="357" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="960" cy="388" r="5" fill="#f5f5f5"/><circle cx="960" cy="388" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<circle cx="872" cy="372" r="6" fill="#f5f5f5"/><circle cx="872" cy="372" r="6" fill="rgba(235,108,54,0.15)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<rect x="798" y="348" width="64" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="830" y="357" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.06em">PLATFORM</text>
|
||||
<text x="88" y="56" fill="rgba(45,49,66,0.25)" font-size="8" font-family="'Geist Mono', monospace">HIGH LEAD TIME</text>
|
||||
<text x="88" y="412" fill="rgba(45,49,66,0.25)" font-size="8" font-family="'Geist Mono', monospace">LOW LEAD TIME</text>
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
<circle cx="52" cy="490" r="6" fill="rgba(235,108,54,0.15)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="68" y="494" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Platform team · best performer</text>
|
||||
<circle cx="256" cy="490" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="272" y="494" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Engineering team</text>
|
||||
<line x1="416" y1="490" x2="440" y2="490" stroke="rgba(45,49,66,0.18)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="448" y="494" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Trend</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<div class="card">
|
||||
<p class="eyebrow">BEST PERFORMER</p>
|
||||
<div class="card-header"><span class="card-dot coral"></span><h3>Platform team · (18, 3)</h3></div>
|
||||
<p>18 deploys/week with a 3-day lead time. This combination is achievable because they own the full stack — no cross-team approval gates, and their CI pipeline runs under 4 minutes.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot ink"></span><h3>Correlation is clear</h3></div>
|
||||
<p>Every team to the right of the median deploy frequency (8/week) is also below the median lead time (12 days). The relationship isn't causal — both are symptoms of the same underlying practice: small, frequent, reversible changes.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header"><span class="card-dot muted"></span><h3>Two outliers worth examining</h3></div>
|
||||
<p>The team at (2,20) ships rarely and slowly — high coupling, long approval chains. The team at (12,10) deploys frequently but with higher-than-expected lead time — likely a large monorepo with slow integration tests.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span>deploy frequency vs. lead time · 12 teams</span>
|
||||
<span>example · diagram design</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Deploy frequency vs lead time · Scatter</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root { --color-paper:#f5f5f5; --color-ink:#2d3142; --color-muted:#4f5d75; --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
|
||||
body { font-family: var(--font-sans); background: var(--color-paper); color: var(--color-ink); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 3rem 2rem; }
|
||||
.frame { max-width: 1200px; width: 100%; }
|
||||
.eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
|
||||
h1 { font-family: var(--font-serif); font-size: clamp(1.5rem, 2.4vw + 0.75rem, 2rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; margin-bottom: 1.5rem; }
|
||||
svg { width: 100%; min-width: 760px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Scatter · Diagram Design</p>
|
||||
<h1>Deploy frequency vs. lead time · 12 teams</h1>
|
||||
|
||||
<!-- X: 0-20 deploys/week → 880px. x = 80 + f/20*880 = 80+44*f -->
|
||||
<!-- Y: 0-24 days lead time → 380px. y = 420 - lt/24*380. High lt = high y (worse) -->
|
||||
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f5f5"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Axis labels -->
|
||||
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">LEAD TIME (DAYS)</text>
|
||||
<text x="520" y="492" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">DEPLOYS PER WEEK</text>
|
||||
|
||||
<!-- Gridlines -->
|
||||
<!-- Horizontal (constant lead time) -->
|
||||
<line x1="80" y1="325" x2="960" y2="325" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="230" x2="960" y2="230" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="135" x2="960" y2="135" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="80" y1="40" x2="960" y2="40" stroke="rgba(45,49,66,0.06)" stroke-width="0.8"/>
|
||||
<!-- Vertical (constant deploy freq) -->
|
||||
<line x1="256" y1="40" x2="256" y2="420" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="432" y1="40" x2="432" y2="420" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="608" y1="40" x2="608" y2="420" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
<line x1="784" y1="40" x2="784" y2="420" stroke="rgba(45,49,66,0.08)" stroke-width="0.8"/>
|
||||
|
||||
<!-- Axes -->
|
||||
<line x1="80" y1="40" x2="80" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
<line x1="80" y1="420" x2="960" y2="420" stroke="rgba(45,49,66,0.25)" stroke-width="1"/>
|
||||
|
||||
<!-- Y-axis labels (lead time: 0 at bottom, 24 at top) -->
|
||||
<text x="72" y="324" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">6</text>
|
||||
<text x="72" y="234" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">12</text>
|
||||
<text x="72" y="139" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">18</text>
|
||||
<text x="72" y="44" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">24</text>
|
||||
<text x="72" y="424" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="end">0</text>
|
||||
|
||||
<!-- X-axis labels (deploy frequency) -->
|
||||
<text x="80" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">0</text>
|
||||
<text x="256" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">4</text>
|
||||
<text x="432" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">8</text>
|
||||
<text x="608" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">12</text>
|
||||
<text x="784" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">16</text>
|
||||
<text x="960" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">20</text>
|
||||
|
||||
<!-- Trend line (dashed, approximate) -->
|
||||
<line x1="168" y1="103" x2="900" y2="388" stroke="rgba(45,49,66,0.18)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
|
||||
<!-- ─── DATA POINTS (non-focal first) ─── -->
|
||||
<!-- (freq, lt) → (x=80+44*f, y=420-380*lt/24) -->
|
||||
<!-- (2,20)→(168,103), (4,18)→(256,135), (4,16)→(256,167), (6,14)→(344,198) -->
|
||||
<!-- (8,12)→(432,230), (8,10)→(432,262), (10,8)→(520,293), (12,6)→(608,325) -->
|
||||
<!-- (12,10)→(608,262), (16,4)→(784,357), (18,3)→(872,372) FOCAL, (20,2)→(960,388) -->
|
||||
|
||||
<circle cx="168" cy="103" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="168" cy="103" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="256" cy="135" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="256" cy="135" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="256" cy="167" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="256" cy="167" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="344" cy="198" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="344" cy="198" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="432" cy="230" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="432" cy="230" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="432" cy="262" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="432" cy="262" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="520" cy="293" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="520" cy="293" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="608" cy="325" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="608" cy="325" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="608" cy="262" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="608" cy="262" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="784" cy="357" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="784" cy="357" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<circle cx="960" cy="388" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="960" cy="388" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<!-- FOCAL: Platform team (18,3) → (872,372) -->
|
||||
<circle cx="872" cy="372" r="6" fill="#f5f5f5"/>
|
||||
<circle cx="872" cy="372" r="6" fill="rgba(235,108,54,0.15)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
|
||||
<!-- Focal label -->
|
||||
<rect x="798" y="348" width="64" height="12" rx="2" fill="#f5f5f5"/>
|
||||
<text x="830" y="357" fill="#eb6c36" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.06em">PLATFORM</text>
|
||||
|
||||
<!-- Quadrant guides (optional) -->
|
||||
<text x="88" y="56" fill="rgba(45,49,66,0.25)" font-size="8" font-family="'Geist Mono', monospace">HIGH LEAD TIME</text>
|
||||
<text x="88" y="412" fill="rgba(45,49,66,0.25)" font-size="8" font-family="'Geist Mono', monospace">LOW LEAD TIME</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<text x="40" y="478" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
|
||||
|
||||
<circle cx="52" cy="490" r="6" fill="rgba(235,108,54,0.15)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
<text x="68" y="494" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Platform team · best performer</text>
|
||||
|
||||
<circle cx="256" cy="490" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="272" y="494" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Engineering team</text>
|
||||
|
||||
<line x1="416" y1="490" x2="440" y2="490" stroke="rgba(45,49,66,0.18)" stroke-width="1" stroke-dasharray="4,3"/>
|
||||
<text x="448" y="494" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Trend</text>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,207 +1,330 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Diagram Design · Gallery</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--paper: #f5f4ed;
|
||||
--paper-2: #efeee5;
|
||||
--ink: #0b0d0b;
|
||||
--muted: #52534e;
|
||||
--soft: #65655c;
|
||||
--rule: rgba(11,13,11,0.12);
|
||||
--rule-strong: rgba(11,13,11,0.25);
|
||||
--accent: #f7591f;
|
||||
--sans: 'Geist', system-ui, sans-serif;
|
||||
--serif: 'Instrument Serif', serif;
|
||||
--mono: 'Geist Mono', ui-monospace, monospace;
|
||||
}
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Diagram Design · Gallery</title>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
:root {
|
||||
--paper: #f5f4ed;
|
||||
--paper-2: #efeee5;
|
||||
--ink: #0b0d0b;
|
||||
--muted: #52534e;
|
||||
--soft: #65655c;
|
||||
--rule: rgba(11, 13, 11, 0.12);
|
||||
--rule-strong: rgba(11, 13, 11, 0.25);
|
||||
--accent: #f7591f;
|
||||
--sans: "Geist", system-ui, sans-serif;
|
||||
--serif: "Instrument Serif", serif;
|
||||
--mono: "Geist Mono", ui-monospace, monospace;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
padding: 1rem 1.25rem 0.625rem;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
.title-row h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1;
|
||||
}
|
||||
.title-row .meta {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.66rem;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.title-row .open-raw {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.66rem;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
padding: 0.3rem 0.625rem;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
.open-raw:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.topbar {
|
||||
padding: 1rem 1.25rem 0.625rem;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.title-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
.title-row h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1;
|
||||
}
|
||||
.title-row .meta {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.66rem;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.title-row .open-raw {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.66rem;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
padding: 0.3rem 0.625rem;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
transition:
|
||||
color 0.15s,
|
||||
border-color 0.15s;
|
||||
}
|
||||
.open-raw:hover {
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.tabs.variants {
|
||||
margin-bottom: 0;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.tab {
|
||||
font-family: var(--sans);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
padding: 0.375rem 0.75rem;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.12s;
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
.tab:hover {
|
||||
color: var(--ink);
|
||||
border-color: var(--rule-strong);
|
||||
}
|
||||
.tab.active {
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
border-color: var(--ink);
|
||||
}
|
||||
.tab .eyebrow {
|
||||
display: inline-block;
|
||||
margin-right: 0.25rem;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.56rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: inherit;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.tab.new::after {
|
||||
content: 'NEW';
|
||||
display: inline-block;
|
||||
margin-left: 0.35rem;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.54rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--accent);
|
||||
vertical-align: 1px;
|
||||
}
|
||||
.tab.active.new::after { color: var(--paper); }
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.tabs.variants {
|
||||
margin-bottom: 0;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.tab {
|
||||
font-family: var(--sans);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
padding: 0.375rem 0.75rem;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.12s;
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
.tab:hover {
|
||||
color: var(--ink);
|
||||
border-color: var(--rule-strong);
|
||||
}
|
||||
.tab.active {
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
border-color: var(--ink);
|
||||
}
|
||||
.tab .eyebrow {
|
||||
display: inline-block;
|
||||
margin-right: 0.25rem;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.56rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: inherit;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.tab.new::after {
|
||||
content: "NEW";
|
||||
display: inline-block;
|
||||
margin-left: 0.35rem;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.54rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--accent);
|
||||
vertical-align: 1px;
|
||||
}
|
||||
.tab.active.new::after {
|
||||
color: var(--paper);
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
background: var(--paper-2);
|
||||
overflow: hidden;
|
||||
}
|
||||
iframe {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: var(--paper);
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div class="title-row">
|
||||
<h1>Diagram Design <span class="meta" style="font-family: var(--mono); font-size: 0.66rem; margin-left: 0.5rem;">· gallery</span></h1>
|
||||
<a class="open-raw" id="open-raw" href="#" target="_blank" rel="noopener">open in new tab ↗</a>
|
||||
</div>
|
||||
main {
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
background: var(--paper-2);
|
||||
overflow: hidden;
|
||||
}
|
||||
iframe {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: var(--paper);
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div class="title-row">
|
||||
<h1>
|
||||
Diagram Design
|
||||
<span
|
||||
class="meta"
|
||||
style="
|
||||
font-family: var(--mono);
|
||||
font-size: 0.66rem;
|
||||
margin-left: 0.5rem;
|
||||
"
|
||||
>· gallery</span
|
||||
>
|
||||
</h1>
|
||||
<div style="display: flex; gap: 0.5rem">
|
||||
<a class="open-raw" href="icons.html" target="_blank" rel="noopener"
|
||||
>icon library ↗</a
|
||||
>
|
||||
<a
|
||||
class="open-raw"
|
||||
id="open-raw"
|
||||
href="#"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>open in new tab ↗</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" id="type-tabs" role="tablist" aria-label="Diagram type">
|
||||
<button class="tab active" data-type="architecture"><span class="eyebrow">01</span>Architecture</button>
|
||||
<button class="tab" data-type="flowchart"><span class="eyebrow">02</span>Flowchart</button>
|
||||
<button class="tab" data-type="sequence"><span class="eyebrow">03</span>Sequence</button>
|
||||
<button class="tab" data-type="state"><span class="eyebrow">04</span>State</button>
|
||||
<button class="tab" data-type="er"><span class="eyebrow">05</span>ER</button>
|
||||
<button class="tab" data-type="timeline"><span class="eyebrow">06</span>Timeline</button>
|
||||
<button class="tab" data-type="swimlane"><span class="eyebrow">07</span>Swimlane</button>
|
||||
<button class="tab" data-type="quadrant"><span class="eyebrow">08</span>Quadrant</button>
|
||||
<button class="tab new" data-type="nested"><span class="eyebrow">09</span>Nested</button>
|
||||
<button class="tab new" data-type="tree"><span class="eyebrow">10</span>Tree</button>
|
||||
<button class="tab new" data-type="org-chart"><span class="eyebrow">11</span>Org chart</button>
|
||||
<button class="tab new" data-type="layers"><span class="eyebrow">12</span>Layers</button>
|
||||
<button class="tab new" data-type="venn"><span class="eyebrow">13</span>Venn</button>
|
||||
<button class="tab new" data-type="pyramid"><span class="eyebrow">14</span>Pyramid</button>
|
||||
</div>
|
||||
<div class="tabs" id="type-tabs" role="tablist" aria-label="Diagram type">
|
||||
<button class="tab active" data-type="architecture">
|
||||
<span class="eyebrow">01</span>Architecture
|
||||
</button>
|
||||
<button class="tab" data-type="flowchart">
|
||||
<span class="eyebrow">02</span>Flowchart
|
||||
</button>
|
||||
<button class="tab" data-type="sequence">
|
||||
<span class="eyebrow">03</span>Sequence
|
||||
</button>
|
||||
<button class="tab" data-type="state">
|
||||
<span class="eyebrow">04</span>State
|
||||
</button>
|
||||
<button class="tab" data-type="er">
|
||||
<span class="eyebrow">05</span>ER
|
||||
</button>
|
||||
<button class="tab" data-type="timeline">
|
||||
<span class="eyebrow">06</span>Timeline
|
||||
</button>
|
||||
<button class="tab" data-type="swimlane">
|
||||
<span class="eyebrow">07</span>Swimlane
|
||||
</button>
|
||||
<button class="tab" data-type="quadrant">
|
||||
<span class="eyebrow">08</span>Quadrant
|
||||
</button>
|
||||
<button class="tab new" data-type="nested">
|
||||
<span class="eyebrow">09</span>Nested
|
||||
</button>
|
||||
<button class="tab new" data-type="tree">
|
||||
<span class="eyebrow">10</span>Tree
|
||||
</button>
|
||||
<button class="tab new" data-type="org-chart">
|
||||
<span class="eyebrow">11</span>Org chart
|
||||
</button>
|
||||
<button class="tab new" data-type="layers">
|
||||
<span class="eyebrow">12</span>Layers
|
||||
</button>
|
||||
<button class="tab new" data-type="venn">
|
||||
<span class="eyebrow">13</span>Venn
|
||||
</button>
|
||||
<button class="tab new" data-type="pyramid">
|
||||
<span class="eyebrow">14</span>Pyramid
|
||||
</button>
|
||||
<button class="tab new" data-type="radar">
|
||||
<span class="eyebrow">15</span>Radar
|
||||
</button>
|
||||
<button class="tab new" data-type="datalake">
|
||||
<span class="eyebrow">16</span>Data lake
|
||||
</button>
|
||||
<button class="tab new" data-type="bar">
|
||||
<span class="eyebrow">17</span>Bar
|
||||
</button>
|
||||
<button class="tab new" data-type="line">
|
||||
<span class="eyebrow">18</span>Line
|
||||
</button>
|
||||
<button class="tab new" data-type="gantt">
|
||||
<span class="eyebrow">19</span>Gantt
|
||||
</button>
|
||||
<button class="tab new" data-type="scatter">
|
||||
<span class="eyebrow">20</span>Scatter
|
||||
</button>
|
||||
<button class="tab new" data-type="medallion">
|
||||
<span class="eyebrow">21</span>Medallion
|
||||
</button>
|
||||
<button class="tab new" data-type="high-level">
|
||||
<span class="eyebrow">22</span>High-Level
|
||||
</button>
|
||||
<button class="tab new" data-type="process">
|
||||
<span class="eyebrow">23</span>Process
|
||||
</button>
|
||||
<button class="tab new" data-type="data-flow">
|
||||
<span class="eyebrow">25</span>Data flow
|
||||
</button>
|
||||
<button class="tab new" data-type="dp-integration">
|
||||
<span class="eyebrow">26</span>DP integration
|
||||
</button>
|
||||
<button class="tab new" data-type="high-level-vertical">
|
||||
<span class="eyebrow">27</span>High-Level / Parametric
|
||||
</button>
|
||||
<button class="tab new" data-type="dp-security-matrix">
|
||||
<span class="eyebrow">31</span>DP security matrix
|
||||
</button>
|
||||
<button class="tab new" data-type="it-state">
|
||||
<span class="eyebrow">32</span>IT current-state
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tabs variants" id="variant-tabs" role="tablist" aria-label="Variant">
|
||||
<button class="tab active" data-variant="">Minimal light</button>
|
||||
<button class="tab" data-variant="-dark">Minimal dark</button>
|
||||
<button class="tab" data-variant="-full">Full editorial</button>
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
class="tabs variants"
|
||||
id="variant-tabs"
|
||||
role="tablist"
|
||||
aria-label="Variant"
|
||||
>
|
||||
<button class="tab active" data-variant="">Minimal light</button>
|
||||
<button class="tab" data-variant="-dark">Minimal dark</button>
|
||||
<button class="tab" data-variant="-full">Full editorial</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<iframe id="preview" src="example-architecture.html" title="Preview"></iframe>
|
||||
</main>
|
||||
<main>
|
||||
<iframe
|
||||
id="preview"
|
||||
src="example-architecture.html"
|
||||
title="Preview"
|
||||
></iframe>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const state = { type: 'architecture', variant: '' };
|
||||
const iframe = document.getElementById('preview');
|
||||
const openRaw = document.getElementById('open-raw');
|
||||
<script>
|
||||
const state = { type: "architecture", variant: "" };
|
||||
const iframe = document.getElementById("preview");
|
||||
const openRaw = document.getElementById("open-raw");
|
||||
|
||||
function update() {
|
||||
const src = `example-${state.type}${state.variant}.html`;
|
||||
iframe.src = src;
|
||||
openRaw.href = src;
|
||||
}
|
||||
function update() {
|
||||
const src = `example-${state.type}${state.variant}.html`;
|
||||
iframe.src = src;
|
||||
openRaw.href = src;
|
||||
}
|
||||
|
||||
function bindTabs(containerId, key) {
|
||||
const container = document.getElementById(containerId);
|
||||
container.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('.tab');
|
||||
if (!btn) return;
|
||||
container.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
state[key] = btn.dataset[key];
|
||||
update();
|
||||
});
|
||||
}
|
||||
function bindTabs(containerId, key) {
|
||||
const container = document.getElementById(containerId);
|
||||
container.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest(".tab");
|
||||
if (!btn) return;
|
||||
container
|
||||
.querySelectorAll(".tab")
|
||||
.forEach((t) => t.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
state[key] = btn.dataset[key];
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
bindTabs('type-tabs', 'type');
|
||||
bindTabs('variant-tabs', 'variant');
|
||||
update();
|
||||
</script>
|
||||
</body>
|
||||
bindTabs("type-tabs", "type");
|
||||
bindTabs("variant-tabs", "variant");
|
||||
update();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# Export to PNG / SVG
|
||||
|
||||
Convert a generated diagram HTML file into a portable `.svg` and/or `.png` next to it. **Manual only — never run unprompted.**
|
||||
|
||||
## Trigger
|
||||
|
||||
Load this file when:
|
||||
|
||||
- The user invokes `/diagram-design:export <html-file>` (the plugin's slash command — defined in `commands/export.md` at the repo root).
|
||||
- The user asks in natural language to export, save, rasterize, convert, or download a diagram in `.svg` or `.png` form. Typical phrasings:
|
||||
- "export this as PNG"
|
||||
- "save as SVG"
|
||||
- "give me a PNG of that diagram"
|
||||
- "rasterize it"
|
||||
- "convert to png and svg"
|
||||
|
||||
The slash command is a thin wrapper that delegates here — both paths run the same procedure below.
|
||||
|
||||
## Scope
|
||||
|
||||
Both formats are **diagram-only** — just the `<svg>` node. Editorial wrappers (header, summary cards, footer in `-full` variants) are intentionally dropped: the export deliverable is the diagram itself, suitable for Figma, slides, social cards, or blog images.
|
||||
|
||||
If the user explicitly asks for "a screenshot of the whole page including the cards", that's a different request — fall back to a normal full-page screenshot via the user's OS or browser.
|
||||
|
||||
## SVG export procedure
|
||||
|
||||
1. Read the source HTML file.
|
||||
2. Extract the **first** `<svg ...>...</svg>` block. Use a multiline regex anchored on `<svg` and `</svg>`. Most generated diagrams have only one SVG; if there are multiple, the first is the diagram (gallery files are an exception — see *Edge cases*).
|
||||
3. Make it standalone:
|
||||
- Ensure the opening tag has `xmlns="http://www.w3.org/2000/svg"`. Add it if missing.
|
||||
- Ensure a `viewBox` is present. The skill's templates always include one; warn the user if absent rather than guessing.
|
||||
- Inject Google Fonts `@import` so the SVG renders with correct typography in a browser:
|
||||
```svg
|
||||
<defs>
|
||||
<style>@import url('https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap');</style>
|
||||
</defs>
|
||||
```
|
||||
If the SVG already contains a `<defs>` block, **merge** the `<style>` into it (don't add a second `<defs>`).
|
||||
4. Prepend `<?xml version="1.0" encoding="UTF-8"?>\n` so the file is well-formed XML.
|
||||
5. Write to `<basename>.svg` next to the source (e.g. `example-architecture.html` → `example-architecture.svg`). Honour an explicit output path if the user provides one.
|
||||
|
||||
### Caveat to surface to the user
|
||||
|
||||
Tools that don't fetch remote fonts at import time (offline Illustrator, some Figma import paths, older SVG viewers) will substitute typography. The SVG renders correctly in any modern browser. For pixel-perfect portability, recommend the PNG export.
|
||||
|
||||
## PNG export procedure
|
||||
|
||||
Render **the original HTML** (not the extracted SVG) and screenshot only the `<svg>` element's bounding box. This keeps font loading reliable (already wired in the source HTML) while satisfying the "diagram only" rule. The PNG always has a **transparent background** (`omit_background=True`) so it can be placed on any slide or doc colour without a white halo.
|
||||
|
||||
### Detection
|
||||
|
||||
Before running anything, verify Playwright is installed:
|
||||
|
||||
```
|
||||
python -c "import playwright" 2>NUL || python -c "import playwright"
|
||||
```
|
||||
|
||||
If the import fails, surface this exact instruction to the user and stop:
|
||||
|
||||
> Playwright isn't installed. To enable PNG export, run:
|
||||
> ```
|
||||
> pip install playwright
|
||||
> playwright install chromium
|
||||
> ```
|
||||
> Then ask me to export again.
|
||||
|
||||
Don't auto-install. The user asked for one feature, not a system change.
|
||||
|
||||
### Rasterize
|
||||
|
||||
Write the snippet below to a temp file and run it with `python <tmp.py> <src.html> <out.png>`:
|
||||
|
||||
```python
|
||||
from playwright.sync_api import sync_playwright
|
||||
import sys, pathlib
|
||||
|
||||
src, out = sys.argv[1], sys.argv[2]
|
||||
scale = int(sys.argv[3]) if len(sys.argv) > 3 else 2
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch()
|
||||
page = browser.new_page(device_scale_factor=scale)
|
||||
page.goto(f"file://{pathlib.Path(src).resolve()}")
|
||||
page.wait_for_load_state("networkidle")
|
||||
page.locator("svg").first.screenshot(path=out, omit_background=True)
|
||||
browser.close()
|
||||
```
|
||||
|
||||
Default `device_scale_factor=2` for crisp output. Accept `1` for compact assets or `3` for print/retina hero use, passed as a third CLI arg.
|
||||
|
||||
### Output naming
|
||||
|
||||
`example-architecture.html` → `example-architecture.png`, written next to the source. Honour explicit user-provided paths.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- **Source is `assets/index.html`** (the gallery, multiple SVGs in one file): refuse the export and ask the user which specific diagram file they meant. Don't guess.
|
||||
- **No `<svg>` block found**: the source isn't a diagram file. Tell the user; don't write anything.
|
||||
- **Surrounding HTML matters to the user**: they want cards/header in the image. Tell them this skill exports diagrams only, and recommend a browser-based full-page screenshot (or a separate PDF print).
|
||||
- **Source is missing fonts at runtime**: Playwright will substitute, the screenshot will look off. Check that the source HTML has the `<link href="...fonts.googleapis.com...">` tag in `<head>`. If absent, the file isn't from a current template — fix the source rather than working around it in export.
|
||||
|
||||
## What this command never does
|
||||
|
||||
- Modifies the source HTML.
|
||||
- Adds export buttons or `<script>` tags to generated diagrams (the "no JS in deliverables" rule in §11 still stands).
|
||||
- Auto-emits `.svg` or `.png` alongside HTML generation. Manual on every call.
|
||||
- Embeds an HTML wrapper (cards, headers) into the SVG via `foreignObject`. Too fragile across renderers.
|
||||
@@ -1,17 +1,23 @@
|
||||
# Onboarding — generate your skin from a website
|
||||
# Onboarding — generate your skin from a design source
|
||||
|
||||
**Goal:** point the skill at a site you like (your own, a competitor, a reference), and have it extract the palette + typography, then rewrite `style-guide.md` so every future diagram inherits that skin.
|
||||
**Goal:** point the skill at a design source — a website, an installed skill, or a local folder — and have it extract the palette + typography, then rewrite `style-guide.md` so every future diagram inherits that skin.
|
||||
|
||||
Takes about 60 seconds.
|
||||
|
||||
Three source methods are supported. Jump to the relevant section:
|
||||
|
||||
- [§ URL](#url) — fetch a live website
|
||||
- [§ Skill](#skill) — read an installed Claude Code skill that carries design tokens
|
||||
- [§ Folder](#folder) — read a local design-system directory (CSS, JSON, Markdown)
|
||||
|
||||
---
|
||||
|
||||
## The flow
|
||||
## The flow (all methods)
|
||||
|
||||
```
|
||||
URL you provide
|
||||
Source you provide (URL / skill name / folder path)
|
||||
↓
|
||||
[1] fetch homepage (headless browser or HTTP)
|
||||
[1] read / fetch the source
|
||||
↓
|
||||
[2] extract dominant colors + fonts
|
||||
↓
|
||||
@@ -26,17 +32,17 @@ future diagrams use your tokens
|
||||
|
||||
---
|
||||
|
||||
## Invocation
|
||||
---
|
||||
|
||||
Ask the skill:
|
||||
## § URL
|
||||
|
||||
### Invocation
|
||||
|
||||
> *"Onboard Schematic to my site — `https://example.com`"*
|
||||
|
||||
Or run it with an explicit URL and the skill reads this file and executes the steps below.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — fetch the page
|
||||
### Step 1 — fetch the page
|
||||
|
||||
Use `agent-browser` (preferred) or a plain `fetch`. If the site has multiple pages worth sampling (landing + blog + product), fetch 2–3 and merge the palette signals.
|
||||
|
||||
@@ -64,6 +70,7 @@ Prefer CSS custom properties when the site exposes them (`:root { --accent: …;
|
||||
### Fonts
|
||||
|
||||
Read the rendered `font-family` stack of:
|
||||
|
||||
- `<h1>` → `title` family
|
||||
- `<body>` → `node-name` family
|
||||
- `<code>`, `<pre>`, or any mono-styled element → `sublabel` family
|
||||
@@ -120,12 +127,13 @@ Also regenerate the dark variant via the inversion rule (`rgba(11,13,11, X)` →
|
||||
Write the new tokens to `style-guide.md`. Suggest running the `/regenerate-examples` flow (if it exists) or rebuilding one example to verify the new skin reads cleanly.
|
||||
|
||||
After onboarding, the user should:
|
||||
1. Open `assets/index.html` (gallery) and confirm the new palette feels coherent across all 14 types.
|
||||
|
||||
1. Open `assets/index.html` (gallery) and confirm the new palette feels coherent across all 26 types.
|
||||
2. If any type looks off, they usually need to tune `muted` (often too dark or too light against the new `paper`).
|
||||
|
||||
---
|
||||
|
||||
## When onboarding fails
|
||||
## When URL onboarding fails
|
||||
|
||||
- **Site uses webfonts you can't replicate** (custom-hosted, paid): keep the schematic defaults for typography and skin only the colors.
|
||||
- **Brand has 6+ colors** and you can't identify a clear hierarchy: pick one as `accent`, demote the rest to `muted` variants or ignore them. The schematic grammar only uses 5–7 roles.
|
||||
@@ -134,6 +142,127 @@ After onboarding, the user should:
|
||||
|
||||
---
|
||||
|
||||
## § Skill
|
||||
|
||||
Extract tokens from an installed Claude Code skill that carries its own design system (e.g. a `brand-design` or `ui-kit` skill).
|
||||
|
||||
### Invocation
|
||||
|
||||
> *"Onboard Schematic from my `acme-design` skill"*
|
||||
|
||||
Or the gate offers this as option (b) and the user names the skill.
|
||||
|
||||
### Step 1 — locate the skill
|
||||
|
||||
Search for the skill in order:
|
||||
|
||||
1. `~/.claude/skills/<skill-name>/` (user install)
|
||||
2. `.claude/skills/<skill-name>/` (project install)
|
||||
3. Any path the user provides explicitly
|
||||
|
||||
If not found, ask the user to confirm the skill name or provide the path.
|
||||
|
||||
### Step 2 — read token sources
|
||||
|
||||
Glob the skill directory for any of these files and read them all:
|
||||
|
||||
| Priority | Pattern | What to look for |
|
||||
|---|---|---|
|
||||
| 1 | `*.css`, `colors*.css`, `tokens.css` | CSS custom properties in `:root { --color-*: …; }` |
|
||||
| 2 | `tokens.json`, `design-tokens.json`, `*.tokens.json` | Style Dictionary / Figma token JSON |
|
||||
| 3 | `SKILL.md`, `README.md` | Markdown tables listing colors, fonts, hex values |
|
||||
| 4 | `style-guide.md`, `*design*.md` | Any narrative design documentation |
|
||||
| 5 | `*.html` (preview/example files) | Inline `<style>` blocks — scan `:root` and `body` rules |
|
||||
|
||||
Read all matches and merge — CSS custom properties take priority over inferred values from HTML.
|
||||
|
||||
### Step 3 — extract colors and fonts
|
||||
|
||||
**From CSS custom properties:**
|
||||
Map variable names to semantic roles using name-heuristics:
|
||||
|
||||
| If the variable name contains… | Map to role |
|
||||
|---|---|
|
||||
| `background`, `bg`, `paper`, `surface`, `canvas` | `paper` |
|
||||
| `foreground`, `text`, `body`, `ink`, `on-surface` | `ink` |
|
||||
| `muted`, `subtle`, `secondary`, `caption` | `muted` |
|
||||
| `accent`, `brand`, `primary`, `cta`, `highlight` | `accent` |
|
||||
| `border`, `rule`, `divider`, `outline` | `rule` |
|
||||
| `mono`, `code`, `pre` | `sublabel` font |
|
||||
|
||||
**From JSON tokens:** follow the same heuristics on key names. If the JSON follows Style Dictionary format (`{ "color": { "brand": { "value": "#…" } } }`), flatten the path and apply heuristics to the leaf key.
|
||||
|
||||
**From Markdown tables:** look for rows with hex values (`#rrggbb`) adjacent to role-like words. A row like `| accent | #eb6c36 |` maps directly.
|
||||
|
||||
**Fonts:** look for `font-family` rules, `@import` or `@font-face` declarations, and Markdown mentions of font names alongside size/weight.
|
||||
|
||||
### Step 4 — map, validate, propose diff
|
||||
|
||||
Same as the URL method: fill the role table, run contrast checks, show the diff, ask for approval before writing.
|
||||
|
||||
### When skill extraction is ambiguous
|
||||
|
||||
- **Skill has no CSS or token files**: fall back to reading all `.md` files and look for hex values mentioned in prose. Surface what you found and ask the user to confirm mappings before applying.
|
||||
- **Multiple accent candidates**: list them and ask the user to pick one. Don't guess.
|
||||
- **Skill is dark-mode first**: ask whether to treat the dark values as the `paper`/`ink` defaults or to invert.
|
||||
|
||||
---
|
||||
|
||||
## § Folder
|
||||
|
||||
Extract tokens from a local directory — a checked-out design system repo, a Figma export, or any folder the user points you at.
|
||||
|
||||
### Invocation
|
||||
|
||||
> *"Onboard Schematic from my design system at `~/projects/brand/design-tokens/`"*
|
||||
|
||||
Or the gate offers this as option (c) and the user provides the path.
|
||||
|
||||
### Step 1 — discover files
|
||||
|
||||
Glob the folder (recursively, up to 3 levels deep) for:
|
||||
|
||||
```
|
||||
**/*.css
|
||||
**/*.scss (read @forward / $variable declarations)
|
||||
**/tokens.json
|
||||
**/*.tokens.json
|
||||
**/design-tokens.json
|
||||
**/colors.json
|
||||
**/*style-guide*.md
|
||||
**/*design-system*.md
|
||||
**/README.md
|
||||
**/*.html (scan <style> blocks only)
|
||||
```
|
||||
|
||||
If the result set is large (>20 files), prefer files in the root and files whose names contain `color`, `token`, `brand`, `palette`, `style`, or `theme`.
|
||||
|
||||
### Step 2 — read and merge
|
||||
|
||||
Read every discovered file. Apply the same extraction logic as the Skill method (§ Skill → Step 3). CSS custom properties and JSON tokens take priority over inferred values from prose.
|
||||
|
||||
**SCSS variables:** treat `$variable-name: value;` the same as a CSS custom property — apply name heuristics to `$variable-name`.
|
||||
|
||||
**Figma token JSON** (Figma Tokens Plugin format):
|
||||
|
||||
```json
|
||||
{ "colors": { "brand": { "primary": { "value": "#eb6c36", "type": "color" } } } }
|
||||
```
|
||||
|
||||
Walk the tree; the leaf `value` fields are the colors, the path segments supply the role heuristic.
|
||||
|
||||
### Step 3 — map, validate, propose diff
|
||||
|
||||
Same as the URL method: run contrast checks, show the full diff against current `style-guide.md`, and write only after the user approves.
|
||||
|
||||
### When folder extraction is ambiguous
|
||||
|
||||
- **No structured token files, only prose docs**: read every `.md` in the root and extract hex values found near role-like words. Show the user a table of what you inferred — don't silently apply uncertain mappings.
|
||||
- **Multiple themes / color schemes found**: list them, ask the user which one to use as the diagram skin.
|
||||
- **Folder has zero readable files**: tell the user and ask for a more specific path or switch to manual token entry.
|
||||
|
||||
---
|
||||
|
||||
## Future: per-project skins
|
||||
|
||||
If the user wants multiple skins (one per project), duplicate `style-guide.md` as `style-guides/<project>.md` and add a header comment pointing the build to the active one. That's a v5.2 feature — for now, one skin per skill install.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -35,6 +35,20 @@ Every token is referred to by **semantic role**, not by its hex value. Type refe
|
||||
|
||||
Any `rgba(28,25,23, X)` in light becomes `rgba(250,247,242, X)` in dark. Same opacities, RGB flipped. The accent gets a slight hue-shift brighter to read on dark paper.
|
||||
|
||||
### Series palette (multi-series chart types only)
|
||||
|
||||
A small set of desaturated, editorial-tone colors for chart types that genuinely need to distinguish multiple overlapping entities (currently: **radar**). The "1-focal" rule still holds — `accent` is reserved for the focal series; the palette below covers the rest.
|
||||
|
||||
| Token | Light | Dark | Notes |
|
||||
|---|---|---|---|
|
||||
| `series-1` | `#7c8f6f` (sage) | `#9caf8f` | Non-focal series |
|
||||
| `series-2` | `#5e7a9b` (dusty-blue) | `#82a0c0` | Non-focal series |
|
||||
| `series-3` | `#b8915a` (mustard) | `#d3ad7a` | Non-focal series |
|
||||
| `series-4` | `#9c6b50` (rust-brown) | `#b88670` | Non-focal series |
|
||||
| `series-5` | `#6e6479` (slate) | `#8d8298` | Non-focal series |
|
||||
|
||||
Fills sit at `0.18` opacity light, `0.22` dark; strokes use the full color. **Don't backfill these tokens to non-chart types** — architecture, swimlane, etc. continue to use muted-ink variants. The series palette is opt-in for diagrams where overlapping shapes demand distinguishable color, not a license to add color elsewhere.
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
@@ -9,6 +9,64 @@
|
||||
- 1–2 coral focal nodes: the primary integration point, the primary data store, or the key decision node.
|
||||
- Dashed boundary rectangles mark regions (VPC, security group, trust zone); labels sit on a paper-colored mask over the boundary line.
|
||||
|
||||
## Connector style
|
||||
|
||||
**Rounded right-angle (orthogonal) connectors are MANDATORY** for all non-horizontal/vertical connections — diagonal `<line>` between off-axis nodes is a hard fail (see SKILL.md §6 Mandatory connector rules). Two-bend elbow path with `r=8`:
|
||||
|
||||
```svg
|
||||
<!-- right+down: from (x1,y1) to (x2,y2), mid = (x1+x2)/2 -->
|
||||
<path d="M x1,y1 H mid-8 Q mid,y1 mid,y1+8 V y2-8 Q mid,y2 mid+8,y2 H x2"
|
||||
fill="none" stroke="…" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Flip the vertical signs for right+up. Use a plain `<line>` only when endpoints share the same x or y. Arrow labels sit on the vertical segment, centered horizontally on `mid` and vertically between the two corners.
|
||||
|
||||
**Port selection — use top/bottom for vertical connectors.** When the destination is noticeably above or below the source, exit the source's top/bottom edge and enter the destination's top/bottom edge. Use a single-bend L-path (horizontal → corner → vertical into the node), not a left/right side port:
|
||||
|
||||
```svg
|
||||
<!-- entering a node from its bottom (destination above source) -->
|
||||
<path d="M x1,y_src H x2-8 Q x2,y_src x2,y_src-8 V y_dst"
|
||||
fill="none" stroke="…" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
Reserve left/right ports for connections that travel primarily horizontally. Entering a node from the side on a mainly-vertical path looks like the arrow punctures the node face rather than arriving from above or below.
|
||||
|
||||
**Dashed paths — same routing rules.** Optional, return, async, and passive flows use `stroke-dasharray="4,3"` and a lighter stroke weight (`stroke-width="1"`). Apply the **same orthogonal routing, port-selection, and bridge/hop rules** as solid paths — the dash pattern only communicates semantic weight, not a different routing grammar. When a dashed path and a solid path must cross, bridge the dashed one (it is by definition the less important connection).
|
||||
|
||||
**Zone label margin.** Leave ≥16px between the bottom of the zone eyebrow label and the top of the first enclosed node. Size the zone rect tall enough to contain this header gap (zone `y` = node_top − 32; label mask `y` = zone_y + 4).
|
||||
|
||||
## Crossing arrows — bridge / hop
|
||||
|
||||
When two orthogonal arrows must cross, add a small arc (hop/bridge) on the **less important** arrow at the crossing point. The more important arrow is drawn uninterrupted.
|
||||
|
||||
```svg
|
||||
<!-- Horizontal hop over a vertical crossing at x=cx, on a line at y -->
|
||||
<path d="M x1,y H cx-8 a 8,8 0 0,1 16,0 H x2"
|
||||
fill="none" stroke="…" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
```
|
||||
|
||||
`a 8,8 0 0,1 16,0` is an SVG arc: rx=ry=8, large-arc=0, sweep=1 (curves visually upward), advancing 16px right — creating an 8px-radius semicircular bump over the crossing. For a vertical hop over a horizontal, use `a 8,8 0 0,0 0,16` on the vertical path.
|
||||
|
||||
Decide which arrow to bridge: bridge the one that is less semantically important (passive, secondary, write-back), or the one with lighter stroke weight (dashed, muted). Never bridge both.
|
||||
|
||||
## Zone grouping
|
||||
|
||||
Group 2+ nodes that serve the same tier or trust boundary with a zone rect — drawn **before** arrows and nodes (z-order: bg → zones → arrows → nodes):
|
||||
|
||||
```svg
|
||||
<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="8"
|
||||
fill="rgba(45,49,66,0.02)" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
|
||||
<rect x="{label_x}" y="{y+4}" width="{label_w}" height="12" rx="2" fill="{paper}"/>
|
||||
<text x="{label_cx}" y="{y+13}" fill="rgba(45,49,66,0.40)" font-size="7"
|
||||
font-family="'Geist Mono', monospace" text-anchor="middle" letter-spacing="0.14em">LAYER</text>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Leave 12–16px above the first enclosed node — the eyebrow label sits in this margin.
|
||||
- Zone fill: `rgba(45,49,66,0.02)` (2% ink wash). Any stronger competes with node fills.
|
||||
- Max 3 zones per diagram. More and it reads like a swimlane (use that type instead).
|
||||
- Dark mode: swap `rgba(45,49,66,…)` → `rgba(245,245,245,…)` same opacities; label mask fill = `paper` (dark).
|
||||
|
||||
## Anti-patterns
|
||||
- Every box in coral ("this is important too") — hierarchy collapses.
|
||||
- Bidirectional arrow when one direction is obvious from context.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Bar / Column Chart
|
||||
|
||||
**Best for:** comparing discrete quantities across categories or time intervals — sprint velocity, monthly revenue, feature adoption, cohort counts. Use when each category has a single numeric value and the comparison between bars is the primary message.
|
||||
|
||||
## Layout conventions
|
||||
|
||||
- **Orientation:** Vertical bars (columns) are default. Horizontal bars are appropriate when category labels are long or you have more than 8 categories.
|
||||
- **Plot area margins:** left 80px (y-axis labels), bottom 60px (x-axis labels), top 40px, right 40px — inside a `0 0 1000 500` viewBox.
|
||||
- **Bar count cap:** 4–8 bars. More than 8 → group into periods or split into two charts.
|
||||
- **Bar width:** ≥ 50% of the column pitch (the gap should never exceed the bar). Typical: pitch=110px, bar=72px.
|
||||
- **Y-axis gridlines:** 4–6 horizontal lines at regular intervals. Stroke `rgba(45,49,66,0.08)` (very faint), 0.8px. X-axis baseline at `rgba(45,49,66,0.25)`, 1px.
|
||||
- **Y-axis labels:** right-aligned Geist Mono 8px muted, at x=72 (8px left of the plot area).
|
||||
- **X-axis labels:** centered below each bar, Geist sans 11px 600 for category names.
|
||||
- **Value labels:** Geist Mono 8px above each bar. Focal bar label in accent; others in muted.
|
||||
- **Focal bar:** 1 bar max in accent fill/stroke. All others in `muted @ 0.15` fill + `muted` stroke.
|
||||
- **Y-axis line:** thin vertical `<line>` at x=80 from y=40 to y=420.
|
||||
|
||||
### Bar element pattern
|
||||
|
||||
```svg
|
||||
<!-- Opaque paper mask prevents bleed from background -->
|
||||
<rect x="X" y="Y" width="W" height="H" fill="#f5f5f5"/>
|
||||
<!-- Bar body -->
|
||||
<rect x="X" y="Y" width="W" height="H" fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<!-- Value label above bar -->
|
||||
<text x="X+W/2" y="Y-8" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" text-anchor="middle">VALUE</text>
|
||||
```
|
||||
|
||||
Focal bar: replace fill with `rgba(235,108,54,0.12)`, stroke with `#eb6c36`, label fill with `#eb6c36`.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- More than 8 bars without grouping (illegible at normal scale).
|
||||
- Truncated y-axis (not starting at 0) — distorts the magnitude comparison.
|
||||
- Accent on more than 1 bar ("everything is important" = nothing is).
|
||||
- 3-D bar extrusion — no shadows, no depth.
|
||||
- Category labels rotated more than 45°; prefer short labels or horizontal chart instead.
|
||||
|
||||
## Variants
|
||||
|
||||
- **Grouped bars:** two bars per category, side by side. Use `accent` for the primary series and `series-1` for the secondary. Max 2 groups.
|
||||
- **Stacked bars:** segments stacked to total. Use `accent` for the focal segment; muted tints for others. Document the total at the top of each stack.
|
||||
|
||||
## Examples
|
||||
|
||||
- `assets/example-bar.html` — minimal light
|
||||
- `assets/example-bar-dark.html` — minimal dark
|
||||
- `assets/example-bar-full.html` — full editorial
|
||||
@@ -0,0 +1,374 @@
|
||||
# Data Flow
|
||||
|
||||
**Best for:** visualising how data moves through a pipeline *across organisational roles* — who initiates, who processes, who publishes, and who consumes. The canonical use case is a multi-role data platform (Admin → Engineers → Scientists → Consumers) with 4–6 process steps. Use when the reader needs to understand **who does what at each stage**, not just the technical components.
|
||||
|
||||
Prefer standard **Swimlane** for cross-functional business processes (HR approvals, support tickets). Use **Data flow** when the subject is a data pipeline with typed payloads (raw files, tables, reports) and role-scoped access boundaries.
|
||||
|
||||
This type is **parametric** — the inputs schema in §1 drives every coordinate via the formulas in §2. Two generations from the same inputs must produce visually identical SVG.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inputs — the parameter contract
|
||||
|
||||
```yaml
|
||||
lanes: # 1..4 horizontal swimlanes (top to bottom)
|
||||
- { name: ["DATA", "ADMINS"], key: "ADM" }
|
||||
- { name: ["DATA", "ENGINEERS"], key: "ENG" }
|
||||
- { name: ["DATA", "SCIENTISTS"], key: "SCI" }
|
||||
- { name: ["DATA", "CONSUMERS"], key: "CON" }
|
||||
|
||||
steps: # 1..6 columns (left to right)
|
||||
- { number: "01", label: "COLLECT" }
|
||||
- { number: "02", label: "STORE" }
|
||||
- { number: "03", label: "TRANSFORM" }
|
||||
- { number: "04", label: "ANALYZE", focal: true } # focal step header chip — accent fill
|
||||
- { number: "05", label: "PUBLISH" }
|
||||
|
||||
nodes: # explicit per-cell entries; empty cells render nothing
|
||||
- { lane: "ADM", step: 0, title: "Project Setup", sub: "create · assign roles", tool: "Platform console" }
|
||||
- { lane: "ADM", step: 1, title: "Access Control", sub: "bucket policies · LDAP", tool: "MinIO · LDAP console",
|
||||
color: "#b85450" } # tinted rust-red to flag governance/identity concern
|
||||
- { lane: "ENG", step: 0, title: "Source Ingest", sub: "ext. sources → raw", tool: "NiFi · API · SFTP",
|
||||
chips: {in: "WB", out: "DB"} } # web payload in, dataset out
|
||||
- { lane: "ENG", step: 1, title: "Raw Store", sub: "raw landing zones", tool: "MinIO raw",
|
||||
chips: {in: "DB", out: "DB"} } # raw stays raw inside the landing zone
|
||||
- { lane: "ENG", step: 2, title: "Clean & Stage", sub: "raw → staging → anon", tool: "NiFi · Trino",
|
||||
chips: {in: "DB", out: "TB"} } # raw dataset → analysis-ready table
|
||||
- { lane: "SCI", step: 3, title: "Explore & Model", sub: "anon data → insights", tool: "JupyterHub · Trino",
|
||||
chips: {in: "TB", out: "FL"}, focal: true } # focal — table in, file/report out
|
||||
- { lane: "SCI", step: 4, title: "Publish Findings", sub: "models → dashboards", tool: "Superset · Reports",
|
||||
chips: {in: "FL", out: "FL"} } # report in, report out (pass-through to publish)
|
||||
- { lane: "CON", step: 4, title: "Query Insights", sub: "aggregated views", tool: "Trino (read-only)",
|
||||
chips: {in: "TB", out: "TB"} } # consumers read tables, hand off tables
|
||||
|
||||
arrows: # explicit edges; styles bind to topology (see §3)
|
||||
- { from: {lane: "ADM", step: 0}, to: {lane: "ADM", step: 1}, style: "muted" }
|
||||
- { from: {lane: "ADM", step: 0}, to: {lane: "ENG", step: 0}, style: "trigger" } # dashed governance
|
||||
- { from: {lane: "ADM", step: 1}, to: {lane: "ENG", step: 1}, style: "trigger" }
|
||||
- { from: {lane: "ENG", step: 0}, to: {lane: "ENG", step: 1}, style: "muted" }
|
||||
- { from: {lane: "ENG", step: 1}, to: {lane: "ENG", step: 2}, style: "muted" }
|
||||
- { from: {lane: "ENG", step: 2}, to: {lane: "SCI", step: 3}, style: "accent", # focal cross-role
|
||||
label: "anon data" }
|
||||
- { from: {lane: "SCI", step: 3}, to: {lane: "SCI", step: 4}, style: "muted" }
|
||||
- { from: {lane: "SCI", step: 4}, to: {lane: "CON", step: 4}, style: "link" } # teal: published
|
||||
|
||||
dark: false
|
||||
```
|
||||
|
||||
**Reserved field semantics:**
|
||||
- `lanes[k].key` — the 3-letter role chip text (e.g., `ADM`, `ENG`, `SCI`, `CON`). Used inside every node in that lane.
|
||||
- `lanes[k].name` — two-line lane label; both lines use the uppercase `eyebrow` role.
|
||||
- `steps[j].focal: true` — exactly **one** step may declare this. Header chip renders in accent.
|
||||
- `nodes[i].focal: true` — exactly **one** node may declare this. Renders with accent border (§5).
|
||||
- `nodes[i].chips` — data-type chips for the node. Either form accepted:
|
||||
- **Object form (preferred):** `{in: "<CODE>", out: "<CODE>"}` — explicit input/output semantic. Either side optional.
|
||||
- **Array form:** `["<INPUT_CODE>", "<OUTPUT_CODE>"]` — first item is input, second is output.
|
||||
- Codes from §8 (`WB`, `DB`, `TB`, `FL`, `LS`). Position is **fixed**: input chip on the node's bottom-**left**, output chip on the bottom-**right**.
|
||||
- `nodes[i].color` — optional **per-node color override**. Any valid `"#hex"` string is accepted; the §4 palette is recommended for cross-diagram consistency but not required. Every node can carry its own color independently of others.
|
||||
|
||||
---
|
||||
|
||||
## 2. Layout formulas — deterministic geometry
|
||||
|
||||
```
|
||||
label_col_w = 140
|
||||
step_slot_w = 112
|
||||
right_pad = 28
|
||||
n_steps = len(steps)
|
||||
n_lanes = len(lanes)
|
||||
|
||||
# Canvas
|
||||
viewBox_w = label_col_w + n_steps * step_slot_w + right_pad # 5 steps → 728
|
||||
header_h = 36
|
||||
lane_h = 80
|
||||
has_color_row = any(node.color or step.color or lane.color in inputs)
|
||||
legend_h = 100 if has_color_row else 80 # 4 rows when colors are present
|
||||
viewBox_h = header_h + n_lanes * lane_h + legend_h # 4 lanes, no colors → 436; with colors → 456
|
||||
|
||||
# Header strip (top)
|
||||
header_y = 0 # ends at header_h = 36
|
||||
step_chip_y = 6 # 16-px chip at y=6..22
|
||||
step_label_y = 29 # text line below chip
|
||||
|
||||
# Lane positions
|
||||
lane_y_top(k) = header_h + k * lane_h # 36, 116, 196, 276
|
||||
lane_y_mid(k) = lane_y_top(k) + lane_h/2 # 76, 156, 236, 316
|
||||
lane_label_x = label_col_w / 2 # 70
|
||||
|
||||
# Step / node center x
|
||||
step_cx(j) = label_col_w + j * step_slot_w + step_slot_w/2 # 196, 308, 420, 532, 644
|
||||
|
||||
# Nodes
|
||||
node_w = 100
|
||||
node_h = 64
|
||||
node_x(j) = step_cx(j) - node_w/2 # 146, 258, 370, 482, 594
|
||||
node_y(k) = lane_y_top(k) + 8 # 44, 124, 204, 284
|
||||
|
||||
# Legend strip (bottom)
|
||||
legend_y_top = header_h + n_lanes * lane_h # 356
|
||||
legend_row_y = [legend_y_top + 16, legend_y_top + 37, legend_y_top + 59]
|
||||
# 372, 393, 415
|
||||
```
|
||||
|
||||
### 2.1 Background structure
|
||||
|
||||
- Paper fill across full viewBox.
|
||||
- Dot pattern: 22×22 grid, `circle r=0.8`, `fill ink @ 0.10`.
|
||||
- Alternating lane tints: odd-indexed lanes (0, 2, …) receive `ink @ 0.018` fill.
|
||||
- Lane dividers: horizontal hairlines at every `lane_y_top(k)` and at `legend_y_top`, stroke `ink @ 0.12` width 0.8.
|
||||
- Label column right border: vertical hairline at `x = label_col_w`, from `y = header_h` to `y = legend_y_top`.
|
||||
|
||||
### 2.2 Step header chip
|
||||
|
||||
Per step `j`:
|
||||
|
||||
```
|
||||
chip_x(j) = step_cx(j) - 16 # 16×16 chip
|
||||
chip_y = 6
|
||||
chip_w = 32
|
||||
chip_h = 16
|
||||
chip_rx = 8 # pill-shaped
|
||||
number_anchor = (step_cx(j), 14)
|
||||
label_anchor = (step_cx(j), 29)
|
||||
```
|
||||
|
||||
Default fill: `ink @ 0.12`, number text ink, label text muted.
|
||||
Focal fill: `accent @ 0.20`, number + label text accent.
|
||||
Per-step `color` override (§4): replaces the fill with `rgba(C, 0.20)` and the text fill with `C`.
|
||||
|
||||
### 2.3 Lane labels
|
||||
|
||||
Two-line `eyebrow` role label, both lines uppercase, fill muted:
|
||||
- Line 1 at `(lane_label_x, lane_y_mid(k) - 4)`
|
||||
- Line 2 at `(lane_label_x, lane_y_mid(k) + 8)`
|
||||
|
||||
Per-lane `color` override (§4): replaces the label fill with `C` and the lane tint with `rgba(C, 0.04)` (instead of the default `ink @ 0.018`).
|
||||
|
||||
### 2.4 Node content layout (inside the 100×64 rect)
|
||||
|
||||
```
|
||||
role_chip rect 18×10 at (node_x+4, node_y+4), rx=3
|
||||
role_chip_text centered at (node_x+13, node_y+9), eyebrow role, font-size=6, weight=600
|
||||
title centered at (step_cx(j), node_y+23), node-name role, font-size=9
|
||||
sub centered at (step_cx(j), node_y+35), sublabel role, font-size=6.5, muted
|
||||
tool centered at (step_cx(j), node_y+47), sublabel role, font-size=6.5, soft
|
||||
data chip IN rect 16×8 at (node_x+4, node_y+54), rx=3 # payload type entering the node
|
||||
data chip OUT rect 16×8 at (node_x+80, node_y+54), rx=3 # payload type leaving the node
|
||||
```
|
||||
|
||||
Empty cells (no node entry) render **nothing**. No placeholder rect, no role chip, no label — the cell is invisible.
|
||||
|
||||
---
|
||||
|
||||
## 3. Arrow rules (mandatory)
|
||||
|
||||
Four styles, bound to topology. Connectors are drawn **before** all node rects (z-order rule).
|
||||
|
||||
| `style` | Stroke | Width | Dash | Marker | When required |
|
||||
|---|---|---|---|---|---|
|
||||
| `muted` | `muted` | 1.0 | — | `arr-muted` | Standard data hand-off between steps or within a lane. |
|
||||
| `trigger` | `muted` | 1.0 | `4,3` | `arr-muted` | Governance trigger — an admin action enables downstream work. Unlabelled. |
|
||||
| `accent` | `accent` | 1.2 | — | `arr-accent` | Focal cross-role handoff. **Exactly one per diagram**, labeled. |
|
||||
| `link` | `link` | 1.0 | — | `arr-link` | Published / externally-consumed output. |
|
||||
|
||||
**Defs block** (required, three markers):
|
||||
|
||||
```svg
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="11" cy="11" r="0.8" fill="{ink @ 0.10}"/>
|
||||
</pattern>
|
||||
<marker id="arr-muted" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="{muted}"/></marker>
|
||||
<marker id="arr-accent" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="{accent}"/></marker>
|
||||
<marker id="arr-link" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="{link}"/></marker>
|
||||
</defs>
|
||||
```
|
||||
|
||||
### 3.1 Routing rules (non-negotiable)
|
||||
|
||||
- **Single-bend routing:** horizontal-first, then vertical. Exit a node from the **right edge**; enter from the **left** (same-lane horizontal) or **top/bottom** (cross-lane vertical).
|
||||
- **No diagonals.** Bends use an 8-px Q-bezier corner.
|
||||
- **Same-step cross-lane (vertical)**: line directly between `(step_cx(j), lane_y_top(k_to)−12)` and `(step_cx(j), lane_y_top(k_to))`. Used for admin → engineers triggers under the same step.
|
||||
- **Cross-lane cross-step (focal)**: exit right, run horizontal past the source node's right edge to a corridor x just before the target's step, then drop vertically.
|
||||
- **Labels:** only the `accent` arrow gets a label. Use a paper-filled rect mask (opaque) 6 px behind the text. Other arrows are unlabelled.
|
||||
- **Z-order:** all arrows emitted before any node rect (the rect fills mask the line ends inside the node).
|
||||
|
||||
---
|
||||
|
||||
## 4. Component color override
|
||||
|
||||
Any node, lane, or step may declare an optional `color: "#hex"`. Mirrors high-level §3.4 and dp-integration §4 so the rule reads identically across types.
|
||||
|
||||
### 4.1 Per-node `color`
|
||||
|
||||
Applied to:
|
||||
|
||||
| Element | Light | Dark |
|
||||
|---|---|---|
|
||||
| Container fill (`rect`) | `rgba(C, 0.06)` | `rgba(C_light, 0.10)` |
|
||||
| Container stroke | `rgba(C, 0.35)` (stroke-width 1) | `rgba(C_light, 0.45)` |
|
||||
| Role chip fill | `rgba(C, 0.18)` | `rgba(C_light, 0.22)` |
|
||||
| Role chip text | `C` | `C_light` |
|
||||
| Title text | `C` | `C_light` |
|
||||
| Sub-label | **unchanged** (muted) | **unchanged** (muted) |
|
||||
| Tool label | **unchanged** (soft) | **unchanged** (soft) |
|
||||
| Data-type chips | **unchanged** | **unchanged** |
|
||||
| Arrows touching this node | **unchanged** — topology-driven | **unchanged** |
|
||||
|
||||
`C_light` = the same hex lightened ~15% for dark-mode contrast (e.g., `#b85450` → `#d97a78`).
|
||||
|
||||
### 4.2 Per-step `color`
|
||||
|
||||
Replaces the step header chip's fill with `rgba(C, 0.20)` and the chip's number + label text fill with `C`. The legend's matching step entry uses the same colors.
|
||||
|
||||
### 4.3 Per-lane `color`
|
||||
|
||||
Replaces the lane stripe tint with `rgba(C, 0.04)` (only for odd-indexed lanes that receive a tint by default — or extend to all lanes if explicitly chosen) and the lane label text fill with `C`. Use sparingly; lane tints are easy to over-apply.
|
||||
|
||||
### 4.4 Rules
|
||||
|
||||
- **Never on focal nodes / focal steps.** The accent already carries that signal. A `color` on a focal element is ignored.
|
||||
- **Never on arrows.** Arrows are topology-driven. If you want a colored edge, pick a different `style` from §3, not a color override.
|
||||
- **Cap at 3 custom-colored elements** per diagram (nodes + lanes + steps combined), in addition to the focal pair (focal node + focal step header). Above 3 the visual signal starts to fragment — if you need more, split the diagram or rethink whether each color carries distinct meaning.
|
||||
- **Subtitles and tool labels stay muted.** Only the primary identity (border + role chip + title + icon) carries the color signal.
|
||||
|
||||
### 4.5 Semantic palette (recommended)
|
||||
|
||||
Same palette as high-level / dp-integration so a reader scanning multiple diagrams sees the same colors meaning the same thing:
|
||||
|
||||
- `#b85450` rust-red — Security / Identity / Governance (admin nodes, LDAP, access control)
|
||||
- `#5a7d9a` slate-blue — Observability / Quality (monitoring, data-quality gates, lineage)
|
||||
- `#7a8c47` olive-green — Governance / Lineage (catalog, metadata)
|
||||
- `#8c6d3f` warm-brown — Backup / DR / Archive
|
||||
|
||||
---
|
||||
|
||||
## 5. Focal rule
|
||||
|
||||
The data-flow diagram is built around **one cross-role handoff** that defines its central claim. Three focal slots, exactly one entry each:
|
||||
|
||||
- **One focal step** (`steps[j].focal: true`) — typically the analytical pivot (Analyze, Model, …). Header chip and legend chip both render in accent.
|
||||
- **One focal node** (`nodes[i].focal: true`) — the node that *receives* the focal handoff. Accent border + accent role chip + ink title.
|
||||
- **One focal arrow** (`arrows[i].style: "accent"`) — the cross-role handoff into the focal node. Solid accent stroke + labeled with a short payload descriptor (e.g., `anon data`).
|
||||
|
||||
If zero or >1 of any focal slot are declared, halt and ask the user.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dark mode
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| Paper | `paper` | `ink` |
|
||||
| Ink | `ink` | `paper` |
|
||||
| Muted | `muted` | `soft` |
|
||||
| Soft | `soft` | `rule-solid` |
|
||||
| Accent | `accent` | `accent` |
|
||||
| Link | `link` | `link` |
|
||||
| Dot pattern | `ink @ 0.10` | `paper @ 0.10` |
|
||||
| Lane tint | `ink @ 0.018` | `paper @ 0.025` |
|
||||
| Dividers | `ink @ 0.12` | `paper @ 0.12` |
|
||||
| Default chip fill | `ink @ 0.12` | `paper @ 0.12` |
|
||||
| Focal chip fill | `accent @ 0.20` | `accent @ 0.22` |
|
||||
| Default node fill | `paper` | `paper @ 0.04` |
|
||||
| Default node stroke | `ink @ 0.25` | `paper @ 0.20` |
|
||||
| Focal node fill | `accent @ 0.07` | `accent @ 0.12` |
|
||||
| Focal node stroke | `accent` | `accent` |
|
||||
| Custom component colors | `C` | `C_light` (lighten ~15%) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Reproducibility checklist (taste gate)
|
||||
|
||||
Before emitting SVG, verify **every** item:
|
||||
|
||||
1. `viewBox = "0 0 {viewBox_w} {viewBox_h}"` derived from `n_steps` and `n_lanes` via §2.
|
||||
2. Header strip at `y=0..36`; legend strip at `y=legend_y_top..viewBox_h` (`legend_y_top = 36 + n_lanes * 80`).
|
||||
3. Every node at `(step_cx(j) - 50, lane_y_top(k) + 8)` size `100×64`.
|
||||
4. Empty cells render nothing — no placeholder rect, no text.
|
||||
5. Exactly **one** focal step (`steps[j].focal: true`).
|
||||
6. Exactly **one** focal node (`nodes[i].focal: true`).
|
||||
7. Exactly **one** focal arrow (`style: accent`). Labeled, with a paper-masked rect behind the label.
|
||||
8. All other arrows unlabelled.
|
||||
9. All arrows emitted before any node rect (z-order rule).
|
||||
10. Single-bend routing only — no diagonals. Q-bezier `r=8` at each bend.
|
||||
11. Custom component colors ≤ 3 (in addition to the focal pair). Arrows never recolored by component `color`.
|
||||
12. Subtitle and tool labels stay muted regardless of any component `color`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Data-type chips reference (input + output)
|
||||
|
||||
Small `16×8 rx=3` badges at the bottom of each node, one for input and one for output. Position is **non-negotiable**:
|
||||
|
||||
- **Input chip** at `(node_x+4, node_y+54)` — bottom-**left** of the node. Represents the payload format *entering* the node from upstream.
|
||||
- **Output chip** at `(node_x+80, node_y+54)` — bottom-**right** of the node. Represents the payload format *leaving* the node toward downstream.
|
||||
|
||||
Either chip may be omitted (e.g., a sink node has only an input chip; a source-only node has only an output chip). Reading the diagram becomes a payload-transformation trace: scan a row, read each node's input → output, and you see exactly what shape the data takes at each hand-off.
|
||||
|
||||
### Chip codes
|
||||
|
||||
| Code | Color | Meaning |
|
||||
|------|-------|---------|
|
||||
| `WB` | `#6e6479` (mauve) | Web / Public data |
|
||||
| `DB` | `#5e7a9b` (steel-blue) | Dataset / Raw file |
|
||||
| `TB` | `#b8915a` (amber) | Table / Analysis-ready |
|
||||
| `FL` | `#9c6b50` (sienna) | File / Report / Export |
|
||||
| `LS` | `#4a7c59` (forest) | Live stream / Event |
|
||||
|
||||
Text inside chip: white, `eyebrow` role at 5px, weight 700.
|
||||
|
||||
Data-type chip colors are a **separate semantic axis** from the per-node color override (§4). The chip colors describe *payload format*; the node color override describes *concern type* (governance, observability, …). Don't conflate them — a node can have both an `out: TB` amber chip and a rust-red border color simultaneously.
|
||||
|
||||
---
|
||||
|
||||
## 9. Legend (3- or 4-row strip)
|
||||
|
||||
Each row introduced by a category label at `x=144`. The default legend has **3 rows** (`STEPS` / `DATA TYPE` / `FLOW`); when one or more nodes carry a `color` override (§4), add a 4th `CONCERN` row and grow `legend_h` to 100 (so `viewBox_h = header_h + n_lanes·80 + 100`).
|
||||
|
||||
- **Row 1 — `STEPS`** at `y = legend_y_top + 16`: repeat the header chips with their labels. Focal step keeps accent fill.
|
||||
- **Row 2 — `DATA TYPE`** at `y = legend_y_top + 37`: one swatch per chip type actually used in the diagram. **Append a small sub-hint** in the muted `sublabel` role after the chips: `left chip = input · right chip = output`. This makes the position-based input/output convention explicit for first-time readers.
|
||||
- **Row 3 — `CONCERN`** (only when color overrides are present) at `y = legend_y_top + 58`: one mini-rect per custom color used, with its semantic label (e.g., `Identity · Governance`, `Data Quality · Observability`). The focal accent swatch is also shown here so the reader sees all three colored axes side-by-side.
|
||||
- **Row 4 — `FLOW`** (last row, position depends on whether `CONCERN` row exists): short line segments with their marker + text label, one per arrow style actually used.
|
||||
|
||||
All legend items align on a single horizontal strip per row. Do not stack vertically inside a box.
|
||||
|
||||
---
|
||||
|
||||
## 10. Complexity budget
|
||||
|
||||
| Dimension | Max |
|
||||
|---|---|
|
||||
| Lanes (roles) | 4 |
|
||||
| Steps | 6 |
|
||||
| Nodes per lane | Nodes = active steps only — empty cells are invisible (no placeholder box) |
|
||||
| Labelled arrows | 1 (focal accent only) |
|
||||
| Data-type chips per node | 2 |
|
||||
| Custom-colored elements (§4) | 3 (in addition to focal node + focal step) |
|
||||
|
||||
Above 4 lanes or 6 steps: split into two diagrams (e.g., ingestion pipeline / analytics pipeline).
|
||||
|
||||
---
|
||||
|
||||
## 11. Anti-patterns
|
||||
|
||||
- **Placeholder empty cells** — if a role doesn't participate in a step, leave the cell empty (no box, no text).
|
||||
- **More than one labelled arrow** — only the focal cross-role handoff gets a label.
|
||||
- **Diagonal arrows** — always horizontal-first, then vertical; single right-angle bend.
|
||||
- **`title` role for node titles** — node titles use the `node-name` role (technical context). Only the page `<h1>` uses `title`.
|
||||
- **Accent on more than one node, one step, one arrow** — focal = one node + one step + one arrow, max.
|
||||
- **`node-name` role for role labels** — lane labels always use the uppercase `eyebrow` role (they are identifiers, not prose).
|
||||
- **`color` override on a focal element** — ignored. Accent always wins.
|
||||
- **Custom-colored arrows** — arrows are topology-driven. Color on a node never spreads to its edges.
|
||||
- **Lane tints over-applied** — a tint on every lane reads as decoration, not signal. Apply to ≤1 lane.
|
||||
|
||||
---
|
||||
|
||||
## 12. Examples
|
||||
|
||||
- `assets/example-data-flow.html` — minimal light (the platform, 4-role × 5-step: Admin, Engineers, Scientists, Consumers). Gallery default.
|
||||
- `assets/example-data-flow-dark.html` — same, dark skin.
|
||||
- `assets/example-data-flow-full.html` — same, editorial-card frame.
|
||||
- `assets/example-data-flow-extended.html` — exercises §4 color override: Access Control node in rust-red (governance), Clean & Stage node in slate-blue (data quality). Focal accent on Analyze step + Explore & Model node + anon-data arrow unchanged.
|
||||
- `assets/example-data-flow-extended-dark.html` — extended pattern, dark skin.
|
||||
- `assets/example-data-flow-extended-full.html` — extended pattern, editorial-card frame.
|
||||
@@ -0,0 +1,410 @@
|
||||
# DP integration
|
||||
|
||||
**Best for:** the integration topology of a data platform — which source systems plug in, which consumer surfaces plug out, and which protocol each one speaks. Hub-and-spoke layout wrapped in an explicit **Data platform** layer; no time/phase axis.
|
||||
|
||||
Use when the question is *"what surfaces does this platform expose, and over what wire?"* rather than *"how does data move through phases?"*.
|
||||
|
||||
This type is **parametric** — like `type-high-level.md`, every coord is derived from a small inputs schema. Two generations from the same inputs must produce visually identical SVG.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inputs — the parameter contract
|
||||
|
||||
```yaml
|
||||
sources: # left column, 0..6 nodes
|
||||
- { name: "Databases", type: "db", subtitle: "SQL · MariaDB",
|
||||
connects_to: [{to: "NiFi", label: "JDBC"},
|
||||
{to: "Trino", label: "FEDERATE", style: "federated"}] }
|
||||
- { name: "SFTP drops", type: "sftp", subtitle: "scheduled pulls",
|
||||
connects_to: [{to: "NiFi", label: "SFTP"}] }
|
||||
- { name: "Email", type: "mail", subtitle: "IMAP attachments",
|
||||
connects_to: [{to: "NiFi", label: "IMAP"}] }
|
||||
- { name: "IBM legacy", type: "mainframe", subtitle: "file export",
|
||||
connects_to: [{to: "NiFi", label: "FILE"}] }
|
||||
|
||||
platform:
|
||||
name: "DATA PLATFORM" # zone label (paper-masked top border)
|
||||
rows: # ordered top→bottom; each is bar or row
|
||||
- { kind: bar, name: "Trino", icon: trino, subtitle: "federated query · push-down",
|
||||
role: "SQL", focal: true }
|
||||
- { kind: row, nodes: [
|
||||
{ name: "Apache NiFi", icon: nifi, role: "INGEST", subtitle: "flow-based ETL" },
|
||||
{ name: "MinIO", icon: minio, role: "STORE", subtitle: "S3 object store · medallion", focal: true },
|
||||
{ name: "JupyterLab", icon: jupyter, role: "NOTEBOOK", subtitle: "Python · R · pandas" }
|
||||
]}
|
||||
- { kind: bar, name: "Apache Airflow", icon: airflow,
|
||||
subtitle: "scheduler · DAG triggers · backfill", role: "DAG" }
|
||||
|
||||
consumers: # right column, 0..6 nodes
|
||||
- { name: "Desktop apps", type: "monitor", subtitle: "SPSS · SAS · Stata",
|
||||
connects_from: [{from: "Trino", label: "ODBC"}] }
|
||||
- { name: "BI & reports", type: "chart", subtitle: "Tableau · Power BI",
|
||||
connects_from: [{from: "Trino", label: "JDBC"}] }
|
||||
- { name: "Public website", type: "globe", subtitle: "NatStat portal",
|
||||
connects_from: [{from: "Trino", label: "HTTPS"}] }
|
||||
- { name: "API gateway", type: "api", subtitle: "3rd-party / OAuth2",
|
||||
connects_from: [{from: "Trino", label: "REST"}] }
|
||||
|
||||
footer: # 0..N cross-cutting bars stacked below zone (full-canvas width)
|
||||
- { name: "Active Directory", icon: key, subtitle: "LDAP · SSO · group RBAC",
|
||||
color: "#b85450" } # tinted red to flag the security concern
|
||||
# additional footer nodes (Observability, Backup, …) stack below this one
|
||||
|
||||
internal_connections: # explicit platform-component edges
|
||||
- { from: "NiFi", to: "MinIO", style: "primary", label: "WRITE" }
|
||||
- { from: "MinIO", to: "JupyterLab", style: "secondary", label: "READ" }
|
||||
- { from: "MinIO", to: "Trino", style: "secondary" }
|
||||
- { from: "JupyterLab", to: "Trino", style: "secondary", dashed: true }
|
||||
- { from: "Airflow", to: ["Apache NiFi", "MinIO", "JupyterLab"], style: "trigger" }
|
||||
|
||||
focal_accent: "#eb6c36" # one color for all focal components (default = SKILL accent)
|
||||
dark: false
|
||||
```
|
||||
|
||||
**Reserved `kind` values for `platform.rows`:**
|
||||
- `bar` — full-zone-width strip. Default height 44 px (focal bars get 56 px). Required fields: `name`, `icon`. Optional: `subtitle`, `role`, `color`, `focal`.
|
||||
- `row` — N nodes evenly spaced across zone width. Required: `nodes` list. Each node has `name`, `icon`, optional `role`, `subtitle`, `color`, `focal`.
|
||||
|
||||
**Source/consumer `type` values** → icon mapping (extends `references/primitive-icons.md`):
|
||||
- `db` → cylinder, `sftp` → folder-with-arrow, `mail` → envelope, `mainframe` → server-with-vents
|
||||
- `monitor` → desktop screen, `chart` → bar-chart, `globe` → globe, `api` → curly braces
|
||||
- `key` → key + ring (identity / IDP)
|
||||
- Any explicit icon name in `primitive-icons.md` is also accepted.
|
||||
|
||||
**Per-component `color: "#hex"`** is optional on any node, bar, or footer entry. See §4.
|
||||
|
||||
---
|
||||
|
||||
## 2. Layout formulas — deterministic geometry
|
||||
|
||||
```
|
||||
# Canvas
|
||||
viewBox_w = 1200
|
||||
n_sources = len(sources)
|
||||
n_consumers = len(consumers)
|
||||
n_footer = len(footer)
|
||||
|
||||
# Side columns (sources left, consumers right)
|
||||
col_top = 92
|
||||
col_node_h = 64
|
||||
col_gap = 24 # stride = col_node_h + col_gap = 88
|
||||
col_h_min = 336 # default fits 4 sources (4 * 88 - 24)
|
||||
col_h = max(col_h_min, max(n_sources, n_consumers) * 88 - 24)
|
||||
left_x = 40
|
||||
left_w = 160
|
||||
right_x = 1000
|
||||
right_w = 160
|
||||
col_node_y(k) = col_top + k * 88
|
||||
col_node_cy(k) = col_node_y(k) + col_node_h/2 # 124, 212, 300, 388 by default
|
||||
|
||||
# Platform zone
|
||||
zone_x = 260
|
||||
zone_w = 696
|
||||
zone_y = 72
|
||||
zone_h = col_h # zone always matches column height
|
||||
zone_cx = zone_x + zone_w/2 # 608
|
||||
zone_pad_x = 16 # inside left/right padding for bars
|
||||
zone_label_y = zone_y + 3 # paper-masked label across top border
|
||||
|
||||
# Footer bars (below zone — each cross-cutting concern is a full-width bar)
|
||||
footer_top = zone_y + zone_h + 52 # 52-px gap below zone
|
||||
footer_bar_h = 56
|
||||
footer_bar_x = 40 # aligned with source column left edge
|
||||
footer_bar_w = viewBox_w - 80 # = 1120 — spans from source col left to consumer col right
|
||||
footer_gap = 8
|
||||
footer_y(k) = footer_top + k * (footer_bar_h + footer_gap)
|
||||
footer_bottom = footer_top + n_footer * (footer_bar_h + footer_gap) - footer_gap
|
||||
|
||||
viewBox_h = max(600, footer_bottom + 84) # 84 reserved for legend
|
||||
|
||||
# Platform.rows allocation inside zone
|
||||
bar_h_focal = 56
|
||||
bar_h_default = 44
|
||||
row_h = 72
|
||||
row_gap = 16
|
||||
```
|
||||
|
||||
### 2.1 Row placement (cursor algorithm)
|
||||
|
||||
Allocate each `platform.rows` entry top-to-bottom. The single `row` (or first row when N>1) anchors to side-column row 2 so its connectors stay horizontal:
|
||||
|
||||
```
|
||||
primary_row_idx = index of first kind=row in platform.rows
|
||||
primary_row_top = col_node_y(1) - (row_h - col_node_h)/2 # 176 by default
|
||||
# 4-px nudge so cy aligns with side row 2
|
||||
|
||||
# Place rows above primary
|
||||
y = primary_row_top
|
||||
for entry in platform.rows[:primary_row_idx] reversed:
|
||||
y -= row_gap
|
||||
entry.h = bar_h_focal if (entry.kind == bar and entry.focal) else bar_h_default
|
||||
y -= entry.h
|
||||
entry.y_top = y # Trino bar lands at y=104
|
||||
|
||||
# Place primary row
|
||||
platform.rows[primary_row_idx].y_top = primary_row_top # NiFi/MinIO/Jupyter at y=176
|
||||
platform.rows[primary_row_idx].h = row_h
|
||||
|
||||
# Place rows below primary
|
||||
y = primary_row_top + row_h
|
||||
for entry in platform.rows[primary_row_idx+1:]:
|
||||
y += row_gap
|
||||
entry.y_top = y
|
||||
entry.h = bar_h_focal if (entry.kind == bar and entry.focal) else bar_h_default
|
||||
y += entry.h
|
||||
|
||||
# Constraint: y <= zone_y + zone_h
|
||||
```
|
||||
|
||||
This produces the canonical layout for the standard shape (top bar / 3-node row / bottom bar): Trino at `y=104 h=56`, primary row at `y=176 h=72`, Airflow at `y=324 h=44`. Bottom anchor of Airflow at y=368 (40-px clear from zone bottom at y=408). **Note**: with the canonical layout there's a 76-px gap between the primary row's bottom (y=248) and Airflow's top (y=324). That gap is intentional — Airflow visually sits at the same y-band as source/consumer row 4 (cy=388), so it reads as a sibling of the bottom side-column row.
|
||||
|
||||
### 2.2 Node placement inside a `row` entry
|
||||
|
||||
```
|
||||
N = len(row.nodes)
|
||||
node_w = (zone_w - 2*zone_pad_x - (N-1) * 16) / N
|
||||
node_x(j) = zone_x + zone_pad_x + j * (node_w + 16)
|
||||
node_cx(j) = node_x(j) + node_w/2
|
||||
```
|
||||
|
||||
For the canonical 3-node row: `node_w = (696 - 32 - 32) / 3 = 210.67`. The shipped example uses fixed `node_w=160` with custom x positions (`288, 480, 672`) chosen so each node's `cx` aligns to the column for connector convenience: 368, 560, 752. **Both layouts are valid**; the formula above is the default for new diagrams. Document any deviation in the rendered SVG with a comment.
|
||||
|
||||
### 2.3 Bar (full-zone-width) placement
|
||||
|
||||
```
|
||||
bar_x = zone_x + zone_pad_x # 276
|
||||
bar_w = zone_w - 2*zone_pad_x # 664
|
||||
bar_cx = zone_cx # 608
|
||||
```
|
||||
|
||||
Bars span the full zone width minus 16-px padding on each side. Bars marked `focal: true` use `bar_h_focal=56` and accent styling (fill `rgba(focal_accent, 0.08)`, stroke `focal_accent`). Non-focal bars use `bar_h_default=44` with muted styling (fill `rgba(45,49,66,0.05)`, stroke `rgba(45,49,66,0.32)`).
|
||||
|
||||
### 2.4 Source / consumer placement (side columns)
|
||||
|
||||
```
|
||||
source_y(k) = col_top + k * 88 # 92, 180, 268, 356, …
|
||||
source_cy(k) = source_y(k) + col_node_h/2 # 124, 212, 300, 388, …
|
||||
consumer_y(k) = source_y(k) # mirrored
|
||||
consumer_cy(k) = source_cy(k)
|
||||
```
|
||||
|
||||
All side-column nodes use fixed `w=160 h=64`. Same fill / stroke pattern: fill `rgba(79,93,117,0.06)`, stroke `#7a8399`, stroke-width 1.
|
||||
|
||||
---
|
||||
|
||||
## 3. Connector rules (mandatory)
|
||||
|
||||
Five styles, bound to topology. Don't let user override style on focal-touching, bar-originating, or Trino → consumer edges — those are fixed by rule.
|
||||
|
||||
| `style` | Stroke | Width | Dash | Marker | When required |
|
||||
|---|---|---|---|---|---|
|
||||
| `primary` | `#eb6c36` (focal_accent) | 1.4 | — | `arrow-accent` | Every edge whose endpoint is a `focal: true` component. Also every Trino → consumer edge (serve-flow rule). |
|
||||
| `secondary` | `#4f5d75` (muted) | 1.2 | — | `arrow` | Default for internal platform-component edges and source → platform edges that don't touch focal. |
|
||||
| `federated` | `#2e5aa8` (link-blue) | 1.0 | `4,3` | `arrow-link` | Federation queries (e.g., source DB → Trino). |
|
||||
| `trigger` | `#4f5d75` (muted) | 1.0 | `4,3` | `arrow` | Every edge originating from a `kind: bar` component (Airflow drops). Unlabelled. |
|
||||
| `auth` | `#eb6c36` | 1.2 | `5,4` | `arrow-accent` | Every edge from a footer node up to the zone bottom edge. **Never to a specific component.** |
|
||||
|
||||
**Defs block** (required, five markers — exactly):
|
||||
|
||||
```svg
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#2e5aa8"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0, 6 2.5, 0 5" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-dim" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="rgba(45,49,66,0.45)"/></marker>
|
||||
</defs>
|
||||
```
|
||||
|
||||
### 3.1 Exit / entry sides (non-negotiable)
|
||||
|
||||
| Edge kind | Exit side of source | Entry side of target |
|
||||
|---|---|---|
|
||||
| Source → platform component | **right** of source | **left** of target |
|
||||
| Platform → platform (same row) | **right** | **left** |
|
||||
| Bar → row node (vertical drop) | **bottom** of bar at `node_cx(target)` | **top** of target |
|
||||
| Platform → consumer | **right** of source platform component | **left** of consumer |
|
||||
| Footer → zone | **top** of footer (at `footer_auth_x(k)`) | zone bottom edge `y = zone_y + zone_h` |
|
||||
| Footer → component (any specific one) | **forbidden** |
|
||||
|
||||
### 3.2 Routing
|
||||
|
||||
- Orthogonal elbows with at most two bends; Q-bezier `r=8` at every corner.
|
||||
- **Fan-out staggering:** when one node fans out to N targets on the same side, stagger the exit y by ±4 px per index so arrows don't overlap (e.g., Trino → 4 consumers exits at y=124, 132, 140, 148). The vertical segments run in the corridor between the zone edge and the consumer column, also y-staggered.
|
||||
- **Z-order:** all connectors drawn **before** any rect (so node fills mask the line ends).
|
||||
- **Markers:** exactly one `marker-end` per `<line>` / `<path>`. Never `marker-start`.
|
||||
- **Labels:** every `primary`, `secondary`, `federated`, `auth` edge gets a protocol label (Geist Mono 8 px, paper-filled rect mask with 6–10 px clear gap above the stroke). `trigger` edges are unlabelled.
|
||||
|
||||
### 3.3 Footer → zone trunk
|
||||
|
||||
When N=1 footer: single vertical line at `x = zone_cx` from `footer_y(0)` to `zone_y + zone_h`.
|
||||
|
||||
When N≥2 footers: stagger AUTH lines so they don't overlap stacked footers. For footer index `k`:
|
||||
|
||||
```
|
||||
footer_auth_x(k) = zone_cx + (k - (N-1)/2) * 32 # 32-px stride per footer
|
||||
```
|
||||
|
||||
Examples:
|
||||
- N=1 → 560
|
||||
- N=2 → 544, 576
|
||||
- N=3 → 528, 560, 592
|
||||
|
||||
Each AUTH line goes from `(footer_auth_x(k), footer_y(k))` up to `(footer_auth_x(k), zone_y + zone_h)`. AUTH labels sit just above the arrowhead at the zone bottom edge.
|
||||
|
||||
### 3.4 Crossings
|
||||
|
||||
Avoid. Re-route via the corridor x positions before accepting a crossing. If unavoidable, the path drawn second carries a 6-px arc hop over the first.
|
||||
|
||||
---
|
||||
|
||||
## 4. Component color override (mirrors `type-high-level.md` §3.4)
|
||||
|
||||
Any source, consumer, platform component (node or bar), or footer node accepts an optional `color: "#hex"`. Mirrors high-level so the rule reads identically across types.
|
||||
|
||||
**Where the color is applied** (`C = color`):
|
||||
|
||||
| Element | Light | Dark |
|
||||
|---|---|---|
|
||||
| Container fill (`rect` body) | `rgba(C, 0.06)` | `rgba(C_light, 0.10)` |
|
||||
| Container stroke | `rgba(C, 0.35)` (`stroke-width=1` for nodes, `0.8` for bars) | `rgba(C_light, 0.45)` |
|
||||
| Role badge stroke | `rgba(C, 0.40)` | `rgba(C_light, 0.55)` |
|
||||
| Role badge text | `rgba(C, 0.85)` | `rgba(C_light, 1.0)` |
|
||||
| Icon stroke / fill | `C` | `C_light` |
|
||||
| Name text | `C` | `C_light` |
|
||||
| Subtitle text | **unchanged** (muted) | **unchanged** (muted) |
|
||||
| Connectors touching this component | **unchanged** — topology-driven | **unchanged** |
|
||||
|
||||
`C_light` = the same hex lightened ~15% for dark-mode contrast (e.g., `#b85450` → `#d97a78`).
|
||||
|
||||
**Rules:**
|
||||
|
||||
- **Never on focal components.** `focal_accent` always wins — a `color` on a focal component is ignored.
|
||||
- **Never on connectors.** If you want a colored edge, pick a different `style` from §3, not a color override.
|
||||
- **Cap at 2 custom-colored components** per diagram (in addition to the focal pair).
|
||||
|
||||
**Semantic palette** (use these unless brand demands otherwise):
|
||||
- `#b85450` rust-red — Security / Identity (AD, Keycloak, Vault)
|
||||
- `#5a7d9a` slate-blue — Observability (Prometheus, Datadog, OpenTelemetry)
|
||||
- `#7a8c47` olive-green — Governance / Lineage (OpenMetadata, DataHub)
|
||||
- `#8c6d3f` warm-brown — Backup / DR (Velero, Restic)
|
||||
|
||||
---
|
||||
|
||||
## 5. Focal rule
|
||||
|
||||
**Exactly two focal components.** Default: the storage hub (MinIO / S3 / similar) and the federation engine (Trino / Dremio / similar). These two surfaces distinguish a "platform" from a pile of tools. Everything else (NiFi, Jupyter, Airflow, AD, all sources, all consumers) stays ink / muted.
|
||||
|
||||
- Mark with `focal: true` on the component entry.
|
||||
- A focal `kind: bar` uses `bar_h_focal=56` (taller) and accent styling.
|
||||
- A focal `kind: row` node keeps `row_h=72` but uses accent styling.
|
||||
- The **Trino → all consumers** edges are always `primary` (accent), regardless of focal flag on each consumer — this is the serve-flow rule.
|
||||
- If fewer than 2 or more than 2 components are marked `focal: true`, halt and ask the user.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dark mode
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| Page paper | `#f5f5f5` | `#2d3142` |
|
||||
| Ink | `#2d3142` | `#f5f5f5` |
|
||||
| Muted | `#4f5d75` | `#bfc0c0` |
|
||||
| Accent | `#eb6c36` | `#f08a59` |
|
||||
| Link (federated) | `#2e5aa8` | `#6a95d8` |
|
||||
| Side-column fill | `rgba(79,93,117,0.06)` | `rgba(245,245,245,0.06)` |
|
||||
| Side-column stroke | `#7a8399` | `rgba(245,245,245,0.30)` |
|
||||
| Zone fill | `rgba(45,49,66,0.025)` | `rgba(245,245,245,0.04)` |
|
||||
| Zone stroke | `rgba(45,49,66,0.32)` | `rgba(245,245,245,0.30)` |
|
||||
| Non-focal bar fill | `rgba(45,49,66,0.05)` | `rgba(245,245,245,0.06)` |
|
||||
| Focal fill | `rgba(235,108,54,0.08)` | `rgba(240,138,89,0.12)` |
|
||||
| Focal stroke | `#eb6c36` | `#f08a59` |
|
||||
| Custom component colors | `C` | `C_light` (lighten ~15%) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Reproducibility checklist (taste gate)
|
||||
|
||||
Before emitting SVG, verify **every** item:
|
||||
|
||||
1. `viewBox = "0 0 1200 {viewBox_h}"` where `viewBox_h = max(600, footer_bottom + 84)`.
|
||||
2. Platform zone at `x=260 y=72 w=696 h=col_h`. Zone label paper-masked across top border at `y=zone_y+3`.
|
||||
3. Left column at `x=40..200`, right column at `x=1000..1160` — both 160 wide.
|
||||
4. Source / consumer rows top at `y=92`, stride 88 px.
|
||||
5. `platform.rows` entries stack inside zone via the §2.1 cursor algorithm; total y-span ≤ `zone_h`.
|
||||
6. Inside each `kind: row`, node x-centers are evenly spaced across zone width (§2.2).
|
||||
7. **Exactly 2** focal components (`focal: true`).
|
||||
8. Every edge originating from a `kind: bar` component uses `style: trigger` (dashed, unlabelled).
|
||||
9. Every Trino → consumer edge uses `style: primary` (the serve-flow rule).
|
||||
10. Footer nodes connect only to the zone bottom edge via `auth` style. **No** edge from a footer to a specific component.
|
||||
11. Custom component colors ≤ 2 (in addition to the focal pair). Connectors never recolored by component `color`.
|
||||
12. All connectors emitted before any node rect (z-order rule).
|
||||
|
||||
---
|
||||
|
||||
## 8. Sources and consumers — icon library
|
||||
|
||||
Define each icon as `<g id="ico-…">` in `<defs>`, drawn at translate(cx, cy) with `stroke="currentColor"` so it inherits the surrounding text color. Common icons:
|
||||
|
||||
- `ico-db` (cylinder) — relational sources
|
||||
- `ico-sftp` (folder with down arrow) — file drops
|
||||
- `ico-mail` (envelope) — email pulls
|
||||
- `ico-mainframe` (server with vents) — legacy systems
|
||||
- `ico-monitor` — desktop analytics tools
|
||||
- `ico-chart` (bars) — BI / report tools
|
||||
- `ico-globe` — public websites
|
||||
- `ico-api` (brackets `{}`) — gateways and 3rd-party clients
|
||||
- `ico-key` — identity / IDP
|
||||
- `ico-monitoring` (chart-line) — observability stack
|
||||
|
||||
If you need more icons, browse `assets/icons.html` and define matching `<symbol>` blocks.
|
||||
|
||||
---
|
||||
|
||||
## 9. Identity, common services → connect to the layer, not to components
|
||||
|
||||
**Active Directory** (or Keycloak, IAM, OPA, any cross-cutting identity / policy / secrets store) authenticates *every* component in the platform. Wiring it to one specific tool would understate the trust scope. Connect it instead with a single arrow to the bottom edge of the platform zone, labeled `AUTH` (§3.3).
|
||||
|
||||
The same rule applies to any other layer-wide service: centralized logging, secrets vault, observability stack, audit sink, mTLS root. Each goes in the `footer` list, each gets its own row, each gets its own AUTH line up to the zone bottom edge (staggered by index per §3.3). The visual reading is "the platform layer delegates to all of these," which is the architectural truth.
|
||||
|
||||
---
|
||||
|
||||
## 10. Budget — this type exceeds the default
|
||||
|
||||
This is the one type where the default 9-node / 12-arrow budget is intentionally exceeded. A realistic platform integration shows:
|
||||
|
||||
- 4–6 source nodes
|
||||
- 5 platform components
|
||||
- 4–6 consumer nodes
|
||||
- 1–3 footer nodes (identity, observability, backup, …)
|
||||
|
||||
That's **14–20 nodes**. The complexity is the point — the diagram is making a claim about the *number of distinct integration surfaces*. Compressing them collapses the claim.
|
||||
|
||||
When this gets unwieldy:
|
||||
- Combine clearly-identical source rows (e.g., four MariaDB databases → one `Databases` node with sublabel `4 × MariaDB`)
|
||||
- Split into two diagrams (one per integration plane: data vs. identity vs. observability)
|
||||
|
||||
---
|
||||
|
||||
## 11. Anti-patterns
|
||||
|
||||
- **Sources or consumers as a single collapsed node** when ≥3 distinct items exist — defeats the whole point of this type. Use Architecture or High-level if you want collapsing.
|
||||
- **One bus arrow from "sources" to "the platform"** — every wire is labeled with its protocol; this is how integration teams read the diagram.
|
||||
- **Per-tool color coding** (teal-NiFi, magenta-MinIO, yellow-Jupyter) inside the zone — collapses hierarchy; only the two focal accents earn coral, plus up to 2 custom colors on cross-cutting components (§4 cap).
|
||||
- **More than 2 focal components** — focal exists to distinguish "platform" from "pile of tools"; >2 erases the signal (same rule as SKILL.md §1).
|
||||
- **`color` override on a focal component** — ignored. Focal_accent always wins.
|
||||
- **Footer wired to one specific tool** (e.g., AD → Airflow only) — wrong unless that service truly only protects one tool. The default is the layer-wide connection.
|
||||
- **Footer or identity inside the zone** — identity gates the layer from outside. Drawing it inside misrepresents the trust model.
|
||||
- **Phase chevrons across the top** — those belong on `high-level`.
|
||||
- **Custom-colored connectors** — connectors are topology-driven. Style picks the color; `color` on a component never spreads to its edges.
|
||||
|
||||
---
|
||||
|
||||
## 12. Examples
|
||||
|
||||
- `assets/example-dp-integration.html` — minimal light (1 footer = AD). Gallery default.
|
||||
- `assets/example-dp-integration-dark.html` — same, dark skin.
|
||||
- `assets/example-dp-integration-full.html` — same, editorial-card frame.
|
||||
- `assets/example-dp-integration-extended.html` — exercises §4 color override + multi-footer: AD in rust-red, Observability (Prometheus/Grafana/Loki) in slate-blue. Canvas height grown to fit 2 footer rows.
|
||||
- `assets/example-dp-integration-extended-dark.html` — extended pattern, dark skin.
|
||||
- `assets/example-dp-integration-extended-full.html` — extended pattern, editorial-card frame.
|
||||
@@ -0,0 +1,379 @@
|
||||
# DP security matrix
|
||||
|
||||
**Best for:** documenting per-role / per-component access permissions for a data platform — a grid where each row is a platform component (Keycloak, MinIO bucket, Trino catalog, JupyterHub, NiFi, …) and each column is a role / AD group (Data Administrators, Data Engineers, Data Scientists, Data Consumers, …). Each intersection cell holds a permission value (Admin / Full / R/W / Read / SELECT / Login / No access) with a visual category that matches the permission level. One cell may be marked focal to flag a critical access rule (e.g., "Data Consumers can ONLY `SELECT` from the aggregated catalog — sole consumer access").
|
||||
|
||||
Use when stakeholders need to audit *who can do what* across the platform. Prefer **DP integration** when the question is *who can talk to what* (topology/protocol) rather than *who can write/read what* (permissions).
|
||||
|
||||
This type is **parametric** — the inputs schema in §1 drives every coordinate via the formulas in §2. The rule shape mirrors `type-medallion.md` / `type-process.md` / `type-data-flow.md` so the focal rule, color override, dark mode, and reproducibility checklist read identically across types.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inputs — the parameter contract
|
||||
|
||||
```yaml
|
||||
title: "Platform Access Matrix"
|
||||
subtitle: "Four canonical groups × platform components"
|
||||
|
||||
roles: # 2..6 columns, ordered left → right
|
||||
- { name: "Data Administrators", code: "DL-DataAdmins" }
|
||||
- { name: "Data Engineers", code: "DL-DataEngineers" }
|
||||
- { name: "Data Scientists", code: "DL-DataScientists" }
|
||||
- { name: "Data Consumers", code: "DL-DataConsumers" }
|
||||
|
||||
components: # 2..14 rows, ordered top → bottom
|
||||
- { name: "Keycloak", hint: "SSO" } # `hint` = right-aligned aside in label cell
|
||||
- { name: "MinIO · raw bucket" }
|
||||
- { name: "MinIO · anon · staging · agg" }
|
||||
- { name: "Trino · raw catalog" }
|
||||
- { name: "Trino · anon-staging" }
|
||||
- { name: "Trino · aggregated" }
|
||||
- { name: "JupyterHub" }
|
||||
- { name: "NiFi" }
|
||||
|
||||
cells: # explicit (row, col) entries; omitted → defaults to "none"
|
||||
# value = displayed text (free-form)
|
||||
# level = visual category: full | rw | read | none (closed vocabulary, drives styling)
|
||||
# focal: true (max 1) — overrides level to focal styling
|
||||
# sub: "second-line text" — used inside focal cell
|
||||
# color: "#hex" — optional per-cell color override (§4)
|
||||
- { row: 0, col: 0, value: "Admin", level: "full" }
|
||||
- { row: 0, col: 1, value: "Login", level: "read" }
|
||||
- { row: 0, col: 2, value: "Login", level: "read" }
|
||||
- { row: 0, col: 3, value: "Login", level: "read" }
|
||||
|
||||
- { row: 1, col: 0, value: "Full", level: "full" }
|
||||
- { row: 1, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 1, col: 2, value: "No access", level: "none" }
|
||||
- { row: 1, col: 3, value: "No access", level: "none" }
|
||||
|
||||
# ... rows 2..4 follow the same pattern ...
|
||||
|
||||
- { row: 5, col: 0, value: "Full", level: "full" }
|
||||
- { row: 5, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 5, col: 2, value: "SELECT", level: "read" }
|
||||
- { row: 5, col: 3, value: "SELECT only", sub: "sole consumer access", focal: true }
|
||||
|
||||
# ... rows 6..7 ...
|
||||
|
||||
none_label: "No access" # default text rendered when a cell is omitted
|
||||
dark: false
|
||||
```
|
||||
|
||||
**Reserved field semantics:**
|
||||
- `roles[j].name` — primary role label (`node-name` role at 11px, white text on the ink banner)
|
||||
- `roles[j].code` — secondary AD-group identifier (`sublabel` role, white text at 0.85 opacity)
|
||||
- `components[i].hint` — optional right-aligned `sublabel` text in the label cell (e.g., `"SSO"`, `"S3 API"`)
|
||||
- `cells[k].level` — closed vocabulary `full | rw | read | none`. Drives fill/stroke/text-color per §2.4.
|
||||
- `cells[k].value` — free-form display text. Domain-specific labels (`"R/W"`, `"SELECT"`, `"Login"`) work without inventing new levels.
|
||||
- `cells[k].focal: true` — exactly **one** cell may declare this. Overrides `level` to focal styling.
|
||||
- `cells[k].sub` — optional 2nd-line text (used with focal). Renders in the `sublabel` role at 8px below the primary value.
|
||||
- `cells[k].color: "#hex"` — optional per-cell color override (§4).
|
||||
|
||||
---
|
||||
|
||||
## 2. Layout formulas — deterministic geometry
|
||||
|
||||
```
|
||||
# Constants
|
||||
left_pad = 12
|
||||
right_pad = 48
|
||||
comp_col_w = 208
|
||||
comp_role_gap = 12
|
||||
role_col_w = 148
|
||||
role_col_gap = 16
|
||||
header_h = 52
|
||||
row_h = 36
|
||||
row_stride = 40
|
||||
|
||||
# Counts
|
||||
n_roles = len(roles) # 2..6
|
||||
n_components = len(components) # 2..14
|
||||
|
||||
# Canvas
|
||||
viewBox_w = left_pad + comp_col_w + comp_role_gap
|
||||
+ n_roles * role_col_w + (n_roles - 1) * role_col_gap
|
||||
+ right_pad
|
||||
# 4 roles → 12 + 208 + 12 + 592 + 48 + 48 = 920
|
||||
|
||||
header_y = 72
|
||||
row_y(k) = 140 + k * row_stride # 140, 180, 220, ...
|
||||
rows_bottom = row_y(n_components - 1) + row_h # 8 rows → 456
|
||||
legend_y_top = rows_bottom + 20 # 476 for 8-row canonical
|
||||
viewBox_h = legend_y_top + 44 # 520 for 8-row canonical
|
||||
|
||||
# Column positions
|
||||
comp_col_x = left_pad # 12
|
||||
role_col_x(j) = left_pad + comp_col_w + comp_role_gap
|
||||
+ j * (role_col_w + role_col_gap)
|
||||
# 232, 396, 560, 724
|
||||
role_col_cx(j) = role_col_x(j) + role_col_w / 2 # 306, 470, 634, 798
|
||||
```
|
||||
|
||||
### 2.1 Background
|
||||
|
||||
Solid paper fill across the full viewBox. No dot pattern.
|
||||
|
||||
### 2.2 Header row (`y = 72, h = 52`)
|
||||
|
||||
**Component-column header cell:**
|
||||
- Rect: `(comp_col_x, header_y, comp_col_w, header_h)`, fill white, stroke `ink @ 0.12` 0.8, `rx=6`
|
||||
- Two-line label centered at `(comp_col_x + comp_col_w/2, header_y+24)` and `(…, header_y+40)`:
|
||||
- Line 1: `"Component"` — `node-name` role at 11px, ink
|
||||
- Line 2: `"vs. AD group"` — `sublabel` role, muted
|
||||
|
||||
**Role banners (one per role):**
|
||||
- Rect: `(role_col_x(j), header_y, role_col_w, header_h)`, fill `ink`, `rx=6`
|
||||
- Two-line label centered:
|
||||
- Line 1 at `y=92`: `roles[j].name` — `node-name` role at 11px, white
|
||||
- Line 2 at `y=108`: `roles[j].code` — `sublabel` role, white at opacity 0.85
|
||||
|
||||
### 2.3 Data row (`y = row_y(k), h = 36`)
|
||||
|
||||
**Component label cell:**
|
||||
- Rect: `(comp_col_x, row_y(k), comp_col_w, row_h)`, fill white, stroke `ink @ 0.12` 0.8, `rx=4`
|
||||
- Name at `(comp_col_x + 12, row_y(k) + 22)`: `node-name` role at 11px, ink, left-aligned
|
||||
- Hint (if present) at `(comp_col_x + comp_col_w − 12, row_y(k) + 22)`: `sublabel` role, muted, right-aligned
|
||||
|
||||
**Value cells (one per role × component):**
|
||||
- Rect: `(role_col_x(j), row_y(k), role_col_w, row_h)`, `rx=4`, stroke `ink @ 0.12` 0.6
|
||||
- Fill and text-color depend on `level` (or focal flag) — see §2.4
|
||||
- Value text centered at `(role_col_cx(j), row_y(k) + 22)`: `node-name` role at 10px
|
||||
- Focal cell uses a slightly raised primary text at `y=18` and a sub-line at `y=30` (`sublabel` role at 8px)
|
||||
|
||||
### 2.4 Cell style table
|
||||
|
||||
| `level` | Fill | Stroke | Text color | Text weight |
|
||||
|---|---|---|---|---|
|
||||
| `full` | `ink @ 0.08` | `ink @ 0.12` | `ink` | 600 |
|
||||
| `rw` | `#FFFFFF` | `ink @ 0.12` | `ink` | 400 |
|
||||
| `read` | `muted @ 0.08` | `ink @ 0.12` | `muted` | 400 |
|
||||
| `none` | `paper` | `ink @ 0.12` | `soft` | 400 |
|
||||
| **focal** | `accent @ 0.07` | `accent` (1.4) | `accent` | 600 |
|
||||
|
||||
The focal cell can carry a 2nd line (`sub:`) rendered in `accent` with the `sublabel` role at 8px and 0.85 opacity.
|
||||
|
||||
### 2.5 Legend (`y_top = legend_y_top, h ≈ 30`)
|
||||
|
||||
Hairline separator at `legend_y_top`. Below the separator, one row of style swatches with their labels — only the styles actually used in the diagram appear in the legend.
|
||||
|
||||
- "LEGEND" eyebrow at `(left_pad, legend_y_top + 20)`: `eyebrow` role, muted, letter-spacing 0.14em
|
||||
- Each style: swatch rect (14×12 `rx=2`) followed by a `sublabel` role label
|
||||
- Item x-positions are tabulated left-to-right with ~120-px stride; legend wraps onto a second visual row only if `n_roles ≥ 6` (otherwise fits in one line)
|
||||
|
||||
---
|
||||
|
||||
## 3. Cells, not connectors
|
||||
|
||||
A matrix diagram has **no connectors** — there are no arrows between cells, no flow lines. The diagram's information is entirely in the cell content + cell styling. The only "connector-like" element is the focal cell's accent border, which visually "calls out" a specific intersection.
|
||||
|
||||
Cells emit **no edges**. Don't add arrows pointing into cells or between cells — they belong in a different diagram type.
|
||||
|
||||
---
|
||||
|
||||
## 4. Color overrides
|
||||
|
||||
Three independent override axes — per-cell, per-component (row), per-role (column). All optional, all use the same `color: "#hex"` field, all draw from the same recommended palette. Mirrors §3.4 of `type-high-level.md`, §4 of `type-process.md`, §4 of `type-medallion.md`.
|
||||
|
||||
### 4.1 Per-cell `color`
|
||||
|
||||
Tints a specific intersection cell. Applied to:
|
||||
|
||||
| Element | Light | Dark |
|
||||
|---|---|---|
|
||||
| Cell fill | `rgba(C, 0.08)` | `rgba(C_light, 0.12)` |
|
||||
| Cell stroke | `rgba(C, 0.45)` width 1.0 | `rgba(C_light, 0.55)` width 1.0 |
|
||||
| Value text | `C` | `C_light` |
|
||||
| Sub text (if present) | `rgba(C, 0.85)` | `rgba(C_light, 0.95)` |
|
||||
|
||||
`C_light` = the same hex lightened ~15% for dark-mode contrast.
|
||||
|
||||
### 4.2 Per-component `color` (`components[i].color`)
|
||||
|
||||
Tints the row's **label cell only** (left column). The data cells in that row keep their per-cell `level` styling — the row color flags *what* this component is, not *what permissions live in it*.
|
||||
|
||||
| Element | Light | Dark |
|
||||
|---|---|---|
|
||||
| Label cell fill | `rgba(C, 0.06)` | `rgba(C_light, 0.10)` |
|
||||
| Label cell stroke | `rgba(C, 0.45)` width 0.8 | `rgba(C_light, 0.55)` width 0.8 |
|
||||
| Component name | `C` | `C_light` |
|
||||
| Hint text | unchanged (muted) | unchanged (muted) |
|
||||
|
||||
### 4.3 Per-role `color` (`roles[j].color`)
|
||||
|
||||
Tints the **column banner only** (top row). Cells underneath keep their `level` styling. Replaces the default navy banner fill with the chosen hex.
|
||||
|
||||
| Element | Light & Dark (banner is the same in both modes) |
|
||||
|---|---|
|
||||
| Banner fill | `C` |
|
||||
| Role name + code text | `#FFFFFF` if `C` is dark (luminance ≤ 0.5), else `ink` |
|
||||
|
||||
If you pick a mid-luminance hex (e.g., yellow `#c9a23a`), the text auto-flips to ink for contrast. Pair `roles[j].text_color: "#hex"` to override this auto-pick.
|
||||
|
||||
### 4.4 Rules
|
||||
|
||||
- **Focal cell wins.** A focal cell ignores `color` overrides — accent always.
|
||||
- **Per-cell `color` overrides `level` styling** for that one cell.
|
||||
- **Per-component / per-role overrides are scoped:** component → row label only; role → banner only. They do **not** spread into the matrix body. To flag a specific intersection, use per-cell.
|
||||
- **Cap:** keep total custom-colored entities ≤ 5 per diagram (combining cells + components + roles). Above 5, the matrix reads as colored noise — split into multiple diagrams or rethink which color carries which concern.
|
||||
|
||||
### 4.5 Recommended palette (same as the other parametric types)
|
||||
|
||||
- `#b85450` rust-red — Security elevation / break-glass / SoX-flagged
|
||||
- `#5a7d9a` slate-blue — Quality / monitoring / observability gate
|
||||
- `#7a8c47` olive-green — Approved / governance-cleared / publication-ready
|
||||
- `#c9a23a` warm yellow — Working / sandbox / data-scientist zone
|
||||
- `#8c6d3f` warm-brown — Archive / cold / DR
|
||||
|
||||
---
|
||||
|
||||
## 5. Focal rule
|
||||
|
||||
Exactly **one** focal cell per diagram (or zero). The focal cell:
|
||||
- Uses focal styling (accent fill + accent stroke 1.4 + accent text bold)
|
||||
- May carry a 2-line content: primary `value` at `y = row_y(k) + 18`, `sub` at `y = row_y(k) + 30`
|
||||
- Calls out the diagram's central security claim — the *one* access rule that distinguishes this platform's posture from a generic permissions table
|
||||
|
||||
If zero or >1 `focal: true` cells are declared, halt and ask the user.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dark mode
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| Paper | `paper` | `ink` |
|
||||
| Ink | `ink` | `paper` |
|
||||
| Muted | `muted` | `soft` |
|
||||
| Soft (no-access text) | `soft` | `muted` |
|
||||
| Accent | `accent` | `accent` |
|
||||
| Role-banner fill | `ink` | `ink` |
|
||||
| Header / row stroke | `ink @ 0.12` | `paper @ 0.18` |
|
||||
| Full / Admin fill | `ink @ 0.08` | `paper @ 0.10` |
|
||||
| R/W fill | `#FFFFFF` | `paper @ 0.06` |
|
||||
| Read fill | `muted @ 0.08` | `soft @ 0.12` |
|
||||
| No-access fill | `paper` | `paper @ 0.02` |
|
||||
| Focal fill | `accent @ 0.07` | `accent @ 0.12` |
|
||||
| Focal stroke | `accent` | `accent` |
|
||||
| Custom component colors | `C` | `C_light` (lighten ~15%) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Reproducibility checklist (taste gate)
|
||||
|
||||
Before emitting SVG, verify **every** item:
|
||||
|
||||
1. `viewBox = "0 0 {viewBox_w} {viewBox_h}"` derived via §2 (4 roles × 8 components → 920 × 520).
|
||||
2. Header row at `y=72 h=52`. Component header cell white-filled with two-line `Component / vs. AD group` label. Role banners filled `ink` with name + AD-group code in white.
|
||||
3. Data rows start at `y=140`, stride 40, height 36. `rows_bottom = 140 + (n_components−1)·40 + 36`.
|
||||
4. Component label cell `rx=4`, name left-aligned at `x=24`, optional `hint` right-aligned at `x = comp_col_x + comp_col_w − 12`.
|
||||
5. Every value cell `rx=4`, stroke `ink @ 0.12` 0.6, fill + text matching §2.4 for its `level`.
|
||||
6. Exactly **one** focal cell (or zero). Focal cell stroke `accent` width 1.4. Primary value at `y = row_y(k) + 18`; `sub` (if present) at `y = row_y(k) + 30`.
|
||||
7. Cells omitted from `cells:` render as `level: "none"` with `none_label` text (default `"No access"`).
|
||||
8. Custom-colored cells ≤ 2 (in addition to the focal cell).
|
||||
9. No connector elements anywhere in the SVG.
|
||||
10. Legend strip at `legend_y_top`, one swatch per `level` actually used, hairline separator above.
|
||||
11. `viewBox_h` grows with `n_components`; `viewBox_w` grows with `n_roles`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Anti-patterns
|
||||
|
||||
- **More than one focal cell** — focal exists to mark *the* critical access rule; >1 erases the signal.
|
||||
- **Connectors anywhere** — matrix is value-driven; arrows belong in DP integration / process diagrams.
|
||||
- **Freeform `level` values** — closed vocabulary is `full | rw | read | none`. Use `value` for free-form displayed text + `color` override for arbitrary tinting.
|
||||
- **Per-row or per-column color tints** — apply `color` per cell only. Whole-row or whole-column highlighting tends to over-emphasize and collapses the matrix into a list.
|
||||
- **Using `none_label` as a placeholder for "TBD"** — `none` means *no access*. If the permission is unknown, leave the cell empty in inputs but document it elsewhere; don't render an ambiguous state.
|
||||
- **More than 6 roles** — split into two matrices (e.g., human roles vs service accounts) before exceeding 6 columns.
|
||||
- **More than 14 components** — split by domain (storage / compute / observability / governance) before exceeding 14 rows.
|
||||
- **Using the matrix to document *how* permissions are granted** — that belongs in a process or sequence diagram. The matrix shows *what* each role can do, not the grant flow.
|
||||
|
||||
---
|
||||
|
||||
## 9. Examples
|
||||
|
||||
- `assets/example-dp-security-matrix.html` — minimal light (NatStat canonical: 4 roles × 8 components, focal at Data Consumers × Trino aggregated). Gallery default.
|
||||
- `assets/example-dp-security-matrix-dark.html` — same, dark skin.
|
||||
- `assets/example-dp-security-matrix-full.html` — same, editorial-card frame with subtitle + summary cards.
|
||||
|
||||
---
|
||||
|
||||
## 10. Worked YAML — full inputs for `example-dp-security-matrix.html`
|
||||
|
||||
The complete inputs that map to the shipped canonical example. Every coordinate in that SVG is derivable from §2 applied to these inputs.
|
||||
|
||||
```yaml
|
||||
title: "Platform Access Matrix"
|
||||
subtitle: "Four canonical groups × platform components"
|
||||
|
||||
roles:
|
||||
- { name: "Data Administrators", code: "DL-DataAdmins" }
|
||||
- { name: "Data Engineers", code: "DL-DataEngineers" }
|
||||
- { name: "Data Scientists", code: "DL-DataScientists" }
|
||||
- { name: "Data Consumers", code: "DL-DataConsumers" }
|
||||
|
||||
components:
|
||||
- { name: "Keycloak", hint: "SSO" }
|
||||
- { name: "MinIO · raw bucket" }
|
||||
- { name: "MinIO · anon · staging · agg" }
|
||||
- { name: "Trino · raw catalog" }
|
||||
- { name: "Trino · anon-staging" }
|
||||
- { name: "Trino · aggregated" }
|
||||
- { name: "JupyterHub" }
|
||||
- { name: "NiFi" }
|
||||
|
||||
cells:
|
||||
# Row 0 — Keycloak
|
||||
- { row: 0, col: 0, value: "Admin", level: "full" }
|
||||
- { row: 0, col: 1, value: "Login", level: "read" }
|
||||
- { row: 0, col: 2, value: "Login", level: "read" }
|
||||
- { row: 0, col: 3, value: "Login", level: "read" }
|
||||
# Row 1 — MinIO raw
|
||||
- { row: 1, col: 0, value: "Full", level: "full" }
|
||||
- { row: 1, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 1, col: 2, value: "No access", level: "none" }
|
||||
- { row: 1, col: 3, value: "No access", level: "none" }
|
||||
# Row 2 — MinIO anon/staging/agg
|
||||
- { row: 2, col: 0, value: "Full", level: "full" }
|
||||
- { row: 2, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 2, col: 2, value: "Read", level: "read" }
|
||||
- { row: 2, col: 3, value: "No access", level: "none" }
|
||||
# Row 3 — Trino raw catalog
|
||||
- { row: 3, col: 0, value: "Full", level: "full" }
|
||||
- { row: 3, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 3, col: 2, value: "No access", level: "none" }
|
||||
- { row: 3, col: 3, value: "No access", level: "none" }
|
||||
# Row 4 — Trino anon-staging
|
||||
- { row: 4, col: 0, value: "Full", level: "full" }
|
||||
- { row: 4, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 4, col: 2, value: "SELECT", level: "read" }
|
||||
- { row: 4, col: 3, value: "No access", level: "none" }
|
||||
# Row 5 — Trino aggregated (focal cell at col 3)
|
||||
- { row: 5, col: 0, value: "Full", level: "full" }
|
||||
- { row: 5, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 5, col: 2, value: "SELECT", level: "read" }
|
||||
- { row: 5, col: 3, value: "SELECT only", sub: "sole consumer access", focal: true }
|
||||
# Row 6 — JupyterHub
|
||||
- { row: 6, col: 0, value: "Admin", level: "full" }
|
||||
- { row: 6, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 6, col: 2, value: "R/W", level: "rw" }
|
||||
- { row: 6, col: 3, value: "No access", level: "none" }
|
||||
# Row 7 — NiFi
|
||||
- { row: 7, col: 0, value: "Admin", level: "full" }
|
||||
- { row: 7, col: 1, value: "R/W", level: "rw" }
|
||||
- { row: 7, col: 2, value: "Read", level: "read" }
|
||||
- { row: 7, col: 3, value: "No access", level: "none" }
|
||||
|
||||
dark: false
|
||||
```
|
||||
|
||||
### 10.1 What this YAML proves
|
||||
|
||||
Run §2 with these inputs:
|
||||
- `n_roles = 4`, `n_components = 8`, no color overrides, one focal cell.
|
||||
- `viewBox_w = 12 + 208 + 12 + 4·148 + 3·16 + 48 = 920` ✓
|
||||
- `row_y(k)` produces `140, 180, 220, 260, 300, 340, 380, 420` ✓
|
||||
- `rows_bottom = 420 + 36 = 456`; `legend_y_top = 476`; `viewBox_h = 520` ✓
|
||||
- `role_col_x(j) = [232, 396, 560, 724]` ✓
|
||||
- Focal cell at `(row=5, col=3)` → rect `(724, 340, 148, 36)` with accent stroke 1.4 ✓
|
||||
|
||||
A fresh generation from this YAML produces a diagram visually indistinguishable from the shipped `example-dp-security-matrix.html`.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Gantt Chart
|
||||
|
||||
**Best for:** project plans and roadmaps — tasks with explicit start and end dates, grouped into phases. Use when the reader needs to see temporal overlap, parallel tracks, and milestone sequencing at a glance.
|
||||
|
||||
## Layout conventions
|
||||
|
||||
- **Left label column:** x=20–200 (180px). Task names in Geist sans 11px 600. Phase labels as Geist Mono 7px eyebrows above each group.
|
||||
- **Timeline area:** x=200–960 (760px). Time axis runs left→right.
|
||||
- **Row height:** 40px per task. Each bar occupies h=24px centered in the row (8px top padding).
|
||||
- **Time axis:** Geist Mono 8px week/month labels at x=200+i×pitch, y=56 (just above first task row). A hairline separator at y=64.
|
||||
- **Phase grouping:** a subtle zone rect (same pattern as architecture zone) behind each phase's rows, with an eyebrow label in the top-left margin. Use `rgba(45,49,66,0.02)` fill, `rgba(45,49,66,0.10)` stroke.
|
||||
- **Focal task bar:** 1 bar in accent fill/stroke (the key deliverable or critical path task). All others: muted fill @ 0.15, muted stroke.
|
||||
- **Today / milestone marker:** optional vertical dashed line in `muted` at the current week x-position.
|
||||
|
||||
### Task bar pattern
|
||||
|
||||
```svg
|
||||
<!-- Non-focal task -->
|
||||
<rect x="X_start" y="ROW_Y+8" width="DURATION_PX" height="24" rx="4"
|
||||
fill="rgba(79,93,117,0.15)" stroke="#4f5d75" stroke-width="1"/>
|
||||
<text x="X_start+8" y="ROW_Y+25" fill="#2d3142" font-size="10" font-weight="600"
|
||||
font-family="'Geist', sans-serif">Task name</text>
|
||||
|
||||
<!-- Focal task -->
|
||||
<rect x="X_start" y="ROW_Y+8" width="DURATION_PX" height="24" rx="4"
|
||||
fill="rgba(235,108,54,0.12)" stroke="#eb6c36" stroke-width="1"/>
|
||||
<text x="X_start+8" y="ROW_Y+25" fill="#eb6c36" font-size="10" font-weight="600"
|
||||
font-family="'Geist', sans-serif">Key task</text>
|
||||
```
|
||||
|
||||
Duration in pixels: `(end_week - start_week) × pitch`. Pitch = timeline_width / total_weeks.
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- More than 12 tasks (splits into sub-plans or collapses into phase-level view).
|
||||
- More than 5 parallel tracks per phase (illegible overlap).
|
||||
- Dependency arrows between tasks in v1 (add only when essential; use the annotation primitive for labels).
|
||||
- Start/end dates in the bar label (put them in the x-axis or tooltip comment instead).
|
||||
- Equal visual weight for all bars (the focal task must stand out).
|
||||
|
||||
## Examples
|
||||
|
||||
- `assets/example-gantt.html` — minimal light
|
||||
- `assets/example-gantt-dark.html` — minimal dark
|
||||
- `assets/example-gantt-full.html` — full editorial
|
||||
@@ -0,0 +1,458 @@
|
||||
# High-Level
|
||||
|
||||
**Best for:** end-to-end data stack overviews — ingestion → storage → query → analytics → visualization — deployed on a container orchestrator (Kubernetes, ECS, Nomad). Combines a phase chevron banner, deployment boundary, orchestration bar, identity footer, and (optionally) a right-side vertical chevron strip for cross-cutting concerns (Orchestration, Security, Observability).
|
||||
|
||||
This type is **parametric**. The diagram is fully determined by a small list of inputs (chevrons, sources, components, connections). The formulas below tell you exactly where every shape lands given those inputs — two generations from the same inputs must produce visually identical SVG.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inputs — the parameter contract
|
||||
|
||||
Before drawing, collect these from the user (or accept them as a YAML/JSON block). Everything in this reference is derived from these inputs. Don't invent geometry on the fly.
|
||||
|
||||
```yaml
|
||||
chevrons: # ordered left → right; reserved names auto-promote to vertical
|
||||
- { name: "Data sources", columns: 1 }
|
||||
- { name: "Ingestion", columns: 1 }
|
||||
- { name: "Storage", columns: 1 }
|
||||
- { name: "Transformation", columns: 1 }
|
||||
- { name: "Visualization", columns: 1 }
|
||||
- { name: "Orchestration", vertical: true } # reserved → pairs with the bar
|
||||
- { name: "Security", vertical: true, color: "#b85450" } # tinted to match the Identity bar below
|
||||
- { name: "Observability", vertical: true } # reserved → pairs with crosscut #2
|
||||
|
||||
sources: # external; rendered in the dashed zone on the left
|
||||
- { name: "PostgreSQL", type: "db", connects_to: ["NiFi"] }
|
||||
- { name: "SFTP drop", type: "ftp", connects_to: ["NiFi"] }
|
||||
- { name: "Web forms", type: "web", connects_to: ["NiFi"] }
|
||||
- { name: "Legacy", type: "legacy", connects_to: ["NiFi"] }
|
||||
|
||||
components: # inside the cluster, plus bars and cross-cutting rows
|
||||
- { name: "NiFi", chevron: "Ingestion", kind: node, icon: nifi, role: "COLL" }
|
||||
- { name: "MinIO", chevron: "Storage", kind: node, icon: minio, role: "STORE", focal: true }
|
||||
- { name: "Trino", chevron: "Storage", kind: node, icon: trino, role: "VIRT" }
|
||||
- { name: "Notebooks", chevron: "Transformation", kind: node, icon: jupyter, role: "ANLZ" }
|
||||
- { name: "Superset", chevron: "Visualization", kind: node, icon: superset, role: "DASH" }
|
||||
- { name: "Airflow", chevron: "Orchestration", kind: bar, icon: airflow, subtitle: "Apache Airflow" }
|
||||
- { name: "Identity", chevron: "Security", kind: cross-cutting, icon: keycloak, subtitle: "Keycloak · LDAP · OIDC", color: "#b85450" }
|
||||
- { name: "Monitoring", chevron: "Observability", kind: cross-cutting, icon: prometheus, subtitle: "Prometheus · Grafana · Loki" }
|
||||
|
||||
connections: # explicit edges; focal-touching ones become accent automatically
|
||||
- { from: "NiFi", to: "MinIO", style: "primary" }
|
||||
- { from: "NiFi", to: "Trino", style: "secondary" }
|
||||
- { from: "MinIO", to: "Notebooks", style: "primary" }
|
||||
- { from: "Trino", to: "MinIO", style: "query" } # read-back (dashed)
|
||||
- { from: "Notebooks", to: "Superset", style: "secondary" }
|
||||
- { from: "Airflow", to: ["NiFi", "Trino", "Notebooks"], style: "trigger" }
|
||||
|
||||
focal: "MinIO" # exactly one; defaults to first kind=node under "Storage"
|
||||
dark: false
|
||||
```
|
||||
|
||||
**Reserved chevron names** (always vertical, even if `vertical: true` is omitted): `Orchestration`, `Security`, `Observability`, `Governance`, `Backup`.
|
||||
|
||||
**Reserved `kind` values:**
|
||||
- `node` — a standard box inside the cluster (default).
|
||||
- `bar` — a horizontal strip spanning the cluster top. Typically one (Orchestration); see §5 for the pairing rule.
|
||||
- `cross-cutting` — a horizontal strip spanning the body width (stops at the strip margin), stacked below the cluster. **Zero or more allowed**; each stacks 44 px below the previous (§2.5) and pairs 1:1 with a vertical chevron (§5).
|
||||
|
||||
**Optional `color`** (per component, hex string): tints the component's container and content while leaving connectors untouched. See §3.4. Use sparingly — a custom color is a semantic flag (e.g., red = security concern), not decoration.
|
||||
|
||||
**Source `type` values** → icon mapping (use `references/primitive-icons.md`):
|
||||
- `db` → `database`
|
||||
- `ftp` → `bucket` or upload arrow
|
||||
- `web` → `internet`
|
||||
- `legacy` → `server`
|
||||
- `api` → `api`
|
||||
- Any explicit icon name in `primitive-icons.md` is also accepted.
|
||||
|
||||
---
|
||||
|
||||
## 2. Layout formulas — deterministic geometry
|
||||
|
||||
Every coordinate below is derived from the inputs. **No hardcoded numbers in examples that aren't justified here.**
|
||||
|
||||
### 2.1 Canvas
|
||||
|
||||
```
|
||||
has_vertical = any(c.vertical or c.name in reserved_names for c in chevrons)
|
||||
right_strip_w = 28 if has_vertical else 0
|
||||
strip_margin = 8 if has_vertical else 0 # gap between body and right strip
|
||||
effective_w = 1000 - right_strip_w - strip_margin # 1000 or 964
|
||||
|
||||
n_cross = count of components with kind == "cross-cutting"
|
||||
strip_y_bot = max(428, 388 + n_cross * 44 - 4) # extends to last crosscut row
|
||||
viewBox_h = max(540, strip_y_bot + 112) # 112 reserved for legend
|
||||
viewBox = f"0 0 1000 {viewBox_h}"
|
||||
```
|
||||
|
||||
Every horizontal element (chevron banner, cluster, orchestration bar, identity / cross-cutting bars) ends at `effective_w`. The right strip sits at `x = 1000 - right_strip_w` (= 972). The 8-px band between them is visual breathing room — never put content there.
|
||||
|
||||
`viewBox_h` grows when more than one cross-cutting bar is declared: 1 crosscut → 540, 2 → 600 (or 584 if you want it tight; the rule rounds to the next multiple of 20 for clean grids).
|
||||
|
||||
### 2.2 Horizontal chevron banner
|
||||
|
||||
```
|
||||
y_banner = 4
|
||||
h_banner = 28
|
||||
horizontals = [c for c in chevrons if not c.vertical and c.name not in reserved_names]
|
||||
sum_columns = sum(c.columns for c in horizontals)
|
||||
base_unit = floor_to_4(effective_w / sum_columns) # multiple of 4
|
||||
widths = [max(120, base_unit * c.columns) for c in horizontals]
|
||||
widths[-1] += effective_w - sum(widths) # trailing absorbs remainder
|
||||
x_boundaries = cumulative_sum([0] + widths) # length sum_columns+1
|
||||
chevron_cx(C) = (x_boundaries[index(C)] + x_boundaries[index(C)+1]) / 2
|
||||
```
|
||||
|
||||
**Polygon shapes:**
|
||||
- First (leftmost): `(x0,4) (x1-12,4) (x1,18) (x1-12,32) (x0,32)`
|
||||
- Middle: `(x0,4) (x1-12,4) (x1,18) (x1-12,32) (x0,32) (x0+12,18)`
|
||||
- Last (rightmost): `(x0,4) (effective_w,4) (effective_w,32) (x0,32) (x0+12,18)`
|
||||
|
||||
Fills alternate `#2d3142` / `#3d4460` (light mode) or `#3d4460` / `#4a5270` (dark mode). Labels: paper-colored mono `font-size=7`, `letter-spacing=0.14em`, `text-anchor=middle`, centered at `chevron_cx, 21`.
|
||||
|
||||
**Color override** (per chevron, both horizontal and vertical): a chevron may declare an optional `color: "#hex"` that replaces the alternation fill for that one chevron. Use it to flag a phase that pairs with a custom-colored component (e.g., `Security` chevron in red when the Identity bar uses `color: "#b85450"`). Rules:
|
||||
|
||||
- Override applies to the polygon fill only. The label stays paper-colored — never recolor chevron labels.
|
||||
- The alternation index doesn't shift; neighboring chevrons keep their natural fill, even if it produces two adjacent same-fill chevrons. Don't try to "fix" this — overrides should be rare (≤ 2 per diagram).
|
||||
- In dark mode, use the same hex unless contrast against paper labels suffers; if it does, pick a darker shade for dark mode and document it as a `color_dark` field on that chevron.
|
||||
- A chevron color override is independent of any paired component's color, but pairing them (same hex on chevron + bar) is the conventional way to make the column "read" as one concern.
|
||||
|
||||
### 2.3 Source zone (dashed, external)
|
||||
|
||||
```
|
||||
sources_x = 4
|
||||
sources_y = 40
|
||||
sources_w = x_boundaries[1] - 8 # width of the first chevron, minus 4px gutter each side
|
||||
sources_h = 336
|
||||
```
|
||||
|
||||
Stroke: `rgba(45,49,66,0.20)`, `stroke-width=0.8`, `stroke-dasharray=6,3`, `rx=6`. Zone fill: `rgba(45,49,66,0.02)`.
|
||||
|
||||
### 2.4 Cluster boundary (solid)
|
||||
|
||||
```
|
||||
cluster_x = x_boundaries[1] + 4 # starts at end of source zone + 4px gutter
|
||||
cluster_y = 40
|
||||
cluster_w = effective_w - cluster_x # extends to right strip / canvas edge
|
||||
cluster_h = 336
|
||||
```
|
||||
|
||||
Stroke: `rgba(45,49,66,0.18)`, `stroke-width=1.2`, `rx=8`. Fill: `rgba(45,49,66,0.02)`. K8s icon + label at `(cluster_x + 16, 352)` (icon) and `(cluster_x + 40, 362)` (text).
|
||||
|
||||
### 2.5 Cross-cutting bars (identity, observability, …)
|
||||
|
||||
Zero or more `kind: cross-cutting` components stack below the cluster. Each gets its own 40-px row with a 4-px gap.
|
||||
|
||||
```
|
||||
crosscuts = [c for c in components if c.kind == "cross-cutting"] # ordered as declared
|
||||
cross_x = 4
|
||||
cross_y(k) = 388 + k * 44 # 388, 432, 476, …
|
||||
cross_w = effective_w - 4 # spans body width, stops at the strip margin
|
||||
cross_h = 40
|
||||
```
|
||||
|
||||
Stroke: `rgba(45,49,66,0.20)`, `stroke-width=0.8`, `rx=6`. Fill: `rgba(45,49,66,0.05)`. Icon at `(16, cross_y(k) + 10)`, name centered at `(effective_w / 2, cross_y(k) + 22)`, subtitle at `(effective_w / 2, cross_y(k) + 34)`.
|
||||
|
||||
Reserved cross-cutting *concerns* (informational; user can name the actual bar whatever they want):
|
||||
- **Identity / Security** — Keycloak, LDAP/AD, Okta, Auth0, OIDC providers
|
||||
- **Observability** — Prometheus + Grafana, Datadog, OpenTelemetry, Loki
|
||||
- **Backup / DR** — Velero, Restic, snapshot orchestrators
|
||||
- **Governance / Lineage** — OpenMetadata, DataHub, Apache Atlas
|
||||
- **Secrets / config** — Vault, Sealed Secrets, External Secrets
|
||||
|
||||
Each cross-cutting bar pairs 1:1 with a vertical chevron in the right strip (§5).
|
||||
|
||||
### 2.6 Orchestration bar component (inside cluster)
|
||||
|
||||
```
|
||||
bar_x = cluster_x + 12
|
||||
bar_y = 52
|
||||
bar_w = cluster_w - 24
|
||||
bar_h = 44
|
||||
```
|
||||
|
||||
Stroke: `rgba(45,49,66,0.18)`, `stroke-width=0.8`, `rx=4`. Fill: `rgba(45,49,66,0.05)`. Tool icon at the far right (`bar_x + bar_w - 50, 58`); name centered at `(bar_x + bar_w/2, 71)`; subtitle at `(bar_x + bar_w/2, 84)`.
|
||||
|
||||
### 2.7 Component nodes (inside cluster)
|
||||
|
||||
```
|
||||
node_w = 152
|
||||
node_h = 80 # focal same height, accent border
|
||||
node_cx(N) = chevron_cx(N.chevron) # ← non-negotiable
|
||||
node_x(N) = node_cx(N) - node_w/2
|
||||
```
|
||||
|
||||
If a chevron has K nodes assigned, stack them vertically:
|
||||
|
||||
```
|
||||
first_top_y = 120 if any bar in this column else 64
|
||||
gap = 16
|
||||
row_top(k) = first_top_y + k * (node_h + gap) # k = 0..K-1
|
||||
```
|
||||
|
||||
**Focal node:** `fill="rgba(235,108,54,0.08)"`, `stroke="#eb6c36"`, `stroke-width=1.2`. Title text in accent color. All other nodes: white fill, `stroke=rgba(45,49,66,0.25)`, `stroke-width=1`.
|
||||
|
||||
Role badge top-left at `(node_x+8, node_y+6)`, size 12 high. Icon top-right at `(node_x+node_w-32, node_y+6)`, 24×24, monochrome via `currentColor`. Name centered at `(node_cx, node_y+44)` size 11 sans semibold. Subtitle at `(node_cx, node_y+56)` size 8 mono muted.
|
||||
|
||||
### 2.8 Source nodes (inside dashed zone)
|
||||
|
||||
```
|
||||
src_node_w = sources_w - 8
|
||||
src_node_h = 64 # uniform; chosen to fit ≤ 4 sources
|
||||
src_node_x = sources_x + 4
|
||||
src_first_top_y = 60
|
||||
src_gap = 16
|
||||
src_row_top(k) = src_first_top_y + k * (src_node_h + src_gap)
|
||||
```
|
||||
|
||||
Same role-badge / icon / name / subtitle pattern as cluster nodes (icon at `src_node_x+54, row_top(k)+6`; name centered at `src_node_x+src_node_w/2, row_top(k)+42`; subtitle one line below). Role badge text: `EXT`. Caps at 4 sources; for more, split into a separate diagram.
|
||||
|
||||
### 2.9 Right strip — vertical chevrons
|
||||
|
||||
```
|
||||
strip_x = 1000 - right_strip_w # 972 when present
|
||||
strip_w = 28
|
||||
verticals = [c for c in chevrons if c.vertical or c.name in reserved_names]
|
||||
strip_y_top = 40
|
||||
strip_y_bot = max(428, 388 + n_cross * 44 - 4) # extends to last crosscut row (see §2.1)
|
||||
strip_h_total = strip_y_bot - strip_y_top
|
||||
heights = [floor_to_4(strip_h_total / len(verticals))] * len(verticals)
|
||||
heights[-1] += strip_h_total - sum(heights) # last absorbs remainder
|
||||
```
|
||||
|
||||
Examples:
|
||||
- 2 verticals (Orchestration + Security), 1 crosscut → `heights = [192, 196]`, layout `[40..232, 232..428]`.
|
||||
- 3 verticals (Orchestration + Security + Observability), 2 crosscuts → `strip_y_bot = 472`, `strip_h_total = 432`, `heights = [144, 144, 144]`, layout `[40..184, 184..328, 328..472]`.
|
||||
|
||||
Adjacent edges share the same y (no gap), like horizontal chevrons share x at their boundary.
|
||||
|
||||
**Polygon shapes** (top-to-bottom flow, mirrors horizontal §2.2):
|
||||
- First (topmost): flat top, point at bottom — `(strip_x, y0) (strip_x+strip_w, y0) (strip_x+strip_w, y1-12) (strip_x+strip_w/2, y1) (strip_x, y1-12)`
|
||||
- Middle: notch on top, point on bottom — `(strip_x, y0) (strip_x+strip_w/2, y0+12) (strip_x+strip_w, y0) (strip_x+strip_w, y1-12) (strip_x+strip_w/2, y1) (strip_x, y1-12)`
|
||||
- Last (bottommost): notch on top, flat bottom — `(strip_x, y0) (strip_x+strip_w/2, y0+12) (strip_x+strip_w, y0) (strip_x+strip_w, y1) (strip_x, y1)`
|
||||
|
||||
Fills alternate `#2d3142` / `#3d4460` (same palette as horizontals). Labels: paper-colored mono `font-size=7`, `letter-spacing=0.14em`, **rotated −90°**, anchored at `(strip_x + strip_w/2, (y0+y1)/2)`.
|
||||
|
||||
Vertical chevrons honor the per-chevron `color` override documented in §2.2 — apply the hex to the polygon fill, leave the rotated label paper-colored. Pair the override with the same hex on the chevron's paired bar/crosscut to bind them visually as one concern.
|
||||
|
||||
---
|
||||
|
||||
## 3. Connector rules (mandatory)
|
||||
|
||||
These are non-negotiable. Pick the style **automatically** from the topology — do not let the user override style on focal-touching or bar-originating edges.
|
||||
|
||||
| `style` | Stroke | Width | Dash | Marker | When required |
|
||||
|---|---|---|---|---|---|
|
||||
| `primary` | `#eb6c36` | 1.2 | — | `arrow-accent` | Every edge whose endpoint is the `focal` node. |
|
||||
| `secondary` | `#4f5d75` | 1.0 | — | `arrow` | Default for source→component and component→component when neither endpoint is focal. |
|
||||
| `trigger` | `#4f5d75` | 1.0 | `4,3` | `arrow-sm` | Every edge originating from a `kind: bar` component. |
|
||||
| `query` | `rgba(45,49,66,0.30)` | 1.0 | `4,3` | `arrow` | Read-back edges (e.g., focal ↔ Trino). |
|
||||
|
||||
**Defs block** (required, exactly these four markers):
|
||||
|
||||
```svg
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0, 6 2.5, 0 5" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-dim" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="rgba(45,49,66,0.45)"/></marker>
|
||||
</defs>
|
||||
```
|
||||
|
||||
### 3.1 Exit / entry sides (non-negotiable)
|
||||
|
||||
| Edge kind | Exit side of source | Entry side of target |
|
||||
|---|---|---|
|
||||
| Source → cluster node | **right** of source node | **left** of target |
|
||||
| Component → component (within cluster) | **right** | **left** |
|
||||
| Bar component → node | **bottom** of bar | **top** of node |
|
||||
| Cross-cutting bar | — (emits no edges) | — |
|
||||
| Vertical chevron | — (labels only; emits no edges) | — |
|
||||
|
||||
### 3.2 Routing
|
||||
|
||||
- Orthogonal elbows, **at most two bends** per path.
|
||||
- Use a Q-bezier 8-px corner radius at every bend.
|
||||
- Z-order: draw **all connectors before any node rectangle** (so node fills mask the line ends).
|
||||
- Exactly one `marker-end` per `<path>` / `<line>`. Never both `marker-start` and `marker-end`.
|
||||
- Labels: every `primary` and `secondary` connector gets a label (small mono, opaque paper-filled rect mask behind). `trigger` and `query` connectors are unlabelled.
|
||||
|
||||
### 3.3 Crossings
|
||||
|
||||
- Avoid. Re-route via the chevron divider trunk (§4) before accepting a crossing.
|
||||
- If unavoidable, the path drawn second carries a 6-px arc hop over the first.
|
||||
|
||||
### 3.4 Component color override
|
||||
|
||||
A component may declare an optional `color: "#hex"` (CSS color string). The override **only** retints the component's container and content — connectors are never recolored. Edges keep their topology-driven style from §3.
|
||||
|
||||
**Where the color is applied** (given `C = color`):
|
||||
|
||||
| Element | Light mode | Dark mode |
|
||||
|---|---|---|
|
||||
| Container fill (`rect` body) | `rgba(C, 0.06)` | `rgba(C, 0.10)` |
|
||||
| Container stroke | `rgba(C, 0.35)` (`stroke-width=1` for nodes, `0.8` for bars) | `rgba(C, 0.45)` |
|
||||
| Role badge stroke (nodes) | `rgba(C, 0.40)` | `rgba(C, 0.55)` |
|
||||
| Role badge text (nodes) | `rgba(C, 0.85)` | `rgba(C, 1.0)` |
|
||||
| Icon fill / stroke | `C` | lighten `C` by ~15% (or use `C` if already light) |
|
||||
| Name text | `C` | lighten `C` |
|
||||
| Subtitle text | **unchanged** (`muted`) | **unchanged** (`muted`) |
|
||||
| Connectors touching this component | **unchanged** (still topology-driven) | **unchanged** |
|
||||
|
||||
The subtitle stays muted because it's parenthetical metadata — only the primary identity (name + icon + border) carries the color signal.
|
||||
|
||||
**Rules**:
|
||||
|
||||
- **Never on the focal node.** The focal node is already colored with the accent (§2.7). A `color` on the focal node is ignored — accent wins.
|
||||
- **Never on source nodes.** Sources live outside the cluster and stay neutral.
|
||||
- **Cap at 2 custom-colored components** per diagram (in addition to the focal). Three or more colored things erases the signal — the same reason §1 limits accent to 1–2 elements.
|
||||
- **No color on connectors.** If you find yourself wanting a colored edge, the right move is to pick a different `style` from §3, not to override.
|
||||
|
||||
**Semantic uses** (recommended):
|
||||
- `#b85450` (rust-red) — Security / Identity (Keycloak, Vault)
|
||||
- `#5a7d9a` (slate-blue) — Observability (Prometheus, Datadog)
|
||||
- `#7a8c47` (olive-green) — Governance / Lineage (OpenMetadata)
|
||||
- `#8c6d3f` (warm-brown) — Backup / DR
|
||||
|
||||
Stick to these unless the user's brand demands otherwise. Random hex per component is exactly the failure mode this skill avoids.
|
||||
|
||||
---
|
||||
|
||||
## 4. Block branching rules (fan-out)
|
||||
|
||||
The single biggest reproducibility hazard. Fix these rules and the diagram becomes predictable.
|
||||
|
||||
### 4.1 Source fan-out (one source → N components)
|
||||
|
||||
```
|
||||
exit_x = source.right
|
||||
trunk_x = cluster_x - 8 # 4-px gutter before cluster border
|
||||
```
|
||||
|
||||
Path per target: `M exit_x,source_cy → H trunk_x → V target_cy → H target.left`. Use Q-bezier corners.
|
||||
|
||||
### 4.2 Component fan-out (one component → N components)
|
||||
|
||||
```
|
||||
exit_x = node.right
|
||||
trunk_x = x_boundaries[index(source.chevron) + 1] + 4 # 4 px past the chevron divider
|
||||
```
|
||||
|
||||
Path per target: `M exit_x,source_cy → H trunk_x → V target_cy → H target.left`.
|
||||
|
||||
### 4.3 Fan-out cap
|
||||
|
||||
**Max 3 outgoing edges per node.** Above 3, introduce a hub (usually the `focal` node). The chevron banner is the legend; if a node is fanning out to four downstream targets, it's secretly the hub — make it explicit.
|
||||
|
||||
### 4.4 Bar drops (Airflow → N nodes)
|
||||
|
||||
```
|
||||
drop_x(target) = target.cx
|
||||
drop_y_start = bar.bottom
|
||||
drop_y_end = target.top
|
||||
```
|
||||
|
||||
Straight vertical line, `style: trigger`. One per target. No bends — bar drops never elbow.
|
||||
|
||||
### 4.5 Source vertical staggering
|
||||
|
||||
When multiple sources connect to the same single target (e.g. four sources → NiFi), stagger their entry y on the target:
|
||||
|
||||
```
|
||||
entry_y(k) = target.top + 8 + k * (target_h - 16) / (N - 1) # k = 0..N-1, evenly spaced
|
||||
```
|
||||
|
||||
This avoids overlapping arrowheads at the target's left edge.
|
||||
|
||||
---
|
||||
|
||||
## 5. Vertical chevrons — semantics
|
||||
|
||||
Reserved names `Orchestration`, `Security`, `Observability`, `Governance`, `Backup` always render in the right strip (§2.9). Any chevron with `vertical: true` is treated as a reserved-style vertical regardless of name. The rules:
|
||||
|
||||
- **Pairing rule (mandatory, 1:1):** every vertical chevron pairs with exactly one cross-spanning component, and every cross-spanning component pairs with exactly one vertical chevron. The two component kinds that pair:
|
||||
- `kind: bar` — lives inside the cluster (top row). Conventionally paired with `Orchestration`.
|
||||
- `kind: cross-cutting` — lives below the cluster, one row per component. Paired with `Security`, `Observability`, `Governance`, etc.
|
||||
|
||||
If the inputs declare a vertical chevron without a paired component (or vice versa), halt and ask the user — the diagram is incomplete.
|
||||
|
||||
- **Count constraint:** `len(verticals) == len(bars) + len(crosscuts)`. The right strip is split evenly among all verticals (§2.9), so visual alignment between a chevron and its bar/row is approximate — the *label* is what matters, not the y-pixel match.
|
||||
|
||||
- **Ordering convention:** declare verticals top-down in the order: bar-paired first (Orchestration), then crosscut-paired in the same order the crosscuts appear below the cluster. This keeps the visual reading order consistent.
|
||||
|
||||
- **No edges:** vertical chevrons emit no connectors themselves. They are *labels for a column of cross-cutting concern*.
|
||||
|
||||
- **No node placement:** no `kind: node` may be assigned to a vertical chevron. Nodes always belong to a horizontal phase.
|
||||
|
||||
- **Right strip presence:** if any vertical chevron exists, the right strip is reserved (`effective_w = 964`) and **all** horizontal chevron widths and cluster geometry shrink accordingly. Do not draw a vertical chevron on top of the cluster.
|
||||
|
||||
The visual contract: the vertical chevron's column visually "owns" the bar/cross-cutting row at its approximate y-band. Orchestration (top of strip) ↔ Airflow bar (top of cluster). Security ↔ Identity bar. Observability ↔ monitoring bar (below identity). And so on.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dark mode
|
||||
|
||||
When `dark: true`, swap these tokens:
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| Page paper | `#f5f5f5` | `#1c1f2e` |
|
||||
| Ink | `#2d3142` | `#f5f5f5` |
|
||||
| Muted text | `#4f5d75` | `rgba(245,245,245,0.65)` |
|
||||
| Chevron dark fill | `#2d3142` | `#3d4460` |
|
||||
| Chevron light fill | `#3d4460` | `#4a5270` |
|
||||
| Chevron label | `#f5f5f5` | `#f5f5f5` (unchanged) |
|
||||
| Dashed border | `rgba(45,49,66,0.20)` | `rgba(245,245,245,0.22)` |
|
||||
| Cluster border | `rgba(45,49,66,0.18)` | `rgba(245,245,245,0.18)` |
|
||||
| Node fill | white | `rgba(245,245,245,0.06)` |
|
||||
| Node stroke | `rgba(45,49,66,0.25)` | `rgba(245,245,245,0.20)` |
|
||||
| Focal fill | `rgba(235,108,54,0.08)` | `rgba(240,138,89,0.12)` |
|
||||
| Focal stroke | `#eb6c36` | `#f08a59` |
|
||||
| Accent connector | `#eb6c36` | `#f08a59` |
|
||||
| Dot pattern | `rgba(45,49,66,0.10)` | `rgba(245,245,245,0.10)` |
|
||||
|
||||
---
|
||||
|
||||
## 7. Reproducibility checklist (the taste gate)
|
||||
|
||||
Before emitting SVG, verify **every** item. If any fails, fix it — don't ship.
|
||||
|
||||
1. Every cluster `node.cx` equals its chevron's `cx` (§2.2 + §2.7). This is what makes the chevron banner a real legend.
|
||||
2. Every chevron `width` is a multiple of 4 and ≥ 120.
|
||||
3. The reserved right strip (28 px) exists **iff** any vertical chevron is declared. If yes, `effective_w = 972`; if no, `effective_w = 1000`.
|
||||
4. Exactly **one** `focal` node. If `focal` is unset in inputs, default to the first `kind: node` under chevron "Storage".
|
||||
5. Every edge whose endpoint is the focal node uses `style: primary` (accent stroke + `arrow-accent` marker).
|
||||
6. Every edge originating from a `kind: bar` component uses `style: trigger` (dashed + `arrow-sm`).
|
||||
7. The cross-cutting bar (if any) emits **no** edges.
|
||||
8. No node has > 3 outgoing edges, or if it does, it is the declared `focal` / hub.
|
||||
9. All `<path>` and `<line>` connectors are emitted **before** any node `<rect>` (z-order).
|
||||
10. Each vertical chevron pairs **1:1** with exactly one `bar` or `cross-cutting` component (§5 pairing rule). `len(verticals) == len(bars) + len(crosscuts)`.
|
||||
11. `viewBox_h = max(540, strip_y_bot + 112)` — grow the canvas when multiple crosscuts are declared so the legend still fits.
|
||||
12. Custom component colors (§3.4) apply only to container + icon + name; connectors stay topology-driven. Cap at 2 custom-colored components in addition to the focal.
|
||||
11. The diagram passes SKILL.md §9 (4-px grid; ≤ 2 accent elements; mono only for technical content; hairlines; no shadows; no `rounded-2xl`).
|
||||
|
||||
---
|
||||
|
||||
## 8. Anti-patterns
|
||||
|
||||
- Chevron banner omitted — it's the key that maps visual columns to functional phases.
|
||||
- Node x-center off-chevron (§7 #1) — breaks the "banner-as-legend" contract.
|
||||
- Vertical chevron drawn on the cluster (overlay) instead of in a reserved right strip.
|
||||
- More than one focal node — MinIO/S3 (or whichever storage hub) is *the* focal point.
|
||||
- External zone with solid border — dashed border is the signal that these components are outside the cluster.
|
||||
- Identity bar inside the cluster boundary — it applies to all components and must span the full canvas width.
|
||||
- Vertical chevron without a paired bar/cross-cutting component — see §5 pairing rule.
|
||||
- Bar-component edges drawn solid — orchestration triggers must be dashed.
|
||||
- Source fanning out to >3 components without a hub.
|
||||
|
||||
---
|
||||
|
||||
## 9. Examples
|
||||
|
||||
- `assets/example-high-level.html` — horizontal-only, 5 phases, light skin.
|
||||
- `assets/example-high-level-dark.html` — same, dark skin.
|
||||
- `assets/example-high-level-full.html` — same, editorial-card frame.
|
||||
- `assets/example-high-level-vertical.html` — adds vertical Orchestration + Security chevrons, Airflow bar, Keycloak cross-cutting. **Reference render of the full parametric pattern.**
|
||||
- `assets/example-high-level-vertical-dark.html` — vertical pattern, dark skin.
|
||||
- `assets/example-high-level-vertical-full.html` — vertical pattern, editorial-card frame.
|
||||
@@ -0,0 +1,470 @@
|
||||
# IT current-state
|
||||
|
||||
**Best for:** documenting the *before* picture of a modernization proposal — the legacy IT landscape grouped by phase or department (Collection → Processing → Dissemination, or Frontend / Backend / Storage, or Survey → Analysts → Reports), with pain-points flagged, file-based hand-offs labelled (CSV / Excel / Email / Copy), and pre-platform tooling visible. The companion to `type-dp-integration.md`: this type shows the gap that a data-platform proposal is going to close.
|
||||
|
||||
Use when stakeholders need to see the friction in the current setup — siloed scripts, manual file shuffles, missing version control, single-points-of-failure — and the path from those to a target platform topology.
|
||||
|
||||
This type is **parametric** — the inputs schema in §1 drives every coordinate via the formulas in §2. The rule shape mirrors `type-dp-integration.md` (zones + cross-cutting footer bars), `type-process.md` (rounded right-angle connectors), and `type-medallion.md` (per-element `color` override) so the focal rule, color override, dark mode, and reproducibility checklist read identically across types.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inputs — the parameter contract
|
||||
|
||||
```yaml
|
||||
title: "Current IT Landscape"
|
||||
subtitle: "Data pipeline before the platform"
|
||||
eyebrow: "NatStat · Before the platform"
|
||||
|
||||
orientation: horizontal # horizontal (default, zones L→R) | vertical (zones T→B)
|
||||
|
||||
zones: # 2..4 zones, ordered along the orientation axis
|
||||
- name: "COLLECTION"
|
||||
components: # 1..5 components per zone
|
||||
- id: survey-solutions
|
||||
name: "Survey Solutions"
|
||||
sub: "CAPI · PostgreSQL"
|
||||
icon: postgres # any id from references/primitive-icons.md
|
||||
kind: standard # standard | focal | external (external → dashed stroke)
|
||||
- { id: aspnet, name: "ASP.NET Apps", sub: "migration · admin portals", icon: server }
|
||||
- { id: civil-reg, name: "Civil Registry", sub: "external · CRVS data", icon: database, kind: external }
|
||||
- name: "PROCESSING"
|
||||
components:
|
||||
- { id: shared-drive, name: "Shared Drive", sub: "No version control · Windows file share", icon: file, kind: focal }
|
||||
- { id: analyst-mach, name: "Analyst Machines", sub: "SPSS · SAS · Stata · Excel", icon: desktop }
|
||||
- { id: sql-server, name: "SQL Server", sub: "on-premises · core RDBMS", icon: sqlserver, color: "#7a8c47" } # custom olive
|
||||
- name: "DISSEMINATION"
|
||||
components:
|
||||
- { id: legacy-portal, name: "LegacyPortal", sub: "manual bottleneck", icon: cloud, kind: focal }
|
||||
- { id: natstat-website, name: "NatStat Website", sub: "public · static pages", icon: internet }
|
||||
- { id: ministry, name: "Ministry Partners", sub: "~6 ministries", icon: users, kind: external }
|
||||
|
||||
connectors: # ordered list; each links two component ids
|
||||
- { from: survey-solutions, to: shared-drive, label: "CSV", icon: csv, style: link }
|
||||
- { from: aspnet, to: shared-drive, label: "EMAIL", icon: file, style: link } # `mail` MISSING in catalog → falls back to `file`
|
||||
- { from: civil-reg, to: shared-drive, label: "EXCEL", icon: excel, style: link, dashed: true }
|
||||
- { from: shared-drive, to: analyst-mach, label: "COPY", style: accent, dashed: true }
|
||||
- { from: analyst-mach, to: sql-server, label: "LOAD", style: neutral }
|
||||
- { from: analyst-mach, to: legacy-portal, label: "EXCEL", icon: excel, style: accent }
|
||||
- { from: legacy-portal, to: natstat-website, label: "WEB", style: neutral }
|
||||
- { from: natstat-website, to: ministry, label: "CSV DL", icon: csv, style: link, dashed: true }
|
||||
|
||||
footer: # 0..3 optional full-canvas-width bars (cross-cutting concerns)
|
||||
- { name: "Identity Manager", sub: "Active Directory · LDAP · SSO", icon: active-directory }
|
||||
- { name: "Observability", sub: "logs · metrics · alerts", icon: monitoring }
|
||||
|
||||
legend: # auto-generated from styles used; user can override labels
|
||||
- { swatch: link, label: "data flow" }
|
||||
- { swatch: accent, label: "pain-point" }
|
||||
- { swatch: dashed, label: "external" }
|
||||
- { swatch: focal, label: "bottleneck" }
|
||||
|
||||
dark: false
|
||||
```
|
||||
|
||||
**Reserved field semantics:**
|
||||
|
||||
- `orientation` — `horizontal` (zones run L→R, components stack vertically inside each zone) or `vertical` (zones stack T→B, components run L→R inside each zone).
|
||||
- `zones[i].name` — uppercase short label (≤ 14 chars). Rendered at the top-left of the zone box in the `eyebrow` role with letter-spacing 0.14em, on a paper-masked break in the zone border.
|
||||
- `components[i][k].id` — globally unique slug; referenced by `connectors[].from/to`.
|
||||
- `components[i][k].name` — `node-name` role (the human-readable label).
|
||||
- `components[i][k].sub` — `sublabel` role at 10px in muted (the technical sub-label; up to 2 lines via auto-wrap when component height grows to 72).
|
||||
- `components[i][k].icon` — any id from `references/primitive-icons.md`. If missing → no icon, the name shifts left. (Catalog has 41 icons; `mail` is currently missing — use `icon: file` as fallback for email hand-offs.)
|
||||
- `components[i][k].kind` — `standard | focal | external`. `focal` triggers the accent palette (§5); `external` switches to a 4-2 dashed stroke and muted ink to signal "this is outside our scope."
|
||||
- `components[i][k].color` — optional per-component color override (§4). Ignored on `kind: focal` (accent wins).
|
||||
- `connectors[k].from` / `connectors[k].to` — refer to a component `id`. Cross-zone, cross-row, same-zone vertical, and same-zone horizontal all legal; routing chosen by §3.
|
||||
- `connectors[k].label` — uppercase short text (≤ 8 chars). `arrow-label` role at 9px, weight 600.
|
||||
- `connectors[k].icon` — optional inline icon to the left of the text. Same catalog as component icons.
|
||||
- `connectors[k].style` — `neutral | link | accent`. Drives stroke color + marker.
|
||||
- `connectors[k].dashed` — `true | false`.
|
||||
- `footer[k]` — optional cross-cutting bar. Spans full canvas width minus margins. No connectors drawn from a footer.
|
||||
- `legend[k].swatch` — `link | accent | dashed | focal | neutral`. Auto-curated based on what the diagram actually uses; user can re-order or rename.
|
||||
|
||||
---
|
||||
|
||||
## 2. Layout formulas — deterministic geometry
|
||||
|
||||
```
|
||||
# Horizontal orientation (default)
|
||||
left_pad = 16
|
||||
right_pad = 16
|
||||
zone_gap = 20
|
||||
zone_y = 52
|
||||
zone_h = 360
|
||||
n_zones = len(zones)
|
||||
|
||||
# Zone widths: base 200 + 24 per component to give vertical room for icons + 2-line subs.
|
||||
# In the canonical example (3 / 3 / 3 components) the replication used 256 / 360 / 272 —
|
||||
# the formula approximates that with hand-picked widths in the worked YAML (§10).
|
||||
zone_w(i) = base + n_components_i * comp_slack # base ≈ 200, slack ≈ 24
|
||||
viewBox_w = left_pad + Σ zone_w(i) + (n_zones-1) * zone_gap + right_pad
|
||||
|
||||
# Component placement within zone i
|
||||
comp_pad_x = 20 # x-inset from zone border
|
||||
comp_h = 56 # 68 for focal (2-line sub), 72 if both sub lines present
|
||||
comp_gap = 32
|
||||
comp_y(i, k) = zone_y + 28 + k * (comp_h + comp_gap)
|
||||
|
||||
# Component centerlines (used for connector routing)
|
||||
comp_x(i) = zone_x(i) + comp_pad_x
|
||||
comp_w(i) = zone_w(i) - 2 * comp_pad_x
|
||||
comp_cx(i) = comp_x(i) + comp_w(i)/2
|
||||
comp_cy(i, k) = comp_y(i, k) + comp_h/2
|
||||
|
||||
# Footer bars (if present)
|
||||
footer_bar_h = 56
|
||||
footer_gap = 8
|
||||
footer_top = zone_y + zone_h + 24
|
||||
footer_y(k) = footer_top + k * (footer_bar_h + footer_gap)
|
||||
footer_bottom = footer_top + N_footer * (footer_bar_h + footer_gap) - footer_gap
|
||||
|
||||
# Total canvas height
|
||||
legend_block_h = 40
|
||||
content_bottom = N_footer > 0 ? footer_bottom : zone_y + zone_h
|
||||
viewBox_h = content_bottom + legend_block_h + 24
|
||||
```
|
||||
|
||||
### 2.1 Background and zone frame
|
||||
|
||||
Solid paper fill across `viewBox`. No dot pattern. Each zone box:
|
||||
|
||||
```svg
|
||||
<rect x="zone_x(i)" y="zone_y" width="zone_w(i)" height="zone_h"
|
||||
fill="{ink @ 0.02}" stroke="{ink @ 0.10}" stroke-width="0.8" rx="8"/>
|
||||
<!-- paper-masked break for the zone label -->
|
||||
<rect x="zone_x(i)+20" y="zone_y-8" width="{label_w}" height="16" fill="{paper}"/>
|
||||
<text x="zone_x(i)+24" y="zone_y+4" fill="{ink @ 0.40}"
|
||||
font-family="{eyebrow}" letter-spacing="0.14em">{name}</text>
|
||||
```
|
||||
|
||||
### 2.2 Component box
|
||||
|
||||
Three visual kinds:
|
||||
|
||||
| `kind` | Fill | Stroke | Stroke width | Stroke dash | Name ink | Sub ink |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| `standard` | `#FFFFFF` | `ink` | 1 | — | `ink` | `muted` |
|
||||
| `focal` | `accent @ 0.07` | `accent` | 1.4 | — | `ink` | `accent` (line 1) + `muted` (line 2) |
|
||||
| `external` | `#FFFFFF` | `muted` | 1 | `4,3` | `ink` | `muted` |
|
||||
|
||||
**Icon placement** (24×24, monochrome via `currentColor` — see `references/primitive-icons.md`):
|
||||
|
||||
```svg
|
||||
<g transform="translate(comp_x + 12, comp_y + (comp_h - 24)/2)" color="{ink_for_kind}">
|
||||
<use href="#icon-{name}"/> <!-- or inline the SVG path from the catalog -->
|
||||
</g>
|
||||
```
|
||||
|
||||
Icon takes 24 × 24 → 36-px total horizontal footprint with the 12-px left pad. Name and sub-label baseline shifts right by 40 px.
|
||||
|
||||
**Name + sub baselines** (left-aligned, with icon to the left):
|
||||
|
||||
```
|
||||
name_x = comp_x + 44
|
||||
name_y = comp_y + (comp_h/2) - 2
|
||||
sub_y = comp_y + (comp_h/2) + 14
|
||||
```
|
||||
|
||||
### 2.3 Connector geometry (§3 holds the routing rules)
|
||||
|
||||
```
|
||||
src_right = comp_x(i_src) + comp_w(i_src)
|
||||
src_left = comp_x(i_src)
|
||||
src_top = comp_y(i_src, k_src)
|
||||
src_bot = src_top + comp_h_src
|
||||
src_cy = src_top + comp_h_src/2
|
||||
|
||||
dst_left = comp_x(i_dst)
|
||||
dst_right = comp_x(i_dst) + comp_w(i_dst)
|
||||
dst_top = comp_y(i_dst, k_dst)
|
||||
dst_bot = dst_top + comp_h_dst
|
||||
dst_cx = comp_cx(i_dst)
|
||||
dst_cy = dst_top + comp_h_dst/2
|
||||
|
||||
# Corridor x for cross-zone H+Q+V routing
|
||||
corridor_x = dst_cx # land arrow on dst horizontal center, enter via top/bot
|
||||
```
|
||||
|
||||
### 2.4 Footer bar
|
||||
|
||||
```svg
|
||||
<rect x="left_pad" y="footer_y(k)" width="viewBox_w - 2*left_pad" height="footer_bar_h"
|
||||
fill="{ink @ 0.03}" stroke="{ink @ 0.18}" stroke-width="0.8" rx="8"/>
|
||||
<g transform="translate(left_pad + 20, footer_y(k) + (footer_bar_h - 24)/2)" color="{ink}">
|
||||
<use href="#icon-{name}"/>
|
||||
</g>
|
||||
<text x="left_pad + 56" y="footer_y(k) + 24" font-family="{node-name}" font-size="14" fill="{ink}">{name}</text>
|
||||
<text x="left_pad + 56" y="footer_y(k) + 40" font-family="{sublabel}" font-size="12" fill="{muted}">{sub}</text>
|
||||
```
|
||||
|
||||
Footer bars are layer-wide services. **No connectors emerge from them.** They sit visually below the zones and let the reader see the cross-cutting concerns at a glance.
|
||||
|
||||
### 2.5 Legend strip
|
||||
|
||||
Hairline divider at `y = content_bottom + 16`, then a row of swatches + labels at `y = content_bottom + 36`. Only the styles actually used by `connectors[]` (plus `focal` and `external` if those component kinds are present) appear in the legend.
|
||||
|
||||
---
|
||||
|
||||
## 3. Connector rules (mandatory)
|
||||
|
||||
### 3.1 Path shape — rounded right-angle Q-bezier, r = 8
|
||||
|
||||
Reused verbatim from `type-process.md` §3.1. No diagonals — ever.
|
||||
|
||||
```svg
|
||||
<!-- Same zone, adjacent component (same vertical column): single vertical line -->
|
||||
<line x1="{src_cx}" y1="{src_bot}" x2="{dst_cx}" y2="{dst_top}"
|
||||
stroke="…" stroke-width="…" marker-end="…"/>
|
||||
|
||||
<!-- Cross-zone or cross-row: exit right → H → Q-bend → V → enter top (or bottom) -->
|
||||
<path d="M {src_right},{src_cy} H {dst_cx - 8} Q {dst_cx},{src_cy} {dst_cx},{src_cy ± 8} V {dst_top_or_bottom}"
|
||||
fill="none" stroke="…" stroke-width="…" marker-end="…"/>
|
||||
```
|
||||
|
||||
- Use `{src_cy + 8}` and `V {dst_top}` when destination lies BELOW source.
|
||||
- Use `{src_cy − 8}` and `V {dst_bottom}` when destination lies ABOVE source.
|
||||
|
||||
### 3.2 Exit / entry sides (configurable; defaults below)
|
||||
|
||||
| Topology | Default exit side of source | Default entry side of destination |
|
||||
| --- | --- | --- |
|
||||
| Same zone, dst below | bottom | top |
|
||||
| Same zone, dst above | top | bottom |
|
||||
| Cross-zone, horizontal flow | right | top (or bottom, whichever is closer to src_cy) |
|
||||
| Vertical-orientation diagram | bottom | top |
|
||||
|
||||
A connector can override via `connectors[k].from_side` / `connectors[k].to_side` (`top | right | bottom | left`). **Backward references** (right→left in horizontal orientation, up in vertical orientation) are permitted only when at least one endpoint has `kind: external`, and must be `dashed: true`.
|
||||
|
||||
### 3.3 Markers MUST touch the destination rectangle
|
||||
|
||||
The path's last command ends at the destination's rectangle edge (`V {dst_top}` or `H {dst_left}`), **not** at the centroid. After applying `refX=7` on the marker, the triangle sits flush against the border. Stopping the line short of the edge — or sending it to the centroid and burying the arrowhead inside the rect — is a hard fail.
|
||||
|
||||
### 3.4 Style → stroke + marker
|
||||
|
||||
| `style` | Stroke color | Stroke width | Marker |
|
||||
| --- | --- | --- | --- |
|
||||
| `neutral` | `muted` | 1.0 | `url(#arrow)` |
|
||||
| `link` | `link` | 1.2 | `url(#arrow-link)` |
|
||||
| `accent` | `accent` | 1.4 | `url(#arrow-accent)` |
|
||||
|
||||
Add `stroke-dasharray="4 3"` when `dashed: true`.
|
||||
|
||||
**Defs block** (always emit all three):
|
||||
|
||||
```svg
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="{muted}"/></marker>
|
||||
<marker id="arrow-link" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="{link}"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="{accent}"/></marker>
|
||||
</defs>
|
||||
```
|
||||
|
||||
### 3.5 Connector label = inline icon + text, placed at the START of the connector with a perpendicular margin
|
||||
|
||||
The label sits **near the source end** of the connector (not at the mid-segment) and is **offset perpendicular to the line** so it never overlaps the stroke. Icon (when `icon:` is set) sits inside the label's paper-fill mask, to the left of the text.
|
||||
|
||||
```svg
|
||||
<g transform="translate({label_cx}, {label_cy})">
|
||||
<rect x="-{w/2}" y="-9" width="{w}" height="18" rx="3" fill="{paper}" stroke="none"/>
|
||||
<use href="#icon-{icon}" x="-{w/2 + 4}" y="-6" width="12" height="12" color="{stroke_color}"/> <!-- if icon set -->
|
||||
<text x="{icon ? (-(w/2) + 22) : 0}" y="3" text-anchor="{icon ? 'start' : 'middle'}"
|
||||
font-family="{arrow-label}" font-size="9" font-weight="600"
|
||||
letter-spacing="0.08em" fill="{stroke_color}">{label}</text>
|
||||
</g>
|
||||
```
|
||||
|
||||
**Placement formulas** (label box is 18px tall × `w` wide; centered on `{label_cx, label_cy}`):
|
||||
|
||||
| Segment exiting source | `label_cx` | `label_cy` | Effect |
|
||||
| --- | --- | --- | --- |
|
||||
| Horizontal (right exit) | `src_right + 6 + w/2` | `src_cy − 14` | Label sits 6 px past the source, 5 px above the line |
|
||||
| Horizontal (left exit, backward) | `src_left − 6 − w/2` | `src_cy − 14` | Label sits 6 px before the source, 5 px above the line |
|
||||
| Vertical (bottom exit) | `src_cx + 6 + w/2` | `src_bot + 14` | Label sits 6 px right of the line, 5 px below the source edge |
|
||||
| Vertical (top exit, backward) | `src_cx + 6 + w/2` | `src_top − 14` | Label sits 6 px right of the line, 5 px above the source edge |
|
||||
|
||||
For cross-zone H+Q+V routes the label binds to the **horizontal** segment, since that segment is anchored at the source. Place the label early on that horizontal run — never on the Q-bend or the vertical tail.
|
||||
|
||||
- `w = text_w + (icon ? 30 : 12)` — auto-fit.
|
||||
- Mask `fill` resolves to `paper` in light mode and `ink` in dark mode. The mask is kept as a safety pad — even though the label no longer sits on the line, it can graze zone backgrounds and component fills, and the mask preserves contrast.
|
||||
- `stroke_color` follows §3.4 (text + icon inherit the connector's accent / link / neutral color).
|
||||
|
||||
### 3.6 Z-order
|
||||
|
||||
All connectors (paths + lines + labels) emit BEFORE any component rect, so node fills mask the line ends. The connector label is the exception — it draws AFTER its line so the mask sits on top.
|
||||
|
||||
---
|
||||
|
||||
## 4. Component color override (per-component `color: "#hex"`)
|
||||
|
||||
Per-component, same shape as every other parametric type in this skill.
|
||||
|
||||
| Element | Light | Dark |
|
||||
| --- | --- | --- |
|
||||
| Container fill | `rgba(C, 0.06)` | `rgba(C_light, 0.10)` |
|
||||
| Container stroke | `rgba(C, 0.45)` (width 1) | `rgba(C_light, 0.55)` |
|
||||
| Component name text | `C` | `C_light` |
|
||||
| Icon glyph | inherits ink via `currentColor` (unchanged) | inherits ink (unchanged) |
|
||||
| Sub-label | muted (unchanged) | muted (unchanged) |
|
||||
| Connectors touching this component | **unchanged** — topology-driven | **unchanged** |
|
||||
|
||||
`C_light` = the same hex lightened ~15 % for dark-mode contrast (e.g., `#7a8c47` → `#9aac67`, `#b85450` → `#d97a78`).
|
||||
|
||||
**Rules:**
|
||||
- **Never on focal components.** `kind: focal` always renders accent; `color` is silently ignored.
|
||||
- **Never on connectors.** Connector style is topology-driven; if you want a colored edge, pick `style: accent` / `link` / `neutral`, not a component color.
|
||||
- **Cap: ≤ 3 custom-colored components per diagram** (in addition to focal components). Above 3 the visual signal fragments.
|
||||
|
||||
**Recommended cross-type palette** (same as `type-medallion.md` / `type-process.md` / `type-dp-integration.md` / `type-dp-security-matrix.md`):
|
||||
|
||||
- `#b85450` rust-red — security / governance / pain-point that isn't focal
|
||||
- `#5a7d9a` slate-blue — observability / quality / monitoring gate
|
||||
- `#7a8c47` olive-green — survivor system (the one tool the new platform keeps)
|
||||
- `#c9a23a` warm yellow — sandbox / dev / scratch
|
||||
- `#8c6d3f` warm-brown — archive / cold / DR
|
||||
|
||||
---
|
||||
|
||||
## 5. Focal rule
|
||||
|
||||
- `kind: focal` components: **≤ 2 per diagram** (zero is also valid for diagrams without a single dominant pain-point).
|
||||
- Auto-styling: accent stroke 1.4, accent-tinted fill 7 %, ink-bold `node-name`, accent line-1 `sublabel`.
|
||||
- Any connector with a focal endpoint automatically renders in `style: accent`; the YAML `style:` is ignored.
|
||||
- Custom `color: "#hex"` on a focal component is silently ignored — accent always wins.
|
||||
|
||||
If your diagram needs more than 2 focal components, you've collapsed two narratives. Split: one "collection pain-points" diagram + one "dissemination pain-points" diagram.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dark mode
|
||||
|
||||
| Role | Light | Dark |
|
||||
| --- | --- | --- |
|
||||
| paper | `paper` | `ink` |
|
||||
| ink | `ink` | `paper` |
|
||||
| muted | `muted` | `muted` |
|
||||
| accent | `accent` | `accent` |
|
||||
| link | `link` | `link` |
|
||||
| zone background | `ink @ 0.02` | `paper @ 0.04` |
|
||||
| zone border | `ink @ 0.10` | `paper @ 0.14` |
|
||||
| standard component fill | `#FFFFFF` | `paper @ 0.04` |
|
||||
| standard component stroke | `ink` | `paper @ 0.32` |
|
||||
| focal fill | `accent @ 0.07` | `accent @ 0.12` |
|
||||
| focal stroke | `accent` | `accent` |
|
||||
| external stroke | `muted` (dashed) | `muted` (dashed) |
|
||||
| footer fill | `ink @ 0.03` | `paper @ 0.05` |
|
||||
| footer stroke | `ink @ 0.18` | `paper @ 0.20` |
|
||||
| label mask fill | `paper` | `ink` |
|
||||
| custom-color components | `C` | `C_light` (≈ +15 %) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Reproducibility checklist (taste gate)
|
||||
|
||||
Before emitting SVG, verify **every** item:
|
||||
|
||||
1. Eyebrow + title + subtitle present at canonical y-positions (24 / 36 / 52); body padding 32 px.
|
||||
2. 2..4 zones; each has its uppercase label at the top-left of its zone box, on a paper-masked break in the border.
|
||||
3. Every component has `id`, `name`. `sub`, `icon`, `kind`, `color` are optional.
|
||||
4. ≤ 2 components with `kind: focal`; focal styling auto-applied (accent fill 7 %, accent stroke 1.4, italic line-1 sub).
|
||||
5. Every connector exits the right (or bottom) of source and enters the top (or left) of destination; rounded right-angle Q-bezier `r=8` at every bend; marker triangle visibly touches the destination rectangle edge.
|
||||
6. Connector labels sit at the **start** of the connector (not mid-segment) and are offset **perpendicular** to the line (5 px gap above for horizontal segments, 6 px gap to the right for vertical segments) — never overlapping the stroke. Paper-fill mask kept behind text; icon (when `icon:` is set) sits left of text inside the same mask.
|
||||
7. ≤ 3 custom-colored components; none on focal.
|
||||
8. ≤ 3 footer bars; each spans `viewBox_w − 2*left_pad`; no connectors emerge from any footer.
|
||||
9. Legend at bottom: hairline separator + one swatch per style actually used.
|
||||
10. `arrow-label` for connector labels, `eyebrow` for the page eyebrow and zone labels, `title` for the page title, `node-name` for the subtitle and component names, and `sublabel` for technical sub-labels.
|
||||
11. Markers `#arrow` / `#arrow-link` / `#arrow-accent` defined once in `<defs>`; no inline marker definitions.
|
||||
12. Dark variant: resolve every semantic token through its dark-mode value; custom colors are lightened ~15 %.
|
||||
|
||||
---
|
||||
|
||||
## 8. Anti-patterns
|
||||
|
||||
- **Diagonal arrows.** The NatStat replication has one (analyst → LegacyPortal). The new type forbids it — always rounded right-angle Q-bezier.
|
||||
- **Marker not touching the target.** Path ends at the centroid or stops short of the border.
|
||||
- **Inline `text` connector labels without a mask rect.** The connector line can bleed through the text and it becomes unreadable.
|
||||
- **Labels sitting on top of the connector line, mid-segment.** Labels belong at the *start* of the connector with a perpendicular margin (see §3.5) — burying them in the middle of the line hides the source-to-destination direction and forces the reader's eye to fight the mask.
|
||||
- **Tiny text badges as icons.** The source uses 7-px `DB` / `APP` / `EXT` badges; this type uses real 24-px catalog icons. Text badges are only acceptable as the label text, not as the component "icon."
|
||||
- **Custom color on a focal component.** Focal always wins; user-set `color` silently ignored on `kind: focal`.
|
||||
- **Footer bar wired to one component.** Footer = cross-cutting layer-wide concern; a connector from a footer to a specific component is a category error (use `type-dp-integration.md`'s AUTH-line pattern only when the footer service truly authenticates *all* components, and even then the line lands at the zone bottom edge, not at a specific tool).
|
||||
- **> 16 total components or > 5 per zone.** Density cap; split into two diagrams.
|
||||
- **Mixing orientations within one diagram.** Pick one — `horizontal` or `vertical` — and apply it to every zone.
|
||||
- **Using `kind: focal` to flag every painful thing.** Focal exists for ≤ 2 narrative pain-points; for "this is bad but not headline-bad", use `color: "#b85450"` rust-red instead.
|
||||
|
||||
---
|
||||
|
||||
## 9. Examples
|
||||
|
||||
- `assets/example-it-state.html` — minimal light (NatStat canonical: 3 zones, 9 components, 8 connectors, 0 footer bars, SQL Server tinted olive). Gallery default.
|
||||
- `assets/example-it-state-dark.html` — same, dark skin.
|
||||
- `assets/example-it-state-full.html` — same, editorial-card frame with summary cards.
|
||||
- `assets/example-it-state-extended.html` — exercises §4 color override + footer bars: 2 footer bars (Identity Manager + Observability) below the zones, third custom color on Analyst Machines (slate-blue, data-quality concern).
|
||||
- `assets/example-it-state-extended-dark.html` — extended pattern, dark skin.
|
||||
|
||||
---
|
||||
|
||||
## 10. Worked YAML — full inputs for `example-it-state.html`
|
||||
|
||||
The complete inputs that map to the shipped canonical example. Every coordinate in that SVG is derivable from §2 applied to these inputs.
|
||||
|
||||
```yaml
|
||||
title: "Current IT Landscape"
|
||||
subtitle: "Data pipeline before the platform"
|
||||
eyebrow: "NatStat · Before the platform"
|
||||
|
||||
orientation: horizontal
|
||||
|
||||
zones:
|
||||
- name: "COLLECTION"
|
||||
components:
|
||||
- { id: survey-solutions, name: "Survey Solutions", sub: "CAPI · PostgreSQL", icon: postgres }
|
||||
- { id: aspnet, name: "ASP.NET Apps", sub: "migration · admin portals", icon: server }
|
||||
- { id: civil-reg, name: "Civil Registry", sub: "external · CRVS data", icon: database, kind: external }
|
||||
- name: "PROCESSING"
|
||||
components:
|
||||
- { id: shared-drive, name: "Shared Drive", sub: "No version control · Windows file share", icon: file, kind: focal }
|
||||
- { id: analyst-mach, name: "Analyst Machines", sub: "SPSS · SAS · Stata · Excel", icon: desktop }
|
||||
- { id: sql-server, name: "SQL Server", sub: "on-premises · core RDBMS", icon: sqlserver, color: "#7a8c47" }
|
||||
- name: "DISSEMINATION"
|
||||
components:
|
||||
- { id: legacy-portal, name: "LegacyPortal", sub: "manual bottleneck", icon: cloud, kind: focal }
|
||||
- { id: natstat-website, name: "NatStat Website", sub: "public · static pages", icon: internet }
|
||||
- { id: ministry, name: "Ministry Partners", sub: "~6 ministries", icon: users, kind: external }
|
||||
|
||||
connectors:
|
||||
- { from: survey-solutions, to: shared-drive, label: "CSV", icon: csv, style: link }
|
||||
- { from: aspnet, to: shared-drive, label: "EMAIL", icon: file, style: link }
|
||||
- { from: civil-reg, to: shared-drive, label: "EXCEL", icon: excel, style: link, dashed: true }
|
||||
- { from: shared-drive, to: analyst-mach, label: "COPY", style: accent, dashed: true }
|
||||
- { from: analyst-mach, to: sql-server, label: "LOAD", style: neutral }
|
||||
- { from: analyst-mach, to: legacy-portal, label: "EXCEL", icon: excel, style: accent }
|
||||
- { from: legacy-portal, to: natstat-website, label: "WEB", style: neutral }
|
||||
- { from: natstat-website, to: ministry, label: "CSV DL", icon: csv, style: link, dashed: true }
|
||||
|
||||
dark: false
|
||||
```
|
||||
|
||||
### 10.1 What this YAML proves
|
||||
|
||||
- `n_zones = 3`, components per zone `= [3, 3, 3]`, custom color count = 1 (SQL Server), focal count = 2 (Shared Drive, LegacyPortal), external count = 2 (Civil Registry, Ministry Partners).
|
||||
- Zone widths in canonical: 256 / 360 / 272 ⇒ `viewBox_w = 16 + 256 + 20 + 360 + 20 + 272 + 16 = 960` ✓
|
||||
- `viewBox_h = 52 + 360 + 40 + 24 = 500` (no footer bars) ✓
|
||||
- Shared Drive (focal) at zone 2, row 0: `x = 340, y = 80, w = 264, h = 68` (focal stretches to 68 to fit 2-line sub) ✓
|
||||
- LegacyPortal (focal) at zone 3, row 0: `x = 704, y = 80, w = 208, h = 60` ✓
|
||||
- SQL Server (custom olive) at zone 2, row 2: container fill `rgba(122,140,71,0.06)`, stroke `rgba(122,140,71,0.45)`, name text `#7a8c47` ✓
|
||||
- Connectors 4, 5 (within zone 2) and 7, 8 (within zone 3) are simple vertical `<line>` elements. Cross-zone connectors take rule-compliant routes (see SKILL.md §6 rules 4 & 5):
|
||||
- **All three Survey-side → Shared Drive connectors (C1 / C2 / C3) enter Shared Drive's LEFT edge.** A top-edge entry would push the marker body (7 px back along travel, given `refX = 7`) *inside* the destination box, where the box's paper-fill mask hides it — only a 1-pixel tip would peek above the stroke. Entering the left edge with a right-going path keeps the body outside the box and the arrow visible (~7 px shown to the left of the box edge). The three left-edge attach points are fanned at **y = 108 / 124 / 140** (16-px spacing, well above the 12 px rule-4 minimum).
|
||||
- **C1** (Survey → Shared Drive) source y matches landing y: single horizontal `M 252,108 H 340`. No bends needed.
|
||||
- **C2** (ASP.NET → Shared Drive) detours up through zone-2 background — vertical at `x = 316` (clear of Shared Drive's left edge at `x = 340`): `H 308 Q 316,196 316,188 V 132 Q 316,124 324,124 H 340`. Lands at `(340, 124)`.
|
||||
- **C3** (Civil Registry → Shared Drive) detours up through zone-2 background — vertical at `x = 332` (clear of Analyst Machines, which starts at `x = 340`): `H 324 Q 332,284 332,276 V 148 Q 332,140 340,140`. Lands at `(340, 140)` via a final Q-bend (no trailing H needed).
|
||||
- **C6** (Analyst Machines → LegacyPortal) cannot use a direct H+Q+V into LegacyPortal's left edge — Analyst Machines and LegacyPortal are in different rows, and the direct horizontal at `y = 268` would cross NatStat Website. It detours through the zone gap and **over** LegacyPortal: `H 654 Q 662,268 662,260 V 72 Q 662,64 670,64 H 800 Q 808,64 808,72 V 80` — vertical at `x = 662` (in zone gap), horizontal at `y = 64` (above LegacyPortal top), then down into LegacyPortal's top center. The path enters LegacyPortal from **above** going down, so the arrow body lives above the box (visible) and only the 1-px tip enters the box.
|
||||
|
||||
**Marker-visibility rule of thumb:** with the standard arrow marker (`markerWidth = 8`, `refX = 7`), the arrow body extends 7 px *backwards* along the path direction from the endpoint. For the arrow to remain visible, that 7-px tail must sit *outside* the destination box. Translation:
|
||||
- Entering a **TOP edge going UP** (path direction up, box below) → body inside box, **only 1 px visible. Avoid this.**
|
||||
- Entering a **TOP edge going DOWN** (path direction down, box below) → body above box, ~7 px visible. ✓
|
||||
- Entering a **LEFT edge going RIGHT** → body to the left of box, ~7 px visible. ✓
|
||||
- Entering a **RIGHT edge going LEFT** → body to the right of box, ~7 px visible. ✓
|
||||
- Entering a **BOTTOM edge going DOWN** (box above) → body inside box, **only 1 px visible. Avoid this.**
|
||||
- Entering a **BOTTOM edge going UP** (box above) → body below box, ~7 px visible. ✓
|
||||
|
||||
When the source row matches the destination row's y range (e.g., Survey at y=108 with Shared Drive at y=80–148), prefer **side-edge** entry — a single horizontal path with a fully visible arrow. When the source row is offset, detour through the destination's nearest zone background to enter a side edge rather than approaching a top/bottom edge from the wrong side.
|
||||
|
||||
The extended example (§9 line 4) demonstrates footer bars + a third custom color and proves `viewBox_h` grows correctly when `N_footer > 0`.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Line Chart
|
||||
|
||||
**Best for:** continuous trends over time or a sequential index — signups over weeks, revenue by month, latency over releases. Use when the direction and rate of change between points is the primary message.
|
||||
|
||||
## Layout conventions
|
||||
|
||||
- **Plot area margins:** left 80px, bottom 60px, top 40px, right 40px — inside `0 0 1000 500` viewBox.
|
||||
- **Points:** 4–12 data points. Fewer → consider a summary stat; more → aggregate into periods.
|
||||
- **X-axis:** evenly spaced time/index labels below the plot. Use Geist Mono 8px, centered on each point x.
|
||||
- **Y-axis gridlines:** 4–6 horizontals at regular intervals. Same faint treatment as bar chart.
|
||||
- **Lines:** `<polyline>` with `fill="none"`. Focal series `stroke-width="1.8"`, others `"1.2"`.
|
||||
- **Vertex dots:** only on the focal series (`r=4`, filled). Other series: line only.
|
||||
- **Area fill (optional):** `<polygon>` closing back to `y=420` (x-axis baseline) at 0.08 opacity. Use for the focal series only when the area meaning is important.
|
||||
- **Multi-series:** up to 5 series. Focal = `accent`. Others = `series-1`, `series-2`, `series-3`, `series-4` from style-guide.md. Apply series palette in order — don't skip.
|
||||
- **Legend:** horizontal strip at the bottom. Swatch = 16×8px rect with the series fill/stroke. One entry per series.
|
||||
|
||||
### Polyline pattern
|
||||
|
||||
```svg
|
||||
<!-- Focal series -->
|
||||
<polyline points="x0,y0 x1,y1 x2,y2 ..."
|
||||
fill="none" stroke="#eb6c36" stroke-width="1.8" stroke-linejoin="round"/>
|
||||
<!-- Dots at each point (focal only) -->
|
||||
<circle cx="x0" cy="y0" r="4" fill="#eb6c36"/>
|
||||
|
||||
<!-- Non-focal series -->
|
||||
<polyline points="x0,y0 x1,y1 ..."
|
||||
fill="none" stroke="#7c8f6f" stroke-width="1.2" stroke-linejoin="round"/>
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- More than 5 series (visual mush — reduce or split).
|
||||
- Lines that don't start at a shared zero baseline unless explicitly annotated.
|
||||
- Smoothed/spline curves when the underlying data is sampled — polyline is honest.
|
||||
- Dots on every series when there are 4+ series (only focal gets dots).
|
||||
- Y-axis that doesn't include zero when the absolute magnitude matters.
|
||||
- Connecting discontinuous data segments without a visual gap.
|
||||
|
||||
## Examples
|
||||
|
||||
- `assets/example-line.html` — minimal light
|
||||
- `assets/example-line-dark.html` — minimal dark
|
||||
- `assets/example-line-full.html` — full editorial
|
||||
@@ -0,0 +1,356 @@
|
||||
# Medallion
|
||||
|
||||
**Best for:** documenting a multi-tier data-storage layout where each tier is a distinct *quality / access level* of the same dataset — typically raw landing zone, anonymised, staging/cleaned, aggregated business indicators, and cold archive. Used when the reader needs to see at a glance *what each bucket contains*, *who writes it*, *with what tool and format*, and *how data is promoted between tiers*.
|
||||
|
||||
Prefer **Process** if the subject is a workflow with role lanes. Prefer **High-Level** if the subject is the cluster architecture rather than the storage tier organisation.
|
||||
|
||||
This type is **parametric** — the inputs schema in §1 drives every coordinate via the formulas in §2. Two generations from the same inputs must produce visually identical SVG. The rule shapes mirror `type-process.md` and `type-data-flow.md` so color override, focal rule, and reproducibility checklist read identically across types.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inputs — the parameter contract
|
||||
|
||||
```yaml
|
||||
title: "Five-Tier Medallion Architecture"
|
||||
subtitle: "Quarterly survey through Raw → Anonymized → Staging → Aggregated → Archive"
|
||||
|
||||
tiers: # 3..6 tier columns, ordered left → right
|
||||
- { name: "Raw", bucket: "raw-bucket", style: "outer",
|
||||
fields: { tool: "NiFi · raw write", format: "CSV · Parquet · JSON", writer: "Data Engineer",
|
||||
example: ["Q1 dump · w/ PII", "verbatim CAPI export"] } }
|
||||
- { name: "Anonymized", bucket: "anon-bucket", style: "default",
|
||||
fields: { tool: "Trino INSERT", format: "Iceberg · partitioned", writer: "Data Engineer",
|
||||
example: ["no name · address", "stable household ID"] } }
|
||||
- { name: "Staging", bucket: "staging-bucket", style: "default", color: "#c9a23a", # warm yellow — analytical working zone
|
||||
fields: { tool: "Trino · JupyterHub", format: "Iceberg · cleaned", writer: "Data Scientist",
|
||||
example: ["weighted records", "harmonised codings"] } }
|
||||
- { name: "Aggregated", bucket: "aggregated-bucket", style: "focal", focal: true,
|
||||
fields: { tool: "Trino INSERT · SAS JDBC", format: "Iceberg · indicators", writer: "Data Scientist",
|
||||
example: ["unemployment rate", "labour participation"] } }
|
||||
- { name: "Archive", bucket: "archive-bucket", style: "cold",
|
||||
fields: { tool: "MinIO lifecycle", format: "cold tier · immutable", writer: "Data Administrator",
|
||||
example: ["historical Q1–Q4 sets", "5+ years retained"] } }
|
||||
|
||||
example_label: "Quarterly survey example" # bottom field label (varies per domain)
|
||||
|
||||
promotions: # adjacent-tier arrows; len = n_tiers - 1
|
||||
- { from: 0, to: 1, label: "PII REMOVE", style: "normal" }
|
||||
- { from: 1, to: 2, label: "CLEAN+WEIGHT", style: "normal" }
|
||||
- { from: 2, to: 3, label: "AGGREGATE", style: "focal" } # auto-accent because target is focal
|
||||
- { from: 3, to: 4, label: "LIFECYCLE", style: "lifecycle" } # dashed
|
||||
|
||||
paths: # 0..2 write-method cards at the bottom (optional)
|
||||
- { tag: "SQL PATH", title: "Trino INSERT INTO … SELECT",
|
||||
sub: "filter · reshape · join · aggregate — set-based transforms" }
|
||||
- { tag: "NOTEBOOK PATH", title: "DuckDB + Python/R in JupyterHub",
|
||||
sub: "stats · ML · interactive analysis — row-iterative work" }
|
||||
|
||||
dark: false
|
||||
```
|
||||
|
||||
**Reserved field semantics:**
|
||||
- `tiers[i].style` — one of `outer`, `default`, `focal`, `cold`. Drives the card's fill/stroke palette (§2.4).
|
||||
- `tiers[i].focal: true` — exactly **one** tier may declare this. Overrides `style` to `focal` and switches the promotion arrow *into* this tier to `focal` automatically.
|
||||
- `tiers[i].fields` — `{tool, format, writer, example}`. `example` is a 1- or 2-item list; the section heading uses `example_label`.
|
||||
- `tiers[i].color` — optional `"#hex"` per-tier color override. See §4.
|
||||
- `promotions[].style` — `normal` | `focal` | `lifecycle`. The connector rule (§3) binds each style to fixed stroke / dash / marker.
|
||||
- `paths` — 0–2 entries. When 0 entries, the bottom row is omitted and `viewBox_h` shrinks accordingly.
|
||||
|
||||
---
|
||||
|
||||
## 2. Layout formulas — deterministic geometry
|
||||
|
||||
```
|
||||
# Tier dimensions
|
||||
tier_w = 172
|
||||
tier_h = 380
|
||||
tier_gap = 16
|
||||
left_pad = 16
|
||||
right_pad = 100
|
||||
n_tiers = len(tiers)
|
||||
|
||||
# Canvas
|
||||
viewBox_w = left_pad + n_tiers * tier_w + (n_tiers - 1) * tier_gap + right_pad
|
||||
# 5 tiers → 16 + 860 + 64 + 100 = 1040
|
||||
arc_band_h = 80 # space above tiers reserved for promotion arcs
|
||||
path_h = 56
|
||||
path_gap = 16 # gap between tier row and path row
|
||||
bottom_pad = 16
|
||||
viewBox_h = arc_band_h + tier_h + (path_gap + path_h if paths else 0) + bottom_pad
|
||||
# with paths → 80+380+72+16 = 548
|
||||
# without paths → 80+380+16 = 476
|
||||
|
||||
# Tier positions
|
||||
tier_x(i) = left_pad + i * (tier_w + tier_gap) # 16, 204, 392, 580, 768
|
||||
tier_y = arc_band_h # 80 — tier tops sit just below the arc band
|
||||
tier_cx(i) = tier_x(i) + tier_w/2 # 102, 290, 478, 666, 854
|
||||
|
||||
# Promotion arcs (between adjacent tiers — over the top, anchored at tier top-centers)
|
||||
arc_src_x(i) = tier_cx(i) # top-center of tier i (102, 290, 478, 666)
|
||||
arc_dst_x(i) = tier_cx(i+1) # top-center of tier i+1 (290, 478, 666, 854)
|
||||
arc_peak_x(i) = (arc_src_x(i) + arc_dst_x(i)) / 2 # midpoint (196, 384, 572, 760)
|
||||
arc_label_y = 50 # label sits inside the arc, 30px below tier top
|
||||
|
||||
# Path row (bottom)
|
||||
path_y = tier_y + tier_h + path_gap # 476
|
||||
path_w = (viewBox_w - 2*left_pad - path_gap) / 2 if len(paths) == 2 else (viewBox_w - 2*left_pad)
|
||||
# Canonical 5-tier shape uses path_w=460 explicitly (see §2.5)
|
||||
```
|
||||
|
||||
### 2.1 Background
|
||||
|
||||
Solid paper fill across the full viewBox. No dot pattern.
|
||||
|
||||
### 2.2 Tier card (172 × 380)
|
||||
|
||||
Each tier renders as a rounded-rect card with a tinted header band, a centered bucket name, four labeled field rows, and a separated `example_label` section near the bottom.
|
||||
|
||||
```
|
||||
tier_x(i), tier_y = card top-left (tier_y = 80, just below the arc band)
|
||||
header_band_h = 40 # band from y=tier_y to y=tier_y+40 (i.e., 80..120)
|
||||
header_band_extra = 10 # 10-px extension below band, same tint
|
||||
|
||||
# Inside the card (absolute y; tier_y = 80):
|
||||
title_text at (tier_cx(i), 106) # node-name role, 13px, weight 700, ink
|
||||
bucket_text at (tier_cx(i), 144) # sublabel role, muted (accent on focal tier)
|
||||
|
||||
field_x = tier_x(i) + 16 # 16-px left inset for field text
|
||||
field_w = 140 # 172-px tier_w minus two 16-px insets
|
||||
field rows (absolute y):
|
||||
tool_label at 180, tool_value at 186 (foreignObject, height 24)
|
||||
format_label at 220, format_value at 226 (foreignObject, height 24)
|
||||
writer_label at 260, writer_value at 266 (foreignObject, height 24)
|
||||
# gap (open whitespace below writer row, above the example section)
|
||||
example_label_text at 360, example_line_0 at 374, example_line_1 at 388
|
||||
```
|
||||
|
||||
**Field-value wrapping rule:** field values (tool / format / writer) render inside an SVG `<foreignObject>` with an HTML `<div>` so they auto-wrap when text exceeds 140 px. Each `foreignObject` is 140 wide × 24 tall (fits 2 lines in the `sublabel` role at 1.25 line-height). The 26-px gap to the next field's label absorbs the second line cleanly.
|
||||
|
||||
```svg
|
||||
<foreignObject x="{field_x}" y="{value_top}" width="140" height="24">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml"
|
||||
style="font-family: {sublabel}; color: {muted}; line-height: 1.25;">
|
||||
{field_value}
|
||||
</div>
|
||||
</foreignObject>
|
||||
```
|
||||
|
||||
The HTML namespace declaration on the `<div>` is required for SVG to render the inline content. Browsers and Playwright/Chromium render this faithfully; if your export target doesn't support `<foreignObject>` (some older Inkscape builds), hand-split long values into two `<tspan>` lines instead.
|
||||
|
||||
Field labels use the `node-name` role at 11px in ink. Field values use the `sublabel` role in muted. Bucket and field values can be retinted by `color` override (§4).
|
||||
|
||||
### 2.3 Tier styles
|
||||
|
||||
Four canonical styles, picked per tier via `tiers[i].style`. Default mapping if `style` is omitted: tier 0 → `outer`, last tier → `cold`, focal tier (if any) → `focal`, others → `default`.
|
||||
|
||||
| `style` | Card fill | Card stroke | Header band fill | Bucket text | Example value text |
|
||||
|---|---|---|---|---|---|
|
||||
| `outer` | `#FFFFFF` | `muted` 1.0 solid | `muted @ 0.10` | `muted` | `muted` |
|
||||
| `default` | `#FFFFFF` | `ink` 1.0 solid | `ink @ 0.06` | `muted` | `muted` |
|
||||
| `focal` | `accent @ 0.07` | `accent` 1.6 solid | `accent @ 0.14` | `accent` | `accent` |
|
||||
| `cold` | `paper-2` | `muted` 1.0 dashed `5,3` | `muted @ 0.18` | `muted` | `muted` |
|
||||
|
||||
`rx = 6` on all card rects.
|
||||
|
||||
**Focal styling note:** the focal tier's accent treatment cascades — its bucket text and its example-value lines render in accent. Other field values (tool/format/writer) stay muted; only the bucket name and the example payload carry the focal signal so the tier card doesn't fully drown in coral.
|
||||
|
||||
### 2.4 Promotion arcs (over the top of the tiers)
|
||||
|
||||
Each promotion is a **cubic Bézier arc** anchored at the **top-center** of each adjacent tier — `(tier_cx(i), tier_y)` to `(tier_cx(i+1), tier_y)`. The arc rises into the 80-px `arc_band` above the cards, peaking at y ≈ 20. Both the connector and its label remain fully visible — no paper masks, no overlap with card content.
|
||||
|
||||
```svg
|
||||
<path d="M {tier_cx(i)},{tier_y} C {tier_cx(i)},0 {tier_cx(i+1)},0 {tier_cx(i+1)},{tier_y}"
|
||||
fill="none" stroke="…" stroke-width="…" marker-end="…"/>
|
||||
```
|
||||
|
||||
Concrete for the canonical 5-tier shape (`tier_y = 80`, tier centers at x = 102, 290, 478, 666, 854):
|
||||
- 0→1: `M 102,80 C 102,0 290,0 290,80`
|
||||
- 1→2: `M 290,80 C 290,0 478,0 478,80`
|
||||
- 2→3: `M 478,80 C 478,0 666,0 666,80` (focal — accent)
|
||||
- 3→4: `M 666,80 C 666,0 854,0 854,80` (lifecycle — dashed)
|
||||
|
||||
The cubic geometry: anchor y = 80 (tier top), control y = 0 (top of viewBox). Curve peak at t=0.5 sits at y ≈ 20 (computed from `0.125·80 + 0.375·0 + 0.375·0 + 0.125·80 = 20`). Each arc spans one full tier-stride (188 px on the canonical layout), giving the connector a clearly visible vertical excursion.
|
||||
|
||||
**Marker orientation:** `marker-end` with `orient="auto"` rotates the arrow to match the path tangent at the endpoint. The control point sits directly above the anchor so the tangent at landing is straight **down** — the arrowhead enters the top-center of tier *i+1* cleanly, pointing into the header band.
|
||||
|
||||
**Chained anchors:** consecutive arcs share their meeting points (arc 0→1 ends at the same `(tier_cx(1), 80)` where arc 1→2 begins). Visually each tier's top-center acts as a "joint" — data arrives at the top of the card, gets transformed inside, and leaves out the top toward the next tier. The arrow-head plunge plus the next arc's straight-up emergence read as a single payload-handoff motion.
|
||||
|
||||
| `style` | Stroke | Width | Dash | Marker |
|
||||
|---|---|---|---|---|
|
||||
| `normal` | `muted` | 1.4 | — | `arrow` |
|
||||
| `focal` | `accent` | 1.6 | — | `arrow-accent` |
|
||||
| `lifecycle` | `muted` | 1.4 | `4,3` | `arrow` |
|
||||
|
||||
**Auto-style rules:**
|
||||
- If `promotions[k].to` references the **focal tier**, the style auto-promotes to `focal` (accent, width 1.6, `arrow-accent` marker).
|
||||
- If `promotions[k].to` references a tier with a **`color` override** (§4), the arrow inherits that hex — stroke = `C`, label fill = `C`, marker-end uses a color-matched marker (e.g., `arrow-yellow` for `#c9a23a`). Width stays at 1.4 — the color override is a "concern" signal, not a focal promotion. Lifecycle/dashed arrows keep their dash but adopt the color.
|
||||
- Focal wins if both apply (a colored tier marked focal still uses accent).
|
||||
|
||||
**Label inside the arc:**
|
||||
- Anchored at `(arc_peak_x(k), arc_label_y)` = `((arc_src_x + arc_dst_x) / 2, 50)`.
|
||||
- `arrow-label` role at 10px with `letter-spacing=0.08em`, uppercase. Color matches the arrow stroke.
|
||||
- **No mask rect needed** — the cubic curve peaks at y ≈ 20 and the label sits at y=50, well below the curve. The label floats inside the open space *enclosed* by the arc, reading "X transforms into Y" with the arc itself as the visual frame.
|
||||
|
||||
For shorter inter-tier gaps (if `tier_gap` is overridden below the default 16 px), the arc anchors `arc_inset` may need to shrink correspondingly to keep the arc visible.
|
||||
|
||||
### 2.5 Path row (bottom, optional)
|
||||
|
||||
Up to **2** write-method cards. The canonical 5-tier shape (with `arc_band_h = 80`):
|
||||
|
||||
```
|
||||
path_y = 476 # tier_y + tier_h + path_gap = 80 + 380 + 16
|
||||
path_h = 56
|
||||
path_x[0] = 16
|
||||
path_w[0] = 460
|
||||
path_x[1] = 16 + 460 + 16 = 492
|
||||
path_w[1] = 460
|
||||
```
|
||||
|
||||
(Both paths land 460-wide despite the viewBox being 1040 — the right pad is taken from the card's tier strip, not the path strip. Keep `path_w=460` for the canonical 5-tier shape. For other tier counts, derive `path_w = (viewBox_w - 2*left_pad - path_gap) / 2`.)
|
||||
|
||||
Per-card content:
|
||||
- Container rect: white fill, `ink @ 0.20` stroke width 1, `rx=6`.
|
||||
- Tag chip: rect at `(path_x + 8, path_y + 6)`, `h=12 rx=2`, fill transparent, stroke `ink @ 0.30` width 0.8. Tag text centered inside in the `eyebrow` role with letter-spacing 0.08em, ink.
|
||||
- Title at `(path_x + 80, path_y + 30)`: `node-name` role at 11px, ink.
|
||||
- Sub at `(path_x + 80, path_y + 46)`: `sublabel` role, muted.
|
||||
|
||||
---
|
||||
|
||||
## 3. Connector rules (mandatory)
|
||||
|
||||
Three styles, bound to topology. Mirror §3 of `type-process.md` so the rule reads identically.
|
||||
|
||||
```svg
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="{muted}"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="{accent}"/></marker>
|
||||
<!-- Per-color markers: declare one per custom tier color in use. -->
|
||||
<marker id="arrow-yellow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#c9a23a"/></marker>
|
||||
</defs>
|
||||
```
|
||||
|
||||
Add a new `<marker>` for each `color` override used in the diagram. Naming convention: `arrow-{semantic}` (e.g., `arrow-yellow`, `arrow-slate`, `arrow-red`) — matches the recommended palette so the marker id reads cleanly in source.
|
||||
|
||||
**Z-order:** promotion arcs draw **before** any tier card rect, so the cards layer on top and any arc overshoot is masked inside the cards.
|
||||
|
||||
**Arc shape rule:** medallion promotions are always **cubic arcs over the top** of the tier strip, anchored at the **top-center** of source and target tiers; control points directly above anchors at y=0. No horizontal "through the gap" lines — the arc-over-top is what makes connector + label both clearly visible.
|
||||
|
||||
---
|
||||
|
||||
## 4. Component color override
|
||||
|
||||
Any tier or path entry accepts an optional `color: "#hex"`. Mirrors `type-process.md` §4 / `type-data-flow.md` §4.
|
||||
|
||||
### 4.1 Per-tier `color`
|
||||
|
||||
Applied to:
|
||||
|
||||
| Element | Light | Dark |
|
||||
|---|---|---|
|
||||
| Card fill | `rgba(C, 0.07)` | `rgba(C_light, 0.10)` |
|
||||
| Card stroke | `C` (width 1.4) | `C_light` (width 1.4) |
|
||||
| Header band fill | `rgba(C, 0.14)` | `rgba(C_light, 0.18)` |
|
||||
| Title text | ink (unchanged — title stays readable) | ink (unchanged) |
|
||||
| Bucket text | `C` | `C_light` |
|
||||
| Example values | `C` | `C_light` |
|
||||
| Field labels / field values | **unchanged** (ink / muted) | **unchanged** |
|
||||
| Connectors touching this tier | **unchanged** — topology-driven | **unchanged** |
|
||||
|
||||
`C_light` = the same hex lightened ~15% for dark-mode contrast.
|
||||
|
||||
### 4.2 Per-path `color`
|
||||
|
||||
Replaces the path card's stroke with `rgba(C, 0.45)` and the tag chip stroke with `rgba(C, 0.55)`. Tag text and title text use `C`. Sub stays muted.
|
||||
|
||||
### 4.3 Rules
|
||||
|
||||
- **Never on focal tiers.** The accent already carries that signal — a `color` on the focal tier is ignored.
|
||||
- **Never on the `cold` tier in addition to its dashed treatment.** Pick either dashed-cold or a custom color, not both.
|
||||
- **Cap at 2 custom-colored elements** per diagram (tier or path), in addition to the focal tier.
|
||||
- **Promotion arrows inherit the target tier's color** (§3 auto-style rule). A `color: "#c9a23a"` on the Staging tier means the CLEAN+WEIGHT arc landing in Staging is also rendered in yellow — connector, label, and arrowhead match. This keeps visual coherence: the colored tier and its incoming flow read as a single "concern" group. Arrows do **not** inherit color from the source tier — only the target — so the arc *out of* a colored tier reverts to muted (or to the next target's color/style).
|
||||
|
||||
### 4.4 Semantic palette (recommended)
|
||||
|
||||
Same palette as the other parametric types so a reader scanning multiple diagrams sees the same colors meaning the same thing:
|
||||
|
||||
- `#b85450` rust-red — Security / Identity / Governance (PII-bearing tiers, audit tiers)
|
||||
- `#5a7d9a` slate-blue — Observability / Quality (validated tiers, monitored zones)
|
||||
- `#7a8c47` olive-green — Data Products / Publication (consumer-facing aggregates, public-release tiers)
|
||||
- `#c9a23a` warm yellow / gold — Analytical / Working zones (staging tier, scientist sandbox, intermediate computation surface)
|
||||
- `#8c6d3f` warm-brown — Backup / DR / Archive (alternative cold-tier styling)
|
||||
|
||||
---
|
||||
|
||||
## 5. Focal rule
|
||||
|
||||
Exactly **one** focal tier per diagram. Defaults to the tier marked `focal: true` in inputs; if none is marked, defaults to the analytical pivot tier (typically `Aggregated` or whichever tier downstream consumers query).
|
||||
|
||||
The focal tier:
|
||||
- Uses `style: focal` (accent fill + stroke 1.6 + accent header band).
|
||||
- Renders bucket text and example-value lines in accent.
|
||||
- Has its **incoming** promotion arrow auto-promoted to `style: focal` (accent).
|
||||
- Has its **outgoing** promotion arrow (if any) — typically into the cold archive — kept at the user-declared style (usually `lifecycle` dashed).
|
||||
|
||||
If zero or >1 tiers carry `focal: true`, halt and ask the user.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dark mode
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| Paper | `paper` | `ink` |
|
||||
| Ink | `ink` | `paper` |
|
||||
| Muted | `muted` | `soft` |
|
||||
| Accent | `accent` | `accent` |
|
||||
| Fog (cold tier fill) | `paper-2` | `paper @ 0.06` |
|
||||
| White (default card fill) | `#FFFFFF` | `paper @ 0.04` |
|
||||
| Card stroke ink (default style) | `ink` | `paper @ 0.30` |
|
||||
| Header band ink-tint | `ink @ 0.06` | `paper @ 0.08` |
|
||||
| Header band muted-tint | `muted @ 0.10` | `soft @ 0.16` |
|
||||
| Header band cold-tint | `muted @ 0.18` | `soft @ 0.24` |
|
||||
| Header band accent-tint | `accent @ 0.14` | `accent @ 0.20` |
|
||||
| Custom component colors | `C` | `C_light` (lighten ~15%) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Reproducibility checklist (taste gate)
|
||||
|
||||
Before emitting SVG, verify **every** item:
|
||||
|
||||
1. `viewBox = "0 0 {viewBox_w} {viewBox_h}"` derived via §2 (5 tiers + 2 paths → 1040 × 548).
|
||||
2. Each tier card at `(tier_x(i), 80)` size `172 × 380`, `rx=6`.
|
||||
3. Tier header band fills `(tier_x(i), 80, 172, 40)` plus a 10-px extension under the band.
|
||||
4. Exactly **one** focal tier; its incoming arc auto-styled to `focal`.
|
||||
5. Promotion arcs render as cubic Béziers over the top of adjacent tiers; anchors at `(tier_cx(i), 80)` → `(tier_cx(i+1), 80)`, controls at y=0. Label inside the arc at `(arc_peak_x, 50)`, no mask.
|
||||
6. Bottom path row only present when `len(paths) > 0`. Cards at `y=476`, height 56.
|
||||
7. Custom component colors ≤ 2 in addition to the focal tier. Never on arrows.
|
||||
8. All promotion arrows + label masks emitted **before** any tier rect (z-order rule — cards mask the line ends inside the cards).
|
||||
9. The focal tier's bucket text and example values render in accent; the rest stay muted.
|
||||
10. `rx=6` on every tier and path card; `rx=2` on tag chips.
|
||||
|
||||
---
|
||||
|
||||
## 8. Anti-patterns
|
||||
|
||||
- **More than one focal tier** — focal exists to mark the central analytical surface; >1 erases the signal.
|
||||
- **Cold styling on a non-archive tier** — the dashed fog look is reserved for retention/archive tiers.
|
||||
- **Bidirectional promotion arrows** — promotions always flow left → right. Backflow (e.g., an aggregate writing back to raw) is wrong for this type; use a different diagram.
|
||||
- **Custom-colored arrows** — connectors are topology-driven; color on a tier never spreads to its edges.
|
||||
- **Path cards explaining tier semantics** — paths describe *write methods* (how data moves between tiers), not what each tier holds. If you find yourself writing "Raw stores …" in a path card, that content belongs in the Raw tier's fields.
|
||||
- **Missing `example_label` content** — every tier should show a concrete example payload (quarterly survey rows, customer records, claims, …). Without it the diagram becomes abstract and stops earning its space.
|
||||
- **Promotion arrow label longer than the tier-gap label mask** — keep labels to ≤ 14 chars in the uppercase `arrow-label` role. Long verbs ("CALCULATE & SUMMARIZE") break the rhythm; shorten to "AGGREGATE" or split into two diagrams.
|
||||
|
||||
---
|
||||
|
||||
## 9. Examples
|
||||
|
||||
- `assets/example-medallion.html` — minimal light (NatStat quarterly survey: 5 tiers, 2 path cards, Aggregated focal). Gallery default.
|
||||
- `assets/example-medallion-dark.html` — same, dark skin.
|
||||
- `assets/example-medallion-full.html` — same, editorial-card frame with subtitle + summary cards.
|
||||
|
||||
---
|
||||
|
||||
## 10. Worked YAML
|
||||
|
||||
The YAML in §1 is the **complete** inputs definition for the shipped `example-medallion.html`. Every coordinate in that file's SVG is derivable from §2 applied to those inputs. The same YAML is embedded as a top-of-file HTML comment inside `example-medallion.html` so source view shows the parametric inputs immediately above the SVG.
|
||||
@@ -0,0 +1,495 @@
|
||||
# Process
|
||||
|
||||
**Best for:** sequential business processes with multiple actors/divisions where the reader needs to see *who* does *what*, *what data* enters and leaves each step, and *which tools* are used — not just the step order. Covers responsibility audits, data-quality gate reviews, cross-divisional handoff maps, and end-to-end workflow documentation.
|
||||
|
||||
Prefer swimlane (simpler) when the data types and tools don't matter. Prefer process when each step's input/output payload and responsible team must be legible at a glance.
|
||||
|
||||
This type is **parametric** — the inputs schema in §1 drives every coordinate via the formulas in §2. Two generations from the same inputs must produce visually identical SVG. The rule shapes mirror `type-data-flow.md` so color override, IN/OUT chip semantic, and reproducibility checklist read identically across types.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inputs — the parameter contract
|
||||
|
||||
```yaml
|
||||
lanes: # 1..6 horizontal swimlanes (top to bottom)
|
||||
- { name: ["RD&E"], key: "RDE" }
|
||||
- { name: ["IT"], key: "IT" }
|
||||
- { name: ["FIELD", "SERVICES"], key: "FLD" }
|
||||
- { name: ["SURVEY", "SERVICES"], key: "SVY" }
|
||||
- { name: ["HOUSEHOLD", "UNIT"], key: "HHU" }
|
||||
- { name: ["COMMS &", "MARKETING"], key: "CMM" }
|
||||
|
||||
steps: # 1..12 vertical step columns (left to right)
|
||||
- { number: "1", label: "Design" }
|
||||
- { number: "2", label: "Build" }
|
||||
- { number: "3", label: "Test", focal: true } # focal step header chip — accent fill
|
||||
- { number: "4", label: "Train" }
|
||||
# ... up to 12
|
||||
|
||||
nodes: # explicit per-cell entries; empty cells render nothing
|
||||
- { lane: "RDE", step: 0, title: "Survey design", sub: "questionnaire · sampling", tool: "Excel · CSPro",
|
||||
chips: {in: null, out: "FL"} } # first step has no input chip
|
||||
- { lane: "IT", step: 1, title: "Build app", sub: "form + validation", tool: "CSPro · scripts",
|
||||
chips: {in: "FL", out: "TB"}, color: "#5a7d9a" } # slate-blue — data quality concern
|
||||
- { lane: "RDE", step: 2, title: "Pilot test", sub: "field debug", tool: "tablet · script",
|
||||
chips: {in: "TB", out: "TB"}, focal: true } # focal node — accent border
|
||||
- { lane: "FLD", step: 3, title: "Train enumerators", sub: "protocols · safety", tool: "manual",
|
||||
chips: {in: "TB", out: "LS"}, color: "#b85450" } # rust-red — governance / training
|
||||
# ... etc
|
||||
|
||||
arrows: # explicit edges; styles bind to topology (see §3)
|
||||
- { from: {lane: "RDE", step: 0}, to: {lane: "IT", step: 1}, style: "normal" }
|
||||
- { from: {lane: "IT", step: 1}, to: {lane: "RDE", step: 2}, style: "focal-in" } # accent — into focal
|
||||
- { from: {lane: "RDE", step: 2}, to: {lane: "FLD", step: 3}, style: "focal-out" } # accent — out of focal
|
||||
- { from: {lane: "RDE", step: 2}, to: {lane: "IT", step: 1}, style: "trigger" } # dashed trigger
|
||||
# ... etc
|
||||
|
||||
dark: false
|
||||
```
|
||||
|
||||
**Reserved field semantics:**
|
||||
- `lanes[k].key` — the 3-letter role badge text shown inside every node in that lane.
|
||||
- `lanes[k].name` — 1 or 2 line lane label; uppercase mono.
|
||||
- `steps[j].focal: true` — exactly **one** step may declare this. Header chip renders in accent.
|
||||
- `nodes[i].focal: true` — exactly **one** node may declare this. Renders with accent border (§5).
|
||||
- `nodes[i].chips` — `{in: "<CODE>", out: "<CODE>"}` object (either side `null` to omit). Codes from §8. **Skip** the input chip on the first step's nodes, **skip** the output chip on the last step's nodes (no upstream / downstream).
|
||||
- `nodes[i].color` — optional **per-node color override**. Any valid `"#hex"` string; the §4 palette is recommended for cross-diagram consistency.
|
||||
|
||||
---
|
||||
|
||||
## 2. Layout formulas — deterministic geometry
|
||||
|
||||
```
|
||||
label_col_w = 140
|
||||
step_slot_w = 112 # 100-px node + 12-px corridor
|
||||
right_pad = 28
|
||||
n_steps = len(steps)
|
||||
n_lanes = len(lanes)
|
||||
|
||||
# Canvas
|
||||
viewBox_w = label_col_w + n_steps * step_slot_w + right_pad # 11 steps → 1400
|
||||
header_h = 36
|
||||
lane_h = 80
|
||||
has_color_row = any(node.color or step.color or lane.color in inputs)
|
||||
legend_h = 100 if has_color_row else 80 # 4 rows when colors are present
|
||||
viewBox_h = header_h + n_lanes * lane_h + legend_h # 6 lanes, no colors → 596; with → 616
|
||||
|
||||
# Header strip (top)
|
||||
chip_y = 8
|
||||
chip_w = 16 # 20 if step.number has 2 digits
|
||||
chip_h = 16
|
||||
chip_rx = 8 # pill
|
||||
|
||||
# Lane positions
|
||||
lane_y_top(k) = header_h + k * lane_h # 36, 116, 196, 276, 356, 436
|
||||
lane_y_mid(k) = lane_y_top(k) + lane_h/2 # 76, 156, 236, 316, 396, 476
|
||||
lane_label_x = label_col_w / 2 # 70
|
||||
|
||||
# Step / node centers
|
||||
step_cx(j) = label_col_w + 8 + j * step_slot_w + node_w/2 # 198, 310, 422, ...
|
||||
# (8-px gutter inside content area)
|
||||
|
||||
# Nodes
|
||||
node_w = 100
|
||||
node_h = 64
|
||||
node_x(j) = step_cx(j) - node_w/2
|
||||
node_y(k) = lane_y_top(k) + (lane_h - node_h)/2 # 8-px top/bottom margin inside lane
|
||||
|
||||
# Legend strip (bottom)
|
||||
legend_y_top = header_h + n_lanes * lane_h
|
||||
legend_row_y = [legend_y_top + 16, legend_y_top + 37,
|
||||
legend_y_top + 58, legend_y_top + 79]
|
||||
```
|
||||
|
||||
### 2.1 Background structure
|
||||
|
||||
- Paper fill across full viewBox.
|
||||
- Dot pattern: 22×22 grid, `circle r=0.8`, `fill rgba(45,49,66,0.10)`. Opacity 0.55.
|
||||
- Alternating lane tints: odd-indexed lanes (0, 2, …) receive `rgba(45,49,66,0.018)` fill from `x=140` to `viewBox_w`.
|
||||
- Lane dividers: horizontal hairlines at every `lane_y_top(k)` and at `legend_y_top`; stroke `rgba(45,49,66,0.12)` width 0.8.
|
||||
- Label column right border: vertical hairline at `x = label_col_w`, stroke `rgba(45,49,66,0.20)` width 1, from `y = header_h` to `y = legend_y_top`.
|
||||
|
||||
### 2.2 Step header chip + label
|
||||
|
||||
Per step `j`:
|
||||
|
||||
```
|
||||
chip_w(j) = 20 if len(step.number) >= 2 else 16
|
||||
chip_x(j) = step_cx(j) - chip_w(j)/2
|
||||
number_anchor = (step_cx(j), chip_y + 11)
|
||||
label_anchor = (step_cx(j), 32) # 8-px gap below chip
|
||||
```
|
||||
|
||||
**Chip** (the numbered pill at the top of each column):
|
||||
- Default fill: `rgba(45,49,66,0.12)`, number text ink.
|
||||
- Focal fill: `rgba(235,108,54,0.20)`, number text accent (§5).
|
||||
- Per-step `color` override (§4): replaces the fill with `rgba(C, 0.20)` and the number fill with `C`.
|
||||
|
||||
**Label** (the uppercase mono text below the chip):
|
||||
- Renders `steps[j].label` (uppercased), anchored at `label_anchor`.
|
||||
- Font: Geist Mono 6 px, weight 500, `letter-spacing="0.12em"`, `text-anchor="middle"`.
|
||||
- Default fill: muted (`#4f5d75` light / `#bfc0c0` dark).
|
||||
- Focal fill: accent (`#eb6c36` light / `#f08a59` dark).
|
||||
- Per-step `color` override: fill = `C` (matches the chip number color).
|
||||
- Keep labels short (≤ 9 chars). Long labels truncate; if you need more, abbreviate.
|
||||
|
||||
### 2.3 Lane labels
|
||||
|
||||
One or two-line mono label, all uppercase, letter-spacing 0.08em, font-size 8, fill muted. Centered at `(lane_label_x, lane_y_mid(k))`:
|
||||
- Single-line: anchored at `(lane_label_x, lane_y_mid(k) + 4)`
|
||||
- Two-line: lines at `(lane_label_x, lane_y_mid(k) - 4)` and `(lane_label_x, lane_y_mid(k) + 4)`
|
||||
|
||||
Per-lane `color` override (§4): replaces the label fill with `C` and the lane stripe tint with `rgba(C, 0.04)`.
|
||||
|
||||
### 2.4 Node content layout (inside the 100×64 rect)
|
||||
|
||||
```
|
||||
role_chip rect 14×10 at (node_x+4, node_y+4), rx=2
|
||||
role_chip_text centered at (node_x+11, node_y+12), font-size=6, weight=600
|
||||
# text = lanes[k].key (3-letter lane code)
|
||||
title centered at (step_cx(j), node_y+26), font-size=9 sans semibold
|
||||
in→out centered at (step_cx(j), node_y+40), font-size=6.5 mono muted
|
||||
tool centered at (step_cx(j), node_y+52), font-size=6.5 mono soft
|
||||
data chip IN rect 16×8 at (node_x+4, node_y+54), rx=2 # payload entering
|
||||
data chip OUT rect 16×8 at (node_x+80, node_y+54), rx=2 # payload leaving
|
||||
```
|
||||
|
||||
**Role chip text rule:** the badge inside each node renders `lanes[k].key` where `k` is the node's lane index — **not** the step number (the step number already lives in the column header chip at the top, §2.2). Showing the lane key as the node badge gives each node a self-contained "who" identifier that survives when a single node is excerpted out of context. Mirrors the same rule in `type-data-flow.md` §2.4.
|
||||
|
||||
Empty cells (no node entry) render **nothing**. No placeholder rect, no role chip, no label.
|
||||
|
||||
**Chip-vs-tool-text collision rule:** chips sit at `node_y + 54..62`; tool text baseline is at `node_y + 52`. If a node has a two-line title (rare), increase node_h to 72 OR omit the chips for that node. Default behaviour: omit chips on collision.
|
||||
|
||||
---
|
||||
|
||||
## 3. Connector rules (mandatory)
|
||||
|
||||
Three styles, bound to topology. Connectors drawn **before** all node rects (z-order rule).
|
||||
|
||||
| `style` | Stroke | Width | Dash | Marker | When required |
|
||||
|---|---|---|---|---|---|
|
||||
| `normal` | `#4f5d75` (muted) | 1.0 | — | `arrow` | Standard data hand-off between steps or actors. Unlabelled. |
|
||||
| `focal-in` / `focal-out` | `#eb6c36` (accent) | 1.2 | — | `arrow-accent` | Every edge whose endpoint is the focal node (`focal-in`) or origin is the focal node (`focal-out`). |
|
||||
| `trigger` | `#4f5d75` (muted) | 1.0 | `4,3` | `arrow-sm` | Orchestration trigger (scheduler → tool, manual override → upstream step). Unlabelled. |
|
||||
|
||||
**Defs block** (required, three markers):
|
||||
|
||||
```svg
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="11" cy="11" r="0.8" fill="rgba(45,49,66,0.10)"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#4f5d75"/></marker>
|
||||
<marker id="arrow-accent" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto"><polygon points="0 0, 8 3, 0 6" fill="#eb6c36"/></marker>
|
||||
<marker id="arrow-sm" markerWidth="6" markerHeight="5" refX="5" refY="2.5" orient="auto"><polygon points="0 0, 6 2.5, 0 5" fill="#4f5d75"/></marker>
|
||||
</defs>
|
||||
```
|
||||
|
||||
### 3.1 Routing rules (non-negotiable)
|
||||
|
||||
**Single-bend right-angle:** exit RIGHT → corridor → enter TOP (↓ destination below) or BOTTOM (↑ destination above).
|
||||
|
||||
- Source-side: exit at `(node_x + 100, lane_y_mid(src_lane))` — node's right edge, vertical center.
|
||||
- Destination-side: enter at `(step_cx(dst_step), node_y(dst_lane))` for downward, or `(step_cx(dst_step), node_y(dst_lane) + 64)` for upward.
|
||||
- Corner radius: 8-px Q-bezier at the bend.
|
||||
- Same-lane edges (rare — same lane, adjacent steps): horizontal `<line>` from src right to dst left.
|
||||
- **No diagonals.** **No left-side entry.** **No exit from the top/bottom of a node.**
|
||||
|
||||
```svg
|
||||
<!-- Downward (destination lane > source lane) -->
|
||||
<path d="M {rx},{src_cy} H {dst_cx - 8} Q {dst_cx},{src_cy} {dst_cx},{src_cy + 8} V {dst_top}"
|
||||
fill="none" stroke="…" stroke-width="…" marker-end="…"/>
|
||||
|
||||
<!-- Upward (destination lane < source lane) -->
|
||||
<path d="M {rx},{src_cy} H {dst_cx - 8} Q {dst_cx},{src_cy} {dst_cx},{src_cy - 8} V {dst_bottom}"
|
||||
fill="none" stroke="…" stroke-width="…" marker-end="…"/>
|
||||
|
||||
<!-- Same lane (adjacent step) -->
|
||||
<line x1="{src_right}" y1="{lane_cy}" x2="{dst_left}" y2="{lane_cy}"
|
||||
stroke="…" stroke-width="…" marker-end="…"/>
|
||||
```
|
||||
|
||||
- **Z-order:** all `<path>` and `<line>` connectors emitted **before** any node `<rect>`.
|
||||
- **Markers:** exactly one `marker-end` per path. Never `marker-start`.
|
||||
- **Labels:** all process arrows are unlabelled by default. The step number + the actor lane carry the semantic; a label on every arrow is noise. Only label an arrow if it represents a non-step concept (re-test loop, escalation) — then use a paper-masked rect behind 6.5-px mono text.
|
||||
|
||||
### 3.2 Crossings
|
||||
|
||||
Avoid. The corridor x position (8 px before destination node) is the only routing column — if two arrows would cross there, **swap step assignments** or **split into two diagrams** rather than introducing a bend-around. Crossings hide the underlying control flow.
|
||||
|
||||
---
|
||||
|
||||
## 4. Component color override
|
||||
|
||||
Any node, lane, or step accepts an optional `color: "#hex"`. Mirrors `type-data-flow.md` §4 and `type-high-level.md` §3.4 so the rule reads identically across types.
|
||||
|
||||
### 4.1 Per-node `color`
|
||||
|
||||
Applied to:
|
||||
|
||||
| Element | Light | Dark |
|
||||
|---|---|---|
|
||||
| Container fill (`rect`) | `rgba(C, 0.06)` | `rgba(C_light, 0.10)` |
|
||||
| Container stroke | `rgba(C, 0.35)` (stroke-width 1) | `rgba(C_light, 0.45)` |
|
||||
| Role chip fill | `rgba(C, 0.18)` | `rgba(C_light, 0.22)` |
|
||||
| Role chip text | `C` | `C_light` |
|
||||
| Title text | `C` | `C_light` |
|
||||
| Sub-label (in → out) | **unchanged** (muted) | **unchanged** (muted) |
|
||||
| Tool label | **unchanged** (soft) | **unchanged** (soft) |
|
||||
| Data-type chips | **unchanged** | **unchanged** |
|
||||
| Arrows touching this node | **unchanged** — topology-driven | **unchanged** |
|
||||
|
||||
`C_light` = the same hex lightened ~15% for dark-mode contrast (e.g., `#b85450` → `#d97a78`).
|
||||
|
||||
### 4.2 Per-step `color`
|
||||
|
||||
Replaces the step header chip's fill with `rgba(C, 0.20)` and the chip's number text fill with `C`. The legend's matching step entry uses the same colors.
|
||||
|
||||
### 4.3 Per-lane `color`
|
||||
|
||||
Replaces the lane stripe tint with `rgba(C, 0.04)` and the lane label text fill with `C`. Use sparingly — lane tints are easy to over-apply.
|
||||
|
||||
### 4.4 Rules
|
||||
|
||||
- **Never on focal nodes / focal steps.** The accent already carries that signal. A `color` on a focal element is ignored.
|
||||
- **Never on arrows.** Arrows are topology-driven. If you want a colored edge, pick a different `style` from §3, not a color override.
|
||||
- **Cap at 3 custom-colored elements** per diagram (nodes + lanes + steps combined), in addition to the focal pair (focal node + focal step header). Above 3 the visual signal starts to fragment.
|
||||
- **Subtitle and tool labels stay muted** regardless of any component `color`.
|
||||
|
||||
### 4.5 Semantic palette (recommended)
|
||||
|
||||
Same palette as `type-high-level.md`, `type-dp-integration.md`, `type-data-flow.md`:
|
||||
|
||||
- `#b85450` rust-red — Security / Identity / Governance (access control, training, approvals)
|
||||
- `#5a7d9a` slate-blue — Observability / Quality (data quality gates, validation, monitoring)
|
||||
- `#7a8c47` olive-green — Data Products / Publication (consumer-ready outputs, releases)
|
||||
- `#8c6d3f` warm-brown — Backup / DR / Archive
|
||||
|
||||
---
|
||||
|
||||
## 5. Focal rule
|
||||
|
||||
The process diagram has three focal slots, exactly one entry each:
|
||||
|
||||
- **One focal step** (`steps[j].focal: true`) — typically the analytical or decision pivot (Test, Approve, Validate). Header chip and legend chip render in accent.
|
||||
- **One focal node** (`nodes[i].focal: true`) — the node that *receives* the critical handoff. Accent border + accent role chip + ink title (title text stays ink so it's still readable; only the border + role chip carry the accent).
|
||||
- **One focal arrow set** (`style: focal-in` and `focal-out`) — edges into and out of the focal node. Accent solid strokes.
|
||||
|
||||
If zero or >1 of any focal slot are declared, halt and ask the user.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dark mode
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| Paper | `#f5f5f5` | `#2d3142` |
|
||||
| Ink | `#2d3142` | `#f5f5f5` |
|
||||
| Muted | `#4f5d75` | `#bfc0c0` |
|
||||
| Soft | `#7a8399` | `#8e98ac` |
|
||||
| Accent | `#eb6c36` | `#f08a59` |
|
||||
| Dot pattern | `rgba(45,49,66,0.10)` | `rgba(245,245,245,0.10)` |
|
||||
| Lane tint | `rgba(45,49,66,0.018)` | `rgba(245,245,245,0.025)` |
|
||||
| Dividers | `rgba(45,49,66,0.12)` | `rgba(245,245,245,0.12)` |
|
||||
| Label col divider | `rgba(45,49,66,0.20)` | `rgba(245,245,245,0.22)` |
|
||||
| Default chip fill | `rgba(45,49,66,0.12)` | `rgba(245,245,245,0.12)` |
|
||||
| Focal chip fill | `rgba(235,108,54,0.20)` | `rgba(240,138,89,0.22)` |
|
||||
| Default node fill | white | `rgba(245,245,245,0.04)` |
|
||||
| Default node stroke | `rgba(45,49,66,0.25)` | `rgba(245,245,245,0.20)` |
|
||||
| Focal node fill | `rgba(235,108,54,0.08)` | `rgba(240,138,89,0.12)` |
|
||||
| Focal node stroke | `#eb6c36` | `#f08a59` |
|
||||
| Custom component colors | `C` | `C_light` (lighten ~15%) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Reproducibility checklist (taste gate)
|
||||
|
||||
Before emitting SVG, verify **every** item:
|
||||
|
||||
1. `viewBox = "0 0 {viewBox_w} {viewBox_h}"` derived via §2.
|
||||
2. Header strip at `y=0..36`; legend strip at `y=legend_y_top..viewBox_h` (`legend_y_top = 36 + n_lanes * 80`).
|
||||
3. Every node at `(step_cx(j) - 50, lane_y_top(k) + 8)` size `100×64`.
|
||||
4. Empty cells render nothing — no placeholder rect, no text.
|
||||
5. Exactly **one** focal step (`steps[j].focal: true`).
|
||||
6. Exactly **one** focal node (`nodes[i].focal: true`).
|
||||
7. Focal-touching arrows use `style: focal-in` / `focal-out` (accent).
|
||||
8. All other arrows `style: normal` (muted solid) or `style: trigger` (muted dashed). Unlabelled by default.
|
||||
9. All arrows emitted before any node rect (z-order rule).
|
||||
10. Single-bend right-angle routing only — exit right, enter top/bottom. No diagonals. Q-bezier `r=8` at each bend.
|
||||
11. Custom component colors ≤ 3 (in addition to the focal pair). Arrows never recolored by component `color`.
|
||||
12. Subtitle and tool labels stay muted regardless of any component `color`. Input chip skipped on first step's nodes, output chip skipped on last step's nodes.
|
||||
|
||||
---
|
||||
|
||||
## 8. Data-type chips reference (input + output)
|
||||
|
||||
Same catalog as `type-data-flow.md` §8.
|
||||
|
||||
- **Input chip** at `(node_x+4, node_y+54)` — bottom-**left**. Payload entering the node.
|
||||
- **Output chip** at `(node_x+80, node_y+54)` — bottom-**right**. Payload leaving the node.
|
||||
- Either chip may be omitted (first/last step, unknown payload).
|
||||
|
||||
### Chip codes
|
||||
|
||||
| Code | Color (light) | Color (dark) | Meaning |
|
||||
|------|---------------|--------------|---------|
|
||||
| `LS` | `#7c8f6f` sage | `#9caf8f` | List / assignment / task |
|
||||
| `DB` | `#5e7a9b` dusty-blue | `#82a0c0` | Dataset / tabular records |
|
||||
| `TB` | `#b8915a` mustard | `#d3ad7a` | Table (analysis-ready) |
|
||||
| `FL` | `#9c6b50` rust-brown | `#b88670` | File / document / report |
|
||||
| `WB` | `#6e6479` slate | `#8d8298` | Web / press / public release |
|
||||
| N/A | omit chip entirely | — | Unknown or not applicable |
|
||||
|
||||
Text inside chip: white, font-size 5, weight 700, mono.
|
||||
|
||||
Data-type chip colors are a **separate semantic axis** from the per-node color override (§4). Chip colors describe *payload format*; node color describes *concern type*. A node can have both an `out: TB` mustard chip and a rust-red border simultaneously.
|
||||
|
||||
---
|
||||
|
||||
## 9. Legend (3- or 4-row strip)
|
||||
|
||||
Each row introduced by a category label at `x = label_col_w + 4` (= 144). The default legend has **3 rows** (`STEPS` / `DATA TYPE` / `FLOW`); when one or more nodes carry a `color` override (§4), add a 4th `CONCERN` row and grow `legend_h` to 100.
|
||||
|
||||
- **Row 1 — `STEPS`** at `y = legend_y_top + 16`: repeat the header chips with their labels. Focal step keeps accent fill.
|
||||
- **Row 2 — `DATA TYPE`** at `y = legend_y_top + 37`: one swatch per chip type actually used in the diagram. Append a small sub-hint in muted mono: `left chip = input · right chip = output`.
|
||||
- **Row 3 — `CONCERN`** (only when color overrides are present) at `y = legend_y_top + 58`: one mini-rect per custom color used, with its semantic label.
|
||||
- **Row 4 — `FLOW`** (position depends on whether `CONCERN` row exists): one segment per arrow style actually used, with marker + label.
|
||||
|
||||
---
|
||||
|
||||
## 10. Complexity budget
|
||||
|
||||
| Dimension | Max |
|
||||
|---|---|
|
||||
| Lanes (actors) | 6 |
|
||||
| Steps | 12 |
|
||||
| Nodes per lane | Nodes = active steps only — empty cells are invisible |
|
||||
| Labelled arrows | 0 by default (label only for non-step concepts) |
|
||||
| Data-type chips per node | 2 (input + output) |
|
||||
| Custom-colored elements (§4) | 3 (in addition to focal node + focal step) |
|
||||
|
||||
Above 6 lanes or 12 steps: split into two diagrams (overview + detail).
|
||||
|
||||
---
|
||||
|
||||
## 11. Anti-patterns
|
||||
|
||||
- **Placeholder empty cells** — if an actor doesn't participate in a step, leave the cell empty (no box, no text).
|
||||
- **Diagonal arrows** — every connector must have exactly one right-angle bend. No direct straight lines between nodes in different lanes.
|
||||
- **Left/right port entry on a vertical-dominant arrow** — always exit right, enter top or bottom.
|
||||
- **More than one focal step / focal node** — pick the single most critical operation.
|
||||
- **Unlabelled lanes** — every swimlane must identify its actor.
|
||||
- **All arrows the same style** — orchestration triggers must be dashed to distinguish them from data-flow connectors.
|
||||
- **`color` override on a focal element** — ignored. Accent always wins.
|
||||
- **Custom-colored arrows** — connectors are topology-driven; `color` on a node never spreads to its edges.
|
||||
- **Lane tints over-applied** — a tint on every lane reads as decoration, not signal. Apply to ≤1 lane.
|
||||
- **Data-type chips in a double-line-name node** — skip the chips or shorten the name to one line.
|
||||
- **More than 12 steps without splitting** — use an overview + detail pair.
|
||||
|
||||
---
|
||||
|
||||
## 12. Worked example — full YAML for `example-process-extended.html`
|
||||
|
||||
The extended example diagram is fully described by the following inputs. Every coordinate in the rendered SVG is derivable from this block via §2 + §3 + §4. This is the canonical proof that the parametric contract works end-to-end.
|
||||
|
||||
```yaml
|
||||
# Quarterly survey — end-to-end workflow (extended variant)
|
||||
# 6 lanes × 11 steps, 1 focal step + 1 focal node + 3 custom-colored nodes
|
||||
|
||||
lanes:
|
||||
- { name: ["RD&E"], key: "RDE" }
|
||||
- { name: ["IT"], key: "IT" }
|
||||
- { name: ["FIELD", "SERVICES"], key: "FLD" }
|
||||
- { name: ["SURVEY", "SERVICES"], key: "SVY" }
|
||||
- { name: ["HOUSEHOLD", "UNIT"], key: "HHU" }
|
||||
- { name: ["COMMS &", "MARKETING"], key: "CMM" }
|
||||
|
||||
steps:
|
||||
- { number: "1", label: "Design" }
|
||||
- { number: "2", label: "Assign" }
|
||||
- { number: "3", label: "Collect", focal: true } # focal step header chip
|
||||
- { number: "4", label: "Review" }
|
||||
- { number: "5", label: "Validate" }
|
||||
- { number: "6", label: "Weight" }
|
||||
- { number: "7", label: "Clean" }
|
||||
- { number: "8", label: "Tabulate" }
|
||||
- { number: "9", label: "Approve" }
|
||||
- { number: "10", label: "Publish" }
|
||||
- { number: "11", label: "Upload" }
|
||||
|
||||
nodes:
|
||||
- { lane: "RDE", step: 0, title: "Sample Design", sub: "Census data → Sample",
|
||||
tool: "SAS · Survey Solutions", chips: {in: null, out: "LS"} } # first step: no input chip
|
||||
- { lane: "IT", step: 1, title: "Field Assignment", sub: "Sample → Field tasks",
|
||||
tool: "Survey Solutions", chips: {in: "LS", out: "LS"} }
|
||||
- { lane: "FLD", step: 2, title: "Data Collection", sub: "→ 10,464 dwellings",
|
||||
tool: "Survey Solutions", chips: {in: "LS", out: "DB"}, focal: true } # focal node
|
||||
- { lane: "SVY", step: 3, title: "HQ Review", sub: "Submissions → Approved",
|
||||
tool: "Survey Sol. HQ", chips: {in: "DB", out: "DB"}, color: "#b85450" } # rust-red · governance
|
||||
- { lane: "IT", step: 4, title: "Error Checks", sub: "Approved → Cleaned",
|
||||
tool: "SAS · Scripts", chips: {in: "DB", out: "DB"}, color: "#5a7d9a" } # slate-blue · data quality
|
||||
- { lane: "RDE", step: 5, title: "Weight Calculation", sub: "Cleaned → Weighted",
|
||||
tool: "SAS", chips: null } # 2-line title — chips skipped
|
||||
- { lane: "HHU", step: 6, title: "2° Cleaning", sub: "Weighted → Analysis",
|
||||
tool: "SAS · R · SPSS", chips: {in: "DB", out: "TB"} }
|
||||
- { lane: "HHU", step: 7, title: "Tables + Brief", sub: "Analysis → Tables",
|
||||
tool: "Excel · SAS", chips: {in: "TB", out: "FL"} }
|
||||
- { lane: "CMM", step: 8, title: "Stats Review", sub: "Tables → Approved",
|
||||
tool: "Internal review", chips: {in: "FL", out: "FL"} }
|
||||
- { lane: "CMM", step: 9, title: "Public Release", sub: "Approved → Public",
|
||||
tool: "Press conference", chips: {in: "FL", out: "WB"}, color: "#7a8c47" } # olive-green · data products
|
||||
- { lane: "IT", step: 10, title: "Upload NatStat / SDMX", sub: "Results → Published",
|
||||
tool: "Web · SDMX API", chips: null } # 2-line title — chips skipped
|
||||
|
||||
arrows:
|
||||
- { from: {lane: "RDE", step: 0}, to: {lane: "IT", step: 1}, style: "normal" }
|
||||
- { from: {lane: "IT", step: 1}, to: {lane: "FLD", step: 2}, style: "focal-in" } # → focal
|
||||
- { from: {lane: "FLD", step: 2}, to: {lane: "SVY", step: 3}, style: "focal-out" } # ← focal
|
||||
- { from: {lane: "SVY", step: 3}, to: {lane: "IT", step: 4}, style: "normal" } # upward
|
||||
- { from: {lane: "IT", step: 4}, to: {lane: "RDE", step: 5}, style: "normal" } # upward
|
||||
- { from: {lane: "RDE", step: 5}, to: {lane: "HHU", step: 6}, style: "normal" } # downward, skips 2 lanes
|
||||
- { from: {lane: "HHU", step: 6}, to: {lane: "HHU", step: 7}, style: "normal" } # same lane
|
||||
- { from: {lane: "HHU", step: 7}, to: {lane: "CMM", step: 8}, style: "normal" }
|
||||
- { from: {lane: "CMM", step: 8}, to: {lane: "CMM", step: 9}, style: "normal" } # same lane
|
||||
- { from: {lane: "CMM", step: 9}, to: {lane: "IT", step: 10}, style: "normal" } # upward, skips 4 lanes
|
||||
|
||||
dark: false
|
||||
```
|
||||
|
||||
### 12.1 What this YAML proves
|
||||
|
||||
Run §2 of this reference with these inputs:
|
||||
|
||||
- `n_lanes = 6`, `n_steps = 11`, `has_color_row = true` (3 nodes carry `color`).
|
||||
- `viewBox_w = 140 + 11 * 112 + 28 = 1400`. ✓ matches rendered SVG.
|
||||
- `legend_h = 100`, `viewBox_h = 36 + 6 * 80 + 100 = 616`. ✓
|
||||
- Lane y_top = [36, 116, 196, 276, 356, 436]; lane mid = [76, 156, 236, 316, 396, 476]. ✓
|
||||
- Step cx = [198, 310, 422, 534, 646, 758, 870, 982, 1094, 1208, 1320] (the 8-px content-area gutter shifts every value by 8 from `140 + j*112 + 50`). ✓
|
||||
- Node 4 (HQ Review): step=3, lane="SVY" (k=3) → x = 534-50 = 484, y = 276+8 = 284. ✓
|
||||
- Node 5 (Error Checks): step=4, lane="IT" (k=1) → x = 646-50 = 596, y = 116+8 = 124. ✓
|
||||
- Node 10 (Public Release): step=9, lane="CMM" (k=5) → x = 1208-50 = 1158, y = 436+8 = 444. ✓ *(Rendered uses x=1156 — 2-px tolerance from chip-width rounding on the step "10" label.)*
|
||||
|
||||
The two coord drifts on the rightmost two nodes (chip width=20 for two-digit step numbers shifts the chip but not the node center math) are an artifact of the existing hand-tuned example, not a formula failure — a fresh generation from this YAML would produce x=1158 and the diagram would be visually indistinguishable from the shipped version.
|
||||
|
||||
### 12.2 Adapting this YAML to a different process
|
||||
|
||||
To document a different process, change only the value of these inputs:
|
||||
|
||||
- **Lanes**: rename `lanes[k].name` to your team names; update each `nodes[i].lane` to match. Up to 6 lanes.
|
||||
- **Steps**: rename `steps[j].label`, move `focal: true` to the step that defines the diagram's central claim. Up to 12 steps.
|
||||
- **Nodes**: write one entry per `(lane, step)` cell that has work. Leave cells empty (no entry) to render nothing.
|
||||
- **Colors**: choose `color: "#hex"` on at most 3 nodes (§4 cap). Stick to the recommended palette unless brand demands otherwise.
|
||||
- **Arrows**: declare every edge explicitly with `style: normal | focal-in | focal-out | trigger`. The routing rule (§3.1) fills in the geometry.
|
||||
|
||||
Everything else — viewBox sizing, chip positions, legend layout, dark-mode token swap — is derivable. The YAML is the **source of truth**; the SVG is one of many possible renderings of it (light/dark/full all derive from the same inputs with different style tokens).
|
||||
|
||||
---
|
||||
|
||||
## 13. Examples
|
||||
|
||||
- `assets/example-process.html` — minimal light (quarterly survey: 11 steps, 6 divisions, data-type chips). Gallery default.
|
||||
- `assets/example-process-dark.html` — same, dark skin.
|
||||
- `assets/example-process-full.html` — same, editorial-card frame.
|
||||
- `assets/example-process-extended.html` — exercises §4 color override: Build app in slate-blue (data quality), Train enumerators in rust-red (governance), Publish results in olive-green (data products). Focal accent on Pilot test step + node unchanged.
|
||||
- `assets/example-process-extended-dark.html` — extended pattern, dark skin.
|
||||
- `assets/example-process-extended-full.html` — extended pattern, editorial-card frame.
|
||||
@@ -0,0 +1,80 @@
|
||||
# Radar / Spider
|
||||
|
||||
**Best for:** comparing 3–5 entities across 3–5 quantitative criteria on a single normalized 0–N scale. Capability matrices, product or backend evaluations, framework/team scorecards. Where a comparison table starts running out of horizontal room, radar makes the shape of each option legible at a glance.
|
||||
|
||||
## Layout conventions
|
||||
|
||||
- **N axes (3–5).** Equally spaced on a regular polygon-N. First axis at the top (`-90°`), going clockwise. **Above 5 → split or use a comparison table.**
|
||||
- **Five concentric grid rings** at fractions `0.2 / 0.4 / 0.6 / 0.8 / 1.0` of the radius. Drawn as closed polygons connecting the axis vertices at that fraction. Inner four at `rule` 0.10 opacity, outer ring at `rule-solid` 0.20 (a hint stronger to anchor the chart).
|
||||
- **Axis spokes** from center to each outer vertex. `rule-solid` 0.20 opacity. **No arrowheads.**
|
||||
- **Axis labels:** one word per spoke (Jobs-minimal). Geist sans 11px weight 600. Place 16px outside the outer ring along the axis vector. Top/bottom = `text-anchor="middle"`; right side = `start`; left side = `end`.
|
||||
- **Scale ticks** (e.g. `2 4 6 8 10`) only on the **first (top) axis** — putting numbers on every spoke clutters the chart fast. Geist Mono 8px, `muted`, anchored end at `cx − 6`.
|
||||
- **Series polygon:** stroke 1.5px at the series color, fill the same color at `0.18` opacity (`0.22` in dark). Stroke 1.8px on the focal series — a subtle weight bump.
|
||||
- **Vertex dots:** **only on the focal series**, `r=4` filled with the series color. Non-focal series are stroke-and-fill only. This is the load-bearing rule that keeps the chart readable at 4–5 series.
|
||||
- **Drawing order:** dots-pattern bg → grid rings → axis spokes → axis labels → scale ticks → non-focal series (smallest area first) → focal series → focal vertex dots → legend.
|
||||
- **Legend:** horizontal strip at the bottom (per the global rule). Swatch is a 16×8 rectangle (matches the polygon stroke+fill, not a circle), then the entity name. ~140px between entries. Optional italic tail on the right with the rationale (`"One coral. Position is the signal — color reserved for the recommended option."`).
|
||||
|
||||
## Math
|
||||
|
||||
For axis `i` (0-indexed) of `N`, value `v` on scale `S`, center `(cx, cy)`, outer radius `R`:
|
||||
|
||||
```
|
||||
angle = -π/2 + 2π · i / N
|
||||
x = cx + (v / S) · R · cos(angle)
|
||||
y = cy + (v / S) · R · sin(angle)
|
||||
```
|
||||
|
||||
A series with values `[v0, v1, ..., v(N-1)]` becomes a `<polygon>` with `points="x0,y0 x1,y1 ..."`.
|
||||
|
||||
### Pre-computed reference (N=5, cx=500, cy=240, R=160, S=10, integer-rounded)
|
||||
|
||||
| Fraction `f` | i=0 (top) | i=1 | i=2 | i=3 | i=4 |
|
||||
|---|---|---|---|---|---|
|
||||
| 0.2 | 500,208 | 530,230 | 519,266 | 481,266 | 470,230 |
|
||||
| 0.4 | 500,176 | 561,220 | 538,292 | 462,292 | 439,220 |
|
||||
| 0.6 | 500,144 | 591,211 | 556,317 | 444,317 | 409,211 |
|
||||
| 0.8 | 500,112 | 622,201 | 575,343 | 425,343 | 378,201 |
|
||||
| 1.0 | 500,80 | 652,191 | 594,369 | 406,369 | 348,191 |
|
||||
|
||||
For an arbitrary value `v` on axis `i`, take the unit offset from the row above for that axis (e.g. axis 1: offset `(152, -49)` from center) and scale by `v/S`. **Drop coords as integers — fractional pixels in SVG render fine, but integers keep the file scannable.**
|
||||
|
||||
### Worked example (N=5)
|
||||
|
||||
Series `[9, 8, 9, 9, 9]` on a 0–10 scale becomes:
|
||||
|
||||
```svg
|
||||
<polygon points="500,96 622,201 585,356 415,356 363,196"
|
||||
fill="rgba(235,108,54,0.18)" stroke="#eb6c36" stroke-width="1.8"/>
|
||||
```
|
||||
|
||||
Each vertex: `center + (v/10) · (outer_i − center)`, rounded to the nearest pixel.
|
||||
|
||||
## Series palette
|
||||
|
||||
The skill's "1-focal" rule still holds: `accent` is reserved for the focal series, and a small editorial palette (`series-1` through `series-5`, defined in [`style-guide.md`](style-guide.md)) covers the non-focal series. Don't reach for free-form colors.
|
||||
|
||||
| Slot | Token | Light | Dark |
|
||||
|---|---|---|---|
|
||||
| Focal | `accent` | `#eb6c36` | `#f08a59` |
|
||||
| 1 | `series-1` (sage) | `#7c8f6f` | `#9caf8f` |
|
||||
| 2 | `series-2` (dusty-blue) | `#5e7a9b` | `#82a0c0` |
|
||||
| 3 | `series-3` (mustard) | `#b8915a` | `#d3ad7a` |
|
||||
| 4 | `series-4` (rust-brown) | `#9c6b50` | `#b88670` |
|
||||
| 5 | `series-5` (slate) | `#6e6479` | `#8d8298` |
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- **More than 5 series** → mush. Split into two charts (e.g. "best by latency" + "best by ops") or switch to a comparison table.
|
||||
- **Axes on inconsistent native scales** (one 0–100, another 0–1) without normalization. **Always normalize to 0–N first** — radar polygons compare *shapes*, not absolute values.
|
||||
- **Zero-baseline tricks** — starting the inner ring at v=5 to amplify differences. The grid starts at 0; if differences look small, that's the truthful reading.
|
||||
- **Dots on every series.** Only the focal carries dots. Adding them to all 4–5 series turns the chart into a bead curtain.
|
||||
- **Radar with 2 series** — a comparison bar chart or a 2-row table is clearer.
|
||||
- **Non-quantitative axes.** All axes must be measurable on the same normalized scale. "Speed" + "color" + "year" mixes don't belong on a radar.
|
||||
- **Mono-font axis labels.** Names go in Geist sans (the global rule). Mono is for technical sublabels only.
|
||||
- **Rainbow palette.** Even with the new `series-*` tokens, you don't need all 5 in one chart — use only as many as you have non-focal entities.
|
||||
|
||||
## Examples
|
||||
|
||||
- `assets/example-radar.html` — minimal light. 4 storage backends × 5 workload dimensions, MinIO focal.
|
||||
- `assets/example-radar-dark.html` — minimal dark, same data.
|
||||
- `assets/example-radar-full.html` — full editorial: container framing + 4 cards (one per backend) with varied widths + footer.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Scatter Plot
|
||||
|
||||
**Best for:** correlation and distribution — two continuous variables plotted against each other. Use when the relationship (or lack of one) between variables is the message, or when you need to identify clusters, outliers, and high/low performers.
|
||||
|
||||
## Layout conventions
|
||||
|
||||
- **Plot area margins:** left 80px, bottom 60px, top 40px, right 40px — inside `0 0 1000 500` viewBox.
|
||||
- **Point count:** 5–30 points. Fewer → just describe the relationship in prose; more → bin into a density contour.
|
||||
- **Axes:** X at y=420 (baseline), Y at x=80. Both use Geist Mono 8px gridline labels. Gridlines 4–6 per axis at equal intervals.
|
||||
- **Point shape:** `<circle>` r=5 for standard points, r=6 for focal. Focal point in `accent` fill. Others in `muted @ 0.20` fill + `muted` stroke.
|
||||
- **Labels on points (optional):** Geist Mono 8px next to a point. Use a paper-fill rect mask behind the label. Label at most 2–3 points; not all.
|
||||
- **Trend line (optional):** `<line>` from lower-left to upper-right, stroke `rgba(45,49,66,0.25)` dashed 4,3. Never force a perfect fit — only add if the trend is visually obvious.
|
||||
- **Quadrant dividers (optional):** light dashed lines at the median x and y to split into quadrants. Label each quadrant in Geist Mono 8px, muted.
|
||||
|
||||
### Point pattern
|
||||
|
||||
```svg
|
||||
<!-- Non-focal point — paper mask + circle -->
|
||||
<circle cx="X" cy="Y" r="5" fill="#f5f5f5"/>
|
||||
<circle cx="X" cy="Y" r="5" fill="rgba(79,93,117,0.20)" stroke="#4f5d75" stroke-width="1"/>
|
||||
|
||||
<!-- Focal point -->
|
||||
<circle cx="X" cy="Y" r="6" fill="#f5f5f5"/>
|
||||
<circle cx="X" cy="Y" r="6" fill="rgba(235,108,54,0.15)" stroke="#eb6c36" stroke-width="1.2"/>
|
||||
```
|
||||
|
||||
## Anti-patterns
|
||||
|
||||
- More than 30 points without clustering (jitter/mush).
|
||||
- Forced trend line when the data is genuinely scattered — dishonest.
|
||||
- Point labels on every point (label the focal and 1–2 notable outliers only).
|
||||
- Bubble size encoding (use a third axis label or color instead; bubble area perception is unreliable).
|
||||
- Axes that don't include zero when the absolute position matters; axes that do include zero when the range is tiny and far from zero.
|
||||
|
||||
## Examples
|
||||
|
||||
- `assets/example-scatter.html` — minimal light
|
||||
- `assets/example-scatter-dark.html` — minimal dark
|
||||
- `assets/example-scatter-full.html` — full editorial
|
||||
Reference in New Issue
Block a user