Skip to content

Commit 9bc61fb

Browse files
committed
feat(flow-php/web): add the Work-Shop
- Blueprint, consulting, how-it-works and success pages under /work-shop - Polar embedded checkout, terms of sale and privacy policy - drop Datadog RUM, run PostHog cookieless - list Anthropic as a sponsor
1 parent 1b1939c commit 9bc61fb

39 files changed

Lines changed: 2092 additions & 42 deletions

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@
345345
]
346346
},
347347
"scripts": {
348-
"pre-autoload-dump": [
348+
"pre-autoload-dump": [
349349
"Google\\Task\\Composer::cleanup"
350350
],
351351
"post-install-cmd": [

web/landing/.env

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,3 @@ TURNSTILE_SITE_KEY=0x4AAAAAACAjJaWImyaJwWCy
1111
TURNSTILE_APPEARANCE=interaction-only
1212
PLAYGROUND_API_URL=http://localhost:8787/api/playground/snippets
1313
PLAYGROUND_SNIPPETS_URL=http://localhost:8787
14-
DATADOG_APPLICATION_ID=4a297124-5ea7-4fc5-b75a-6fb71e699f54
15-
DATADOG_CLIENT_TOKEN=pubf4d7626f7fd335a27355e01cd877c277
16-
DATADOG_SITE=datadoghq.eu

web/landing/.env.dev

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
POSTHOG_KEY=
22
POSTHOG_API_HOST=
33
POSTHOG_UI_HOST=
4-
DATADOG_APPLICATION_ID=
5-
DATADOG_CLIENT_TOKEN=
6-
DATADOG_SITE=

web/landing/.env.test

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ DOMAIN='flow-php.wip'
66
POSTHOG_KEY=
77
POSTHOG_API_HOST=
88
POSTHOG_UI_HOST=
9-
DATADOG_APPLICATION_ID=
10-
DATADOG_CLIENT_TOKEN=
11-
DATADOG_SITE=
129
FLOW_VERSION=1.x-dev
1310
TURNSTILE_SITE_KEY=0x4AAAAAACAjJaWImyaJwWCy
1411
TURNSTILE_APPEARANCE=interaction-only

web/landing/assets/codemirror/completions/dsl.js

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* CodeMirror Completer for Flow PHP DSL Functions
33
*
4-
* Total functions: 821
4+
* Total functions: 826
55
*
66
* This completer provides autocompletion for all Flow PHP DSL functions:
77
* - Extractors (flow-extractors)
@@ -2963,6 +2963,21 @@ const dslFunctions = [
29632963
},
29642964
apply: snippet("\\Flow\\PostgreSql\\DSL\\current_date()"),
29652965
boost: 10
2966+
}, {
2967+
label: "current_row",
2968+
type: "function",
2969+
detail: "flow\u002Ddsl\u002Ddata\u002Dframe",
2970+
info: () => {
2971+
const div = document.createElement("div")
2972+
div.innerHTML = `
2973+
<div style="font-family: 'Fira Code', 'JetBrains Mono', monospace; margin-bottom: 8px;">
2974+
<span class=\"fn-name\">current_row</span><span class=\"fn-operator\">(</span><span class=\"fn-operator\">)</span> <span class=\"fn-operator\">:</span> <span class=\"fn-return\">FrameBound</span>
2975+
</div>
2976+
`
2977+
return div
2978+
},
2979+
apply: snippet("\\Flow\\ETL\\DSL\\current_row()"),
2980+
boost: 10
29662981
}, {
29672982
label: "current_time",
29682983
type: "function",
@@ -4205,6 +4220,21 @@ const dslFunctions = [
42054220
},
42064221
apply: snippet("\\Flow\\ETL\\DSL\\flow_context(" + "$" + "{" + "1:config" + "}" + ")"),
42074222
boost: 10
4223+
}, {
4224+
label: "following",
4225+
type: "function",
4226+
detail: "flow\u002Ddsl\u002Ddata\u002Dframe",
4227+
info: () => {
4228+
const div = document.createElement("div")
4229+
div.innerHTML = `
4230+
<div style="font-family: 'Fira Code', 'JetBrains Mono', monospace; margin-bottom: 8px;">
4231+
<span class=\"fn-name\">following</span><span class=\"fn-operator\">(</span><span class=\"fn-type\">int</span> <span class=\"fn-param\">$offset</span><span class=\"fn-operator\">)</span> <span class=\"fn-operator\">:</span> <span class=\"fn-return\">FrameBound</span>
4232+
</div>
4233+
`
4234+
return div
4235+
},
4236+
apply: snippet("\\Flow\\ETL\\DSL\\following(" + "$" + "{" + "1:offset" + "}" + ")"),
4237+
boost: 10
42084238
}, {
42094239
label: "foreign_key",
42104240
type: "function",
@@ -7913,6 +7943,21 @@ const dslFunctions = [
79137943
},
79147944
apply: snippet("\\Flow\\ETL\\Adapter\\Doctrine\\postgresql_update_options(" + "$" + "{" + "1:primary_key_columns" + "}" + ", " + "$" + "{" + "2:update_columns" + "}" + ")"),
79157945
boost: 10
7946+
}, {
7947+
label: "preceding",
7948+
type: "function",
7949+
detail: "flow\u002Ddsl\u002Ddata\u002Dframe",
7950+
info: () => {
7951+
const div = document.createElement("div")
7952+
div.innerHTML = `
7953+
<div style="font-family: 'Fira Code', 'JetBrains Mono', monospace; margin-bottom: 8px;">
7954+
<span class=\"fn-name\">preceding</span><span class=\"fn-operator\">(</span><span class=\"fn-type\">int</span> <span class=\"fn-param\">$offset</span><span class=\"fn-operator\">)</span> <span class=\"fn-operator\">:</span> <span class=\"fn-return\">FrameBound</span>
7955+
</div>
7956+
`
7957+
return div
7958+
},
7959+
apply: snippet("\\Flow\\ETL\\DSL\\preceding(" + "$" + "{" + "1:offset" + "}" + ")"),
7960+
boost: 10
79167961
}, {
79177962
label: "prepare_transaction",
79187963
type: "function",
@@ -12626,6 +12671,36 @@ const dslFunctions = [
1262612671
},
1262712672
apply: snippet("\\Flow\\ETL\\DSL\\ulid(" + "$" + "{" + "1:value" + "}" + ")"),
1262812673
boost: 10
12674+
}, {
12675+
label: "unbounded_following",
12676+
type: "function",
12677+
detail: "flow\u002Ddsl\u002Ddata\u002Dframe",
12678+
info: () => {
12679+
const div = document.createElement("div")
12680+
div.innerHTML = `
12681+
<div style="font-family: 'Fira Code', 'JetBrains Mono', monospace; margin-bottom: 8px;">
12682+
<span class=\"fn-name\">unbounded_following</span><span class=\"fn-operator\">(</span><span class=\"fn-operator\">)</span> <span class=\"fn-operator\">:</span> <span class=\"fn-return\">FrameBound</span>
12683+
</div>
12684+
`
12685+
return div
12686+
},
12687+
apply: snippet("\\Flow\\ETL\\DSL\\unbounded_following()"),
12688+
boost: 10
12689+
}, {
12690+
label: "unbounded_preceding",
12691+
type: "function",
12692+
detail: "flow\u002Ddsl\u002Ddata\u002Dframe",
12693+
info: () => {
12694+
const div = document.createElement("div")
12695+
div.innerHTML = `
12696+
<div style="font-family: 'Fira Code', 'JetBrains Mono', monospace; margin-bottom: 8px;">
12697+
<span class=\"fn-name\">unbounded_preceding</span><span class=\"fn-operator\">(</span><span class=\"fn-operator\">)</span> <span class=\"fn-operator\">:</span> <span class=\"fn-return\">FrameBound</span>
12698+
</div>
12699+
`
12700+
return div
12701+
},
12702+
apply: snippet("\\Flow\\ETL\\DSL\\unbounded_preceding()"),
12703+
boost: 10
1262912704
}, {
1263012705
label: "union_schema",
1263112706
type: "function",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
export default class extends Controller {
4+
static targets = ["track", "slide", "dot"]
5+
static values = { index: { type: Number, default: 0 } }
6+
7+
indexValueChanged() {
8+
const count = this.slideTargets.length
9+
10+
if (count === 0) {
11+
return
12+
}
13+
14+
if (this.indexValue < 0) {
15+
this.indexValue = count - 1
16+
return
17+
}
18+
19+
if (this.indexValue > count - 1) {
20+
this.indexValue = 0
21+
return
22+
}
23+
24+
this.trackTarget.style.transform = `translateX(-${this.indexValue * 100}%)`
25+
26+
this.slideTargets.forEach((slide, i) => {
27+
slide.setAttribute('aria-hidden', i === this.indexValue ? 'false' : 'true')
28+
})
29+
30+
this.dotTargets.forEach((dot, i) => {
31+
const active = i === this.indexValue
32+
dot.classList.toggle('bg-white', active)
33+
dot.classList.toggle('bg-white/40', !active)
34+
dot.setAttribute('aria-current', active ? 'true' : 'false')
35+
})
36+
}
37+
38+
next() {
39+
this.indexValue++
40+
}
41+
42+
prev() {
43+
this.indexValue--
44+
}
45+
46+
goTo(event) {
47+
this.indexValue = Number(event.params.index)
48+
}
49+
50+
keydown(event) {
51+
if (event.key === "ArrowLeft") {
52+
event.preventDefault()
53+
this.prev()
54+
} else if (event.key === "ArrowRight") {
55+
event.preventDefault()
56+
this.next()
57+
}
58+
}
59+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
import { PolarEmbedCheckout } from "@polar-sh/checkout/embed"
3+
4+
export default class extends Controller {
5+
static values = { url: String }
6+
7+
open(event) {
8+
event.preventDefault()
9+
10+
const theme = document.documentElement.classList.contains("dark") ? "dark" : "light"
11+
12+
PolarEmbedCheckout.create(this.urlValue, { theme })
13+
}
14+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
// Brand palette from tailwind.config.js, plus white so the fall reads on the dark theme.
4+
const COLORS = ["#ff5547", "#806dfe", "#e1761a", "#5945d8", "#ffffff"]
5+
const PIECES = 150
6+
const GRAVITY = 0.04
7+
const MAX_FALL = 3.5
8+
// Pieces start staggered above the fold so they keep arriving instead of landing all at once.
9+
const STAGGER = 1.1
10+
const FADE_FROM = 0.82
11+
12+
export default class extends Controller {
13+
connect() {
14+
this.motion = window.matchMedia("(prefers-reduced-motion: reduce)")
15+
this.pieces = []
16+
this.frame = null
17+
this.onResize = () => this.#resize()
18+
19+
this.burst()
20+
}
21+
22+
disconnect() {
23+
this.#stop()
24+
this.#unmount()
25+
}
26+
27+
burst() {
28+
if (this.motion.matches) {
29+
return
30+
}
31+
32+
this.#mount()
33+
34+
const width = window.innerWidth
35+
const height = window.innerHeight
36+
37+
for (let i = 0; i < PIECES; i++) {
38+
this.pieces.push({
39+
x: Math.random() * width,
40+
y: -20 - Math.random() * height * STAGGER,
41+
vx: (Math.random() - 0.5) * 0.7,
42+
vy: 1 + Math.random() * 1.5,
43+
sway: 0.4 + Math.random() * 1.1,
44+
swaySpeed: 0.01 + Math.random() * 0.03,
45+
phase: Math.random() * Math.PI * 2,
46+
width: 5 + Math.random() * 5,
47+
height: 9 + Math.random() * 6,
48+
rotation: Math.random() * Math.PI * 2,
49+
spin: (Math.random() - 0.5) * 0.12,
50+
flip: Math.random() * Math.PI * 2,
51+
flipSpeed: 0.05 + Math.random() * 0.09,
52+
color: COLORS[Math.floor(Math.random() * COLORS.length)],
53+
})
54+
}
55+
56+
this.#start()
57+
}
58+
59+
#tick() {
60+
const context = this.context
61+
const height = window.innerHeight
62+
63+
context.clearRect(0, 0, window.innerWidth, height)
64+
65+
this.pieces = this.pieces.filter((piece) => {
66+
piece.vy = Math.min(piece.vy + GRAVITY, MAX_FALL)
67+
piece.phase += piece.swaySpeed
68+
piece.y += piece.vy
69+
piece.x += piece.vx + Math.sin(piece.phase) * piece.sway
70+
piece.rotation += piece.spin
71+
piece.flip += piece.flipSpeed
72+
73+
if (piece.y > height + 40) {
74+
return false
75+
}
76+
77+
const travelled = piece.y / height
78+
79+
context.save()
80+
context.translate(piece.x, piece.y)
81+
context.rotate(piece.rotation)
82+
// Squashing the height as it tumbles fakes a flat piece turning over.
83+
context.scale(1, Math.abs(Math.cos(piece.flip)))
84+
context.globalAlpha = travelled > FADE_FROM ? Math.max(0, 1 - (travelled - FADE_FROM) / (1 - FADE_FROM)) : 1
85+
context.fillStyle = piece.color
86+
context.fillRect(-piece.width / 2, -piece.height / 2, piece.width, piece.height)
87+
context.restore()
88+
89+
return true
90+
})
91+
92+
if (this.pieces.length === 0) {
93+
this.#stop()
94+
this.#unmount()
95+
96+
return
97+
}
98+
99+
this.frame = window.requestAnimationFrame(() => this.#tick())
100+
}
101+
102+
#mount() {
103+
if (this.canvas) {
104+
return
105+
}
106+
107+
this.canvas = document.createElement("canvas")
108+
this.canvas.setAttribute("aria-hidden", "true")
109+
this.canvas.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:60"
110+
document.body.appendChild(this.canvas)
111+
this.context = this.canvas.getContext("2d")
112+
113+
this.#resize()
114+
window.addEventListener("resize", this.onResize)
115+
}
116+
117+
#resize() {
118+
if (!this.canvas) {
119+
return
120+
}
121+
122+
const ratio = window.devicePixelRatio || 1
123+
124+
this.canvas.width = window.innerWidth * ratio
125+
this.canvas.height = window.innerHeight * ratio
126+
this.canvas.style.width = `${window.innerWidth}px`
127+
this.canvas.style.height = `${window.innerHeight}px`
128+
this.context.setTransform(ratio, 0, 0, ratio, 0, 0)
129+
}
130+
131+
#unmount() {
132+
window.removeEventListener("resize", this.onResize)
133+
134+
if (this.canvas) {
135+
this.canvas.remove()
136+
this.canvas = null
137+
this.context = null
138+
}
139+
}
140+
141+
#start() {
142+
if (this.frame === null) {
143+
this.frame = window.requestAnimationFrame(() => this.#tick())
144+
}
145+
}
146+
147+
#stop() {
148+
if (this.frame !== null) {
149+
window.cancelAnimationFrame(this.frame)
150+
this.frame = null
151+
}
152+
}
153+
}

0 commit comments

Comments
 (0)