diff options
Diffstat (limited to 'web/tvl')
-rw-r--r-- | web/tvl/blog/default.nix | 18 | ||||
-rw-r--r-- | web/tvl/blog/rewriting-nix.md | 90 | ||||
-rw-r--r-- | web/tvl/default.nix | 131 | ||||
-rw-r--r-- | web/tvl/footer/default.nix | 21 | ||||
-rw-r--r-- | web/tvl/logo/default.nix | 97 | ||||
-rw-r--r-- | web/tvl/logo/logo-shapes.svg | 27 | ||||
-rw-r--r-- | web/tvl/template/default.nix | 52 | ||||
-rw-r--r-- | web/tvl/tvl.dot | 172 |
8 files changed, 608 insertions, 0 deletions
diff --git a/web/tvl/blog/default.nix b/web/tvl/blog/default.nix new file mode 100644 index 000000000000..963bb635e3b9 --- /dev/null +++ b/web/tvl/blog/default.nix @@ -0,0 +1,18 @@ +{ depot, ... }: + +{ + config = { + name = "TVL's blog"; + footer = depot.web.tvl.footer { }; + baseUrl = "https://tvl.fyi/blog"; + }; + + posts = [ + { + key = "rewriting-nix"; + title = "Tvix: We are rewriting Nix"; + date = 1638381387; + content = ./rewriting-nix.md; + } + ]; +} diff --git a/web/tvl/blog/rewriting-nix.md b/web/tvl/blog/rewriting-nix.md new file mode 100644 index 000000000000..1b143e5b0ed9 --- /dev/null +++ b/web/tvl/blog/rewriting-nix.md @@ -0,0 +1,90 @@ +Evaluating the Nix programming language, used by the Nix package +manager, is currently very slow. This becomes apparent in all projects +written in Nix that are not just simple package definitions, for +example: + +* the NixOS module system +* TVL projects like + [`//nix/yants`](https://at.tvl.fyi/?q=%2F%2Fnix%2Fyants) and + [`//web/bubblegum`](https://at.tvl.fyi/?q=%2F%2Fweb%2Fbubblegum). +* the code that [generates build + instructions](https://at.tvl.fyi/?q=%2F%2Fops%2Fpipelines) for TVL's + [CI setup](https://tvl.fyi/builds) + +Whichever project you pick, they all suffer from issues with the +language implementation. At TVL, it takes us close to a minute to +create the CI instructions for our monorepo at the moment - despite it +being a plain Nix evaluation. Running our Nix-native build systems for +[Go](https://code.tvl.fyi/about/nix/buildGo) and [Common +Lisp](https://code.tvl.fyi/about/nix/buildLisp) takes much more time +than we would like. + +Some time last year a few of us got together and started investigating +ways to modernise the current architecture of Nix and figure out how +to improve the speed of some of the components. We created over [250 +commits](https://cl.tvl.fyi/q/topic:tvix) in our fork of the Nix 2.3 +codebase at the time, tried [performance +experiments](https://cl.tvl.fyi/c/depot/+/1123/) aimed at improving +the current evaluator and fought [gnarly +bugs](https://cl.tvl.fyi/c/depot/+/1504). + +After a while we realised that we were treading water: Some of our +ideas are too architecturally divergent from Nix to be done on top of +the existing codebase, and the memory model of Nix causes significant +headaches when trying to do any kind of larger change. + +We needed an alternative approach and started brainstorming on a bent +whiteboard in a small flat in Hurghada, Egypt. + +![flokli & tazjin brainstorming](https://static.tvl.fyi/latest/files/flokli_tazjin_tvix.webp) + +Half a year later we are now ready to announce our new project: +**Tvix**, a re-imagined Nix with full nixpkgs compatibility. Tvix is +generously funded [by NLNet](https://nlnet.nl/project/Tvix/) (thanks!) +and we are ready to start implementing it. + +The [Tvix +architecture](https://code.tvl.fyi/about/tvix/docs/components.md) is +designed to be modular: It should be possible to write an evaluator +that plugs in the Guile language (for compatibility with GNU Guix), to +use arbitrary builders, and to replace the store implementation. + +Tvix has these high-level goals: + +* Creating an alternative implementation of Nix that is **fully + compatible with nixpkgs**. + + The package collection is an enormous effort with hundreds of + thousands of commits, encoding expert knowledge about lots of + different software and ways of building and managing it. It is a + very valuable piece of software and we must be able to reuse it. + +* More efficient Nix language evaluation, leading to greatly increased + performance. + +* No more strict separation of evaluation and build phases: Generating + Nix data structures from build artefacts ("IFD") should be supported + first-class and not incur significant performance cost. + +* Well-defined interaction protocols for how the three different + components (evaluator, builder, store) interact. + +* A builder implementation using OCI instead of custom sandboxing + code. + +![adisbladis & tazjin brainstorming](https://static.tvl.fyi/latest/files/adisbladis_tazjin_tvix.webp) + +Tvix is not intended to *replace* Nix, instead we want to improve the +ecosystem by offering an alternative, fast and reliable implementation +for Nix features that are in use today. + +As things ramp up we will be posting more information on this blog, +for now you can keep an eye on +[`//tvix`](https://cs.tvl.fyi/depot/-/tree/tvix) in the TVL monorepo +and subscribe to [our feed](https://tvl.fyi/feed.atom). + +Stay tuned! + +<span style="font-size: small;">PS: TVL is international, but a lot of +the development will take place in our office in Moscow. Say hi if +you're around and interested!</span> diff --git a/web/tvl/default.nix b/web/tvl/default.nix new file mode 100644 index 000000000000..262be54c0ef1 --- /dev/null +++ b/web/tvl/default.nix @@ -0,0 +1,131 @@ +{ depot, lib, pkgs, ... }: + +with depot.nix.yants; + +let + inherit (builtins) filter; + inherit (pkgs) graphviz runCommandNoCC writeText; + inherit (depot.web) atom-feed blog tvl; + + listPosts = defun [ (list blog.post) string ] (posts: + lib.concatStringsSep "\n" (map (p: "* [${p.title}](blog/${p.key})") posts) + ); + + postRenderingCommands = defun [ (list blog.post) string ] (posts: + lib.concatStringsSep "\n" + (map (p: "cp ${blog.renderPost tvl.blog.config p} $out/blog/${p.key}.html") posts) + ); + + tvlGraph = runCommandNoCC "tvl.svg" + { + nativeBuildInputs = with pkgs; [ fontconfig freetype cairo jetbrains-mono ]; + } '' + ${graphviz}/bin/neato -Tsvg ${./tvl.dot} > $out + ''; + + publishedPosts = filter blog.includePost tvl.blog.posts; + + feed = { + id = "https://tvl.fyi/"; + title = "TVL blog"; + subtitle = "Thoughts and news from The Virus Lounge"; + authors = [ "tazjin" ]; # TODO(tazjin): Extract from post info + + links = lib.singleton { + rel = "self"; + href = "https://tvl.fyi/feed.atom"; + }; + + entries = map (blog.toFeedEntry tvl.blog.config) publishedPosts; + }; + + atomFeed = writeText "feed.atom" (atom-feed.renderFeed feed); + + homepage = tvl.template { + title = "The Virus Lounge"; + content = '' + The Virus Lounge + ================ + + ---------------- + + <img class="tvl-logo" src="https://static.tvl.fyi/${depot.web.static.drvHash}/logo-animated.svg" + alt="Virus with lambda-shaped spike proteins sitting on an armchair"> + + Welcome to **The Virus Lounge**. We're a group of people who got + together in 2020, when we felt that there was not enough + spontaneous socialising on the internet. + + Because of our shared interests in topics like **build systems** + and **monorepos** we started working on code together, in our + monorepo called the *depot*. + + Feel free to explore the tech we have built so far, all our + systems are linked in the footer. + + We mostly hang out on IRC. You can find us in [`#tvl`][tvl-irc] + on [hackint][], which is also reachable [via XMPP][hackint-xmpp] + at [`#tvl@irc.hackint.org`][tvl-xmpp] (sic!) and [via + Matrix][hackint-matrix] at [`#tvl:hackint.org`][tvl-matrix]. + + Hackint also provide a [web chat][tvl-webchat]. + + [tvl-irc]: ircs://irc.hackint.org:6697/#tvl + [hackint]: https://hackint.org/ + [hackint-xmpp]: https://hackint.org/transport/xmpp + [tvl-xmpp]: xmpp:#tvl@irc.hackint.org?join + [hackint-matrix]: https://hackint.org/transport/matrix + [tvl-matrix]: https://matrix.to/#/#tvl:hackint.org + [tvl-webchat]: https://webirc.hackint.org/#ircs://irc.hackint.org/#tvl + + ---------------- + + ## Blog + + Here are the most recent TVL blog posts. + + ${listPosts publishedPosts} + + You can also follow our [atom feed](https://tvl.fyi/feed.atom). + + ---------------- + + ## Where did all these people come from? + + It's pretty straightforward. Feel free to click on people, too. + + <div class="tvl-graph-container"> + <!-- + cheddar leaves HTML inside of HTML alone, + so wrapping the SVG prevents it from messing it up + --> + ${builtins.readFile tvlGraph} + </div> + ''; + extraHead = '' + <style> + .tvl-graph-container { + max-width: inherit; + } + + .tvl-graph-container svg { + max-width: inherit; + height: auto; + } + + .tvl-logo { + width: 60%; + display: block; + margin-left: auto; + margin-right: auto; + } + </style> + ''; + }; +in +runCommandNoCC "website" { } '' + mkdir -p $out/blog + cp ${homepage} $out/index.html + ${postRenderingCommands tvl.blog.posts} + cp ${atomFeed} $out/feed.atom +'' diff --git a/web/tvl/footer/default.nix b/web/tvl/footer/default.nix new file mode 100644 index 000000000000..dc2c963f90f4 --- /dev/null +++ b/web/tvl/footer/default.nix @@ -0,0 +1,21 @@ +# Footer fragment for TVL homepages, used by //web/tvl/template for +# our static pages and also via //web/blog for blog posts. +{ lib, ... }: + +args: '' + <p class="footer"> + <a class="uncoloured-link" href="https://at.tvl.fyi/?q=%2F%2FREADME.md">code</a> + | + <a class="uncoloured-link" href="https://cl.tvl.fyi/">reviews</a> + | + <a class="uncoloured-link" href="https://tvl.fyi/builds">ci</a> + | + <a class="uncoloured-link" href="https://b.tvl.fyi/">bugs</a> + | + <a class="uncoloured-link" href="https://todo.tvl.fyi/">todos</a> + | + <a class="uncoloured-link" href="https://atward.tvl.fyi/">search</a> +'' + lib.optionalString (args ? extraFooter) args.extraFooter + '' + </p> + <p class="lod">ಠ_ಠ</p> +'' diff --git a/web/tvl/logo/default.nix b/web/tvl/logo/default.nix new file mode 100644 index 000000000000..d9e023946a42 --- /dev/null +++ b/web/tvl/logo/default.nix @@ -0,0 +1,97 @@ +# Creates an output containing the logo in SVG format (animated and +# static, one for each background colour) and without animations in +# PNG. +{ depot, lib, pkgs, ... }: + +let + palette = { + purple = "#CC99C9"; + blue = "#9EC1CF"; + green = "#9EE09E"; + yellow = "#FDFD97"; + orange = "#FEB144"; + red = "#FF6663"; + }; + + staticCss = colour: '' + #armchair-background { + fill: ${colour}; + } + ''; + + # Create an animated CSS that equally spreads out the colours over + # the animation duration (1min). + animatedCss = colours: + let + # Calculate at which percentage offset each colour should appear. + stepSize = 100 / ((builtins.length colours) - 1); + frames = lib.imap0 (idx: colour: { inherit colour; at = idx * stepSize; }) colours; + frameCss = frame: "${toString frame.at}% { fill: ${frame.colour}; }"; + in + '' + #armchair-background { + animation: 30s infinite alternate armchairPalette; + } + + @keyframes armchairPalette { + ${lib.concatStringsSep "\n" (map frameCss frames)} + } + ''; + + # Dark version of the logo, suitable for light backgrounds. + darkCss = armchairCss: '' + .structure { + fill: #383838; + } + #letters { + fill: #fefefe; + } + ${armchairCss} + ''; + + # Light version, suitable for dark backgrounds. + lightCss = armchairCss: '' + .structure { + fill: #e4e4ef; + } + #letters { + fill: #181818; + } + ${armchairCss} + ''; + + logoShapes = builtins.readFile ./logo-shapes.svg; + logoSvg = style: '' + <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="420 860 1640 1500" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <style>${style}</style> + ${logoShapes} + </svg> + ''; + +in +depot.nix.readTree.drvTargets (lib.fix (self: { + # Expose the logo construction functions. + inherit palette darkCss lightCss animatedCss staticCss; + + # Create a TVL logo SVG with the specified style. + logoSvg = style: pkgs.writeText "logo.svg" (logoSvg style); + + # Create a PNG of the TVL logo with the specified style and DPI. + logoPng = style: dpi: pkgs.runCommandNoCC "logo.png" { } '' + ${pkgs.inkscape}/bin/inkscape \ + --export-area-drawing \ + --export-background-opacity 0 \ + --export-dpi ${toString dpi} \ + ${self.logoSvg style} -o $out + ''; + + # Animated dark SVG logo with all colours. + pastelRainbow = self.logoSvg (darkCss (animatedCss (lib.attrValues palette))); +} + + # Add individual outputs for static dark logos of each colour. + // (lib.mapAttrs' + (k: v: lib.nameValuePair "${k}Png" + (self.logoPng (darkCss (staticCss v)) 96)) + palette))) diff --git a/web/tvl/logo/logo-shapes.svg b/web/tvl/logo/logo-shapes.svg new file mode 100644 index 000000000000..d35971e0a91a --- /dev/null +++ b/web/tvl/logo/logo-shapes.svg @@ -0,0 +1,27 @@ +<polygon id="armchair-background" points="463 2030 567 1814 1904 1814 1978 2030 1935 2169 1720 2155 1590 2311 873 2305 778 2142 570 2186"/> +<g class="structure"> + <path id="virusbody" d="M 707.524,1820.74 701.038,1401.58 970,1100 h 542 l 271.37,310.47 -16.99,419.17 -295.93,284.34 -445.46,-4.15 z"/> +</g> +<g class="structure" id="lambdas"> + <!-- virus lambdas and feet, clockwise starting at the top left --> + <path id="topleft" d="m 1002,1045 38,75 -65,35 -140,-260 h 78 l 47,80 45,-80 h 45 l 17.39,34.968" /> + <use id="topright" xlink:href="#topleft" transform="matrix(-1,0,0,1,2482,0)" /> + <use id="midright" xlink:href="#topleft" transform="matrix(-0.70710678,-0.70710678,-0.70710678,0.70710678,3284.799,1331.4128)" /> + <use id="bottomright" xlink:href="#topleft" transform="matrix(-0.25881905,-0.96592583,-0.96592583,0.25881905,3120.6829,2438.0653)" /> + <use id="rightfoot" xlink:href="#topleft" transform="matrix(-0.60515932,0.14752194,-0.14752194,-0.60515932,2234.5287,2616.7665)" /> + <use id="leftfoot" xlink:href="#topleft" transform="matrix(0.60515932,0.14752194,0.14752194,-0.60515932,253.62404,2616.7665)" /> + <use id="bottomleft" xlink:href="#topleft" transform="rotate(-75,1263.0635,1635.2798)" /> + <use id="midleft" xlink:href="#topleft" transform="rotate(-45,1209.002,1626.9386)" /> +</g> +<g class="structure" id="armchair"> + <path d="M742.781 2172.23s-89.208 93.93-210.767 22.78c-121.56-71.14-124.755-220.09-47.72-318 78.865-100.24 220.899-86.94 221.229-85.38.274 1.3 247.178 196.08 328.597 260.28 16.08 12.68 25.71 20.27 25.71 20.27l-37.68 41.02s-209.519-177.76-290.729-250.45c-9.975 1.38-150.662-67.27-214.983 108.51-24.251 74.65 15.983 145.09 69.889 167.71 91.689 19.32 94.88 1.94 121.523-18.39"/> + <path d="M1738.4 2174.64s91.9 88.75 209.97 16.51c118.07-72.25 115.91-216.85 39.26-313.11-78.47-98.55-217.31-83.5-217.61-81.95-.26 1.29-239.43 197.97-318.3 262.8-15.58 12.8-24.9 20.46-24.9 20.46l37.4 40.26s202.73-184.66 281.29-257.92c9.78 1.23 134.36-50.54 211.78 110.07 28.32 92.64-13.71 144.64-66.18 167.81-89.5 20.38-90.29.61-116.63-19.24"/> + <path d="m899.02 2276.92 680.44-.32 98.56-134.61 51.64 32.46-121.94 160.78-739.1-1.03-125.507-162.22 54.172-39.79 101.735 144.73Z"/> + <path d="m744.143 2173.36 56.05-35.55s-44.914-79.17-102.074-8.6"/> + <path d="M1728.8 2176.06c-7.6 2.16-53.69-30.58-53.69-30.58s43.06-84.48 102.63-21.21c59.57 63.27-52.85 47.65-48.94 51.79Z"/> +</g> +<g id="letters" fill="#fefefe"> + <path id="t" d="M970.081 1776.8c-22.214 0-40.017-6.45-53.41-19.35-13.394-12.9-20.09-30.14-20.09-51.7v-158.27h-75.95v-40.18h75.95v-75.95h44.1v75.95h107.799v40.18H940.681v158.27c0 9.15 2.695 16.58 8.085 22.3 5.39 5.72 12.495 8.57 21.315 8.57h73.499v40.18h-73.499Z"/> + <path id="v" d="M 1205.77 1776.8 L 1112.18 1507.3 L 1157.75 1507.3 L 1235.66 1742.99 L 1311.12 1507.3 L 1357.18 1507.3 L 1263.59 1776.8 L 1205.77 1776.8 L 1205.77 1776.8 Z"/> + <path id="lambda" d="M 1406.18 1776.8 L 1506.14 1511.71 L 1469.88 1419.1 L 1516.92 1419.1 L 1651.18 1776.8 L 1604.14 1776.8 L 1539.95 1601.87 L 1530.64 1571.49 L 1453.71 1776.8 L 1406.18 1776.8 Z"/> +</g> diff --git a/web/tvl/template/default.nix b/web/tvl/template/default.nix new file mode 100644 index 000000000000..6b6a5b03038e --- /dev/null +++ b/web/tvl/template/default.nix @@ -0,0 +1,52 @@ +{ depot, pkgs, lib, ... }: + +{ + # content of the <title> tag + title + # main part of the page, usually wrapped with <main> +, content + # optional extra html to inject into <head> +, extraHead ? null + # optional extra html to inject into <footer> +, extraFooter ? null + # URL at which static assets are located +, staticUrl ? "https://static.tvl.fyi/${depot.web.static.drvHash}" +}@args: + +let + inherit (pkgs) runCommandNoCC lib; + inherit (depot.tools) cheddar; +in + +runCommandNoCC "${lib.strings.sanitizeDerivationName title}-index.html" +{ + headerPart = '' + <!DOCTYPE html> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="description" content="The Virus Lounge"> + <link rel="stylesheet" type="text/css" href="${staticUrl}/tvl.css" media="all"> + <link rel="icon" type="image/webp" href="${staticUrl}/favicon.webp"> + <link rel="alternate" type="application/atom+xml" title="Atom Feed" href="https://tvl.fyi/feed.atom"> + <title>${title}</title> + '' + lib.optionalString (args ? extraHead) extraHead + '' + </head> + <body class="light"> + ''; + + inherit content; + + footerPart = '' + <hr> + <footer> + ${depot.web.tvl.footer args} + </footer> + </body> + ''; + + passAsFile = [ "headerPart" "content" "footerPart" ]; +} '' + ${cheddar}/bin/cheddar --about-filter content.md < $contentPath > rendered.html + cat $headerPartPath rendered.html $footerPartPath > $out +'' diff --git a/web/tvl/tvl.dot b/web/tvl/tvl.dot new file mode 100644 index 000000000000..b28c529e0c3a --- /dev/null +++ b/web/tvl/tvl.dot @@ -0,0 +1,172 @@ +digraph tvl { + node [fontname = "JetBrains Mono"]; + overlap = false; + splines = polyline; + + TVL [style="bold" href="http://tvl.fyi"]; + tazjin -> TVL [style="bold"]; + + // people + subgraph { + Irenes [href="https://www.pluralpride.com/"]; + adisbladis [href="http://nixos.expert/"]; + andi [label="andi-" href="https://andreas.rammhold.de/"]; + anon1 [color="grey" fontcolor="grey"]; + aurora [href="https://nonegenderleftfox.aventine.se/"]; + benjojo [href="https://benjojo.co.uk/"]; + cynthia [href="https://cynthia.re/"]; + edef [href="https://edef.eu/files/edef.hs"]; + ericvolp [href="https://ericv.me"]; + espes; + eta [href="https://theta.eu.org/"]; + firefly [href="http://firefly.nu/"]; + flokli [href="https://flokli.de/"]; + fzakaria [href="https://fzakaria.com/"]; + ghuntley [href="https://ghuntley.com/"]; + grfn [href="http://gws.fyi"]; + htbf [href="https://htbf.dev/"]; + implr [href="https://twitter.com/implring"]; + isomer [href="https://www.lorier.net/"]; + jusrin [href="https://jusrin.dev/"]; + kn; + lassulus; + leah2 [href="https://leahneukirchen.org/"]; + lukegb [href="https://lukegb.com/"]; + marcusr [href="http://marcus.nordaaker.com/"]; + mdjnsn; + ncl; + nikky [href="http://nikky.moe/"]; + nyanotech [href="https://twitter.com/nyanotech"]; + poigon; + Profpatsch [href="http://profpatsch.de/"]; + qyliss [href="https://alyssa.is"]; + seven [href="https://open.spotify.com/user/so7"]; + spacekookie [href="https://spacekookie.de/"]; + sterni [href="https://sterni.lv/"]; + tazjin [href="https://tazj.in/"]; + wpcarro [href="https://wpcarro.dev/"]; + yuuko; + } + + // companies (blue) + subgraph { + node [color="#4285f4" fontcolor="#4285f4"]; + spotify [href="https://www.spotify.com/"]; + google [href="https://www.google.com/"]; + } + + // communities? (red) + subgraph { + node [color="#db4437" fontcolor="#db4437"]; + eve [href="https://www.eveonline.com/"]; + nix [href="https://nixos.org/nix/"]; + ircv3 [href="https://ircv3.net/"]; + lgbtslack [label="lgbt.tech" href="https://lgbtq.technology/"]; + muccc [label="µccc" href="https://muc.ccc.de/"]; + afra [label="AfRA" href="https://afra-berlin.de/"]; + } + + // special + subgraph { + baby [color="pink" fontcolor="pink" href="https://cynthia.re/s/baby"]; + unspecific [color="grey" fontcolor="grey"]; + } + + // primary edges (how did they end up in TVL?) + subgraph { + // Direct edges + nix -> tazjin; + spotify -> tazjin; + google -> tazjin; + eve -> tazjin; + unspecific -> tazjin; + edef -> tazjin; + + // via nix + adisbladis -> nix; + jusrin -> nix; + ghuntley -> nix; + flokli -> nix; + andi -> nix; + Profpatsch -> nix; + lassulus -> nix; + + // via edef + benjojo -> edef; + espes -> edef; + firefly -> edef; + leah2 -> aurora; + ncl -> edef; + qyliss -> edef; + + // via spotify + seven -> spotify; + + // via google + htbf -> google; + Irenes -> google; + isomer -> google; + lukegb -> google; + wpcarro -> google; + fzakaria -> google; + mdjnsn -> google; + + // random primary + grfn -> wpcarro; + anon1 -> google; + aurora -> eve; + cynthia -> benjojo; + eta -> anon1; + ericvolp -> lukegb; + marcusr -> unspecific; + poigon -> eve; + implr -> lukegb; + afra -> unspecific; + nikky -> afra; + spacekookie -> qyliss; + kn -> flokli; + sterni -> Profpatsch; + yuuko -> ncl; + } + + // secondary edges (how are they connected otherwise?) + subgraph { + edge [weight=0 style="dotted" color="grey" arrowhead="none"]; + + // lgbt slack + aurora -> lgbtslack; + leah2 -> lgbtslack; + edef -> lgbtslack; + + // ircv3 + eta -> ircv3; + firefly -> ircv3; + + // µccc + leah2 -> muccc; + + // random + leah2 -> edef; + lukegb -> isomer; + eta -> firefly; + cynthia -> firefly; + cynthia -> lukegb; + implr -> google; + nyanotech -> google; + lukegb -> benjojo; + espes -> benjojo; + espes -> aurora; + qyliss -> nix; + grfn -> nix; + edef -> nix; + spacekookie -> afra; + qyliss -> afra; + } + + // baby + subgraph { + edge [weight=0 style="dotted" color="pink" arrowhead="none"]; + cynthia -> baby; + eta -> baby; + } +} |