diff --git a/app/src/lib/graph-interface/edges/MeshGradientLine/MeshGradientLineMaterial.svelte b/app/src/lib/graph-interface/edges/MeshGradientLine/MeshGradientLineMaterial.svelte
new file mode 100644
index 0000000..d30a38a
--- /dev/null
+++ b/app/src/lib/graph-interface/edges/MeshGradientLine/MeshGradientLineMaterial.svelte
@@ -0,0 +1,111 @@
+
+
+
+ {@render children?.({ ref: material })}
+
diff --git a/app/src/lib/graph-interface/edges/MeshGradientLine/fragment.frag b/app/src/lib/graph-interface/edges/MeshGradientLine/fragment.frag
new file mode 100644
index 0000000..4163333
--- /dev/null
+++ b/app/src/lib/graph-interface/edges/MeshGradientLine/fragment.frag
@@ -0,0 +1,30 @@
+uniform vec3 colorStart;
+uniform vec3 colorEnd;
+
+uniform float useDash;
+uniform float dashArray;
+uniform float dashOffset;
+uniform float dashRatio;
+uniform sampler2D alphaMap;
+uniform float useAlphaMap;
+
+varying vec2 vUV;
+varying vec4 vColor;
+varying float vCounters;
+
+vec4 CustomLinearTosRGB( in vec4 value ) {
+ return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
+}
+
+void main() {
+
+ vec4 c = mix(vec4(colorStart,1.0),vec4(colorEnd, 1.0), vCounters);
+
+ if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUV ).r;
+
+ if( useDash == 1. ){
+ c.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio));
+ }
+
+ gl_FragColor = CustomLinearTosRGB(c);
+}
diff --git a/app/src/lib/graph-interface/edges/MeshGradientLine/types.ts b/app/src/lib/graph-interface/edges/MeshGradientLine/types.ts
new file mode 100644
index 0000000..15e07da
--- /dev/null
+++ b/app/src/lib/graph-interface/edges/MeshGradientLine/types.ts
@@ -0,0 +1,68 @@
+import type { Props } from '@threlte/core';
+import type { BufferGeometry, Vector3 } from 'three';
+import type { ColorRepresentation, ShaderMaterial, Texture } from 'three';
+
+export type MeshLineGeometryProps = Props & {
+ /**
+ * @default []
+ */
+ points: Vector3[];
+
+ /**
+ * @default 'none'
+ */
+ shape?: 'none' | 'taper' | 'custom';
+
+ /**
+ * @default () => 1
+ */
+ shapeFunction?: (p: number) => number;
+};
+
+export type MeshLineMaterialProps =
+ & Omit<
+ Props,
+ 'uniforms' | 'fragmentShader' | 'vertexShader'
+ >
+ & {
+ /**
+ * @default 1
+ */
+ opacity?: number;
+
+ /**
+ * @default '#ffffff'
+ */
+ color?: ColorRepresentation;
+
+ /**
+ * @default 0
+ */
+ dashOffset?: number;
+
+ /**
+ * @default 0
+ */
+ dashArray?: number;
+
+ /**
+ * @default 0
+ */
+ dashRatio?: number;
+
+ /**
+ * @default true
+ */
+ attenuate?: boolean;
+
+ /**
+ * @default 1
+ */
+ width?: number;
+
+ /**
+ * @default 0
+ */
+ scaleDown?: number;
+ alphaMap?: Texture | undefined;
+ };
diff --git a/app/src/lib/graph-interface/edges/MeshGradientLine/vertex.vert b/app/src/lib/graph-interface/edges/MeshGradientLine/vertex.vert
new file mode 100644
index 0000000..039a59f
--- /dev/null
+++ b/app/src/lib/graph-interface/edges/MeshGradientLine/vertex.vert
@@ -0,0 +1,83 @@
+attribute vec3 previous;
+attribute vec3 next;
+attribute float side;
+attribute float width;
+attribute float counters;
+
+uniform vec2 resolution;
+uniform float lineWidth;
+uniform vec3 color;
+uniform float opacity;
+uniform float sizeAttenuation;
+uniform float scaleDown;
+
+varying vec2 vUV;
+varying vec4 vColor;
+varying float vCounters;
+
+vec2 intoScreen(vec4 i) {
+ return resolution * (0.5 * i.xy / i.w + 0.5);
+}
+
+void main() {
+ float aspect = resolution.y / resolution.x;
+
+ mat4 m = projectionMatrix * modelViewMatrix;
+
+ vec4 currentClip = m * vec4( position, 1.0 );
+ vec4 prevClip = m * vec4( previous, 1.0 );
+ vec4 nextClip = m * vec4( next, 1.0 );
+
+ vec4 currentNormed = currentClip / currentClip.w;
+ vec4 prevNormed = prevClip / prevClip.w;
+ vec4 nextNormed = nextClip / nextClip.w;
+
+ vec2 currentScreen = intoScreen(currentNormed);
+ vec2 prevScreen = intoScreen(prevNormed);
+ vec2 nextScreen = intoScreen(nextNormed);
+
+ float actualWidth = lineWidth * width;
+
+ vec2 dir;
+ if(nextScreen == currentScreen) {
+ dir = normalize( currentScreen - prevScreen );
+ } else if(prevScreen == currentScreen) {
+ dir = normalize( nextScreen - currentScreen );
+ } else {
+ vec2 inDir = currentScreen - prevScreen;
+ vec2 outDir = nextScreen - currentScreen;
+ vec2 fullDir = nextScreen - prevScreen;
+
+ if(length(fullDir) > 0.0) {
+ dir = normalize(fullDir);
+ } else if(length(inDir) > 0.0){
+ dir = normalize(inDir);
+ } else {
+ dir = normalize(outDir);
+ }
+ }
+
+ vec2 normal = vec2(-dir.y, dir.x);
+
+ if(sizeAttenuation != 0.0) {
+ normal /= currentClip.w;
+ normal *= min(resolution.x, resolution.y);
+ }
+
+ if (scaleDown > 0.0) {
+ float dist = length(nextNormed - prevNormed);
+ normal *= smoothstep(0.0, scaleDown, dist);
+ }
+
+ vec2 offsetInScreen = actualWidth * normal * side * 0.5;
+
+ vec2 withOffsetScreen = currentScreen + offsetInScreen;
+ vec3 withOffsetNormed = vec3((2.0 * withOffsetScreen/resolution - 1.0), currentNormed.z);
+
+ vCounters = counters;
+ vColor = vec4( color, opacity );
+ vUV = uv;
+
+ gl_Position = currentClip.w * vec4(withOffsetNormed, 1.0);
+
+}