diff --git a/apps/desktop/electron/site.test.ts b/apps/desktop/electron/site.test.ts
index 060194c..439c537 100644
--- a/apps/desktop/electron/site.test.ts
+++ b/apps/desktop/electron/site.test.ts
@@ -176,6 +176,11 @@ describe.each([
it("show the button text clearly", () => {
expect(contrastRatio(get("--inverse-ink"), get("--inverse-bg"))).toBeGreaterThanOrEqual(7);
});
+ it("keeps the structure colours readable on their quiet backgrounds", () => {
+ expect(contrastRatio(get("--structure"), get("--structure-bg"))).toBeGreaterThanOrEqual(4.5);
+ expect(contrastRatio(get("--data"), get("--data-bg"))).toBeGreaterThanOrEqual(4.5);
+ expect(contrastRatio(get("--decision"), get("--decision-bg"))).toBeGreaterThanOrEqual(4.5);
+ });
});
describe("detectSystem", () => {
diff --git a/site/index.html b/site/index.html
index 2aaa682..cfc1edd 100644
--- a/site/index.html
+++ b/site/index.html
@@ -134,44 +134,121 @@
Engineers think in structure.
-
You say
+
One spoken thought
- The browser sends API calls to a load balancer , which forwards them to
- the order service. It reads from Redis and queries PostgreSQL on a cache miss.
- We’re not going with MongoDB , PostgreSQL it is.
+ The browser sends API calls to a load balancer , which forwards them to the order service. It
+ reads from Redis and queries PostgreSQL on a cache miss .
+ We’re not going with MongoDB. PostgreSQL it is.
+
+ Flow
+ Data
+ Decision
+
-
-
+
+
+
+ connections
+ dependencies
+ choices
+
-
Lucid builds
-
## The request flow
-
-
- Browser
-
-
- Load balancer
-
-
- Order service
-
-
- Redis
-
- PostgreSQL
-
-
## The database
-
+
One structured page
+
+
◇ Diagram
+
The request flow
+
+
+
+
+
+
+
+
+
+
+
+
+
+ read
+ miss
+
+
+
+ Browser
+
+
+
+ Load balancer
+
+
+
+ Order service
+
+
+
+
+ Redis
+
+
+
+
+ PostgreSQL
+
+
+
+
+
✓ Decision
+
+ PostgreSQL
+ chosen
+
+
+ MongoDB
+ rejected
+
+
diff --git a/site/style.css b/site/style.css
index 890d319..54fcfd9 100644
--- a/site/style.css
+++ b/site/style.css
@@ -1,7 +1,7 @@
/*
* The Lucid site. One page, no framework, no third-party requests: the typeface is served from here.
*
- * Design rules (the same language as the app: black and white, hairlines, one filled button, iA Writer Mono):
+ * Design rules (the same language as the app: neutral surfaces, hairlines, one filled button, iA Writer Mono):
* - Spacing is a 4 px scale (--s1 .. --s10), type is a ratio scale of about 1.25 (--t-1 .. --t6); nothing else is used.
* - Colours are tokens; text on every surface keeps at least 4.5:1 (body) and 3:1 (tertiary), in both themes.
* - Radii: 8 (controls), 12 (cards), 16 (the window picture). Borders are 1 px.
@@ -37,6 +37,12 @@
--inverse-ink: #ffffff;
--ok: #3f8a69;
--red: #b8453d;
+ --structure: #315eba;
+ --structure-bg: #e8f0ff;
+ --data: #6d4e9c;
+ --data-bg: #f1ebf8;
+ --decision: #2f7657;
+ --decision-bg: #e8f3ed;
--shadow-1: 0 1px 2px rgb(15 15 15 / 6%), 0 6px 20px rgb(15 15 15 / 6%);
--shadow-window: 0 2px 4px rgb(15 15 15 / 6%), 0 24px 64px -12px rgb(15 15 15 / 22%);
@@ -80,6 +86,12 @@
--inverse-ink: #191919;
--ok: #5fb892;
--red: #e56458;
+ --structure: #8cb4ff;
+ --structure-bg: #202f49;
+ --data: #c2a7e6;
+ --data-bg: #322940;
+ --decision: #75c7a4;
+ --decision-bg: #20352c;
--shadow-1: 0 1px 2px rgb(0 0 0 / 40%), 0 6px 24px rgb(0 0 0 / 35%);
--shadow-window: 0 2px 4px rgb(0 0 0 / 40%), 0 24px 64px -12px rgb(0 0 0 / 70%);
}
@@ -98,6 +110,12 @@
--inverse-ink: #191919;
--ok: #5fb892;
--red: #e56458;
+ --structure: #8cb4ff;
+ --structure-bg: #202f49;
+ --data: #c2a7e6;
+ --data-bg: #322940;
+ --decision: #75c7a4;
+ --decision-bg: #20352c;
--shadow-1: 0 1px 2px rgb(0 0 0 / 40%), 0 6px 24px rgb(0 0 0 / 35%);
--shadow-window: 0 2px 4px rgb(0 0 0 / 40%), 0 24px 64px -12px rgb(0 0 0 / 70%);
}
@@ -460,9 +478,9 @@ h3 {
/* ---- the idea: say, then structure ---- */
.demo {
display: grid;
- grid-template-columns: minmax(0, 5fr) auto minmax(0, 6fr);
- gap: var(--s5);
- align-items: stretch;
+ grid-template-columns: minmax(240px, 0.82fr) 132px minmax(460px, 1.35fr);
+ gap: var(--s4);
+ align-items: center;
}
.card {
background: var(--bg);
@@ -471,8 +489,7 @@ h3 {
box-shadow: var(--shadow-1);
}
.say {
- align-self: start;
- padding: var(--s5);
+ padding: var(--s6);
}
.say .label,
.doc .label {
@@ -487,80 +504,237 @@ h3 {
font-size: var(--t0);
line-height: 1.7;
}
-.say blockquote span {
- background: linear-gradient(transparent 62%, var(--panel-2) 62%);
+.thought {
+ border-radius: 3px;
+ box-decoration-break: clone;
+ -webkit-box-decoration-break: clone;
+ padding: 1px 2px;
}
-.arrow {
- align-self: center;
- color: var(--ink-3);
+.thought-flow {
+ background: var(--structure-bg);
+ color: var(--structure);
}
-.arrow svg {
- width: 36px;
- height: 36px;
- fill: none;
- stroke: currentColor;
- stroke-width: 1.5;
- stroke-linecap: round;
- stroke-linejoin: round;
+.thought-data {
+ background: var(--data-bg);
+ color: var(--data);
}
-.doc {
- grid-template-columns: minmax(0, 1fr);
- padding: var(--s5);
+.thought-decision {
+ background: var(--decision-bg);
+ color: var(--decision);
+}
+.thought-legend {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--s3);
+ margin-top: var(--s5);
+ padding-top: var(--s4);
+ border-top: 1px solid var(--line);
+ color: var(--ink-2);
+ font-size: var(--t-1);
+}
+.thought-legend span,
+.structure-key span:not(.structure-key-line) {
+ display: inline-flex;
+ gap: 7px;
+ align-items: center;
+}
+.thought-legend i,
+.structure-key i {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: currentColor;
+}
+.thought-legend .flow,
+.structure-key .flow {
+ color: var(--structure);
+}
+.thought-legend .data,
+.structure-key .data {
+ color: var(--data);
+}
+.thought-legend .decision,
+.structure-key .decision {
+ color: var(--decision);
+}
+.structure-key {
+ position: relative;
display: grid;
gap: var(--s4);
- align-content: start;
+ align-content: center;
+ min-height: 210px;
+ color: var(--ink-2);
+ font-size: var(--t-1);
}
-.doc h3 {
- font-size: var(--t1);
+.structure-key-line {
+ position: absolute;
+ inset: 18px auto 18px 3px;
+ width: 1px;
+ background: var(--line-strong);
+}
+.structure-key-line::before,
+.structure-key-line::after {
+ content: "";
+ position: absolute;
+ left: -3px;
+ width: 7px;
+ height: 7px;
+ border: solid var(--line-strong);
+ border-width: 1px 1px 0 0;
+ transform: rotate(45deg);
+}
+.structure-key-line::before {
+ top: -1px;
}
-.mini-list {
+.structure-key-line::after {
+ bottom: -1px;
+}
+.structure-key span:not(.structure-key-line) {
+ position: relative;
+ padding-left: var(--s4);
+}
+.doc {
display: grid;
- gap: var(--s1);
- margin: 0;
- padding: 0;
- list-style: none;
+ grid-template-columns: minmax(0, 1fr) minmax(150px, 0.42fr);
+ gap: var(--s5);
+ align-content: start;
+ padding: var(--s5);
+ border-color: color-mix(in srgb, var(--structure) 36%, var(--line));
+ box-shadow:
+ var(--shadow-1),
+ inset 3px 0 0 var(--structure);
+}
+.doc > .label {
+ grid-column: 1 / -1;
}
-.mini-list li::before {
- content: "• ";
+.doc h3 {
+ margin-top: var(--s1);
+ font-size: var(--t0);
}
-.mini-list .chosen {
+.doc-block {
+ min-width: 0;
+ padding: var(--s4);
+ border: 1px solid var(--line);
+ border-radius: var(--r1);
+ background: var(--panel);
+}
+.block-kind {
+ display: flex;
+ gap: var(--s2);
+ align-items: center;
+ color: var(--structure);
+ font-size: var(--t-1);
font-weight: 700;
}
-.mini-list .chosen::after {
- content: " ✓";
+.decision-block .block-kind {
+ color: var(--decision);
+}
+.decision-row {
+ display: flex;
+ justify-content: space-between;
+ gap: var(--s2);
+ margin-top: var(--s4);
+ color: var(--ink);
+ font-size: var(--t-1);
+}
+.decision-row > :last-child {
+ color: var(--decision);
}
-.mini-list .struck {
+.decision-row.rejected,
+.decision-row.rejected > :last-child {
color: var(--ink-3);
+}
+.decision-row.rejected > :first-child {
text-decoration: line-through;
}
.mini-diagram {
width: 100%;
- max-width: 340px;
+ margin-top: var(--s3);
color: var(--ink);
}
-.mini-diagram rect,
-.mini-diagram path,
-.mini-diagram ellipse {
- fill: var(--panel);
- stroke: var(--ink-2);
- stroke-width: 1.2;
+.mini-diagram .node rect,
+.mini-diagram .node path,
+.mini-diagram .node ellipse {
+ stroke-width: 1.5;
+}
+.mini-diagram .flow-node rect {
+ fill: var(--structure-bg);
+ stroke: var(--structure);
}
-.mini-diagram path.line {
+.mini-diagram .data-node path,
+.mini-diagram .data-node ellipse {
+ fill: var(--data-bg);
+ stroke: var(--data);
+}
+.mini-diagram .connector {
fill: none;
+ stroke: var(--structure);
+ stroke-width: 1.5;
+}
+.mini-diagram .connector.data-line {
+ stroke: var(--data);
+}
+.mini-diagram .flow-arrow path {
+ fill: var(--structure);
+ stroke: none;
+}
+.mini-diagram .data-arrow path {
+ fill: var(--data);
+ stroke: none;
}
.mini-diagram text {
fill: var(--ink);
font-family: var(--mono);
- font-size: 11px;
+ font-size: 13px;
text-anchor: middle;
}
+.mini-diagram .edge-label {
+ fill: var(--ink-3);
+ font-size: 10px;
+}
@media (max-width: 860px) {
.demo {
grid-template-columns: minmax(0, 1fr);
}
- .arrow {
- justify-self: center;
- transform: rotate(90deg);
+ .structure-key {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ min-height: auto;
+ padding: var(--s3) var(--s4);
+ }
+ .structure-key-line {
+ inset: 0 var(--s4) auto var(--s4);
+ width: auto;
+ height: 1px;
+ }
+ .structure-key-line::before,
+ .structure-key-line::after {
+ top: -3px;
+ }
+ .structure-key-line::before {
+ left: 0;
+ }
+ .structure-key-line::after {
+ right: 0;
+ bottom: auto;
+ left: auto;
+ }
+ .structure-key span:not(.structure-key-line) {
+ padding: var(--s3) 0 0;
+ }
+}
+@media (max-width: 620px) {
+ .doc {
+ grid-template-columns: minmax(0, 1fr);
+ }
+ .structure-key {
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--s2);
+ }
+ .structure-key-line {
+ display: none;
+ }
+ .structure-key span:not(.structure-key-line) {
+ padding: 0;
}
}