Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update initial messaging #342

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
353 changes: 347 additions & 6 deletions src/components/landing/Intro.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import { ButtonLink } from './SharedComponents';
import { useEffect, useLayoutEffect } from 'react';
import { ButtonLink, classNames } from './SharedComponents';
import { Side } from '@site/src/pages/arrows/types';
import { Rect } from '@site/src/pages/arrows/rect';
import {
bendPath,
getInnerGridLines,
getLineSegmentsFromGridLines,
getSvgPathFromSegments,
oppositeSide,
pathToD,
} from '@site/src/pages/arrows/path';
import { maybeStringToNumber } from '@site/src/pages/arrows/index';

export function Intro() {
return (
<section className="py-36 bgimage-gradient-blue">
<div className="container m-auto max-w-7xl">
<h1 className="max-w-2xl m-auto text-5xl font-semibold leading-tight tracking-tighter text-center md:text-6xl lg:text-6xl text-white/90 drop-shadow-sm">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-semibold leading-tight tracking-tighter text-center text-white/90 drop-shadow-sm max-w-[645px] m-auto">
Turn ideas into diagrams and code in minutes.
</h1>

<div className="flex flex-col items-center w-full">
<p className="max-w-2xl mt-16 mb-8 text-base leading-normal tracking-tight text-center md:text-lg lg:text-xl text-white/60 drop-shadow-sm">
From frontend user flows to backend workflows, visually build and
deploy any type of logic with Stately as your source of truth.
<div className="flex flex-col items-center w-full pt-24">
<ConversionBoxes />
<p className="max-w-3xl mt-36 mb-16 text-lg leading-normal tracking-tight text-center md:text-xl lg:text-2xl text-white/60 drop-shadow-sm">
From frontend user flows to backend workflows, build and deploy any
type of logic with Stately as your source of truth.
</p>
<CallToActionButtons />
</div>
Expand All @@ -31,6 +44,334 @@ export function Intro() {
);
}

interface DrawnPath {
redraw: () => void;
}

function useArrows(
config: Record<
string,
Array<{
target: string;
sourceSide?: 'top' | 'right' | 'bottom' | 'left';
targetSide?: 'top' | 'right' | 'bottom' | 'left';
// used for window.matchMedia(media)
media?: string;
}>
>,
) {
useLayoutEffect(() => {
const svgEl = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

svgEl.setAttribute(
'style',
`
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
max-width: 100%;
overflow: visible;
pointer-events: none;
z-index: 2;
`,
);

const defEl = document.createElementNS(
'http://www.w3.org/2000/svg',
'defs',
);

defEl.innerHTML = `
<marker
id="arrow"
viewBox="0 0 10 10"
markerWidth="5"
markerHeight="5"
refX="0"
refY="5"
markerUnits="strokeWidth"
orient="auto"
>
<path data-arrow="marker" d="M0,0 L0,10 L10,5 z" fill="currentcolor" />
</marker>
`;
svgEl.setAttribute('id', 'arrows');
svgEl.appendChild(defEl);
document.body.appendChild(svgEl);

return () => {
svgEl.remove();
};
}, []);

useEffect(() => {
const nodeEls =
document.querySelectorAll<HTMLElement>('[data-edge-source]');
const paths: DrawnPath[] = [];
const resizeObserverFns: Array<() => void> = [];

function onResize(el: any, cb: (rect: Rect) => void) {
const resizeObserver = new ResizeObserver(() => {
requestAnimationFrame(() => {
if (el.ownerDocument.contains(el)) {
cb(el.getBoundingClientRect());
}
});
});
resizeObserver.observe(el);

resizeObserverFns.push(() => resizeObserver.unobserve(el));
}

const svgEl = document.querySelector('#arrows')! as SVGElement;

function drawPath(config: {
source: Element;
sourceSide: Side;
/**
* Distance (%) from left or top of source side.
*
* @default 0.5
*/
sourcePosition?: number;
target: Element;
targetSide: Side;
/**
* Distance (%) from left or top of target side.
*
* @default 0.5
*/
targetPosition?: number;
/**
* Distance (%) between start and end of zig-zag edge where it
* should cut across.
*
* @default 0.5
*/
bendPosition?: number;
radius?: number;
color?: string;
attributes?: Record<string, string>;
}): DrawnPath {
const resolvedConfig = {
radius: 10,
...config,
};

const { source, target } = resolvedConfig;

function getPathD() {
const svgRect = new Rect(svgEl.getBoundingClientRect());

const sourceRect = new Rect(source.getBoundingClientRect());
const targetRect = new Rect(target.getBoundingClientRect());
const startPoint = sourceRect.relativeSide(
resolvedConfig.sourceSide,
resolvedConfig.sourcePosition ?? 0.5,
);
let endPoint = targetRect.relativeSide(
resolvedConfig.targetSide,
resolvedConfig.targetPosition ?? 0.5,
);

if (
oppositeSide[resolvedConfig.sourceSide] ===
resolvedConfig.targetSide &&
resolvedConfig.targetPosition === undefined
) {
if (
['top', 'bottom'].includes(resolvedConfig.targetSide) &&
startPoint.x > targetRect.left &&
startPoint.x < targetRect.right
) {
endPoint.x = startPoint.x;
} else if (
startPoint.y > targetRect.top &&
startPoint.y < targetRect.bottom
) {
endPoint.y = startPoint.y;
}
}
startPoint.y -= svgRect.top;
endPoint.y -= svgRect.top;
const lines = getInnerGridLines(
startPoint,
endPoint,
resolvedConfig.bendPosition ?? 0.5,
);
const lineSegments = getLineSegmentsFromGridLines(
startPoint,
endPoint,
lines,
);

const svgPath = getSvgPathFromSegments(
lineSegments.allLineSegments,
startPoint,
endPoint,
);

const pathD = pathToD(bendPath(svgPath, resolvedConfig.radius));

return pathD;
}

const pathEl = document.createElementNS(
'http://www.w3.org/2000/svg',
'path',
);

pathEl.setAttribute('stroke', resolvedConfig.color ?? 'white');
pathEl.setAttribute('stroke-width', '2');
pathEl.setAttribute('fill', 'none');
pathEl.setAttribute('d', getPathD());
pathEl.setAttribute('marker-end', 'url(#arrow)');

if (resolvedConfig.attributes) {
Object.entries(resolvedConfig.attributes).forEach(([key, value]) => {
pathEl.setAttribute(key, value);
});
}
svgEl.appendChild(pathEl);

const obj = {
redraw: () => {
pathEl.setAttribute('d', getPathD());
},
};

onResize(source, obj.redraw);
onResize(target, obj.redraw);

return obj;
}

Object.entries(config).forEach(([nodeKey, nodeConfig]) => {
const elNode = document.querySelector<HTMLElement>(
`[data-edge-source="${nodeKey}"]`,
);
if (!elNode) {
return;
}

nodeConfig.forEach((targetConfig) => {
const elTarget = document.querySelector(
`[data-edge-source="${targetConfig.target}"]`,
);

if (!elTarget) {
return;
}

const sourceSide = targetConfig.sourceSide ?? 'bottom';
const targetSide = targetConfig.targetSide ?? 'top';
const sourcePosition = maybeStringToNumber(
elNode.dataset.edgeSourcePosition,
);
const targetPosition = maybeStringToNumber(
elNode.dataset.edgeTargetPosition,
);
const bendPosition = maybeStringToNumber(
elNode.dataset.edgeBendPosition,
);

paths.push(
drawPath({
source: elNode,
sourceSide,
sourcePosition,
target: elTarget,
targetSide,
targetPosition,
bendPosition,
attributes: {
class: 'edge',
},
radius: 20,
}),
);
});
});

const resizeHandler = () => {
paths.forEach((path) => path.redraw());
};

window.addEventListener('resize', resizeHandler);

return () => {
window.removeEventListener('resize', resizeHandler);

resizeObserverFns.forEach((fn) => fn());
};
});
}

function ConversionBoxes() {
const boxStyles =
'bg-gradient-to-b from-gray-700/50 to-gray-700/10 border-[0.5px] shadow-md shadow-blue-900 border-blue-850 rounded-2xl py-4 px-6 h-fit w-80';
const headerStyles = 'text-xl font-black';
const listStyles = 'text-white/60 text-sm pt-1 font-medium space-y-1';
useArrows({
diagrams: [
{
target: 'code',
sourceSide: 'right',
targetSide: 'bottom',
media: '(min-width: 1024px)',
},
],
});

return (
<div className="grid grid-cols-1 lg:grid-cols-3 lg:grid-rows-2 gap-10 w-fit max-w-4xl m-auto">
<div
className={classNames(boxStyles, 'lg:col-start-1 lg:col-span-2')}
data-edge-source="ideas"
>
<h3 className={headerStyles}>Ideas</h3>
<ul className={listStyles}>
<li>Requirements</li>
<li>User stories</li>
<li>Features</li>
<li>Specifications</li>
</ul>
</div>

<div
className={classNames(
boxStyles,
'lg:row-start-2 lg:col-start-2 lg:col-span-3 justify-self-center',
)}
data-edge-source="diagrams"
>
<h3 className={headerStyles}>Diagrams</h3>
<ul className={listStyles}>
<li>State machines</li>
<li>Flowcharts</li>
<li>Statecharts</li>
<li>Sequence diagrams</li>
</ul>
</div>

<div
className={classNames(boxStyles, 'lg:col-start-4 lg:col-span-2')}
data-edge-source="code"
>
<h3 className={headerStyles}>Code</h3>
<ul className={listStyles}>
<li>Workflows</li>
<li>App logic</li>
<li>JS, TS, JSON</li>
<li>Mermaid</li>
</ul>
</div>
</div>
);
}

function CallToActionButtons() {
return (
<div className="flex justify-center gap-4 md:justify-start">
Expand Down
Empty file added src/pages/arrows.ts
Empty file.
Loading