diff --git a/packages/console/app/src/component/light-rays.tsx b/packages/console/app/src/component/light-rays.tsx index a435f56cd6..6c17410733 100644 --- a/packages/console/app/src/component/light-rays.tsx +++ b/packages/console/app/src/component/light-rays.tsx @@ -44,7 +44,7 @@ export const defaultConfig: LightRaysConfig = { saturation: 0.325, followMouse: false, mouseInfluence: 0.05, - noiseAmount: 0.25, + noiseAmount: 0.5, distortion: 0.0, opacity: 0.35, } @@ -107,136 +107,136 @@ interface UniformData { } const WGSL_SHADER = ` -struct Uniforms { - iTime: f32, - _pad0: f32, - iResolution: vec2, - rayPos: vec2, - rayDir: vec2, - raysColor: vec3, - raysSpeed: f32, - lightSpread: f32, - rayLength: f32, - sourceWidth: f32, - pulsating: f32, - pulsatingMin: f32, - pulsatingMax: f32, - fadeDistance: f32, - saturation: f32, - mousePos: vec2, - mouseInfluence: f32, - noiseAmount: f32, - distortion: f32, - _pad1: f32, - _pad2: f32, - _pad3: f32, -}; + struct Uniforms { + iTime: f32, + _pad0: f32, + iResolution: vec2, + rayPos: vec2, + rayDir: vec2, + raysColor: vec3, + raysSpeed: f32, + lightSpread: f32, + rayLength: f32, + sourceWidth: f32, + pulsating: f32, + pulsatingMin: f32, + pulsatingMax: f32, + fadeDistance: f32, + saturation: f32, + mousePos: vec2, + mouseInfluence: f32, + noiseAmount: f32, + distortion: f32, + _pad1: f32, + _pad2: f32, + _pad3: f32, + }; -@group(0) @binding(0) var uniforms: Uniforms; + @group(0) @binding(0) var uniforms: Uniforms; -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) vUv: vec2, -}; + struct VertexOutput { + @builtin(position) position: vec4, + @location(0) vUv: vec2, + }; -@vertex -fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput { - // Full-screen triangle - var positions = array, 3>( - vec2(-1.0, -1.0), - vec2(3.0, -1.0), - vec2(-1.0, 3.0) - ); - - var output: VertexOutput; - let pos = positions[vertexIndex]; - output.position = vec4(pos, 0.0, 1.0); - output.vUv = pos * 0.5 + 0.5; - return output; -} - -fn noise(st: vec2) -> f32 { - return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123); -} - -fn rayStrength(raySource: vec2, rayRefDirection: vec2, coord: vec2, - seedA: f32, seedB: f32, speed: f32) -> f32 { - let sourceToCoord = coord - raySource; - let dirNorm = normalize(sourceToCoord); - let cosAngle = dot(dirNorm, rayRefDirection); - - let distortedAngle = cosAngle + uniforms.distortion * sin(uniforms.iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2; - - let spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(uniforms.lightSpread, 0.001)); - - let distance = length(sourceToCoord); - let maxDistance = uniforms.iResolution.x * uniforms.rayLength; - let lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0); - - let fadeFalloff = clamp((uniforms.iResolution.x * uniforms.fadeDistance - distance) / (uniforms.iResolution.x * uniforms.fadeDistance), 0.5, 1.0); - let pulseCenter = (uniforms.pulsatingMin + uniforms.pulsatingMax) * 0.5; - let pulseAmplitude = (uniforms.pulsatingMax - uniforms.pulsatingMin) * 0.5; - var pulse: f32; - if (uniforms.pulsating > 0.5) { - pulse = pulseCenter + pulseAmplitude * sin(uniforms.iTime * speed * 3.0); - } else { - pulse = 1.0; + @vertex + fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput { + // Full-screen triangle + var positions = array, 3>( + vec2(-1.0, -1.0), + vec2(3.0, -1.0), + vec2(-1.0, 3.0) + ); + + var output: VertexOutput; + let pos = positions[vertexIndex]; + output.position = vec4(pos, 0.0, 1.0); + output.vUv = pos * 0.5 + 0.5; + return output; } - let baseStrength = clamp( - (0.45 + 0.15 * sin(distortedAngle * seedA + uniforms.iTime * speed)) + - (0.3 + 0.2 * cos(-distortedAngle * seedB + uniforms.iTime * speed)), - 0.0, 1.0 - ); - - return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse; -} - -@fragment -fn fragmentMain(@builtin(position) fragCoord: vec4, @location(0) vUv: vec2) -> @location(0) vec4 { - let coord = vec2(fragCoord.x, fragCoord.y); - - let normalizedX = (coord.x / uniforms.iResolution.x) - 0.5; - let widthOffset = -normalizedX * uniforms.sourceWidth * uniforms.iResolution.x; - - let perpDir = vec2(-uniforms.rayDir.y, uniforms.rayDir.x); - let adjustedRayPos = uniforms.rayPos + perpDir * widthOffset; - - var finalRayDir = uniforms.rayDir; - if (uniforms.mouseInfluence > 0.0) { - let mouseScreenPos = uniforms.mousePos * uniforms.iResolution; - let mouseDirection = normalize(mouseScreenPos - adjustedRayPos); - finalRayDir = normalize(mix(uniforms.rayDir, mouseDirection, uniforms.mouseInfluence)); + fn noise(st: vec2) -> f32 { + return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453123); } - let rays1 = vec4(1.0) * - rayStrength(adjustedRayPos, finalRayDir, coord, 36.2214, 21.11349, - 1.5 * uniforms.raysSpeed); - let rays2 = vec4(1.0) * - rayStrength(adjustedRayPos, finalRayDir, coord, 22.3991, 18.0234, - 1.1 * uniforms.raysSpeed); + fn rayStrength(raySource: vec2, rayRefDirection: vec2, coord: vec2, + seedA: f32, seedB: f32, speed: f32) -> f32 { + let sourceToCoord = coord - raySource; + let dirNorm = normalize(sourceToCoord); + let cosAngle = dot(dirNorm, rayRefDirection); - var fragColor = rays1 * 0.5 + rays2 * 0.4; + let distortedAngle = cosAngle + uniforms.distortion * sin(uniforms.iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2; + + let spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(uniforms.lightSpread, 0.001)); - if (uniforms.noiseAmount > 0.0) { - let n = noise(coord * 0.01 + uniforms.iTime * 0.1); - fragColor = vec4(fragColor.rgb * (1.0 - uniforms.noiseAmount + uniforms.noiseAmount * n), fragColor.a); + let distance = length(sourceToCoord); + let maxDistance = uniforms.iResolution.x * uniforms.rayLength; + let lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0); + + let fadeFalloff = clamp((uniforms.iResolution.x * uniforms.fadeDistance - distance) / (uniforms.iResolution.x * uniforms.fadeDistance), 0.5, 1.0); + let pulseCenter = (uniforms.pulsatingMin + uniforms.pulsatingMax) * 0.5; + let pulseAmplitude = (uniforms.pulsatingMax - uniforms.pulsatingMin) * 0.5; + var pulse: f32; + if (uniforms.pulsating > 0.5) { + pulse = pulseCenter + pulseAmplitude * sin(uniforms.iTime * speed * 3.0); + } else { + pulse = 1.0; + } + + let baseStrength = clamp( + (0.45 + 0.15 * sin(distortedAngle * seedA + uniforms.iTime * speed)) + + (0.3 + 0.2 * cos(-distortedAngle * seedB + uniforms.iTime * speed)), + 0.0, 1.0 + ); + + return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse; } - let brightness = 1.0 - (coord.y / uniforms.iResolution.y); - fragColor.x = fragColor.x * (0.1 + brightness * 0.8); - fragColor.y = fragColor.y * (0.3 + brightness * 0.6); - fragColor.z = fragColor.z * (0.5 + brightness * 0.5); + @fragment + fn fragmentMain(@builtin(position) fragCoord: vec4, @location(0) vUv: vec2) -> @location(0) vec4 { + let coord = vec2(fragCoord.x, fragCoord.y); + + let normalizedX = (coord.x / uniforms.iResolution.x) - 0.5; + let widthOffset = -normalizedX * uniforms.sourceWidth * uniforms.iResolution.x; + + let perpDir = vec2(-uniforms.rayDir.y, uniforms.rayDir.x); + let adjustedRayPos = uniforms.rayPos + perpDir * widthOffset; + + var finalRayDir = uniforms.rayDir; + if (uniforms.mouseInfluence > 0.0) { + let mouseScreenPos = uniforms.mousePos * uniforms.iResolution; + let mouseDirection = normalize(mouseScreenPos - adjustedRayPos); + finalRayDir = normalize(mix(uniforms.rayDir, mouseDirection, uniforms.mouseInfluence)); + } - if (uniforms.saturation != 1.0) { - let gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114)); - fragColor = vec4(mix(vec3(gray), fragColor.rgb, uniforms.saturation), fragColor.a); + let rays1 = vec4(1.0) * + rayStrength(adjustedRayPos, finalRayDir, coord, 36.2214, 21.11349, + 1.5 * uniforms.raysSpeed); + let rays2 = vec4(1.0) * + rayStrength(adjustedRayPos, finalRayDir, coord, 22.3991, 18.0234, + 1.1 * uniforms.raysSpeed); + + var fragColor = rays1 * 0.5 + rays2 * 0.4; + + if (uniforms.noiseAmount > 0.0) { + let n = noise(coord * 0.01 + uniforms.iTime * 0.1); + fragColor = vec4(fragColor.rgb * (1.0 - uniforms.noiseAmount + uniforms.noiseAmount * n), fragColor.a); + } + + let brightness = 1.0 - (coord.y / uniforms.iResolution.y); + fragColor.x = fragColor.x * (0.1 + brightness * 0.8); + fragColor.y = fragColor.y * (0.3 + brightness * 0.6); + fragColor.z = fragColor.z * (0.5 + brightness * 0.5); + + if (uniforms.saturation != 1.0) { + let gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114)); + fragColor = vec4(mix(vec3(gray), fragColor.rgb, uniforms.saturation), fragColor.a); + } + + fragColor = vec4(fragColor.rgb * uniforms.raysColor, fragColor.a); + + return fragColor; } - - fragColor = vec4(fragColor.rgb * uniforms.raysColor, fragColor.a); - - return fragColor; -} ` const UNIFORM_BUFFER_SIZE = 96 diff --git a/packages/console/app/src/lib/github.ts b/packages/console/app/src/lib/github.ts index cc266f58c4..ccde5972d3 100644 --- a/packages/console/app/src/lib/github.ts +++ b/packages/console/app/src/lib/github.ts @@ -14,13 +14,14 @@ export const github = query(async () => { fetch(`${apiBaseUrl}/releases`, { headers }).then((res) => res.json()), fetch(`${apiBaseUrl}/contributors?per_page=1`, { headers }), ]) + if (!Array.isArray(releases) || releases.length === 0) { + return undefined + } const [release] = releases - const contributorCount = Number.parseInt( - contributors.headers - .get("Link")! - .match(/&page=(\d+)>; rel="last"/)! - .at(1)!, - ) + const linkHeader = contributors.headers.get("Link") + const contributorCount = linkHeader + ? Number.parseInt(linkHeader.match(/&page=(\d+)>; rel="last"/)?.at(1) ?? "0") + : 0 return { stars: meta.stargazers_count, release: { diff --git a/packages/console/app/src/routes/black.css b/packages/console/app/src/routes/black.css index 7510dfb88c..e02b17a8b4 100644 --- a/packages/console/app/src/routes/black.css +++ b/packages/console/app/src/routes/black.css @@ -1,44 +1,34 @@ ::view-transition-group(*) { - animation-duration: 200ms; - animation-timing-function: cubic-bezier(0.25, 0, 0.5, 1); + animation-duration: 250ms; + animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } ::view-transition-old(root), ::view-transition-new(root) { - animation-duration: 200ms; - animation-timing-function: cubic-bezier(0.25, 0, 0.5, 1); + animation-duration: 250ms; + animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } -@keyframes reveal { - from { - mask-position: 0% 200%; - opacity: 0; - } - to { - mask-position: 0% 50%; - opacity: 1; - } +::view-transition-image-pair(root) { + isolation: isolate; } -@keyframes reveal-reverse { - from { - mask-position: 0% 50%; - opacity: 1; - } - to { - mask-position: 0% 200%; - opacity: 0; - } +::view-transition-old(root) { + animation: none; + mix-blend-mode: normal; +} + +::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; } @keyframes fade-in { from { opacity: 0; - transform: translateY(4px); } to { opacity: 1; - transform: translateY(0); } } @@ -51,20 +41,93 @@ } } -::view-transition-new(terms) { - animation: reveal 400ms cubic-bezier(0.25, 0, 0.5, 1) forwards; +@keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } } -::view-transition-old(terms) { - animation: reveal-reverse 200ms cubic-bezier(0.25, 0, 0.5, 1) forwards; +@keyframes reveal-terms { + from { + mask-position: 0% 200%; + } + to { + mask-position: 0% 50%; + } } -::view-transition-new(action) { - animation: fade-in 300ms cubic-bezier(0.25, 0, 0.5, 1) forwards; +@keyframes hide-terms { + from { + mask-position: 0% 50%; + } + to { + mask-position: 0% 200%; + } } -::view-transition-old(action) { - animation: fade-out 150ms cubic-bezier(0.25, 0, 0.5, 1) forwards; +::view-transition-old(terms-20), +::view-transition-old(terms-100), +::view-transition-old(terms-200) { + mask-image: linear-gradient(to bottom, transparent, black 25% 75%, transparent); + mask-repeat: no-repeat; + mask-size: 100% 200%; + animation: hide-terms 200ms cubic-bezier(0.25, 0, 0.5, 1) forwards; +} + +::view-transition-new(terms-20), +::view-transition-new(terms-100), +::view-transition-new(terms-200) { + mask-image: linear-gradient(to bottom, transparent, black 25% 75%, transparent); + mask-repeat: no-repeat; + mask-position: 0% 200%; + mask-size: 100% 200%; + animation: reveal-terms 300ms cubic-bezier(0.25, 0, 0.5, 1) 50ms forwards; +} + +::view-transition-old(action-20), +::view-transition-old(action-100), +::view-transition-old(action-200) { + animation: fade-out 100ms cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +::view-transition-new(action-20), +::view-transition-new(action-100), +::view-transition-new(action-200) { + animation: fade-in-up 200ms cubic-bezier(0.16, 1, 0.3, 1) 250ms forwards; + opacity: 0; +} + +::view-transition-group(plan-card-20), +::view-transition-group(plan-card-100), +::view-transition-group(plan-card-200) { + animation-duration: 200ms; + animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +::view-transition-image-pair(plan-card-20), +::view-transition-image-pair(plan-card-100), +::view-transition-image-pair(plan-card-200) { + isolation: isolate; +} + +::view-transition-old(plan-card-20), +::view-transition-old(plan-card-100), +::view-transition-old(plan-card-200) { + animation: fade-out 120ms cubic-bezier(0.4, 0, 0.2, 1) forwards; + mix-blend-mode: normal; +} + +::view-transition-new(plan-card-20), +::view-transition-new(plan-card-100), +::view-transition-new(plan-card-200) { + animation: fade-in 150ms cubic-bezier(0.4, 0, 0.2, 1) 50ms forwards; + opacity: 0; + mix-blend-mode: normal; } [data-page="black"] { @@ -285,15 +348,15 @@ gap: 12px; padding: 24px; border: 1px solid rgba(255, 255, 255, 0.17); - background: rgba(0, 0, 0, 0.75); - backdrop-filter: blur(10px); + background: black; + background-clip: padding-box; border-radius: 4px; text-decoration: none; transition: border-color 0.15s ease; cursor: pointer; text-align: left; - &:hover { + &:hover:not(:active) { border-color: rgba(255, 255, 255, 0.35); } @@ -388,10 +451,6 @@ flex-direction: column; gap: 8px; text-align: left; - mask-image: linear-gradient(to bottom, transparent, black 25% 75%, transparent); - mask-repeat: no-repeat; - mask-position: 0% 50%; - mask-size: 100% 200%; li { color: rgba(255, 255, 255, 0.59); diff --git a/packages/console/app/src/routes/black/index.tsx b/packages/console/app/src/routes/black/index.tsx index d1c6bd250f..8f7ee95f3a 100644 --- a/packages/console/app/src/routes/black/index.tsx +++ b/packages/console/app/src/routes/black/index.tsx @@ -47,12 +47,12 @@ export default function Black() { type="button" onClick={() => select(plan.id)} data-slot="pricing-card" - style={{ "view-transition-name": `plan-card-${plan.id}` }} + style={{ "view-transition-name": `card-${plan.id}` }} > -
- {" "} +
+
-

+

${plan.id} per month {plan.multiplier} @@ -66,18 +66,18 @@ export default function Black() { {(plan) => (

-
-
+
+
-

+

${plan().id}{" "} per person billed monthly {plan().multiplier}

-
    +
    • Your subscription will not start immediately
    • You will be added to the waitlist and activated soon
    • Your card will be only charged when your subscription is activated
    • @@ -86,7 +86,77 @@ export default function Black() {
    • Limits may be adjusted and plans may be discontinued in the future
    • Cancel your subscription at anytime
    -
    +
    + + + Continue + +
    +
    +
+ )} + + + {(plan) => ( +
+
+
+ +
+

+ ${plan().id}{" "} + per person billed monthly + + {plan().multiplier} + +

+
    +
  • Your subscription will not start immediately
  • +
  • You will be added to the waitlist and activated soon
  • +
  • Your card will be only charged when your subscription is activated
  • +
  • Usage limits apply, heavily automated use may reach limits sooner
  • +
  • Subscriptions for individuals, contact Enterprise for teams
  • +
  • Limits may be adjusted and plans may be discontinued in the future
  • +
  • Cancel your subscription at anytime
  • +
+
+ + + Continue + +
+
+
+ )} +
+ + {(plan) => ( +
+
+
+ +
+

+ ${plan().id}{" "} + per person billed monthly + + {plan().multiplier} + +

+
    +
  • Your subscription will not start immediately
  • +
  • You will be added to the waitlist and activated soon
  • +
  • Your card will be only charged when your subscription is activated
  • +
  • Usage limits apply, heavily automated use may reach limits sooner
  • +
  • Subscriptions for individuals, contact Enterprise for teams
  • +
  • Limits may be adjusted and plans may be discontinued in the future
  • +
  • Cancel your subscription at anytime
  • +
+