From f429db6369cb792e8306c62851edf6c0f989fad7 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Wed, 9 Aug 2023 09:28:08 -0700 Subject: [PATCH] Revamp the Graphite website (#1265) Revamp the website with more content --- README.md | 4 +- docs/README.md | 15 - docs/codebase/README.md | 3 - docs/design/README.md | 8 - docs/design/inputs-and-keybindings.md | 155 ---- docs/design/node-data.md | 24 - docs/design/tool-behavior.md | 11 - docs/editor/1-overview.md | 69 -- docs/editor/2-interface.md | 0 docs/editor/3-panels.md | 11 - docs/editor/4-documents-and-assets.md | 0 docs/editor/5-tools.md | 0 docs/editor/6-masking.md | 5 - frontend/assets/LICENSE.md | 2 +- frontend/index.html | 5 +- frontend/src/components/README.md | 2 +- frontend/src/io-managers/panic.ts | 4 +- website/.gitignore | 1 + website/config.toml | 1 + website/content/_index.md | 518 +++++++++++ website/content/about.md | 205 +++++ website/content/blog/_index.md | 2 +- .../content/{contact/_index.md => contact.md} | 7 +- website/content/contribute/_index.md | 149 ---- website/content/donate.md | 56 ++ website/content/features.md | 304 +++++++ website/content/features/_index.md | 220 ----- website/content/learn/_index.md | 14 + website/content/learn/introduction/_index.md | 8 + .../content/learn/introduction/artboards.md | 6 + .../content/learn/introduction/interface.md | 6 + website/content/learn/introduction/layers.md | 6 + website/content/learn/introduction/tools.md | 6 + website/content/learn/node-graph/_index.md | 9 + website/content/learn/node-graph/imaginate.md | 77 ++ website/content/learn/node-graph/nodes.md | 6 + .../content/{license/_index.md => license.md} | 9 +- website/content/{logo/_index.md => logo.md} | 25 +- website/content/press.md | 23 + website/content/press/_index.md | 22 - website/content/volunteer/_index.md | 70 ++ website/content/volunteer/guide/_index.md | 11 + .../guide/codebase-overview/_index.md | 67 ++ .../contributing-guidelines.md | 47 + .../guide/codebase-overview/debugging.md | 30 + .../guide/codebase-overview/tech-stack.md | 16 + .../volunteer/guide/getting-started/_index.md | 48 ++ .../getting-started/editor-and-tooling.md | 14 + .../guide/getting-started/getting-help.md | 14 + .../guide/getting-started/picking-a-task.md | 17 + .../volunteer/guide/product-design/_index.md | 18 + .../product-design/glossary-of-terminology.md | 68 ++ .../guide/product-design/product-outline.md | 12 +- .../product-design/uses-and-workflows.md | 108 ++- website/sass/about.scss | 51 ++ website/sass/article.scss | 30 +- website/sass/base.scss | 806 +++++++++++++++--- website/sass/blog.scss | 14 +- website/sass/book.scss | 111 +++ website/sass/donate.scss | 8 + website/sass/features.scss | 81 +- website/sass/index.scss | 369 ++++---- website/sass/volunteer.scss | 19 + website/static/js/book.js | 52 ++ website/static/js/carousel.js | 164 ---- website/static/js/fundraising.js | 32 + website/static/js/image-interaction.js | 279 ++++++ website/static/js/navbar.js | 17 +- website/templates/404.html | 6 +- website/templates/article.html | 30 +- website/templates/base.html | 50 +- website/templates/blog.html | 13 +- website/templates/book.html | 150 ++++ website/templates/index.html | 340 -------- website/templates/page.html | 9 +- website/templates/section.html | 7 + 76 files changed, 3506 insertions(+), 1670 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/codebase/README.md delete mode 100644 docs/design/README.md delete mode 100644 docs/design/inputs-and-keybindings.md delete mode 100644 docs/design/node-data.md delete mode 100644 docs/design/tool-behavior.md delete mode 100644 docs/editor/1-overview.md delete mode 100644 docs/editor/2-interface.md delete mode 100644 docs/editor/3-panels.md delete mode 100644 docs/editor/4-documents-and-assets.md delete mode 100644 docs/editor/5-tools.md delete mode 100644 docs/editor/6-masking.md create mode 100644 website/content/_index.md create mode 100644 website/content/about.md rename website/content/{contact/_index.md => contact.md} (72%) delete mode 100644 website/content/contribute/_index.md create mode 100644 website/content/donate.md create mode 100644 website/content/features.md delete mode 100644 website/content/features/_index.md create mode 100644 website/content/learn/_index.md create mode 100644 website/content/learn/introduction/_index.md create mode 100644 website/content/learn/introduction/artboards.md create mode 100644 website/content/learn/introduction/interface.md create mode 100644 website/content/learn/introduction/layers.md create mode 100644 website/content/learn/introduction/tools.md create mode 100644 website/content/learn/node-graph/_index.md create mode 100644 website/content/learn/node-graph/imaginate.md create mode 100644 website/content/learn/node-graph/nodes.md rename website/content/{license/_index.md => license.md} (94%) rename website/content/{logo/_index.md => logo.md} (75%) create mode 100644 website/content/press.md delete mode 100644 website/content/press/_index.md create mode 100644 website/content/volunteer/_index.md create mode 100644 website/content/volunteer/guide/_index.md create mode 100644 website/content/volunteer/guide/codebase-overview/_index.md create mode 100644 website/content/volunteer/guide/codebase-overview/contributing-guidelines.md create mode 100644 website/content/volunteer/guide/codebase-overview/debugging.md create mode 100644 website/content/volunteer/guide/codebase-overview/tech-stack.md create mode 100644 website/content/volunteer/guide/getting-started/_index.md create mode 100644 website/content/volunteer/guide/getting-started/editor-and-tooling.md create mode 100644 website/content/volunteer/guide/getting-started/getting-help.md create mode 100644 website/content/volunteer/guide/getting-started/picking-a-task.md create mode 100644 website/content/volunteer/guide/product-design/_index.md create mode 100644 website/content/volunteer/guide/product-design/glossary-of-terminology.md rename docs/editor/README.md => website/content/volunteer/guide/product-design/product-outline.md (91%) rename docs/design/feature-goals.md => website/content/volunteer/guide/product-design/uses-and-workflows.md (70%) create mode 100644 website/sass/about.scss create mode 100644 website/sass/book.scss create mode 100644 website/sass/donate.scss create mode 100644 website/sass/volunteer.scss create mode 100644 website/static/js/book.js delete mode 100644 website/static/js/carousel.js create mode 100644 website/static/js/fundraising.js create mode 100644 website/static/js/image-interaction.js create mode 100644 website/templates/book.html delete mode 100644 website/templates/index.html create mode 100644 website/templates/section.html diff --git a/README.md b/README.md index 3b75197b..0d6438e2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@

Redefining state-of-the-art graphics editing

-Graphite is an in-development raster and vector 2D graphics editor that is free and open source. It is powered by a node graph compositing engine that supercharges your layer stack, providing a completely non-destructive editing experience. +**Graphite** is an in-development raster and vector graphics package that's free and open source. It is powered by a node graph compositing engine that fuses layers with nodes, providing a fully non-destructive editing experience. Graphite is a lightweight vector graphics editor that runs in your browser. Its nascent node-based compositor lets you apply raster effects and co-create amazing art with AI in a non-destructive workflow. Fully-featured raster image editing and a native desktop application are the current focus of development and will be made available in the coming months. @@ -28,6 +28,8 @@ We need Rust and web developers! See [instructions here](https://graphite.rs/con We are also in search of artists to create beautiful sample work in Graphite and illustrations for the website and social media. Please [get in touch](https://graphite.rs/contact/) if you are able to help out. +By submitting code for inclusion in the project, you are agreeing to license your changes under the Apache 2.0 license, and that you have the authority to do so. Some directories may have other licenses, like dual-licensed MIT/Apache 2.0, and code submissions to those directories mean you agree to the applicable license(s). + ## Feature and roadmap [See the web page for this information.](https://graphite.rs/features/) diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index e2e7f987..00000000 --- a/docs/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# Graphite project documentation - -WARNING: A lot of the information in these documents is out of date. - -## [Graphite editor manual](editor/README.md) - -This will evolve into the official manual for the Graphite editor. It contains documentation for each feature of Graphite focused on both usage instructions and technical details. - -## [Graphite codebase docs](codebase/README.md) - -This is meant as a good starting point for new developers to jump into the Graphite codebase. It aims to explain the architecture, code structure, and other useful details. - -## [Graphite design notes](design/README.md) - -This is a collection of notes and ideas pertaining to the user experience, workflow, and UI design of Graphite. It should aim to be mostly up-to-date and reflect the current state of understanding of the design for relatively finalized features, but in-progress feature designs may contain ideas, concepts, and proposals. diff --git a/docs/codebase/README.md b/docs/codebase/README.md deleted file mode 100644 index bc0a086f..00000000 --- a/docs/codebase/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Graphite codebase docs - -Until this is written, please check the [contributing guide](https://graphite.rs/contribute) and the README.md files in individual directories of the project structure. diff --git a/docs/design/README.md b/docs/design/README.md deleted file mode 100644 index 2d700d95..00000000 --- a/docs/design/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Graphite design notes - -Notes and details about the product design, workflow, user experience, and UI for the Graphite editor and systems. More to come as scattered outdated notes get cleaned up and moved here. - -- [Feature goals](feature-goals.md) - - [Possible use cases](feature-goals.md#possible-use-cases) - - [User stories](feature-goals.md#user-stories) -- [Inputs and keybindings](inputs-and-keybindings.md) diff --git a/docs/design/inputs-and-keybindings.md b/docs/design/inputs-and-keybindings.md deleted file mode 100644 index a7e50eb5..00000000 --- a/docs/design/inputs-and-keybindings.md +++ /dev/null @@ -1,155 +0,0 @@ -# Inputs and keybindings - -WARNING: This document is out of date and some of the information contained herein does not accurately explain the current status or plans. - -## Input categories - -- Keyboard - - Modifier keys (Ctrl, Shift, Alt) - - Letter keys (AZ) - - Number keys (09) - - Left edge keys (Escape, \`, Tab) - - Right edge keys (Backspace, \\, Return, /) - - Space bar () - - Symbol pair keys (-/=, \[/\], ;/', ,/.) - - Function keys (F1F12) - - Navigation keys (Insert/Delete, Home/End, Page Up/Page Down) - - Arrow keys (, , , ) - - Numpad Enter should be equivalent to CtrlReturn (Enter) - - Other numpad keys should reflect their main keyboard counterparts -- Mouse - - Cursor movement - - LMB - - RMB - - MMB - - Vertical scroll wheel up/down - - Horizontal scroll wheel (less common) - - Forward navigation (less common) - - Backward navigation (less common) -- Tablet - - Hover movement - - Stroke movement - - With pressure - - With tilt - - With angle (less common) - - Lower stylus button - - Upper stylus button - - Eraser hover movement (less common) - - Eraser stroke movement (less common) -- Touch - - Tap - - Drag - - Pinch - - Rotate - - Multiple fingers (1–4) - -## Document-specific commands - -- CtrlS Save document. -- CtrlShiftS Save as. -- CtrlE Export. - -## Panel-specific commands - -### Document Panel - -#### Viewport navigation - -Ctrl- Zoom out to the next discrete increment. -Ctrl=/Ctrl+ Zoom in to the next discrete increment. -Ctrl0 Zoom to show the entire canvas. -Ctrl` Zoom to show the entire selection. -Ctrl1 Zoom to 100% scale. -Ctrl2 Zoom to 200% scale. - -#### Selection-specific - -- H Hide/show selection, equivalent to turning off the eye icon on every selected layer -- AltH Show hidden direct children of the selection, equivalent to turning on the eye icon on every direct child layer of the selected layers -- X Delete selection (with confirmation) -- CtrlI Invert selected, by applying an Invert node. - -#### Masking - -- Tab Enter/exit Mask Mode. - -#### Working colors - -- ShiftX Swap the primary and secondary working colors. -- CtrlShiftX Reset the primary and secondary working colors to black and white. - -#### Tool shelf - -| Tool | Graphite | Photoshop | Illustrator | XD | Affinity Designer | Inkscape | Gimp | -| --------------- | ------------ | ------------------------------------------ | ----------------------------- | ---------------- | ----------------------------- | -------- | -------- | -Select Tool | V | **V** | V | V | V | | | -Artboard Tool | | C | ShiftO | A | | | | -Navigate Tool | Z | **Z**/H/R | **Z**/H | **Z** | **Z**/H | | | -Eyedropper Tool | I | **I** | **I** | | **I** | | | -Fill Tool | F | G | | | G | | | -Gradient Tool | H | G | G | | G | | | -Path Tool | A | **A** | **A** | | **A** | | | -Pen Tool | P | **P** | **P** | **P** | **P** | | | -Freehand Tool | N | P | **N** | | **N** | | | -Spline Tool | | P | Shift~ | | P | | | -Line Tool | L | U | \\ | **L** | P | | | -Rectangle Tool | M | U/**M** | **M** | R | **M** | | | -Ellipse Tool | E | U/M | L | **E** | M | | | -Polygon Tool | Y | U | | **Y** | | | | -Text Tool | T | **T** | **T** | **T** | **T** | | | -Brush Tool | B | **B** | **B** | | **B** | | | -Heal Tool | J | **J** | | | | | | -Clone Tool | C | S | | | | | | -Patch Tool | | J | | | | | | -Relight Tool | O | **O** | | | | | | -Detail Tool | D | | | | | | | - -#### Tool-specific keys - -Excluding mouse inputs and modifier keys. - -##### Select Tool - -- G Grab (translate) the selected items. Hit X or Y to constrain to that global axis, hit X or Y again to constrain to that local axis. Type a number to move along that axis by that many pixels (`px` or `pixel` or `pixels`), or type a unit suffix. -- R Rotate the selected items. Type a number to rotate by that many degrees (`°` or `deg` or `degree` or `degrees`), or type a unit suffix (`turn` or `turns` or `rev` or `revs`, `rad` or `radians`, `min` or `minutes` or `'`, `sec` or `seconds` or `"`, `grad`). -- S Scale the selected items. Hit X or Y to constrain to that global axis, hit X or Y again to constrain to that local axis. Type a number to scale along that axis by that factor (`fac` or `factor`), or type a unit suffix (`%`). - -##### Artboard Tool - -##### Navigate Tool - -##### Eyedropper Tool - -##### Fill Tool - -##### Gradient Tool - -##### Path Tool - -##### Pen Tool - -##### Freehand Tool - -##### Spline Tool - -##### Line Tool - -##### Rectangle Tool - -##### Ellipse Tool - -##### Polygon Tool - -##### Text Tool - -##### Brush Tool - -##### Heal Tool - -##### Clone Tool - -##### Patch Tool - -##### Relight Tool - -##### Detail Tool diff --git a/docs/design/node-data.md b/docs/design/node-data.md deleted file mode 100644 index 551282d5..00000000 --- a/docs/design/node-data.md +++ /dev/null @@ -1,24 +0,0 @@ -Data can **flow** or **composite**. - -# Types -Data types in Rust. - -- Color - A color description freely convertible between color models and color spaces. -- Raster - A monadic data format that allows a color to be sampled at any rectangle position. - - - -# Table Archetypes -A set of standard named columns in a table of specific type(s), where some can be optional. - -- XY - A vector2 (2D vector). - `X`: number, `Y`: number - -- XYZ - A vector3 (3D vector). - `X`: number, `Y`: number, `Z`: number - -- XYZW - A vector4 (4D vector). - `X`: number, `Y`: number, `Z`: number, `W`: number - -- Trace - Sequence of points in 2D or (rarely) 3D space. Often recorded as mouse or stylus movements where time is seconds since the stroke began. - `X`: number, `Y`: number, `Z?`: number, `Pressure?`: number, `Pitch?`: number, `Roll?`: number, `Yaw?`: number, `Time?`: number diff --git a/docs/design/tool-behavior.md b/docs/design/tool-behavior.md deleted file mode 100644 index 3b80b89d..00000000 --- a/docs/design/tool-behavior.md +++ /dev/null @@ -1,11 +0,0 @@ -# Tool behavior - -# Select Tool - -LMB Click selects the outermost group. - -LMB Double Click "digs down" the layer tree and selects the layer or group in the currently selected group. - -CtrlLMB Click lets you pick the innermost layer. While holding down the click, the cursor can be dragged outwards from that innermost layer and it selects parent groups that contain it. - -Holding Ctrl displays vector points and they can be LMB Clicked on to select the individual points, and so long as the modifier key is still held down, they may be dragged. Instead of selecting individual points, they are all selected by default and individual points can be dragged to move the entire shape around, which is useful for snapping the final destination in relation to the point being dragged from. diff --git a/docs/editor/1-overview.md b/docs/editor/1-overview.md deleted file mode 100644 index 32a50099..00000000 --- a/docs/editor/1-overview.md +++ /dev/null @@ -1,69 +0,0 @@ -# Overview - -## Key concepts - -TODO - -## Glossary of terminology - -TODO: Add more to make a comprehensive list, finish writing definitions, separate into categories, alphabetize - -- ## Document - A design source file created and edited in the Graphite editor. Saved to disk as a Graphite Design Document in a _GDD file_. Documents can be included as _layers_ inside other documents, and in doing so they take the form of _groups_. The _layer graph_ contents of a _group_ actually belong to the _embedded_ document's _subgraph_. Because a document is a _group_ which is a _layer_ in the _layer graph_, documents have _properties_ such as the _frames_ in the _canvas_. Documents are composed of a layer graph, a defined set of properties of set _data types_ that are _imported_ and _exported_, and the _properties_ of the _root layer_. -- ## Asset - A portable mechanism for distributing a "compiled" Graphite _document_ in a format that is immediately ready for rendering. Saved to disk as a Graphite Digital Asset in a _GDA file_. Assets are created by "flattening" a _document's_ complex, nested _layer graph_ structure into a single, simple directed acyclic graph (DAG). The Graphite editor internally maintains an asset version of any open _document_ in order to draw the _canvas_ live in the _viewport_. An asset also includes certain exposed _properties_ of specified _data types_ that are _imported_ and _exported_, as defined by the asset's author in the source _document's_ _layer graph_. They can be shared and _embedded_ in another _layer graph_ as a black box (meaning it can't be expanded to reveal or edit its interior graph), as compared to _embedded_ _documents_ from _GDD files_ which are white boxes (they can be expanded to reveal their _subgraph_ which can be edited). Assets are helpful for defining custom _nodes_ that perform some useful functionality. Tangible examples include custom procedural effects, shape generators, and image filters. Many of the Graphite editor's own built-in _nodes_ are actually assets rather than being implemented directly in code. The _Asset Manager_ panel helps maintain these assets from various sources. The _Asset Store_ can be used to share and sell assets for easy inclusion in other projects. -- ## GDD file - Graphite Design Document. A binary serialization of a _document_ source file. The format includes a chain of _operations_ that describe changes to the _layer graph_ and the _properties_ of _layers_ throughout the history of the document since its creation. It also stores certain metadata and the raw data of _embedded_ files. Because GDD files are editable (unlike _GDA files_), the _layers_ of GDD files imported into another _document_ may be expanded in its _layer graph_ to reveal and modify their contents using a copy-on-write scheme stored to the _asset's_ _layer_. -- ## GDA file - Graphite Digital Asset. A binary serialization of an _asset_ file. Because GDA files are read-only and can't be edited (unlike _GDD files_), the _layers_ created from _assets_ do not offer an ability to be expanded in the _layer graph_ of a _document_ that _embeds_ them. GDA files are useful for sharing _assets_ when their authors do not wish to provide the source _documents_ to author them. _DGA files_ are also the input format included in games that utilize the _Graphite Renderer Core Library_ to render graphical content at runtime, as well as similar applications like headless renderers on web servers and image processing pipelines. -- ## Window -- ## Main window -- ## Popout window -- ## Title bar -- ## Status bar -- ## Workspace - The part of the Graphite editor's UI that houses the _panels_ in a _window_. The workspace occupies the large space below the _title bar_ and above the _status bar_ of the _main window_. It occupies the entirety of _popout windows_ (window buttons are added in the _tab bar_). -- ## Workspace layout - The specific configuration of panels in the _main window_ and any _popout windows_. Workspace layout presets are provided by the Graphite editor and users may customize and save their own. -- ## Tab bar - The bar at the top of a _panel group_ which includes a clickable tab for each panel that is docked there. Each tab bar has at least one tab and one active tab. -- ## Active tab - The one tab in a _tab bar_ that is currently active. The user can click any inactive tab to make it become the active tab. The active tab shows the _panel content_ beneath it unless it is a _folded panel_. -- ## Folded panel - A shrunken _panel_ showing only the _tab bar_. A _panel_ consists of the _tab bar_ and _panel body_ except when the latter is folded away. The user may click the _active tab_ to fold and restore a panel, however a panel cannot be folded if there are no other unfolded panels in its column. -- ## Panel -- ## Panel body -- ## Options bar - The bar that spans horizontally across the top of a _panel_ (located under the _tab bar_) which displays options related to the _panel_. -- ## Viewport - The area that takes up the main space in a _panel_ (located beneath the _options bar_) which displays the primary content of the _panel_. -- ## Shelf - The bar that spans vertically along the left side of some _panels_ (located left of the _viewport_) which displays a catalog of available items, such as document editing _tools_ or common _nodes_. -- ## Tool - An instrument for interactively editing _documents_ through a collection of related behavior. Each tool puts the editor into a mode that provides the ability to perform certain _operations_ on the document interactively. Each _operation_ is run based on the current context of mouse and modifier buttons, key presses, tool options, selected layers, editor state, and document state. The _operations_ that get run are appended to the document history and update the underlying _layer graph_ in real time. -- ## Canvas - The infinite coordinate system that shows the visual output of an open _document_ at the current zoom level and pan position. It is drawn in the document panel's _viewport_ within the area inside the scroll bars on the bottom/right edges and the _rulers_ on the top/left edges. The canvas can be panned and zoomed in order to display all or part of the artwork in any _frames_. A canvas has a coordinate system spanning infinitely in all directions with an origin always located at the top left of the primary _artboard_. The purpose of an infinite canvas is to offer a convenient editing experience when there is no logical edge to the artwork, for example a loosely-arranged board of logo design concepts, a mood board, or whiteboard-style notes. -- ## Artboard - An area inside a _canvas_ that provides rectangular bounds to the artwork contained within, as well as default bounds for an exported image. The _Artboard tool_ adjusts the bounds and placement of frames in the _document_ and each artboard is stored in a "artboard list" property of the _root layer_. When there is at least one artboard, the infinite _canvas_ area outside any artboard displays a configurable background color. Artwork can be placed outside of a artboard but it will appear mostly transparent. The purpose of using one artboard is to provide convenient cropping to the edges of the artwork, such as a single digital painting or photograph. The purpose of using multiple frames is to work on related artwork with separate bounds, such as the layout for a book. -- ## Layer graph - A (directed acyclic) graph structure composed of _layers_ with _connections_ between their input and output _ports_. This is commonly referred to as a "node graph" in other software, but Graphite's layer graph is more suited towards layer-based compositing compared to traditional compositor node graphs. -- ## Node - A definition of a _layer_. A node is a graph "operation" or "function" that receives input and generates deterministic output. -- ## Layer - Any instance of a _node_ that lives in the _layer graph_. Layers (usually) take input data, then they transform it or synthesize new data, then they provide it as output. Layers have _properties_ as well as exposed input and output _ports_ for sending and receiving data. -- ## Root layer -- ## Group -- ## Raster -- ## Vector -- ## Mask -- ## Data type -- ## Subgraph -- ## Port -- ## Connection -- ## Core Libraries -- ## Graphite Editor (Frontend) -- ## Graphite Editor (Backend) -- ## Graphene (Node Graph Engine) -- ## Trace -- ## Path -- ## Shape diff --git a/docs/editor/2-interface.md b/docs/editor/2-interface.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/editor/3-panels.md b/docs/editor/3-panels.md deleted file mode 100644 index aede6bd5..00000000 --- a/docs/editor/3-panels.md +++ /dev/null @@ -1,11 +0,0 @@ -# Panels - -## Document - -### Canvas and frames - -### Rulers - -### Tool menu - -### Options bar diff --git a/docs/editor/4-documents-and-assets.md b/docs/editor/4-documents-and-assets.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/editor/5-tools.md b/docs/editor/5-tools.md deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/editor/6-masking.md b/docs/editor/6-masking.md deleted file mode 100644 index 9e1b8fc9..00000000 --- a/docs/editor/6-masking.md +++ /dev/null @@ -1,5 +0,0 @@ -# Masking - -## Mask mode - -At any time while in the viewport, Tab may be pressed to enter mask mode. The underlying canvas seen before entering this mode is still shown, but masks are drawn as marching ants (or other optional overlays) above the main document content. While in this mode, an island layer group is provided as the destination for drawing new mask layers using the regular set of tools. The Layer Panel also still shows the underlying main document, which lets the user select layers as contextual inputs for tools that are aware of input layers, like the Fill Tool. Rather than showing the full-color shapes over the main document canvas, they are overlaid in outline view mode and surrounded by a marching ants marquee outline. The mask group may be isolated (meaning it becomes the render output to the viewport, and a breadcrumb trail is shown leading from the document to the isolated layer/group) which makes the viewport output show the mask in grayscale and has the Layer Panel host the contents of the mask group. While in mask mode, the working colors are temporarily replaced with a grayscale pair. Certain tools, such as the Freehand Tool and Pen Tool, may default to a "closed" form in mask mode by turning off stroke and setting fill to white in order to provide functionality akin to the lasso or polygonal lasso selection tools. Tab may be hit again to exit mask mode, but the marching ants still show up. However now, all tools used and commands performed will take into account the working mask. CtrlD will discard the working mask. diff --git a/frontend/assets/LICENSE.md b/frontend/assets/LICENSE.md index 476bb8c6..897464cc 100644 --- a/frontend/assets/LICENSE.md +++ b/frontend/assets/LICENSE.md @@ -2,6 +2,6 @@ Copyright (c) 2021-2023 Keavon Chambers The design assets in this directory (including SVG code for icons and logos) are NOT licensed under the Apache 2.0 license terms applied to other Graphite source code files. This directory and its entire contents are excluded from the Apache 2.0 source code license, and copyrights are held by the author for the creative works contained as files herein. -Parties interested in using Graphite source code in a capacity that deploys the Graphite Editor reference frontend are advised to substitute all assets and "Graphite" branding or otherwise arrange written permission from the rightsholder. The recommended use case for adopting Graphite open source code is to develop one's own unique frontend user interface implementation that integrates Graphite's backend technology. +Parties interested in using Graphite source code in a capacity that deploys the Graphite editor reference frontend are advised to substitute all assets and "Graphite" branding or otherwise arrange written permission from the rightsholder. The recommended use case for adopting Graphite open source code is to develop one's own unique frontend user interface implementation that integrates Graphite's backend technology. The author and rightsholder, Keavon Chambers, may be reached through the email address listed at https://graphite.rs/contact/ or https://keavon.com. diff --git a/frontend/index.html b/frontend/index.html index 49b24c28..b77249ed 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,14 +2,15 @@ - - Graphite + + + diff --git a/frontend/src/components/README.md b/frontend/src/components/README.md index af774b1d..bbb22bc0 100644 --- a/frontend/src/components/README.md +++ b/frontend/src/components/README.md @@ -1,6 +1,6 @@ # Overview of `/frontend/src/components/` -Each component represents a (usually reusable) part of the Graphite Editor GUI. These all get mounted in `Editor.svelte` (in the `/src` directory above this one). +Each component represents a (usually reusable) part of the Graphite editor GUI. These all get mounted in `Editor.svelte` (in the `/src` directory above this one). ## Floating Menus: `floating-menus/` diff --git a/frontend/src/io-managers/panic.ts b/frontend/src/io-managers/panic.ts index 60a2f350..59d2b5b3 100644 --- a/frontend/src/io-managers/panic.ts +++ b/frontend/src/io-managers/panic.ts @@ -66,7 +66,7 @@ function githubUrl(panicDetails: string): string { **Steps To Reproduce** Describe precisely how the crash occurred, step by step, starting with a new editor window. - 1. Open the Graphite Editor at https://editor.graphite.rs + 1. Open the Graphite editor at https://editor.graphite.rs 2. 3. 4. @@ -79,7 +79,7 @@ function githubUrl(panicDetails: string): string { ${browserVersion()}, ${operatingSystem(true).replace("Unknown", "YOUR OPERATING SYSTEM")} **Stack Trace** - Copied from the crash dialog in the Graphite Editor: + Copied from the crash dialog in the Graphite editor: `; body += "\n\n```\n"; diff --git a/website/.gitignore b/website/.gitignore index 0a030458..d35f3b5b 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -1,2 +1,3 @@ node_modules/ public/ +static/syntax-highlighting.css diff --git a/website/config.toml b/website/config.toml index 0f0c585e..b11b1ee2 100644 --- a/website/config.toml +++ b/website/config.toml @@ -13,6 +13,7 @@ feed_filename = "rss.xml" # Theme can be customized by setting the `highlight_theme` variable to a theme supported by Zola highlight_code = true highlight_theme = "css" +highlight_themes_css = [{ theme = "kronuz", filename = "syntax-highlighting.css" }] [extra] # Put all your custom variables here diff --git a/website/content/_index.md b/website/content/_index.md new file mode 100644 index 00000000..d1e20f1f --- /dev/null +++ b/website/content/_index.md @@ -0,0 +1,518 @@ ++++ +title = "Web-based vector graphics editor and design tool" +template = "section.html" # Avoids needing a separate `index.html` template that's identical to `section.html` + +[extra] +css = ["/index.css"] +js = ["/image-interaction.js", "/fundraising.js"] ++++ + + + + + + + +
+ +# Redefining state-of-the-art graphics editing + +

Graphite is an in-development raster and vector graphics package that's free and open source. It is powered by a node graph compositing engine that fuses layers with nodes, providing a fully nondestructive editing experience.

+ +
+ +
+
+ + + + + + + +
+
+ + + +
+
+ +
+ +# Graphite today + +
+
+ + Vector editing +
+
+ + Node graph image effects +
+
+ + AI-assisted art creation +
+
+ + Open source and free forever +
+
+ +Graphite is a lightweight vector graphics editor that runs in your browser. Its node-based compositor lets you apply image effects and co-create art with generative AI. + +
+
+ +# Graphite tomorrow + +
+
+ + Looks and feels like traditional WYSIWYG design apps +
+
+ + Clean, intuitive interface built by designers, for designers +
+
+ + Real-time collaborative editing +
+
+ + Windows/Mac/Linux/Web/iPad +
+
+ +Roadmap + +
+ +
+
+ +
+
+
+
+ +
+ +# Stay in the loop + +Subscribe to the newsletter for quarterly updates on major development progress. + +
+ +## Thanks! + +You'll receive your first newsletter email with the next major Graphite news. + +
+ +
+
+
+ + +
+
+ + +
+ +
+
+ +
+
+ +
+ + + +
+
+
+
+ +
+
+ +# Art takes shape + +Make vector art out of shapes ranging from simple geometric primitives to complex Bézier curves. + +Style your shapes with strokes, fills, and gradients. Mix your layers with blend modes. Then export as SVG. + +
+ +
+ +

+ Vector art of Just of Potted Cactus + + Just a Potted Cactus +
+
+ Download + the artwork and +
+ open it in the Graphite editor +
+

+ +
+
+ + + +
+ +
+
+ +

Co-createIdeateIllustrateGenerateIterate with Imaginate

+ +**Imaginate** is a node powered by Stable Diffusion that makes AI-assisted art creation an easy, nondestructive process. + + +
+
+ +
+
+ +
+ +

Make it stylish

+ +**Magically reimagine your vector drawings** in a fresh new style. Just place an Imaginate frame over your layers and describe how it should end up looking. + +
+
+ Vector illustration of a light bulb +
+
+ Watercolor painting of a light bulb +
+
+
+
+ + + + + + +
+
+
+ +
Watercolor painting of a light bulb gleaming with an exclamation mark inside
+ +
+ +
+ +## Work fast, be sloppy + +**Doodle a rough draft** without stressing over the details. Let Imaginate add the finishing touches to your artistic vision. Iterate with more passes until you're happy. + +
+
+ Sloppy poppy: vector doodle of California poppy flowers wrapped around a circle +
+
+ Polished poppy: artistic, high-quality illustration of California poppy flowers wrapped around a circle +
+
+
+
+ + + + + + +
+
+
+ +
Botanical illustration of California poppies wrapped around a circle
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +# Support the mission + +

+You can help realize Graphite's ambitious vision of building the ultimate 2D creative tool. +Graphite is built by a small, dedicated crew of volunteers in need of resources to grow. +

+ +### Summer 2023 fundraising goal: + +
+
+
+
+
+ Progress: 0% + Goal: $0/month +
+
+ +[Become a monthly supporter](https://github.com/sponsors/Keavon) this summer to collect an exclusive 💚 badge. Each season you support, a new heart design is yours to keep. In the future, they'll be shown on Graphite account profiles and community areas like forums and in-app collaboration. + +Donate + +
+ + + +
+
+
+ +
+
+ +# One app to rule them all + +Stop jumping between programs. Planned features will make Graphite a first-class design tool for these disciplines (listed by priority): + +
+
+ + Graphic Design +
+
+ + Image Editing +
+
+ + Digital Painting +
+
+ + Desktop Publishing +
+
+ + VFX Compositing +
+
+ + Motion Graphics +
+
+ +
+
+ +
+
+ +# Powerful proceduralism + +The data-driven approach to design affords unique capabilities that are presently in-development. + +
+
+ + Fully nondestructive editing with node-driven layers +
+
+ + Infinitely scalable raster content with no pixelation +
+
+ + Integrates generative AI models and graphics algorithms +
+
+ + Procedural pipelines for studio production environments +
+
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+ +
+ +# Get involved + +

The Graphite project could not exist without its community. Building its ambitious and versatile feature set will require contributions from developers, designers, technical experts, creative professionals, and eagle-eyed bug hunters. Help build the future of digital art.

+ +Volunteer + +
+
+ +
+ +
+
+
diff --git a/website/content/about.md b/website/content/about.md new file mode 100644 index 00000000..658bc599 --- /dev/null +++ b/website/content/about.md @@ -0,0 +1,205 @@ ++++ +title = "About Graphite" + +[extra] +css = ["/about.css"] ++++ + +
+
+ +# About Graphite + +Graphite is a community-built, open source software project that is free to use for any purpose. If you find Graphite valuable, consider [supporting financially](/donate) or [getting involved](/volunteer). + +
+
+ +
+
+ +## Project + +The idea for Graphite began with a desire to create artwork and edit photos using free software that felt user-friendly and truly modern. Over time, that dream evolved to reconsider what "modern" meant for the landscape of 2D graphics editing. By borrowing concepts popular in 3D software, what could a procedural, nondestructive design tool look like if nothing was too ambitious? Answering that question took years of design exploration, leading to a community of savvy developers volunteering to turn that formidable dream into a reality. + +
+
+ +
+ +
+ +
+ +## Mission + +Graphite strives to unshackle the creativity of every budding artist and seasoned professional by building the best comprehensive art and design tool that's accessible to all. + +Mission success will come when Graphite is an industry standard. A cohesive product vision and prioritizing innovation over imitation will make that possible. + +
+
+ +## Commitment + +As an independent community-driven software project, Graphite will always remain free. It has no investors to answer to. Its founder keeps costs low and relies on [your support](/donate) while he works full-time bringing Graphite to life. To sustainably grow, the long-term funding model will pair donations with paid accounts that provide optional online services like document storage/syncing and render acceleration via cloud GPUs. + +
+ +
+ +
+ + + + + + + +
+
+ +

Meet the core team

+ +--- + +
+ +
+ +![Photo of Keavon Chambers](https://static.graphite.rs/content/about/core-team-photo-keavon-chambers.png) + +## Keavon Chambers 🇺🇸 + +*@Keavon* + +### Founder + +*UI & product design, frontend engineering* + +Keavon is a creative generalist with a love for the fusion of arts and technology. Photographer, UX and graphic designer, technical artist, game developer, and everything in-between— he is equal parts designer and engineer. His multidisciplinary background in the digital arts is aptly suited for concocting the unique vision needed to bring Graphite to fruition. + +
+
+ +![Photo of Dennis Kobert](https://static.graphite.rs/content/about/core-team-photo-dennis-kobert.png) + +## Dennis Kobert 🇩🇪 + +*@TrueDoctor* + +### Lead Engineer + +*Graphene node engine, research, architecture* + +Dennis is a mix between a mathematician and a mad scientist. While still enjoying the art of photography and image editing (which drew him to the project early on), he thrives when challenged with designing complex systems and pushing boundaries. His method of building generalized solutions wrapped in elegant layers of abstraction led to his creation of the Graphene engine. + +
+
+ +![Photo of Hypercube](https://static.graphite.rs/content/about/core-team-photo-hypercube.png) + +## "Hypercube" 🇬🇧 + +*@0Hypercube* + +### Software Engineer + +*Editor systems, nodes, architecture* + +"Hypercube" is a light speed code monkey who excels at developing, refactoring, and maintaining the editor code base. With an unmatched ability to comprehend intricate code, he delivers lasting and efficient solutions at an impressive pace. He takes ownership of many central editor systems including tools, typography, transforms, layers, and node graph integration. + +
+ +
+ +
+
+ + +
+ +
+ +
+ +## Credits + +In addition to the work of the Core Team listed above, dozens of contributors have written code that makes Graphite what it is today: + +Credits + +
+
+ +## License + +The Graphite editor and source code are provided under the Apache License 2.0 terms. See below for details and exclusions: + +License + +
+
+ +## Logo + +More information about the Graphite logo, including its community-focused usage policy and downloadable assets, is available on the logo page: + +Logo + +
+ +
+ +
diff --git a/website/content/blog/_index.md b/website/content/blog/_index.md index 8ee37ec8..5fb180ec 100644 --- a/website/content/blog/_index.md +++ b/website/content/blog/_index.md @@ -9,7 +9,7 @@ generate_feed = true
-# Blog. +# Blog
diff --git a/website/content/contact/_index.md b/website/content/contact.md similarity index 72% rename from website/content/contact/_index.md rename to website/content/contact.md index aeceb71c..98ed2cb3 100644 --- a/website/content/contact/_index.md +++ b/website/content/contact.md @@ -1,17 +1,16 @@ +++ title = "Contact the team" -template = "page.html" +++ -
+
-# Contact the team. +# Contact the team * Members of the press, please see the [press resources](/press) page. * For general discussions, reach out on [Discord](https://discord.graphite.rs) or [Reddit](https://www.reddit.com/r/graphite/). * To report a bug or request a feature, please [file an issue](https://github.com/GraphiteEditor/Graphite/issues/new) on GitHub. -* For other inquiries, please get in touch by email at . +* For other inquiries, get in touch by email at .
diff --git a/website/content/contribute/_index.md b/website/content/contribute/_index.md deleted file mode 100644 index 31388834..00000000 --- a/website/content/contribute/_index.md +++ /dev/null @@ -1,149 +0,0 @@ -+++ -title = "Contribute to Graphite" -template = "page.html" -+++ - -
-
- -# Contribute to Graphite. - -It's great to hear you are interested in contributing to Graphite! We want to make it as easy and frictionless as possible for you to get started. Here are the basics. - -## Building and running the codebase. - -Graphite is built with Rust and web technologies. Install the latest LTS version of [Node.js](https://nodejs.org/) and stable release of [Rust](https://www.rust-lang.org/), as well as [Git](https://git-scm.com/). - -Clone the project: -``` -git clone https://github.com/GraphiteEditor/Graphite.git -``` - -Then install the required Node.js packages: -``` -cd frontend -npm install -``` - -You only need to explicitly install Node.js dependencies. Rust's cargo dependencies will be installed automatically on your first build. One dependency in the build chain, `wasm-pack`, will be installed automatically on your system when the Node.js packages are installing. (If you prefer to install this manually, get it from the [wasm-pack website](https://rustwasm.github.io/wasm-pack/), then install your npm dependencies with `npm install --no-optional` instead.) - -One tool in the Rust ecosystem does need to be installed: - -``` -cargo install cargo-watch -``` - -That's it! Now, to run the project while developing, just execute: -``` -npm start -``` - -This spins up the dev server at with a file watcher that performs hot reloading of the web page. You should be able to start the server, edit and save web and Rust code, and rarely have to kill the server (by hitting CtrlC twice). You sometimes may need to reload the web page if the hot reloading didn't behave perfectly. This method compiles Graphite code in debug mode which includes debug symbols for viewing function names in stack traces. - -To compile a production build with full optimizations: -``` -cargo install cargo-about -npm run build -``` - -It produces the `/frontend/dist` directory containing the static site files that must be served by your own web server. - -While developing Rust code, `cargo check` and `cargo clippy` may be run from the root directory. You can also use `npm run lint` or `npm run lint-no-fix` to solve web code formatting and `cargo fmt` for Rust code formatting. If you don't use VS Code and its format-on-save feature, please remember to format before committing or consider [setting up a `pre-commit` hook](https://githooks.com/) to do that automatically. - -We provide default configurations for VS Code users. When you open the project, watch for a prompt to install the project's suggested extensions. They will provide helpful web and Rust tooling. If you use a different IDE, you won't get default configurations for the project out of the box, so please remember to format your code and check CI for errors. - -## Task board. - -Visit our [**Task Board**](https://github.com/orgs/GraphiteEditor/projects/1/views/1) and look through the current sprint's column. You are also welcome to work on tasks prioritized for upcoming sprints. Find any issues with the green "Available" tag. - -Pay attention to the tags which provide some useful information like which ones are a [**Good First Issue**](https://github.com/GraphiteEditor/Graphite/issues?q=is%3Aissue+is%3Aopen+label%3AAvailable+label%3A%22Good+First+Issue%22+) and whether they involve [**only Rust**](https://github.com/GraphiteEditor/Graphite/issues?q=is%3Aissue+is%3Aopen+label%3ARust+label%3AAvailable+-label%3AWeb+), [**only Web**](https://github.com/GraphiteEditor/Graphite/issues?q=is%3Aissue+is%3Aopen+label%3AWeb+label%3AAvailable+-label%3ARust+) (HTML/CSS/TypeScript/Svelte), or [**both**](https://github.com/GraphiteEditor/Graphite/issues?q=is%3Aissue+is%3Aopen+label%3AAvailable+label%3ARust+label%3AWeb+). Feel free to pick whatever task interests you, then comment on the issue that you would like to start. After commenting, you can dig in right away, then we will assign the issue to your GitHub user to keep the status of things organized. - -## Mentorship. - -Join the [project's Discord server](https://discord.graphite.rs) then hop on the `#development` channel and ping @Keavon and @TrueDoctor. We would be delighted to help you get started with in-depth explanations of the code, one-on-one mentorship and pair programming. This is very valuable and not at all an inconvenience to us because it helps you avoid the intimidating step of getting started, so please do not hesitate to reach out right away. - -## Docs. - -Look for `README.md` files within select folders of the codebase and read the code comments at the top of some Rust files. As many folders are missing docs, this currently isn't a substitute for mentorship described in the section above. If you also want to dig into the code and solidify your understanding by writing documentation, that would be equally valuable to the project! - -## Codebase overview. - -The Graphite Editor is built as a web app powered by Svelte in the frontend and Rust in the backend which is compiled to WebAssembly (wasm) and run in the browser. - -The Editor's frontend web code lives in `/frontend/src` and the backend Rust code lives in `/editor`. The web-based frontend is intended to be semi-temporary and eventually replaceable with a pure-Rust GUI frontend. Therefore, all backend code should be unaware of JavaScript or web concepts and all Editor application logic should be written in Rust not JS. - -### Frontend/backend communication. - -Frontend (JS) -> backend (Rust/wasm) communication is achieved through a thin Rust translation layer in `/frontend/wasm/src/editor_api.rs` which wraps the Editor backend's complex Rust data type API and provides the JS with a simpler API of callable functions. These wrapper functions are compiled by wasm-bindgen into autogenerated JS functions that serve as an entry point into the wasm. - -Backend (Rust) -> frontend (JS) communication happens by sending a queue of messages to the frontend message dispatcher. After the JS calls any wrapper API function to get into backend (Rust) code execution, the Editor's business logic runs and queues up `FrontendMessage`s (defined in `/editor/src/messages/frontend/frontend_message.rs`) which get mapped from Rust to JS-friendly data types in `/frontend/src/wasm-communication/messages.ts`. Various JS code subscribes to these messages by calling `subscribeJsMessage(MessageName, (messageData) => { /* callback code */ });`. - -### The Editor backend and Legacy Document modules. - -The Graphite Editor backend handles all the day-to-day logic and responsibilities of a user-facing interactive application. Some duties include: user input, GUI state management, viewport tool behavior, layer management and selection, and handling of multiple document tabs. - -The actual document (the artwork data and layers included in a saved `.graphite` file) is part of another core module located in `/document-legacy`. The (soon-to-be-replaced) Legacy Document codebase manages a user's document. Once it is replaced, the new Document module (that will be located in `/document`) will store a document's node graph and change history. While it's OK for the Editor to read data from—or make immutable function calls upon—the user's document controlled by the Legacy Document module, it should never be directly mutated. Instead, messages (called Operations) should be sent to the document to request changes occur. The Legacy Document code is designed to be used by the Editor or by third-party Rust or C/C++ code directly so a careful separation of concerns between the Editor and Legacy Document modules should be considered. - -### The message bus. - -Every part of the Graphite stack works based on the concept of message passing. Messages are pushed to the front or back of a queue and each one is processed by the module's dispatcher in the order encountered. Only the dispatcher owns a mutable reference to update its module's state. - -
Additional technical details (click to show) - -A message is an enum variant of a certain message sub-type like `FrontendMessage`, `ToolMessage`, `PortfolioMessage`, or `DocumentMessage`. An example is `DocumentMessage::DeleteSelectedLayers` (which carries no data) or `DocumentMessage::RenameLayer(Vec, String)` (which carries a layer path and a string as data). - -Message sub-types hierarchically wrap other message sub-types; for example, `DocumentMessage` is wrapped by `PortfolioMessage` via `PortfolioMessage::Document(DocumentMessage)` (this carries the child message as data), and `EllipseMessage` is wrapped by `ToolMessage` via `ToolMessage::Ellipse(EllipseMessage)` (again, this carries the child message as data). Every message sub-type is wrapped by the top-level `Message`, so the previous example is actually `Message::Tool(ToolMessage::Ellipse(EllipseMessage))`. - -Because this is cumbersome, we have a proc macro `#[child]` that automatically implements the `From` trait on message sub-types and lets you write `DocumentMessage::DeleteSelectedLayers.into()` instead of `Message(PortfolioMessage::Document(DocumentMessage::DeleteSelectedLayers))`. - -
- -## Debugging. - -Use the browser console (F12) to check for warnings and errors. Use the Rust macro `debug!("A debug message")` to print to the browser console. These statements should be for temporary debugging. Remove them before committing to master. Print-based debugging is necessary because breakpoints are not supported in WebAssembly. - -Additional print statements are available that *should* be committed. - -- `error!()` is for descriptive user-facing error messages -- `warn!()` is for non-critical problems that may indicate a bug somewhere -- `trace!()` is for verbose logs of ordinary internal activity, hidden by default - -To show trace logs, activate *Help* > *Debug: Print Trace Logs*. - -To also view logs of the messages dispatched by the message bus system, activate *Help* > *Debug: Print Messages* > *Only Names*. Or use *Full Contents* for more verbose insight with the actual data being passed. - -## Contributing guide. - -### Code style. - -The Graphite project highly values code quality and accessibility to new contributors. Therefore, please make an effort to make your code readable and well-documented. - -- **Naming:** - Please use descriptive variable/function/symbol names and keep abbreviations to a minimum. Prefer to spell out full words most of the time, so `gen_doc_fmt` should be written out as `generate_document_format` instead. This avoids the mental burden of expanding abbreviations into semantic meaning. Monitors are wide enough to display long variable/function names, so descriptive is better than cryptic. To streamline code review, it's recommended that you set up a spellcheck plugin in your editor. This project uses American English spelling conventions. - -- **Linting:** - Please ensure Clippy is enabled. This should be set up automatically in VS Code. Try to avoid committing code with lint warnings. - -- **Imports:** - At the top of Rust files, please follow the convention of separating imports into three blocks, in this order: - 1. Local (`use super::` and `use crate::`) - 2. First-party crates (e.g. `use editor::`) - 3. Third-party libraries (e.g. `use std::` or `use serde::`) - - Combine related imports with common paths at the same depth. For example, the lines `use crate::A::B::C;`, `use crate::A::B::C::Foo;`, and `use crate::A::B::C::Bar;` should be combined into `use crate::A::B::C::{self, Foo, Bar};`. But do not combine imports at mixed path depths. For example, `use crate::A::{B::C::Foo, X::Hello};` should be split into two separate import lines. In simpler terms, avoid putting a `::` inside `{}`. - -- **Tests:** - It's great if you can write tests for your code, especially if it's a tricky stand-alone function. However at the moment, we are prioritizing rapid iteration and will usually accept code without associated unit tests. That stance will change in the near future as we begin focusing more on stability than iteration speed. - -Additional best practices will be added here soon. Please ask @Keavon in the mean time. - -### Draft pull requests. - -Once you begin writing code, please open a pull request immediately and mark it as a **Draft**. Please push to this on a frequent basis, even if things don't compile or work fully yet. It's very helpful to have your work-in-progress code up on GitHub so the status of your feature is less of a mystery. - -Open a new PR as a draft / Convert an existing PR to a draft: - -![Screenhots showing GitHub's "Create pull request (arrow) > Create draft pull request" and "Still in progress? Convert to draft" buttons](https://static.graphite.rs/content/contribute/draft-pr.png) - -
-
diff --git a/website/content/donate.md b/website/content/donate.md new file mode 100644 index 00000000..60071362 --- /dev/null +++ b/website/content/donate.md @@ -0,0 +1,56 @@ ++++ +title = "Donate" + +[extra] +css = ["/donate.css"] +js = ["/fundraising.js"] ++++ + +
+
+ +# Donate + +**Safeguard the sustainable, independent future of Graphite.** Your monthly support (or one-off contribution) is needed for the speedy growth of the project. + +
+
+ +
+
+
+ +
+ +# Support the mission + +

+You can help realize Graphite's ambitious vision of building the ultimate 2D creative tool. +Graphite is built by a small, dedicated crew of volunteers in need of resources to grow. +

+ +### Summer 2023 fundraising goal: + +
+
+
+
+
+ Progress: 0% + Goal: $0/month +
+
+ +[Become a monthly supporter](https://github.com/sponsors/Keavon) this summer to collect an exclusive 💚 badge. Each season you support, a new heart design is yours to keep. In the future, they'll be shown on Graphite account profiles and community areas like forums and in-app collaboration. + +Donate + +
+ + + +
+
+
diff --git a/website/content/features.md b/website/content/features.md new file mode 100644 index 00000000..c0488bbc --- /dev/null +++ b/website/content/features.md @@ -0,0 +1,304 @@ ++++ +title = "Graphite features" + +[extra] +css = ["/features.css"] ++++ + +
+
+ +# Graphite features + +The current version of Graphite provides tools for designing vector art and graphic design in SVG format. It also supports some experimental, rudimentary raster editing that is improving rapidly. This page covers where the project is at and where it's heading next. + +
+
+ +
+ +
+ +
+ +## Powered by Graphene + +**Graphene** is the node graph engine that powers Graphite's compositor and procedural graphics pipeline. It's a visual scripting environment built upon the high-performance Rust programming language. Its runtime is [designed](/blog/distributed-computing-in-the-graphene-runtime/) to distribute rendering across CPU cores, GPUs, and network/cloud machines while optimizing for interactive frame rates. + + + +
+
+ + +## Written in Rust + +Always on the bleeding edge and built to last— Graphite is written on a robust foundation with Rust, a modern programming language optimized for creating fast, reliable, future-proof software. Even the GPU compute shaders are written in Rust, enabling reuse of CPU code implementations for nodes. + + + +
+ +
+ +
+ +
+ +
+ +
+ +## Layers & nodes: hybrid compositing
work-in-progress + +Graphite combines the best ideas from multiple categories of digital content creation software to redefine the workflows of 2D graphics editing. It is influenced by the core editing experience of traditional layer-based raster and vector apps, the nondestructive approaches of VFX compositing programs used by film studios, and the boundless creative possibilities of procedural production tools daily-driven by the 3D industry. + +Classic layer-based image editing is easy to understand, with collapsable folders that help artists stay organized. A variety of interactive viewport tools make it easy to manipulate the layers by drawing directly onto the canvas. On the other hand, node-based editing is like artist-friendly programming. It works by describing manipulations as steps in a flowchart, which is vastly more powerful but comes with added complexity. + +The hybrid workflow of Graphite offers a classic tool-centric, layer-based editing experience built around a procedural, node-based compositor. Users can ignore the node graph, use it exclusively, or switch back and forth with the press of a button while creating content. Interacting with the canvas using tools will manipulate the nodes behind the scenes. And the layer panel and node graph provide two equivalent, interchangeable views of the same document structure. + +
+
+ +## Raster & vector: sharp at all sizes
1–2 months away + +Digital 2D art commonly takes two forms. Raster artwork is made out of pixels which means it can look like anything imaginable, but it becomes blurry or pixelated when upscaling to a higher resolution. Vector artwork is made out of curved shapes which is perfect for some art styles but limiting to others. The magic of vector is that its mathematically-described curves can be enlarged to any size and remain crisp. + +Other apps usually focus on just raster or vector, forcing artists to buy and learn both products. Mixing art styles requires shuttling content back and forth between programs. And since picking a raster document resolution is a one-time deal, artists may choose to start really big, resulting in sluggish editing performance and multi-gigabyte documents. + +Graphite reinvents raster rendering so it stays sharp at any scale. Artwork is treated as data, not pixels, and is always redrawn at the current viewing resolution. Zoom the viewport and export images at any size— the document's paint brushes, masks, filters, and effects will all be rendered at the native resolution. + +Marrying vector and raster under one roof enables both art forms to complement each other in a cohesive content creation workflow. + +
+ +
+ +
+ +
+
+ +## Roadmap + +
+
+
+

— Pre-Alpha —

+
+
+ + Editor systems; basic vector art tools +
+
+

— Alpha Milestone 1 —

+
+
+ + Tool upgrades; Graphene prototyping +
+
+

— Alpha Milestone 2 —

+
+
+ + Brush tool +
+
+ + WebGPU accelerated rendering +
+
+ + Layer stack compositing +
+
+ + Graph-based documents +
+
+ + Node-based layer tree +
+
+ + Resolution-agnostic raster rendering +
+
+ + Document history system +
+
+ + Stable document format +
+
+ + Custom subgraph document nodes +
+
+ + Select mode and masking +
+
+ + New viewport overlays system +
+
+ + Self-updating desktop app +
+
+

— Alpha Milestone 3 —

+
+
+ + Graph data attributes +
+
+ + Spreadsheet-based vector data +
+
+ + Editable SVG import +
+
+ + Rust-based vector renderer +
+
+ + RAW photo import and processing +
+
+ + Powerful snapping and grid system +
+
+ + Remote compile/render server +
+
+ + Code editor for custom nodes +
+
+

— Beta —

+
+
+ + Procedural paint brush styling +
+
+ + Frozen history references +
+
+ + Internationalization and accessability +
+
+ + Reconfigurable workspace panels +
+
+ + Liquify and non-affine rendering +
+
+ + Interactive graph auto-layout +
+
+ + Automation and batch processing +
+
+ + Guide mode +
+
+ + CAD-like constraint solver +
+
+ + Constraint models for UI layouts +
+
+ + Advanced typography and typesetting +
+
+ + PDF export +
+
+ + HDR and WCG color handling +
+
+ + Node manager and marketplace +
+
+ + Predictive graph rendering/caching +
+
+ + Distributed graph rendering +
+
+ + Cloud document storage +
+
+ + Multiplayer collaborative editing +
+
+ + Offline edit resolution with CRDTs +
+
+ + Native UI rewrite from HTML frontend +
+
+

— 1.0 Release —

+
+
+ + Timeline and renderer for animation +
+
+ + Live video compositing +
+
+ + Pen and touch-only interaction +
+
+ + iPad app +
+
+ + Portable render engine +
+
+ + SVG animation +
+
+ + …and that's all just the beginning… +
+
+
+ +
+
diff --git a/website/content/features/_index.md b/website/content/features/_index.md deleted file mode 100644 index 4b9586fc..00000000 --- a/website/content/features/_index.md +++ /dev/null @@ -1,220 +0,0 @@ -+++ -title = "Features and roadmap" -template = "page.html" - -[extra] -css = "/features.css" -+++ - -
-
- -# Features and roadmap. - -The current version of Graphite provides tools for designing vector art with Bézier curves, similar to desktop applications like Inkscape, Illustrator, and Affinity Designer. -
-
- -
-
- -
- -## Graphite today. - -Graphite is a lightweight vector graphics editor that runs in your browser. Its nascent node-based compositor lets you apply image effects and co-create amazing art with AI. - -
-
- - Vector editing -
-
- - Node graph image effects -
-
- - AI-assisted art creation -
-
- - Open source and free forever -
-
- -Launch Graphite - -
-
- -## Graphite tomorrow. - -All the digital content creation tools a professional needs— in one streamlined package: - -
-
- - Looks and feels like traditional WYSIWYG design apps -
-
- - Sleek, intuitive interface built by designers, for designers -
-
- - Real-time collaborative editing -
-
- - Windows/Mac/Linux/Web/iPad -
-
- -The full Graphite vision wholly embraces procedural workflows: - -
-
- - Fully non-destructive editing with node-driven layers -
-
- - Infinitely scalable raster content with no pixelation -
-
- - Integrates leading-edge AI models and graphics algorithms -
-
- - Procedural pipelines for studio production environments -
-
- -
- -
-
- -
-
- -

Upcoming Tech: How it works

- ---- - - - -
- -
- -## Layers & nodes: hybrid compositing. - -Graphite combines the best ideas from multiple categories of digital content creation software to form a design for the ultimate general-purpose 2D graphics editor. It is influenced by the central editing experience of traditional layer-based raster and vector apps. It takes inspiration from the non-destructive workflows of VFX compositing programs used in Hollywood. And it borrows the creative superpowers of procedural asset creation applications in the 3D industry. - -Classic layer-based image editing is easy to understand and its collapsable folders help artists stay organized. A variety of interactive viewport tools make it easy to manipulate the layers by drawing directly onto the canvas. On the other hand, node-based editing is like artist-friendly programming. It works by describing manipulations as steps in a flowchart, which is vastly more powerful but comes with added complexity. - -The hybrid workflow of Graphite offers a classic tool-centric, layer-based editing experience built around a procedural, node-based compositor. Users can ignore the node graph, use it exclusively, or switch back and forth with the press of a button while creating content. Interacting with the canvas using tools will manipulate the nodes behind the scenes. And the layer panel and node graph provide two equivalent, interchangeable views of the same document structure. - -
-
- -## Raster & vector: sharp at all sizes. - -Digital 2D art commonly takes two forms. Raster artwork is made out of pixels which means it can look like anything imaginable, but it becomes blurry or pixelated from upscaling to a higher resolution. Vector artwork is made out of curved shapes which is perfect for some art styles but limiting to others. The magic of vector is that its mathematically-described curves can be enlarged to any size and remain crisp. - -Other apps usually focus on just raster or vector, forcing artists to buy and learn both products. Mixing art styles requires shuttling content back and forth between programs. And since picking a raster document resolution is a one-time deal, artists may choose to start really big, resulting in sluggish editing performance and multi-gigabyte documents. - -Graphite reinvents raster rendering so it stays sharp at any scale. Artwork is treated as data, not pixels, and is always drawn at the current view resolution. Zoom the viewport and export images at any size— the document's paint brushes, masks, filters, and effects will all be rendered at the native resolution. - -Marrying vector and raster under one roof enables both art forms to complement each other in a holistic content creation workflow. - -
- -
- -
-
- -
-
- -## Development roadmap. - -Short-to-medium-term feature development is tracked at a granular level in the [Task Board](https://github.com/GraphiteEditor/Graphite/projects/1) on GitHub. Graphite uses a continuous release cycle without version numbers where changes can be tracked by [commit hash](https://github.com/GraphiteEditor/Graphite/commits/master). The stable release at [editor.graphite.rs](https://editor.graphite.rs) deploys a [recent commit](https://github.com/GraphiteEditor/Graphite/releases/tag/latest-stable) from the past several weeks, while [dev.graphite.rs](https://dev.graphite.rs) hosts the latest commit. - -

— Pre-Alpha (complete) —

-
    -
  • First year of development (complete, details omitted)
  • -
-

— Alpha Milestone 1 (ongoing) —

-
    -
  • Second year of development (complete, details omitted)
  • -
  • Brush tool (in-progress)
  • -
  • WebGPU in supported browsers (in-progress)
  • -
  • Vertical compositing of nodes
  • -
  • Node-based layer tree
  • -
  • Graph-based documents
  • -
  • Self-updating desktop app
  • -
  • Custom subgraph document nodes
  • -
-

— Alpha Milestone 2 —

-
    -
  • Graph data attributes
  • -
  • Spreadsheet-based vector data
  • -
  • Editable SVG import
  • -
  • Rust-based vector renderer
  • -
  • Select mode and masking
  • -
  • New viewport overlays system
  • -
  • Resolution-agnostic raster rendering
  • -
  • Powerful snapping and grid system
  • -
  • Remote compile/render server
  • -
  • Code editor for custom nodes
  • -
  • Document history system
  • -
  • Stable document format
  • -
-

— Alpha Milestone 3 —

-
    -
  • RAW photo import and processing
  • -
  • Procedural paint brush styling
  • -
  • Frozen history references
  • -
  • Internationalization and accessability
  • -
  • Reconfigurable workspace panels
  • -
  • Liquify and non-affine rendering
  • -
  • Interactive graph auto-layout
  • -
  • Automation and batch processing
  • -
-

— Beta —

-
    -
  • Guide mode
  • -
  • CAD-like constraint solver
  • -
  • Constraint models for UI layouts
  • -
  • Advanced typography and typesetting
  • -
  • PDF export
  • -
  • HDR and WCG color handling
  • -
  • Node manager and marketplace
  • -
  • Predictive graph rendering/caching
  • -
  • Distributed graph rendering
  • -
  • Cloud document storage
  • -
  • Multiplayer collaborative editing
  • -
  • Offline edit resolution with CRDTs
  • -
  • Native UI rewrite from HTML frontend
  • -
-

— 1.0 Release —

-
    -
  • Timeline and renderer for animation
  • -
  • Live video compositing
  • -
  • Pen and touch-only interaction
  • -
  • iPad app
  • -
  • Portable render engine
  • -
  • SVG animation
  • -
-

— Future Releases —

-
    -
  • …and that's just the beginning…
  • -
- -
-
diff --git a/website/content/learn/_index.md b/website/content/learn/_index.md new file mode 100644 index 00000000..710b1e91 --- /dev/null +++ b/website/content/learn/_index.md @@ -0,0 +1,14 @@ ++++ +title = "User manual" +template = "book.html" +page_template = "book.html" + +[extra] +book = true ++++ + +The user manual is coming very soon. Please check back in a few days. + + + + diff --git a/website/content/learn/introduction/_index.md b/website/content/learn/introduction/_index.md new file mode 100644 index 00000000..14c5ef08 --- /dev/null +++ b/website/content/learn/introduction/_index.md @@ -0,0 +1,8 @@ ++++ +title = "Introduction" +template = "book.html" +page_template = "book.html" + +[extra] +order = 1 ++++ diff --git a/website/content/learn/introduction/artboards.md b/website/content/learn/introduction/artboards.md new file mode 100644 index 00000000..f0e0b7a8 --- /dev/null +++ b/website/content/learn/introduction/artboards.md @@ -0,0 +1,6 @@ ++++ +title = "Artboards" + +[extra] +order = 4 ++++ diff --git a/website/content/learn/introduction/interface.md b/website/content/learn/introduction/interface.md new file mode 100644 index 00000000..0769a259 --- /dev/null +++ b/website/content/learn/introduction/interface.md @@ -0,0 +1,6 @@ ++++ +title = "Interface" + +[extra] +order = 1 ++++ diff --git a/website/content/learn/introduction/layers.md b/website/content/learn/introduction/layers.md new file mode 100644 index 00000000..a3274b7a --- /dev/null +++ b/website/content/learn/introduction/layers.md @@ -0,0 +1,6 @@ ++++ +title = "Layers" + +[extra] +order = 3 ++++ diff --git a/website/content/learn/introduction/tools.md b/website/content/learn/introduction/tools.md new file mode 100644 index 00000000..c178e72a --- /dev/null +++ b/website/content/learn/introduction/tools.md @@ -0,0 +1,6 @@ ++++ +title = "Tools" + +[extra] +order = 2 ++++ diff --git a/website/content/learn/node-graph/_index.md b/website/content/learn/node-graph/_index.md new file mode 100644 index 00000000..46b1bdda --- /dev/null +++ b/website/content/learn/node-graph/_index.md @@ -0,0 +1,9 @@ ++++ +title = "Node graph" +template = "book.html" +page_template = "book.html" + +[extra] +order = 2 ++++ + diff --git a/website/content/learn/node-graph/imaginate.md b/website/content/learn/node-graph/imaginate.md new file mode 100644 index 00000000..73d6ceda --- /dev/null +++ b/website/content/learn/node-graph/imaginate.md @@ -0,0 +1,77 @@ ++++ +title = "Imaginate" + +[extra] +order = 2 ++++ + + diff --git a/website/content/learn/node-graph/nodes.md b/website/content/learn/node-graph/nodes.md new file mode 100644 index 00000000..e8932d92 --- /dev/null +++ b/website/content/learn/node-graph/nodes.md @@ -0,0 +1,6 @@ ++++ +title = "Nodes" + +[extra] +order = 1 ++++ diff --git a/website/content/license/_index.md b/website/content/license.md similarity index 94% rename from website/content/license/_index.md rename to website/content/license.md index 8b75d0e0..49830c5f 100644 --- a/website/content/license/_index.md +++ b/website/content/license.md @@ -1,16 +1,17 @@ +++ title = "Graphite license" -template = "page.html" +++ -
+
-# Graphite license. +# Graphite license Graphite is open source software built by the community. The application is free to use by anyone for any purpose, even commercially. The artwork you produce is solely yours. -The source code [available on GitHub](https://github.com/GraphiteEditor/Graphite) (including the Graphite editor application, Graphene system, libraries, and other software materials) is provided under the Apache 2.0 license posted below, unless otherwise noted. Artwork, including but not limited to logos, icons, SVG code, branding imagery, and sample art are excluded from this license and held under copyright by their respective owners. Learn about the [Graphite brand](/logo) for more information. +The source code [available on GitHub](https://github.com/GraphiteEditor/Graphite) (including the Graphite editor application, libraries, and other software materials) is provided under the Apache 2.0 license posted below, unless otherwise noted within the repository. + +Visual assets, including but not limited to logos, icons, SVG code, branding imagery, and sample artwork are excluded from this license and held under copyright by their respective owners. Learn about the [Graphite brand](/logo) for more information. ---- diff --git a/website/content/logo/_index.md b/website/content/logo.md similarity index 75% rename from website/content/logo/_index.md rename to website/content/logo.md index c24f7488..ccc248f3 100644 --- a/website/content/logo/_index.md +++ b/website/content/logo.md @@ -1,30 +1,35 @@ +++ title = "Graphite logo" -template = "page.html" [extra] -css = "/logo.css" +css = ["/logo.css"] +++ -
+
-# Graphite logo. +# Graphite logo + +Graphite's logo is represented by the end of a pencil protruding from a hexagon and drawing a sketch mark. + +The pencil and its streak, composed of the substance graphite, signifies the software's name and its role as a drawing tool and versatile art medium. + +The hexagon represents one unit in the lattice grid chemical structure of graphene. The angled line is a double bond. This references the Graphene node engine which powers the Graphite editor. The lattice grid also resembles the nodes and edges of a mathematical graph, referencing the node graph that is fundamental to both the Graphene engine and Graphite editor. The first syllable of both names also alludes to that central node graph. + +## Usage policy The Graphite logo is made available for community use. While the software is free and open source, the brand identity is more restrictive. Please be respectful of the Graphite brand by reviewing the usage policy. -## Usage policy. - -The Graphite logo is not part of the software's Apache 2.0 license. Users of the logo must adhere to the usage policy: +Please be advised that the logo is not part of the software's Apache 2.0 license. Users of the logo must adhere to the usage policy: 1. Do not use the Graphite logo as your own. It should not be used as your primary, or most visually prominent, branding. Your usage should not imply that it's a part of the official Graphite project, nor that it's endorsed or affiliated. 2. Only use the logo when talking about, describing, referencing, or crediting the official Graphite project or software. If used as a hyperlink, it should point only to . -3. Do not modify the logo. Only the solid-colored version may be recolored if necessary, but this is discouraged. +3. Do not modify the logo. The solid-colored version may only be recolored with another solid color if doing so is necessary for visual consistency when presented alongside other logos of the same color. Sufficient blank space should be preserved around the logo so it does not compete with impinging design elements. 4. Commercial use of the logo (for example, merchandise sales) is not allowed without permission. -If in doubt, please send an email to for permission. +If in doubt, please get in touch by email to request clarification or permission. -## Download. +## Download Download the complete [logo kit](https://static.graphite.rs/logos/graphite-logo-kit.zip) or a specific version in PNG or SVG format below. diff --git a/website/content/press.md b/website/content/press.md new file mode 100644 index 00000000..5aff3248 --- /dev/null +++ b/website/content/press.md @@ -0,0 +1,23 @@ ++++ +title = "Press resources" ++++ + +
+
+ +# Press resources + +Materials for journalists and creators looking to share Graphite with their audiences. + +## Contact + +Please get in touch if you'd like to conduct an interview or have questions answered. + +Send an email to and you can usually expect a quick reply. + +## Logo + +Logos in PNG and SVG format are available for use in articles talking about Graphite. See the [logo](/logo) page for downloads. + +
+
diff --git a/website/content/press/_index.md b/website/content/press/_index.md deleted file mode 100644 index 0083f880..00000000 --- a/website/content/press/_index.md +++ /dev/null @@ -1,22 +0,0 @@ -+++ -title = "Press resources" -template = "page.html" -+++ - -
-
- -# Press resources. - -This page provides materials for members of the press and anyone looking to share Graphite with their audiences. - -## Contact. - -Please get in touch if you'd like to conduct an interview, get any questions answered, or anything else. Send an email to and you can usually expect a quick reply. - -## Logo. - -Logos in PNG and SVG format are available for use in articles talking about Graphite. See the [logo](/logo) page for downloads. - -
-
diff --git a/website/content/volunteer/_index.md b/website/content/volunteer/_index.md new file mode 100644 index 00000000..568a924e --- /dev/null +++ b/website/content/volunteer/_index.md @@ -0,0 +1,70 @@ ++++ +title = "Volunteer" + +[extra] +css = ["/volunteer.css"] ++++ + +
+
+ +# Volunteer + +Graphite is built by volunteers. Join the effort to bring great, free creative software to the world. + +
+
+ +
+ +
+ +
+ +## Creative contributions + +Ask how to begin (Discord) + +
+ +### ART TEAM + +Use your artistic talents to plan and produce ambitious open art projects published by the Graphite project to stress-test and showcase the editor's capabilities. + +
+
+ +### PUBLICITY TEAM + +Become the author of feature announcements, [blog](/blog) posts, website content, the user manual, press releases, social media posts, and industry outreach. + + +
+ +
+
+ +## Code contributions + +Contributor guide + +
+ +### EDITOR TEAM + +Write code to build and maintain the Graphite editor itself, ranging from the Svelte/TypeScript frontend UI to the tooling business logic written in Rust. + +
+
+ +### GRAPHENE TEAM + +Develop the core engine that powers Graphite's node graph and rendering. Graphene is a programming language, compiler, and distributed runtime ecosystem built upon Rust. + +
+ +
+ +
+ +
diff --git a/website/content/volunteer/guide/_index.md b/website/content/volunteer/guide/_index.md new file mode 100644 index 00000000..ebb9a43e --- /dev/null +++ b/website/content/volunteer/guide/_index.md @@ -0,0 +1,11 @@ ++++ +title = "Contributor guide" +template = "book.html" +page_template = "book.html" +aliases = ["/contribute"] + +[extra] +book = true ++++ + +We're glad that you are interested in contributing to Graphite! We want to make it as easy and frictionless as possible for you to get started. Here are the basics. diff --git a/website/content/volunteer/guide/codebase-overview/_index.md b/website/content/volunteer/guide/codebase-overview/_index.md new file mode 100644 index 00000000..31c3a72c --- /dev/null +++ b/website/content/volunteer/guide/codebase-overview/_index.md @@ -0,0 +1,67 @@ ++++ +title = "Codebase overview" +template = "book.html" +page_template = "book.html" + +[extra] +order = 2 # Chapter number ++++ + +
+ +
+ +The Graphite editor is built as a web app powered by Svelte in the frontend and Rust in the backend which is compiled to WebAssembly (wasm) and run in the browser. + +The Editor's frontend web code lives in `/frontend/src` and the backend Rust code lives in `/editor`. The web-based frontend is intended to be semi-temporary and eventually replaceable with a pure-Rust GUI frontend. Therefore, all backend code should be unaware of JavaScript or web concepts and all Editor application logic should be written in Rust not JS. + +## Frontend/backend communication + +Frontend (JS) -> backend (Rust/wasm) communication is achieved through a thin Rust translation layer in `/frontend/wasm/src/editor_api.rs` which wraps the Editor backend's complex Rust data type API and provides the JS with a simpler API of callable functions. These wrapper functions are compiled by wasm-bindgen into autogenerated JS functions that serve as an entry point into the wasm. + +Backend (Rust) -> frontend (JS) communication happens by sending a queue of messages to the frontend message dispatcher. After the JS calls any wrapper API function to get into backend (Rust) code execution, the Editor's business logic runs and queues up `FrontendMessage`s (defined in `/editor/src/messages/frontend/frontend_message.rs`) which get mapped from Rust to JS-friendly data types in `/frontend/src/wasm-communication/messages.ts`. Various JS code subscribes to these messages by calling `subscribeJsMessage(MessageName, (messageData) => { /* callback code */ });`. + +## The Editor backend and Legacy Document modules + +The Graphite editor backend handles all the day-to-day logic and responsibilities of a user-facing interactive application. Some duties include: user input, GUI state management, viewport tool behavior, layer management and selection, and handling of multiple document tabs. + +The actual document (the artwork data and layers included in a saved `.graphite` file) is part of another core module located in `/document-legacy`. The (soon-to-be-replaced) Legacy Document codebase manages a user's document. Once it is replaced, the new Document module (that will be located in `/document`) will store a document's node graph and change history. While it's OK for the Editor to read data from—or make immutable function calls upon—the user's document controlled by the Legacy Document module, it should never be directly mutated. Instead, messages (called Operations) should be sent to the document to request changes occur. The Legacy Document code is designed to be used by the Editor or by third-party Rust or C/C++ code directly so a careful separation of concerns between the Editor and Legacy Document modules should be considered. + +## The message bus + +Every part of the Graphite stack works based on the concept of message passing. Messages are pushed to the front or back of a queue and each one is processed by the module's dispatcher in the order encountered. Only the dispatcher owns a mutable reference to update its module's state. + +### Additional technical details + +A message is an enum variant of a certain message sub-type like `FrontendMessage`, `ToolMessage`, `PortfolioMessage`, or `DocumentMessage`. Two example messages: +```rs +// Carries no data +DocumentMessage::DeleteSelectedLayers + +// Carries a layer path and a string as data +DocumentMessage::RenameLayer(Vec, String) +``` + +Message sub-types hierarchically wrap other message sub-types; for example, `DocumentMessage` is wrapped by `PortfolioMessage` via: +```rs +// Carries the child message as data +PortfolioMessage::Document(DocumentMessage) +``` +and `EllipseMessage` is wrapped by `ToolMessage` via: +```rs +// Carries the child message as data +ToolMessage::Ellipse(EllipseMessage) +``` +Every message sub-type is wrapped by the top-level `Message`, so the previous example is actually: +```rs +Message::Tool(ToolMessage::Ellipse(EllipseMessage)) +``` + +Because this is cumbersome, we have a proc macro `#[child]` that automatically implements the `From` trait on message sub-types and lets you write: +```rs +DocumentMessage::DeleteSelectedLayers.into() +``` +instead of: +```rs +Message(PortfolioMessage::Document(DocumentMessage::DeleteSelectedLayers)) +``` diff --git a/website/content/volunteer/guide/codebase-overview/contributing-guidelines.md b/website/content/volunteer/guide/codebase-overview/contributing-guidelines.md new file mode 100644 index 00000000..458835dd --- /dev/null +++ b/website/content/volunteer/guide/codebase-overview/contributing-guidelines.md @@ -0,0 +1,47 @@ ++++ +title = "Contributing guidelines" + +[extra] +order = 2 # Page number after chapter intro ++++ + +## Code style + +The Graphite project prizes code quality and accessibility to new contributors. Therefore, we ask you please make all efforts to contribute readable, well-documented code according to these best practices. + +### Naming + +Please use descriptive variable/function/symbol names and keep abbreviations to a minimum. Prefer to spell out full words most of the time, so `gen_doc_fmt` should be written out as `generate_document_format` instead. + +This avoids the mental burden of expanding abbreviations into semantic meaning. Monitors are wide enough to display long variable/function names, so descriptive is better than cryptic. To streamline code review, it's recommended that you set up a spellcheck plugin in your editor. The project uses American English spelling conventions. + +### Linting + +Please ensure Clippy is enabled. This should be set up automatically in VS Code. Try to avoid committing code with lint warnings. + +### Comments + +For consistency, please try to write comments in *Sentence case* (starting with a capital letter). End with a period only if multiple sentences are used in the same comment. For doc comments (`///`), always write in full sentences (ending with a period). + +Comments should be placed on a separate line, but exceptions are permitted where sensible. They should target the maximum line length of 200 characters (don't go over, and don't target a considerably lower number like 80 for line breaks). + +### Imports + +At the top of Rust files, please follow the convention of separating imports into three blocks, in this order: +1. Local (`use super::` and `use crate::`) +2. First-party crates (e.g. `use editor::`) +3. Third-party libraries (e.g. `use std::` or `use serde::`) + +Combine related imports with common paths at the same depth. For example, the lines `use crate::A::B::C;`, `use crate::A::B::C::Foo;`, and `use crate::A::B::C::Bar;` should be combined into `use crate::A::B::C::{self, Foo, Bar};`. But do not combine imports at mixed path depths. For example, `use crate::A::{B::C::Foo, X::Hello};` should be split into two separate import lines. In simpler terms, avoid putting a `::` inside `{}`. + +## Tests + +It's great if you can write tests for your code, especially if it's a tricky stand-alone function. However at the moment, we are prioritizing rapid iteration and will usually accept code without associated unit tests. That stance will change in the near future as we begin focusing more on stability than iteration speed. + +## Draft pull requests + +Once you begin writing code, please open a pull request immediately and mark it as a **Draft**. Please push to this on a frequent basis, even if things don't compile or work fully yet. It's very helpful to have your work-in-progress code up on GitHub so the status of your feature is less of a mystery. + +Open a new PR as a draft / convert an existing PR to a draft: + +![Screenhots showing GitHub's "Create pull request (arrow) > Create draft pull request" and "Still in progress? Convert to draft" buttons](https://static.graphite.rs/content/contribute/draft-pr.png) diff --git a/website/content/volunteer/guide/codebase-overview/debugging.md b/website/content/volunteer/guide/codebase-overview/debugging.md new file mode 100644 index 00000000..a1ecf23b --- /dev/null +++ b/website/content/volunteer/guide/codebase-overview/debugging.md @@ -0,0 +1,30 @@ ++++ +title = "Debugging" + +[extra] +order = 1 # Page number after chapter intro ++++ + +## Deployed builds + +When tracking down a bug, first check if the issue you are noticing also exists in `master` or just your branch. Use [dev.graphite.rs](https://dev.graphite.rs) which should always deploy the lastest commit on `master`. By comparison, [editor.graphite.rs](https://editor.graphite.rs) is manually updated every few days or weeks to ensure stability. Use *Help* > *About Graphite* in the editor to view the build's [commit hash](https://github.com/GraphiteEditor/Graphite/commits/master). + +## Printing to the console + +Use the browser console (F12) to check for warnings and errors. Use the Rust macro `debug!("A debug message");` to print to the browser console. These statements should be for temporary debugging. Remove them before committing to `master`. Print-based debugging is necessary because breakpoints are not supported in WebAssembly. + +Additional print statements are available that *should* be committed. + +- `error!()` is for descriptive user-facing error messages arising from a bug +- `warn!()` is for non-critical problems that likely indicate a bug somewhere +- `trace!()` is for verbose logs of ordinary internal activity, hidden by default + +To show `trace!()` logs, activate *Help* > *Debug: Print Trace Logs*. + +## Message system logs + +To also view logs of the messages dispatched by the message bus system, activate *Help* > *Debug: Print Messages* > *Only Names*. Or use *Full Contents* for more verbose insight with the actual data being passed. This is an invaluable window into the activity of the message flow and works well together with `debug!()` printouts for tracking down message-related issues. + +## Layer paths and document IDs + +In debug mode, hover over a layer's name in the Layer Tree panel to view a tooltip with its `u64` path. Likewise, document IDs may be read by hovering over their tabs. diff --git a/website/content/volunteer/guide/codebase-overview/tech-stack.md b/website/content/volunteer/guide/codebase-overview/tech-stack.md new file mode 100644 index 00000000..681d6bca --- /dev/null +++ b/website/content/volunteer/guide/codebase-overview/tech-stack.md @@ -0,0 +1,16 @@ ++++ +title = "Tech stack" + +[extra] +order = 3 # Page number after chapter intro ++++ + +- rustc: Compiler for node graph generics and custom nodes +- rust-gpu: Compiler backend to generate compute shaders from Rust source code +- wgpu: Portable graphics API for running compute shaders on desktop and web +- Tauri: lightweight desktop web UI shell while the backend runs natively (experimental) + + + + + diff --git a/website/content/volunteer/guide/getting-started/_index.md b/website/content/volunteer/guide/getting-started/_index.md new file mode 100644 index 00000000..0d57706e --- /dev/null +++ b/website/content/volunteer/guide/getting-started/_index.md @@ -0,0 +1,48 @@ ++++ +title = "Getting started" +template = "book.html" +page_template = "book.html" + +[extra] +order = 1 # Chapter number ++++ + +Graphite is built with Rust and web technologies. Install the latest LTS version of [Node.js](https://nodejs.org/) and stable release of [Rust](https://www.rust-lang.org/), as well as [Git](https://git-scm.com/). + +## Installing + +Clone the project: +```sh +git clone https://github.com/GraphiteEditor/Graphite.git +``` + +Then install the required Node.js packages: +```sh +cd frontend +npm install +``` + +You only need to explicitly install Node.js dependencies. Rust's cargo dependencies will be installed automatically on your first build. One dependency in the build chain, `wasm-pack`, will be installed automatically on your system when the Node.js packages are installing. (If you prefer to install this manually, get it from the [wasm-pack website](https://rustwasm.github.io/wasm-pack/), then install your npm dependencies with `npm install --no-optional` instead.) + +One tool in the Rust ecosystem does need to be installed: + +``` +cargo install cargo-watch +``` + +That's it! Now, to run the project while developing, just execute: +``` +npm start +``` + +This spins up the dev server at with a file watcher that performs hot reloading of the web page. You should be able to start the server, edit and save web and Rust code, and rarely have to kill the server (by hitting CtrlC twice). You sometimes may need to reload the browser's web page if the hot reloading didn't behave perfectly. This method compiles Graphite code in debug mode which includes debug symbols for viewing function names in stack traces. But be aware, it runs slower and takes more memory. + +## Production builds + +You'll rarely ever need to do this, but to compile a production build with full optimizations: +```sh +cargo install cargo-about +npm run build +``` + +It produces the `/frontend/dist` directory containing the static site files that must be served by your own web server. diff --git a/website/content/volunteer/guide/getting-started/editor-and-tooling.md b/website/content/volunteer/guide/getting-started/editor-and-tooling.md new file mode 100644 index 00000000..07d63bee --- /dev/null +++ b/website/content/volunteer/guide/getting-started/editor-and-tooling.md @@ -0,0 +1,14 @@ ++++ +title = "Editor and tooling" + +[extra] +order = 1 # Page number after chapter intro ++++ + +We provide default configurations for VS Code users. When you open the project, watch for a prompt to install the project's suggested extensions. They will provide helpful web and Rust tooling. If you use a different IDE, you won't get default configurations for the project out of the box, so please remember to format your code and check CI for errors. + +## Checking, linting, and formatting + +While developing Rust code, `cargo check`, `cargo clippy`, and `cargo fmt` terminal commands may be run from the root directory. For web code, `npm run lint` and `npm run lint-no-fix` can be used from the `/frontend` directory to fix or view formatting issues. + +If you don't use VS Code and its format-on-save feature, please remember to format before committing or consider [setting up a `pre-commit` hook](https://githooks.com/) to do that automatically. Disabling VS Code's *Auto Save* files feature is recommended to ensure you actually save (and thus format) file changes. diff --git a/website/content/volunteer/guide/getting-started/getting-help.md b/website/content/volunteer/guide/getting-started/getting-help.md new file mode 100644 index 00000000..9f527605 --- /dev/null +++ b/website/content/volunteer/guide/getting-started/getting-help.md @@ -0,0 +1,14 @@ ++++ +title = "Getting help" + +[extra] +order = 3 # Page number after chapter intro ++++ + +## Discord developer community + +Join the [project's Discord server](https://discord.graphite.rs) then hop on the `#development` channel and ping @Keavon, @TrueDoctor, or @0Hypercube. The team would be delighted to help you get started by providing in-depth explanations of the code and programming assistance as you work. Please do not hesitate to reach out right away! + +## Code documentation + +Look out for `README.md` files in some folders of the codebase and doc comments at the top of some Rust files. The quantity of those files is limited right now, but documenting code is an excellent contribution if you wish to explain what you've learned for the sake of others, and improve your own understanding in the process. diff --git a/website/content/volunteer/guide/getting-started/picking-a-task.md b/website/content/volunteer/guide/getting-started/picking-a-task.md new file mode 100644 index 00000000..a56d4c9a --- /dev/null +++ b/website/content/volunteer/guide/getting-started/picking-a-task.md @@ -0,0 +1,17 @@ ++++ +title = "Picking a task" + +[extra] +order = 2 # Page number after chapter intro ++++ + +The [task board](https://github.com/orgs/GraphiteEditor/projects/1/views/1) provides a list of [available tasks](https://github.com/orgs/GraphiteEditor/projects/1/views/5), as well as a [beginner-friendly](https://github.com/orgs/GraphiteEditor/projects/1/views/6) subset. + +If you have Rust and/or web experience, you may also pick based on: +- [Only Rust](https://github.com/orgs/GraphiteEditor/projects/1/views/5?filterQuery=status%3AShort-Term%2CMedium-Term%2CLonger-Term+label%3ARust+-label%3AWeb) tasks +- [Only web](https://github.com/orgs/GraphiteEditor/projects/1/views/5?filterQuery=status%3AShort-Term%2CMedium-Term%2CLonger-Term+label%3AWeb+-label%3ARust) tasks (HTML/CSS/TypeScript/Svelte) +- [Combined Rust and web](https://github.com/orgs/GraphiteEditor/projects/1/views/5?filterQuery=status%3AShort-Term%2CMedium-Term%2CLonger-Term+label%3ARust+label%3AWeb) tasks + +Feel free to pick whatever task interests you, then comment on the issue that you would like to start. After commenting, you can dig in right away, then we will assign the issue to your GitHub user to keep the work status of tasks organized. + +Writing new documentation by commenting existing code is another valuable way to contribute as you learn. diff --git a/website/content/volunteer/guide/product-design/_index.md b/website/content/volunteer/guide/product-design/_index.md new file mode 100644 index 00000000..e77383cf --- /dev/null +++ b/website/content/volunteer/guide/product-design/_index.md @@ -0,0 +1,18 @@ ++++ +title = "Product design" +template = "book.html" +page_template = "book.html" + +[extra] +order = 3 # Chapter number ++++ + +**NOTE: Developers probably don't need to read this chapter.** + +A vital part in the success of open source software development is having a clear vision and direction for the product design. Failure to pull that off is a leading factor in the stagnation and mediocrity of many software projects. + +The Graphite project's founder and its principal product designer, Keavon, is in charge of maintaining and evolving the product design to ensure cohesion within the development process. This ranges from building the user interface design system and content layout as a mockup before it's replicated in HTML/CSS, to determining user workflows, to translating high-level goals into the constraints and requirements of specific features for the development team. + +The hardest part is translating an always-evolving network of ideas and thoughts comprehensively into written form in order to communicate the big (and little) ideas to collaborators. As the product develops, the design grows alongside the code and real usage of the developed features helps bring clarity to what's planned next. Since the design process never stops evolving, any attempt to write it down is inevitably doomed to become out of date. And the multitudes of mental state can't possibly all be written down. + +Despite the challenges with documenting design, this chapter aims to provide a living catalog of ideas surrounding some topics in the product design process. It won't be organized, nor complete, nor will it be always current. The best way to seek clarity about a topic is asking Keavon about it on Discord. **This chapter is optional reading** and is likely of minimal relevance to most code contributors. diff --git a/website/content/volunteer/guide/product-design/glossary-of-terminology.md b/website/content/volunteer/guide/product-design/glossary-of-terminology.md new file mode 100644 index 00000000..4575926b --- /dev/null +++ b/website/content/volunteer/guide/product-design/glossary-of-terminology.md @@ -0,0 +1,68 @@ ++++ +title = "Glossary of terminology" + +[extra] +order = 3 # Page number after chapter intro ++++ + +**NOTE: This is old. Some parts may not match current usage.** + +### Document +A design source file created and edited in the Graphite editor. Saved to disk as a Graphite Design Document in a _GDD file_. Documents can be included as _layers_ inside other documents, and in doing so they take the form of _groups_. The _layer graph_ contents of a _group_ actually belong to the _embedded_ document's _subgraph_. Because a document is a _group_ which is a _layer_ in the _layer graph_, documents have _properties_ such as the _frames_ in the _canvas_. Documents are composed of a layer graph, a defined set of properties of set _data types_ that are _imported_ and _exported_, and the _properties_ of the _root layer_. +### Asset +A portable mechanism for distributing a "compiled" Graphite _document_ in a format that is immediately ready for rendering. Saved to disk as a Graphite Digital Asset in a _GDA file_. Assets are created by "flattening" a _document's_ complex, nested _layer graph_ structure into a single, simple directed acyclic graph (DAG). The Graphite editor internally maintains an asset version of any open _document_ in order to draw the _canvas_ live in the _viewport_. An asset also includes certain exposed _properties_ of specified _data types_ that are _imported_ and _exported_, as defined by the asset's author in the source _document's_ _layer graph_. They can be shared and _embedded_ in another _layer graph_ as a black box (meaning it can't be expanded to reveal or edit its interior graph), as compared to _embedded_ _documents_ from _GDD files_ which are white boxes (they can be expanded to reveal their _subgraph_ which can be edited). Assets are helpful for defining custom _nodes_ that perform some useful functionality. Tangible examples include custom procedural effects, shape generators, and image filters. Many of the Graphite editor's own built-in _nodes_ are actually assets rather than being implemented directly in code. The _Asset Manager_ panel helps maintain these assets from various sources. The _Asset Store_ can be used to share and sell assets for easy inclusion in other projects. +### GDD file +Graphite Design Document. A binary serialization of a _document_ source file. The format includes a chain of _operations_ that describe changes to the _layer graph_ and the _properties_ of _layers_ throughout the history of the document since its creation. It also stores certain metadata and the raw data of _embedded_ files. Because GDD files are editable (unlike _GDA files_), the _layers_ of GDD files imported into another _document_ may be expanded in its _layer graph_ to reveal and modify their contents using a copy-on-write scheme stored to the _asset's_ _layer_. +### GDA file +Graphite Digital Asset. A binary serialization of an _asset_ file. Because GDA files are read-only and can't be edited (unlike _GDD files_), the _layers_ created from _assets_ do not offer an ability to be expanded in the _layer graph_ of a _document_ that _embeds_ them. GDA files are useful for sharing _assets_ when their authors do not wish to provide the source _documents_ to author them. _DGA files_ are also the input format included in games that utilize the _Graphite Renderer Core Library_ to render graphical content at runtime, as well as similar applications like headless renderers on web servers and image processing pipelines. +### Window +### Main window +### Popout window +### Title bar +### Status bar +### Workspace +The part of the Graphite editor's UI that houses the _panels_ in a _window_. The workspace occupies the large space below the _title bar_ and above the _status bar_ of the _main window_. It occupies the entirety of _popout windows_ (window buttons are added in the _tab bar_). +### Workspace layout +The specific configuration of panels in the _main window_ and any _popout windows_. Workspace layout presets are provided by the Graphite editor and users may customize and save their own. +### Tab bar +The bar at the top of a _panel group_ which includes a clickable tab for each panel that is docked there. Each tab bar has at least one tab and one active tab. +### Active tab +The one tab in a _tab bar_ that is currently active. The user can click any inactive tab to make it become the active tab. The active tab shows the _panel content_ beneath it unless it is a _folded panel_. +### Folded panel +A shrunken _panel_ showing only the _tab bar_. A _panel_ consists of the _tab bar_ and _panel body_ except when the latter is folded away. The user may click the _active tab_ to fold and restore a panel, however a panel cannot be folded if there are no other unfolded panels in its column. +### Panel +### Panel body +### Options bar +The bar that spans horizontally across the top of a _panel_ (located under the _tab bar_) which displays options related to the _panel_. +### Viewport +The area that takes up the main space in a _panel_ (located beneath the _options bar_) which displays the primary content of the _panel_. +### Shelf +The bar that spans vertically along the left side of some _panels_ (located left of the _viewport_) which displays a catalog of available items, such as document editing _tools_ or common _nodes_. +### Tool +An instrument for interactively editing _documents_ through a collection of related behavior. Each tool puts the editor into a mode that provides the ability to perform certain _operations_ on the document interactively. Each _operation_ is run based on the current context of mouse and modifier buttons, key presses, tool options, selected layers, editor state, and document state. The _operations_ that get run are appended to the document history and update the underlying _layer graph_ in real time. +### Canvas +The infinite coordinate system that shows the visual output of an open _document_ at the current zoom level and pan position. It is drawn in the document panel's _viewport_ within the area inside the scroll bars on the bottom/right edges and the _rulers_ on the top/left edges. The canvas can be panned and zoomed in order to display all or part of the artwork in any _frames_. A canvas has a coordinate system spanning infinitely in all directions with an origin always located at the top left of the primary _artboard_. The purpose of an infinite canvas is to offer a convenient editing experience when there is no logical edge to the artwork, for example a loosely-arranged board of logo design concepts, a mood board, or whiteboard-style notes. +### Artboard +An area inside a _canvas_ that provides rectangular bounds to the artwork contained within, as well as default bounds for an exported image. The _Artboard tool_ adjusts the bounds and placement of frames in the _document_ and each artboard is stored in a "artboard list" property of the _root layer_. When there is at least one artboard, the infinite _canvas_ area outside any artboard displays a configurable background color. Artwork can be placed outside of a artboard but it will appear mostly transparent. The purpose of using one artboard is to provide convenient cropping to the edges of the artwork, such as a single digital painting or photograph. The purpose of using multiple frames is to work on related artwork with separate bounds, such as the layout for a book. +### Layer graph +A (directed acyclic) graph structure composed of _layers_ with _connections_ between their input and output _ports_. This is commonly referred to as a "node graph" in other software, but Graphite's layer graph is more suited towards layer-based compositing compared to traditional compositor node graphs. +### Node +A definition of a _layer_. A node is a graph "operation" or "function" that receives input and generates deterministic output. +### Layer +Any instance of a _node_ that lives in the _layer graph_. Layers (usually) take input data, then they transform it or synthesize new data, then they provide it as output. Layers have _properties_ as well as exposed input and output _ports_ for sending and receiving data. +### Root layer +### Group +### Raster +### Vector +### Mask +### Data type +### Subgraph +### Port +### Connection +### Core Libraries +### Graphite Editor (Frontend) +### Graphite Editor (Backend) +### Graphene (Node Graph Engine) +### Trace +### Path +### Shape diff --git a/docs/editor/README.md b/website/content/volunteer/guide/product-design/product-outline.md similarity index 91% rename from docs/editor/README.md rename to website/content/volunteer/guide/product-design/product-outline.md index dd6b8dde..8c17a01a 100644 --- a/docs/editor/README.md +++ b/website/content/volunteer/guide/product-design/product-outline.md @@ -1,10 +1,12 @@ -# Graphite editor manual ++++ +title = "Product outline" -Work in progress. +[extra] +order = 1 # Page number after chapter intro ++++ + +**NOTE: This is old. Some parts may not match current usage.** -- [Overview](1-overview.md) - - [Key concepts](1-overview.md#key-concepts) - - [Glossary of terminology](1-overview.md#glossary-of-terminology) - Interface - Title bar - Menu bar diff --git a/docs/design/feature-goals.md b/website/content/volunteer/guide/product-design/uses-and-workflows.md similarity index 70% rename from docs/design/feature-goals.md rename to website/content/volunteer/guide/product-design/uses-and-workflows.md index e3269015..c05356a9 100644 --- a/docs/design/feature-goals.md +++ b/website/content/volunteer/guide/product-design/uses-and-workflows.md @@ -1,43 +1,67 @@ -# Feature goals ++++ +title = "Uses and workflows" -## Possible use cases +[extra] +order = 2 # Page number after chapter intro ++++ -- Photography - - RAW photo editing/processing - - Batch processing pipeline -- Motion graphics - - Title sequences/kinetic typography/etc. -- Live broadcast/streaming - - Live video compositing/overlays - - Interactive exhibits (rendering live content for museum/art/festival exhibits) -- Web - - SVG design - - Animated or interactive SVG design -- Automation - - Batch multimedia editing/conversion -- Graphic design - - Print design - - Web/digital-focused graphics (marketing, branding, infographics, ads, etc.) - - Templates filled with file/spreadsheet data (e.g. prototyping/iterating components of a board/card game) -- Illustration - - Digital painting - - Logo and icon design -- Desktop publishing - - Templates filled with Markdown/HTML content with export to PDF -- Video compositing -- Data Visualization - - Data-powered graphs/charts/etc. - - Automated rendering with live/often-updated data -- 3D/Gamedev - - PBR procedural material authorship - - 3D model UV map texturing -- AI-assisted tools -- HDR processing -- 360° and panoramic stitching and spherical editing +**NOTE: This is old. Some parts may not match current usage.** + +This list describes some long-term aspirational goals and ideas. It represents an incomplete brainstorm idea dump, not a roadmap. + +## Use cases + +General goals for product capabilities are categorized by discipline below. + +### Photography +- RAW photo editing/processing +- Batch processing pipeline + +### Motion graphics +- Title sequences/kinetic typography/etc. + +### Live broadcast/streaming +- Live video compositing/overlays +- Interactive exhibits (rendering live content for museum/art/festival exhibits) + +### Web +- SVG design +- Animated or interactive SVG design + +### Automation +- Batch multimedia editing/conversion + +### Graphic design +- Print design +- Web/digital-focused graphics (marketing, branding, infographics, ads, etc.) +- Templates filled with file/spreadsheet data (e.g. prototyping/iterating components of a board/card game) + +### Illustration +- Digital painting +- Logo and icon design + +### Desktop publishing +- Templates filled with Markdown/HTML content with export to PDF + +### Video compositing + +### Data Visualization +- Data-powered graphs/charts/etc. +- Automated rendering with live/often-updated data + +### 3D/Gamedev +- PBR procedural material authorship +- 3D model UV map texturing + +### AI-assisted tools + +### HDR processing + +### 360° and panoramic stitching and spherical editing ## User stories -Contributions welcome! If you think of something Graphite would be great for, submit a pull request or send Keavon a message on Graphite's Discord server to add it here. +Example user workflows are categorized by discipline below. ### Photography - Using a face detection node to sort photos into the correct folders upon export @@ -58,25 +82,21 @@ Contributions welcome! If you think of something Graphite would be great for, su - Creating a chart from a CSV - Rendering an always-up-to-date chart powered by real-time updates from a database - Data-driven infographics like an org chart that can be updated with text instead of manual design work -- Request the weather from an API and render live visualizations which gets displayed on a monitor in your house or a museum (export to a Windows screen saver?) +- Rendering a timelapse video of every operation done in the history of a document ### Digital painting - Creating a digital acrylic or oil painting using various brushes - Preventing mixing/smearing of previous wet paint layers by drying it with a hair dryer tool - Smearing wet paint colors together on a simulated paint palette and then sampling paint colors from that palette to paint with -### Education -- Rendering a timelapse video of everything operation done in the history of a document - ### Graphic design - Prototyping cards for board games fed with data in a spreadsheet which generates the cards from a template - Creating an image that has been shredded but pieced back together, where the image can be updated then return to the shredded one without having to redo the editing steps to shred it -### Broadcast and streaming +### Broadcast, interactive exhibits, and digital signage - Rendering overlays for live streams or television broadcasts based on live input data, for example somebody donates and leaves a comment on a live stream and this web hook could trigger an animated display containing the user and their comment, or live telemetry for a rocket launch streams in and gets rendered as graphical overlays for a webcast. - -### Interactive exhibits and digital signage - Rendering a custom live clockface with hour/minute/second hands based on an input of the current time, then showing them fullscreen on a display +- Request the weather from an API and render live visualizations which gets displayed on a monitor in your house or a museum (export to a Windows screen saver?) - Data from sensors can render interactive 2D graphics at a museum art installation - A storefront can have a monitor set up showing daily or hourly sales items based on web hooks or polling from the company’s website - Polling an API for content like Twitter or an RSS feed and displaying the tweet or headline when it arrives on screen, styled as desired @@ -89,7 +109,7 @@ Contributions welcome! If you think of something Graphite would be great for, su ### Automation - Laser cutter artwork processing for automating custom Etsy orders - Running on a server to let users upload images for a custom T-shirt printing website, and it renders their graphic on the model’s shirt (or other custom printing online stores) -- Generating a PDF invoice based on data in a pipeline in a server +- Generating a PDF invoice based on data in a pipeline on a server ### Computer vision and industrial control -- Factory line is examining its fruit for defects. In order to verify the quality, they need to enhance the contrast, automatically orient the image and correct the lighting. They then pass the results into a machine learning algorithm to classify, and sometimes need to snoop on the stream of data to manually do quality control (ImageMagick or custom Python libraries are often used for this right now) +- Factory line is examining its fruit for defects. In order to verify the quality, they need to enhance the contrast, automatically orient the image and correct the lighting. They then pass the results into a machine learning algorithm to classify, and sometimes need to snoop on the stream of data to manually do quality control (ImageMagick or custom Python scripts are often used for this right now) diff --git a/website/sass/about.scss b/website/sass/about.scss new file mode 100644 index 00000000..b3a58b00 --- /dev/null +++ b/website/sass/about.scss @@ -0,0 +1,51 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Color+Emoji&display=swap'); + +.loading-data:empty::after { + content: "(loading...)"; + font-style: italic; + opacity: 0; + animation: fadeInAfterWait 2s ease-in 2s forwards; + + @keyframes fadeInAfterWait { + 0% { opacity: 0; } + 100% { opacity: 1; } + } +} + +#core-team { + background-color: var(--color-parchment); + background-blend-mode: color-burn; + + h2, + h2 + p, + h3, + h3 + p { + width: 100%; + text-align: center; + } + + h2 + p { + margin-top: 4px; + opacity: 0.75; + } + + h3 + p { + margin-top: 0; + } + + h2 .flag { + font-family: "Noto Color Emoji", sans-serif; + font-size: 0.8em; + margin-left: 0.2em; + cursor: default; + } + + p:first-child { + width: 100%; + text-align: center; + + img { + max-width: Min(100%, 480px); + } + } +} diff --git a/website/sass/article.scss b/website/sass/article.scss index 6853ca3a..6cf20018 100644 --- a/website/sass/article.scss +++ b/website/sass/article.scss @@ -1,5 +1,6 @@ -#article { +.reading-material { max-width: 800px; + flex: 0 1 auto; .details { display: flex; @@ -18,33 +19,6 @@ } } - hr { - margin-top: 40px; - margin-bottom: 40px; - } - - article { - h1, - h2, - h3 { - font-family: "Inter", sans-serif; - line-height: 1.5; - font-weight: 800; - } - - h1 { - font-size: var(--font-size-article-h1); - } - - h2 { - font-size: var(--font-size-article-h2); - } - - h3 { - font-size: var(--font-size-article-h3); - } - } - .social { width: 100%; display: flex; diff --git a/website/sass/base.scss b/website/sass/base.scss index 143b5b88..7bc8ac47 100644 --- a/website/sass/base.scss +++ b/website/sass/base.scss @@ -1,15 +1,28 @@ :root { + --color-black: #000000; --color-white: #ffffff; --color-fog: #eeeeee; + --color-parchment: #f5edeb; + --color-mustard: #e5c299; --color-navy: #16323f; --color-walnut: #473a3a; --color-crimson: #803847; - --color-mustard: #e5c299; + --color-lilac: #cabdc8; + --color-lime: #c5e0af; + --color-lemon: #efe2b2; + --color-ale: #cd8f7a; + --color-flamingo: #d2697c; + --color-seaside: #a5d5c8; + --color-cove: #60b3ae; + --color-storm: #627088; + --color-sage: #739c7e; + --color-seaside-rgb: 165, 213, 200; --font-size-intro-heading: 60px; --font-size-intro-body: 22px; --font-size-link: 24px; - --font-size-heading: 40px; + --font-size-heading-h1: 48px; + --font-size-heading-h2: 42px; --font-size-subheading: 24px; --font-size-body: 18px; --font-size-article-h1: 32px; @@ -27,7 +40,8 @@ --font-size-intro-heading: 40px; --font-size-intro-body: 18px; --font-size-link: 20px; - --font-size-heading: 32px; + --font-size-heading-h1: 38px; + --font-size-heading-h2: 36px; --font-size-body: 16px; --page-edge-padding: 28px; @@ -37,7 +51,8 @@ --font-size-intro-heading: 32px; --font-size-intro-body: 16px; --font-size-link: 16px; - --font-size-heading: 28px; + --font-size-heading-h1: 34px; + --font-size-heading-h2: 32px; --font-size-body: 16px; --page-edge-padding: 20px; @@ -48,7 +63,8 @@ --font-size-intro-heading: 24px; --font-size-intro-body: 14px; --font-size-link: 14px; - --font-size-heading: 24px; + --font-size-heading-h1: 28px; + --font-size-heading-h2: 26px; --font-size-body: 16px; --page-edge-padding: 12px; @@ -68,6 +84,12 @@ body { font-weight: 500; font-size: var(--font-size-body); color: var(--color-navy); + tab-size: 4; +} + +* { + min-width: 0; + min-height: 0; } a { @@ -89,34 +111,25 @@ h1.box-header { margin-top: 20px; } - + hr + .diptych, - + hr + .triptych { + + hr + * { margin-top: 40px; } } -h1.hero { - font-size: var(--font-size-intro-heading); - - ~ p { - font-size: var(--font-size-intro-body); - } -} - h1 { - font-family: "Bona Nova", serif; + font-family: "Bona Nova", Palatino, serif; font-feature-settings: "lnum"; line-height: 1.25; font-weight: 700; - font-size: var(--font-size-heading); + font-size: var(--font-size-heading-h1); } h2 { - font-family: "Bona Nova", serif; + font-family: "EB Garamond", Garamond, serif; font-feature-settings: "lnum"; - line-height: 1.25; - font-size: var(--font-size-heading); - font-weight: 400; + line-height: 1.2; + font-weight: 500; + font-size: var(--font-size-heading-h2); } h3 { @@ -139,6 +152,12 @@ p { text-align: justify; } +p ~ img, +p ~ iframe { + width: 100%; + height: auto; +} + p ~ p { margin-top: 1.5em; } @@ -152,15 +171,29 @@ h3 ~ ol li p, h1 ~ img, h2 ~ img, h3 ~ img, -p ~ .informational-group, +h1 + .section-row, +h1 ~ .informational-group, +.video-embed + p, p ~ h1, p ~ h2, p ~ h3, p ~ details summary, -p + .link { +p ~ blockquote, +p ~ video, +p ~ .video-embed, +p ~ .informational-group, +p ~ .image-comparison, +p + .link, +.video-embed + .link, +.video-embed + .button, +img + .link { margin-top: 20px; } +h1 ~ h2 { + margin-top: 40px; +} + h1 ~ hr { margin-top: 40px; margin-bottom: 20px; @@ -176,20 +209,118 @@ li + li { code { background: var(--color-fog); + color: var(--color-black); padding: 0 4px; - word-break: break-all; + overflow-wrap: anywhere; hyphens: none; } pre { - padding: 20px; - background: var(--color-navy); color: var(--color-fog); + // This zero transform sets this element as the root for `position: fixed` + transform: translate(0); + // Color overrides + &, + &.z-code { + background: var(--color-navy); + + .z-path { + color: #679f70; + + span { + color: #e6e1dc; + } + } + } + + // Container for the element (span or table) containing the lines of code code { background: initial; - padding: 0; + color: inherit; + display: block; + overflow-x: auto; + padding: 20px; } + + // Language name in top right corner + &[data-lang] { + padding-top: 28px; + + &::before { + content: attr(data-lang); + color: rgba(var(--color-seaside-rgb), 0.5); + text-transform: lowercase; + font-family: "Inter", sans-serif; + font-size: 0.75em; + font-weight: bold; + font-style: italic; + user-select: none; + pointer-events: none; + position: fixed; + top: 0; + line-height: 28px; + display: block; + width: 100%; + text-indent: 20px; + background: rgba(0, 0, 0, 0.25); + + &[data-lang="sh"] { + content: "Shell"; + } + + &[data-lang="rs"] { + content: "Rust"; + } + + &[data-lang="js"] { + content: "JavaScript"; + } + + &[data-lang="ts"] { + content: "TypeScript"; + } + } + } + + // Code blocks with line numbers + &[data-linenos] table { + border-spacing: 0; + margin: -20px; + + tr { + &:first-child td { + padding-top: 20px; + } + + &:last-child td { + padding-bottom: 20px; + } + + td { + &:first-child { + padding-left: 20px; + padding-right: 10px; + user-select: none; + vertical-align: top; + text-align: right; + background: rgba(0, 0, 0, 0.25); + } + + &:last-child { + padding-left: 10px; + padding-right: 20px; + } + } + } + } +} + +.atlas { + object-fit: cover; + object-position: calc(-48px * var(--atlas-index)) 0; + width: 48px; + height: 48px; } kbd { @@ -205,6 +336,49 @@ summary { cursor: pointer; } +.reading-material.reading-material.reading-material { + max-width: 800px; + flex: 0 1 auto; + + hr { + margin-top: 40px; + margin-bottom: 40px; + } + + article { + margin-top: 20px; + + h1, + h2, + h3 { + font-family: "Inter", sans-serif; + line-height: 1.5; + font-weight: 800; + } + + h1 { + font-size: var(--font-size-article-h1); + } + + h2 { + font-size: var(--font-size-article-h2); + } + + h3 { + font-size: var(--font-size-article-h3); + } + + h1, + h2, + h3, + h4, + h5, + h6 { + display: block; + } + } +} + .video-embed { position: relative; width: 100%; @@ -222,58 +396,287 @@ summary { } } -.atlas { - object-fit: cover; - object-position: calc(-48px * var(--atlas-index)) 0; - width: 48px; - height: 48px; +.image-comparison { + position: relative; + touch-action: pan-y pinch-zoom; + + .crop-container { + height: 100%; + + &:nth-child(2) { + overflow: hidden; + width: calc(100% - var(--comparison-percent)); + + &, + img { + position: absolute; + top: 0; + right: 0; + } + } + + &.crop-container.crop-container img { + display: block; + width: auto; + height: 100%; + } + } + + .slide-bar { + position: absolute; + background: var(--color-navy); + margin-left: -2px; + width: 4px; + height: 100%; + top: 0; + left: var(--comparison-percent); + box-shadow: 0 0 2px rgba(255, 255, 255, 0.5); + + .arrows { + position: absolute; + top: calc(50% - (40px / 2)); + left: calc(4px / 2); + width: 0; + height: 0; + opacity: 1; + transition: opacity 0.25s; + + svg { + position: absolute; + width: 6.5px; + height: 11px; + top: calc(11px / -2); + fill: var(--color-white); + + @keyframes pulse-left { + from { right: 2px; } + to { right: 9px; } + } + + @keyframes pulse-right { + from { left: 1px; } + to { left: 9px; } + } + + @keyframes pulse-opacity { + 0% { opacity: 0; } + 40% { opacity: 1; } + 90% { opacity: 1; } + 100% { opacity: 0; } + } + + &:nth-of-type(1) { + animation: 3s infinite ease-out pulse-left, 3s infinite ease-out pulse-opacity; + right: 6px; + } + + &:nth-of-type(2) { + animation: 3s infinite ease-out pulse-right, 3s infinite ease-out pulse-opacity; + left: 6px; + transform: scaleX(-1); + } + } + + div { + content: ""; + position: absolute; + background: var(--color-navy); + // border-radius: 50%; + top: 0; + left: 0; + width: 32px; + height: 32px; + transform: translate(-50%, -50%) rotate(45deg); + box-shadow: 0 0 2px rgba(255, 255, 255, 0.5); + } + + // Cover up the box-shadow at the top and bottom of the circle so it connects to the vertical line + &::after { + content: ""; + position: absolute; + background: var(--color-navy); + left: -2px; + top: -24px; + width: 4px; + height: 48px; + } + } + } + + &:hover .slide-bar .arrows { + opacity: 0; + } } -.informational-group { - display: flex; - flex-wrap: wrap; - width: 100%; - margin-bottom: 20px; +.carousel { + margin-top: calc(80 * var(--variable-px)); + transform: translate(0); - &.features { - gap: 16px; - - .informational { - padding: 16px; - gap: 16px; - background: rgba(0, 0, 0, 0.0625); - // Half width, minus own padding on both sides, minus half a gap - flex: 1 0 calc(50% - (16px * 2) - (16px / 2)); - - @media screen and (max-width: 1100px) { - // Full width, minus own padding on both sides - flex: 1 0 calc(100% - (16px * 2)); - } - } - } - - &.concepts { - justify-content: space-between; - - .informational { - flex-direction: column; - flex: 0 1 auto; - - img { - width: 72px; - height: 72px; - object-position: calc(-72px * var(--atlas-index)) 0; - margin-bottom: 8px; - } - } - } - - .informational { + .carousel-slide { display: flex; - align-items: center; + white-space: nowrap; + touch-action: pan-y pinch-zoom; + cursor: grab; img { + position: relative; + display: inline-block; flex: 0 0 auto; + padding: 0 20px; + + &:first-child { + margin-left: -20px; + } + + &:last-child { + margin-right: -20px; + } + } + } + + &:not(.dragging) .carousel-slide img { + transition: transform 500ms; + } + + .carousel-slide:not(.torn) { + overflow: hidden; + } + + .carousel-slide.torn { + position: fixed; + top: 0; + z-index: -1; + // Torn edge mask + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-size: contain; + + &.left { + padding-left: 80px; + margin-left: -80px; + -webkit-mask-image: url("https://static.graphite.rs/textures/torn-edge-left.png"); + mask-image: url("https://static.graphite.rs/textures/torn-edge-left.png"); + -webkit-mask-position: top left; + mask-position: top left; + } + + &.right { + padding-right: 120px; + margin-right: -120px; + -webkit-mask-image: url("https://static.graphite.rs/textures/torn-edge-right.png"); + mask-image: url("https://static.graphite.rs/textures/torn-edge-right.png"); + -webkit-mask-position: top right; + mask-position: top right; + } + } + + .screenshot-details { + display: flex; + margin: 20px 0; + gap: 20px 40px; + + @media screen and (max-width: 800px) { + flex-wrap: wrap; + justify-content: center; + } + + .carousel-controls { + display: flex; + align-items: center; + flex: 0 0 auto; + + button { + outline: none; + background: none; + border: none; + padding: 0; + color: inherit; + cursor: pointer; + + svg { + display: block; + } + + + button { + margin-left: 20px; + } + } + + .direction { + fill: currentColor; + } + + .dot { + width: 16px; + height: 16px; + box-sizing: border-box; + border-radius: 50%; + border: var(--border-thickness) solid currentColor; + + &.active { + border: none; + background: var(--color-crimson); + } + } + } + + .screenshot-description { + display: flex; + align-items: center; + min-height: calc(2em * 1.5); + + p + p { + margin: 0; + } + + p:not(.active) { + display: none; + } + } + } + + &.window-size-1 .carousel-slide img { + width: 100%; + } + + &.window-size-2 .carousel-slide img { + width: calc(100% / 2); + } + + &.window-size-3 .carousel-slide img { + width: calc((100% + 2 * 10px) / 3 - (3 - 1) * 10px); + padding: 0 10px; + + &:first-child { + margin-left: -10px; + } + + &:last-child { + margin-right: -10px; + } + } + + @media screen and (max-width: 1000px) { + margin-left: calc(-1 * var(--page-edge-padding)); + margin-right: calc(-1 * var(--page-edge-padding)); + + .screenshot-details { + margin-left: var(--page-edge-padding); + margin-right: var(--page-edge-padding); + } + + hr { + width: calc(100% - (32px + var(--page-edge-padding)) * 2); + margin-left: auto; + margin-right: auto; + } + } + + // 1600px is var(--max-width) + @media screen and (max-width: 1600px) { + .carousel-slide.torn { + display: none; } } } @@ -355,13 +758,41 @@ hr, } } +.graphic { + max-width: 200px; + flex: 1 1 100%; + display: flex; + + img { + display: block; + width: 100%; + object-fit: contain; + + @media screen and (max-width: 800px) { + width: auto; + height: 120px; + } + } +} + +.section { + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + + &.centered { + align-items: center; + } +} + .section-row { display: flex; align-items: stretch; gap: calc(40 * var(--variable-px)) calc(80 * var(--variable-px)); - &.right { - flex-direction: row-reverse; + &.vertical { + flex-direction: column; } @media screen and (max-width: 800px) { @@ -370,30 +801,14 @@ hr, align-items: center; } } +} - .graphic { - max-width: 280px; - flex: 1 1 100%; - display: flex; - - img { - display: block; - width: 100%; - height: auto; - - @media screen and (max-width: 800px) { - width: auto; - height: 120px; - } - } - } - - .section { - display: flex; - flex-direction: column; - align-items: flex-start; - width: 100%; - } +.info-box { + margin-top: calc(40 * var(--variable-px)); + padding: calc(80 * var(--variable-px)); + background-image: url("https://static.graphite.rs/textures/noise.png"); + background-blend-mode: overlay; + background-position: center; } .feature-box { @@ -464,16 +879,110 @@ hr, } } - + div { + + div, + hr + .section-row + & { margin-top: calc(80 * var(--variable-px)); } } +.informational-group { + display: flex; + flex-wrap: wrap; + width: 100%; + margin-bottom: 20px; + + &.features { + gap: 16px; + + .informational { + padding: 16px; + gap: 16px; + background: rgba(0, 0, 0, 0.0625); + // TODO: Look into maybe switching this approach to https://developer.mozilla.org/en-US/docs/Web/CSS/column-count + // Half width, minus own padding on both sides, minus half a gap + flex: 1 0 calc(50% - (16px * 2) - (16px / 2)); + + @media screen and (max-width: 1100px) { + // Quarter width, minus own padding on both sides + flex: 1 0 calc(100% - (16px * 2)); + } + } + + &.four-wide .informational { + flex: 1 0 calc(25% - (16px * 4) - (16px / 4)); + + @media screen and (max-width: 1100px) { + // Half width, minus own padding on both sides, minus half a gap + flex: 1 0 calc(50% - (16px * 2) - (16px / 2)); + } + + @media screen and (max-width: 840px) { + // Quarter width, minus own padding on both sides + flex: 1 0 calc(100% - (16px * 2)); + } + } + } + + &.concepts { + justify-content: space-between; + + .informational { + flex-direction: column; + flex: 0 1 auto; + + img { + width: 72px; + height: 72px; + object-position: calc(-72px * var(--atlas-index)) 0; + margin-bottom: 8px; + } + } + } + + .informational { + display: flex; + align-items: center; + + img { + flex: 0 0 auto; + } + } +} + +blockquote { + padding: 32px 80px; + background: rgba(0, 0, 0, 0.0625); + position: relative; + border-left: 4px solid var(--color-navy); + + &::before, + &::after { + content: "“"; + font-family: "EB Garamond", Garamond, serif; + font-weight: 500; + font-size: 8em; + line-height: 1; + position: absolute; + opacity: 0.25; + } + + + &::before { + top: -8px; + left: 8px; + } + + &::after { + transform: rotate(180deg); + bottom: -8px; + right: 8px; + } +} + // Page content .page { box-sizing: border-box; - overflow: hidden; min-width: 320px; header { @@ -514,14 +1023,14 @@ hr, a { color: inherit; - font-family: "Bona Nova", serif; + font-family: "Bona Nova", Palatino, serif; font-feature-settings: "lnum"; line-height: 1.25; font-weight: 700; text-decoration: none; --height: 60px; --button-padding: 24px; - --nav-font-size: 32px; + --nav-font-size: 28px; font-size: var(--nav-font-size); &.button { @@ -530,10 +1039,6 @@ hr, padding-right: var(--button-padding); line-height: calc(var(--height) - 2 * var(--border-thickness)); font-size: var(--nav-font-size); - - &::after { - content: " »"; - } } img { @@ -543,67 +1048,82 @@ hr, } } + &.left img { + // Don't show the alt text if the image doesn't load + font-size: 0; + } + + @media screen and (max-width: 1200px) { + gap: 30px; + + a { + --height: 50px; + --button-padding: 16px; + --nav-font-size: 24px; + } + } + @media screen and (max-width: 960px) { gap: 30px; a { --height: 50px; --button-padding: 16px; - --nav-font-size: 26px; + --nav-font-size: 20px; } } - @media screen and (max-width: 760px) { + @media screen and (max-width: 880px) { gap: 20px; a { --height: 40px; --button-padding: 12px; - --nav-font-size: 22px; + --nav-font-size: 16px; } } - @media screen and (max-width: 600px) { + @media screen and (max-width: 680px) { gap: 16px; a { --height: 30px; --button-padding: 8px; - --nav-font-size: 18px; + --nav-font-size: 14px; } } - @media screen and (max-width: 500px) { + @media screen and (max-width: 600px) { gap: 12px; a { --height: 24px; --button-padding: 8px; - --nav-font-size: 16px; + --nav-font-size: 13px; } } - @media screen and (max-width: 460px) { + @media screen and (max-width: 540px) { gap: 10px; a { --height: 22px; --button-padding: 6px; - --nav-font-size: 14px; + --nav-font-size: 12px; } } - @media screen and (max-width: 400px) { + @media screen and (max-width: 480px) { gap: 8px; a { --height: 20px; --button-padding: 4px; - --nav-font-size: 12px; + --nav-font-size: 11px; } } - @media screen and (max-width: 360px) { + @media screen and (max-width: 430px) { gap: 6px; a { @@ -646,13 +1166,13 @@ hr, section { max-width: var(--max-width); - margin: auto; + margin: 0 auto; // Puts the content in front of the hexagon decoration position: relative; z-index: 1; ~ section { - margin-top: calc(80 * var(--variable-px)); + margin-top: calc(120 * var(--variable-px)); } p img { @@ -679,6 +1199,7 @@ hr, gap: 40px; padding: 40px; padding-top: 0; + color: var(--color-walnut); nav { display: flex; @@ -686,6 +1207,10 @@ hr, justify-content: center; gap: 8px 40px; + a { + color: var(--color-walnut); + } + @media screen and (max-width: 900px) { max-width: 500px; } @@ -704,3 +1229,46 @@ hr, } } } + +.fundraising { + margin-top: 20px; + width: 100%; + + .fundraising-bar { + width: 100%; + height: 32px; + border-radius: 10000px; + background: var(--color-fog); + overflow: hidden; + + .fundraising-bar-progress { + width: calc(var(--fundraising-percent) - (4px * 2) - (32px - 4px * 2)); + padding-left: calc(32px - 4px * 2); + height: calc(100% - 4px * 2); + margin: 4px; + border-radius: 10000px; + background: linear-gradient(to right, var(--color-navy), var(--color-crimson)); + transition: opacity 1s, width 2s; + } + } + + .goal-metrics { + display: flex; + justify-content: space-between; + font-weight: 800; + margin-top: 8px; + margin-left: 20px; + width: calc(100% - 40px); + + > span { + transition: opacity 1s; + } + } + + &.fundraising.loading { + .goal-metrics > span, + .fundraising-bar .fundraising-bar-progress { + opacity: 0; + } + } +} diff --git a/website/sass/blog.scss b/website/sass/blog.scss index c43dcba7..237efe4f 100644 --- a/website/sass/blog.scss +++ b/website/sass/blog.scss @@ -28,6 +28,7 @@ .link { vertical-align: top; + margin-top: 0; } } } @@ -37,9 +38,8 @@ #articles { display: flex; flex-direction: column; - gap: 80px; - article { + section { display: flex; gap: 20px 80px; @@ -58,9 +58,13 @@ flex-direction: column; gap: 20px; - .headline a { - text-decoration: none; - color: var(--color-navy); + .headline { + margin-top: -0.5em; + + a { + text-decoration: none; + color: var(--color-navy); + } } .publication { diff --git a/website/sass/book.scss b/website/sass/book.scss new file mode 100644 index 00000000..0083474c --- /dev/null +++ b/website/sass/book.scss @@ -0,0 +1,111 @@ +.three-column-layout { + display: flex; + + .reading-material { + width: 800px; + + .prev-next { + display: flex; + justify-content: space-between; + width: 100%; + + a { + display: flex; + align-items: center; + gap: 20px; + + svg { + fill: var(--color-navy); + } + } + } + } + + aside { + position: sticky; + align-self: flex-start; + overflow-y: auto; + top: 0; + width: 300px; + max-height: 100vh; + margin-top: -40px; + + li { + margin-top: 0.5em; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + a { + color: var(--color-walnut); + + &:hover { + color: var(--color-crimson); + } + } + + &:not(.title) a { + text-decoration: none; + } + + &.title, + &.chapter { + font-weight: bold; + } + } + + + &.chapters li.active a { + color: var(--color-ale); + } + + &.contents { + li { + position: relative; + padding-left: 12px; + + &.active::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 4px; + background: var(--color-ale); + } + + a:not(:hover) span { + display: none; + } + } + } + + ul { + list-style: none; + padding: 0; + margin: 0; + margin-top: 40px; + + ul { + margin-top: 0; + text-indent: 1em; + + ul { + text-indent: 2em; + + ul { + text-indent: 3em; + + ul { + text-indent: 4em; + + ul { + text-indent: 5em; + } + } + } + } + } + } + } +} diff --git a/website/sass/donate.scss b/website/sass/donate.scss new file mode 100644 index 00000000..c0decbf4 --- /dev/null +++ b/website/sass/donate.scss @@ -0,0 +1,8 @@ +#fundraising { + background-color: var(--color-seaside); + color: rgba(0, 0, 0, 0.9); + + .graphic { + max-width: 400px; + } +} diff --git a/website/sass/features.scss b/website/sass/features.scss index 2b46fb22..0f2f68a7 100644 --- a/website/sass/features.scss +++ b/website/sass/features.scss @@ -1,8 +1,79 @@ -#upcoming-tech { - background-color: var(--color-navy); - color: var(--color-fog); +#roadmap { + width: 100%; + text-align: center; +} - a { - color: var(--color-mustard); +.roadmap { + margin: auto; + margin-top: 20px; + padding: 0 16px; + + .informational-group { + flex-direction: column; + gap: 0; + width: 100%; + + .informational { + position: relative; + padding-left: calc(16px + 8px); + + &::before { + content: ""; + position: absolute; + left: 0; + width: 4px; + height: 100%; + background: #ddd; + } + + &:first-of-type::before { + bottom: 0; + height: 50%; + } + + &:last-of-type::before { + top: 0; + height: 50%; + } + + &::after { + content: ""; + position: absolute; + left: calc((24px - 4px) / -2); + width: 24px; + height: 24px; + border-radius: 50%; + } + + &:not(.complete, .ongoing)::after { + background: white; + border: 4px solid #ddd; + width: 16px; + height: 16px; + } + + &.complete::after { + background: var(--color-seaside); + background-image: url('data:image/svg+xml,'); + background-repeat: no-repeat; + background-position: center; + } + + &.ongoing::after { + background: var(--color-lemon); + background-image: url('data:image/svg+xml,'); + background-repeat: no-repeat; + background-position: center; + } + + &.heading { + background: none; + + h3 { + width: 100%; + text-align: center; + } + } + } } } diff --git a/website/sass/index.scss b/website/sass/index.scss index e58812b1..7872deb7 100644 --- a/website/sass/index.scss +++ b/website/sass/index.scss @@ -1,3 +1,7 @@ +.page { + overflow: hidden; +} + #logo { display: flex; @@ -23,7 +27,7 @@ } #quick-links { - margin-bottom: calc(120 * var(--variable-px)); + margin-top: calc(80 * var(--variable-px)); display: flex; gap: calc(var(--font-size-link) * 0.8); flex-wrap: wrap; @@ -41,8 +45,14 @@ } #hero-message { - @media screen and (max-width: 1400px) { - p { + h1 { + font-size: var(--font-size-intro-heading); + } + + p { + font-size: var(--font-size-intro-body); + + @media screen and (max-width: 1400px) { max-width: unset !important; } } @@ -80,162 +90,13 @@ } } -#screenshots { - transform: translate(0); - - .carousel { - display: flex; - white-space: nowrap; - touch-action: pan-y pinch-zoom; - cursor: grab; - - img { - position: relative; - display: inline-block; - width: 100%; - flex: 0 0 auto; - padding: 0 20px; - - &:first-child { - margin-left: -20px; - } - - &:last-child { - margin-right: -20px; - } - } - } - - &:not(.dragging) .carousel img { - transition: transform 500ms; - } - - .carousel:not(.torn) { - overflow: hidden; - } - - .carousel.torn { - position: fixed; - top: 0; - z-index: -1; - // Torn edge mask - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-size: contain; - mask-size: contain; - - &.left { - padding-left: 80px; - margin-left: -80px; - -webkit-mask-image: url("https://static.graphite.rs/textures/torn-edge-left.png"); - mask-image: url("https://static.graphite.rs/textures/torn-edge-left.png"); - -webkit-mask-position: top left; - mask-position: top left; - } - - &.right { - padding-right: 120px; - margin-right: -120px; - -webkit-mask-image: url("https://static.graphite.rs/textures/torn-edge-right.png"); - mask-image: url("https://static.graphite.rs/textures/torn-edge-right.png"); - -webkit-mask-position: top right; - mask-position: top right; - } - } - - .screenshot-details { - display: flex; - margin: 20px 0; - gap: 20px 40px; - - @media screen and (max-width: 800px) { - flex-wrap: wrap; - justify-content: center; - } - - .carousel-controls { - display: flex; - align-items: center; - - button { - outline: none; - background: none; - border: none; - padding: 0; - color: inherit; - cursor: pointer; - - svg { - display: block; - } - - + button { - margin-left: 20px; - } - } - - .direction { - fill: currentColor; - } - - .dot { - width: 16px; - height: 16px; - box-sizing: border-box; - border-radius: 50%; - border: var(--border-thickness) solid currentColor; - - &.active { - border: none; - background: var(--color-crimson); - } - } - } - - .screenshot-description { - display: flex; - align-items: center; - min-height: calc(2em * 1.5); - - p + p { - margin: 0; - } - - p:not(.active) { - display: none; - } - } - } - - @media screen and (max-width: 1000px) { - margin-left: calc(-1 * var(--page-edge-padding)); - margin-right: calc(-1 * var(--page-edge-padding)); - - .screenshot-details { - margin-left: var(--page-edge-padding); - margin-right: var(--page-edge-padding); - } - - hr { - width: calc(100% - (32px + var(--page-edge-padding)) * 2); - margin-left: auto; - margin-right: auto; - } - } - - // 1600px is var(--max-width) - @media screen and (max-width: 1600px) { - .carousel.torn { - display: none; - } - } -} - -#demo-video { - max-width: 1000px; +#graphite-tomorrow > img { + margin: 16px 0; } #disciplines { + align-items: center; + .informational-group { margin-top: 0; @@ -245,7 +106,145 @@ } } +// #node-graph { +// #node-graph-intro { +// .section { +// align-items: center; +// } + +// ~ section { +// margin-top: calc(80 * var(--variable-px)); +// } +// } +// } + +#vector-art { + .section { + align-items: center; + + .background-video { + position: relative; + font-size: 0; + + video { + max-width: Min(100%, 1280px); + } + + // Uses a white border over the video to cover up the edges of the video which, due to a Chrome rendering bug, displays black edges sometimes when scrolling + &::after { + content: ""; + position: absolute; + inset: 0; + border: 2px solid white; + pointer-events: none; + } + } + + .download-artwork { + display: flex; + align-items: center; + + img { + width: 128px; + height: 128px; + border: 12px solid var(--color-walnut); + vertical-align: top; + } + + span { + margin-left: 40px; + } + } + } +} + +#imaginate { + #imaginate-intro { + .alternating-text { + position: relative; + + span { + // Move left by half (since it's centered) the average (half the 74px difference) of the variance in word lengths + margin-left: calc(-1.54em / 2 / 2); + opacity: 0; + $alternate-duration: 15s; + $alternate-words: 5; + animation: $alternate-duration infinite linear 0s fade-word; + + // The 1st child is the widest + &:not(:nth-child(1)) { + position: absolute; + right: 0; + } + + @for $i from 1 through $alternate-words { + &:nth-child(#{$i}) { + animation-delay: ($alternate-duration / $alternate-words * ($i - 1)); + } + } + + @keyframes fade-word { + // Fade in at the start (which begins staggered on each item by `animation-delay`) + #{0.0%} { opacity: 0; } + #{2.5%} { opacity: 1; } + // Remain visible for this item's slice of time, then fade out + #{0.0% + 100% / ($alternate-words + 1)} { opacity: 1; } + #{2.5% + 100% / ($alternate-words + 1)} { opacity: 0; } + } + } + } + + .section { + align-items: center; + } + + ~ section { + margin-top: calc(80 * var(--variable-px)); + } + } + + .image-comparison { + aspect-ratio: 1; + max-width: Min(100%, 512px); + } + + // #imaginate-creative-concepts-carousel { + // margin-top: 20px; + + // .screenshot-details { + // justify-content: center; + // } + + // + blockquote { + // margin-top: 0; + // } + // } +} + +.feature-explainer { + margin-top: 40px; + + .diptych { + width: 100%; + + .section { + justify-content: center; + align-items: center; + + h2 { + text-align: center; + } + } + } + + .section { + flex: 1 1 calc(50% - (80 * var(--variable-px)) / 2); + } +} + #community { + background-color: var(--color-lime); + #newsletter { #newsletter-success { background: var(--color-crimson); @@ -276,6 +275,10 @@ gap: 20px; flex: 100000 1 0; + div { + min-height: auto; + } + @media screen and (max-width: 1200px) { flex-direction: column; flex: 1 1 100%; @@ -291,6 +294,7 @@ display: flex; flex-direction: column; justify-content: flex-end; + --input-focus-color: var(--color-ale); &.name { flex: 1 0 0; @@ -314,7 +318,7 @@ } &.submit { - flex: 1 0 0; + flex: 1 0 auto; } label, @@ -344,7 +348,7 @@ box-sizing: border-box; &:focus { - border-color: var(--color-mustard); + border-color: var(--input-focus-color); } } @@ -354,8 +358,8 @@ cursor: pointer; &:focus { - border-color: var(--color-mustard); - color: var(--color-mustard); + border-color: var(--input-focus-color); + color: var(--input-focus-color); } } } @@ -384,6 +388,7 @@ span { line-height: 48px; + margin: 0; margin-left: 20px; } } @@ -391,40 +396,20 @@ } } -#upcoming-tech { - background-color: var(--color-navy); - color: var(--color-fog); +#demo-video { + max-width: 1000px; +} - a { - color: var(--color-mustard); +#fundraising { + background-color: var(--color-seaside); + color: rgba(0, 0, 0, 0.9); + + .graphic { + max-width: 400px; } } - -#recent-news { - background-color: var(--color-mustard); - color: var(--color-navy); - - article { - display: flex; - flex-direction: column; - gap: 20px; - - .headline a { - text-decoration: none; - color: var(--color-navy); - } - - .publication { - font-weight: 800; - } - - .summary { - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 8; - } - } +#get-involved-box { + background-color: var(--color-lemon); + background-blend-mode: color-burn; } diff --git a/website/sass/volunteer.scss b/website/sass/volunteer.scss new file mode 100644 index 00000000..5be39929 --- /dev/null +++ b/website/sass/volunteer.scss @@ -0,0 +1,19 @@ +.creative-contributions .info-box { + &:nth-of-type(1) { + background-color: var(--color-seaside); + } + + &:nth-of-type(2) { + background-color: var(--color-cove); + } +} + +.code-contributions .info-box { + &:nth-of-type(1) { + background-color: var(--color-mustard); + } + + &:nth-of-type(2) { + background-color: var(--color-ale); + } +} diff --git a/website/static/js/book.js b/website/static/js/book.js new file mode 100644 index 00000000..b637de6d --- /dev/null +++ b/website/static/js/book.js @@ -0,0 +1,52 @@ +addEventListener("DOMContentLoaded", trackScrollHeadingInTOC); + +// Listen for scroll events and update the active section in the table of contents to match the visible content's heading +function trackScrollHeadingInTOC() { + const updateVisibleHeading = () => { + const content = Array.from(document.querySelectorAll("article > *")); + + // Find the first element in `content` that is visible in the top of the viewport + let firstVisible = content.find((element) => element.getBoundingClientRect().bottom >= 0); + + // Find the next heading + let heading = firstVisible; + while (heading && !heading.tagName.match(/^H[1-6]$/)) { + if (!heading.nextElementSibling) break; + heading = heading.nextElementSibling; + } + + // If the next heading isn't fully visible, use the previous heading + if (heading && heading.getBoundingClientRect().bottom > window.innerHeight) { + prevHeading = firstVisible; + while (prevHeading && !prevHeading.tagName.match(/^H[1-6]$/)) { + if (!prevHeading.previousElementSibling) break; + prevHeading = prevHeading.previousElementSibling; + } + + if (prevHeading && prevHeading.tagName.match(/^H[1-6]$/)) heading = prevHeading; + } + + // If the headding isn't an h1-h6, use the last heading + if (!heading || !heading.tagName.match(/^H[1-6]$/)) { + const filtered = content.filter((element) => element.tagName.match(/^H[1-6]$/)); + heading = filtered[filtered.length - 1]; + } + + // If there is no heading, use the first heading + if (!heading) heading = document.querySelector("article > h1"); + + // Remove the existing active heading + const existingActive = document.querySelector("aside.contents li.active"); + existingActive?.classList.remove("active"); + + // Exit if there are no headings + if (!heading) return; + + // Set the new active heading + const tocHeading = document.querySelector(`aside.contents a[href="#${heading.id}"]`)?.parentElement; + if (tocHeading instanceof HTMLElement) tocHeading.classList.add("active"); + }; + + addEventListener("scroll", updateVisibleHeading); + updateVisibleHeading(); +} diff --git a/website/static/js/carousel.js b/website/static/js/carousel.js deleted file mode 100644 index a305d243..00000000 --- a/website/static/js/carousel.js +++ /dev/null @@ -1,164 +0,0 @@ -const FLING_VELOCITY_THRESHOLD = 10; -const FLING_VELOCITY_WINDOW_SIZE = 20; - -let carouselImages; -let carouselDirectionPrev; -let carouselDirectionNext; -let carouselDots; -let carouselDescriptions; -let carouselDragLastClientX; -const velocityDeltaWindow = Array.from({ length: FLING_VELOCITY_WINDOW_SIZE }, () => ({ time: 0, delta: 0 })); - -window.addEventListener("DOMContentLoaded", initializeCarousel); -window.addEventListener("pointerup", () => dragEnd(false)); -window.addEventListener("scroll", () => dragEnd(true)); -window.addEventListener("pointermove", dragMove); - -function initializeCarousel() { - carouselImages = document.querySelectorAll(".carousel img"); - carouselImages.forEach((image) => { - image.addEventListener("pointerdown", dragBegin); - }); - - carouselDirectionPrev = document.querySelector(".carousel-controls .direction.prev"); - carouselDirectionNext = document.querySelector(".carousel-controls .direction.next"); - carouselDots = document.querySelectorAll(".carousel-controls .dot"); - carouselDescriptions = document.querySelectorAll(".screenshot-description p"); - - carouselDirectionPrev.addEventListener("click", () => slideDirection("prev", true, false)); - carouselDirectionNext.addEventListener("click", () => slideDirection("next", true, false)); - Array.from(carouselDots).forEach((dot) => - dot.addEventListener("click", (event) => { - const index = Array.from(carouselDots).indexOf(event.target); - slideTo(index, true); - }) - ); -} - -function slideDirection(direction, smooth, clamped = false) { - const directionIndexOffset = { prev: -1, next: 1 }[direction]; - const offsetDotIndex = currentClosestImageIndex() + directionIndexOffset; - - const nextDotIndex = (offsetDotIndex + carouselDots.length) % carouselDots.length; - const unwrappedNextDotIndex = clamp(offsetDotIndex, 0, carouselDots.length - 1); - - if (clamped) slideTo(unwrappedNextDotIndex, smooth); - else slideTo(nextDotIndex, smooth); -} - -function slideTo(index, smooth) { - const activeDot = document.querySelector(".carousel-controls .dot.active"); - activeDot.classList.remove("active"); - carouselDots[index].classList.add("active"); - - const activeDescription = document.querySelector(".screenshot-description p.active"); - activeDescription.classList.remove("active"); - carouselDescriptions[index].classList.add("active"); - - setCurrentTransform(index * -100, "%", smooth); -} - -function currentTransform() { - const currentTransformMatrix = window.getComputedStyle(carouselImages[0]).transform; - // Grab the X value from the format that looks like: `matrix(1, 0, 0, 1, -1332.13, 0)` or `none` - return Number(currentTransformMatrix.split(",")[4] || "0"); -} - -function setCurrentTransform(x, unit, smooth) { - Array.from(carouselImages).forEach((image) => { - image.style.transitionTimingFunction = smooth ? "ease-in-out" : "cubic-bezier(0, 0, 0.2, 1)"; - image.style.transform = `translateX(${x}${unit})`; - }); -} - -function currentClosestImageIndex() { - const currentTransformX = -currentTransform(); - - const imageWidth = carouselImages[0].getBoundingClientRect().width; - return Math.round(currentTransformX / imageWidth); -} - -function currentActiveDotIndex() { - const activeDot = document.querySelector(".carousel-controls .dot.active"); - return Array.from(carouselDots).indexOf(activeDot); -} - -function dragBegin(event) { - event.preventDefault(); - - carouselDragLastClientX = event.clientX; - - setCurrentTransform(currentTransform(), "px", false); - document.querySelector("#screenshots").classList.add("dragging"); -} - -function dragEnd(dropWithoutVelocity) { - if (!carouselImages) return; - - carouselDragLastClientX = undefined; - - document.querySelector("#screenshots").classList.remove("dragging"); - - const onlyRecentVelocityDeltaWindow = velocityDeltaWindow.filter((delta) => delta.time > Date.now() - 1000); - const timeRange = Date.now() - (onlyRecentVelocityDeltaWindow[0]?.time ?? NaN); - // Weighted (higher by recency) sum of velocity deltas from previous window of frames - const recentVelocity = onlyRecentVelocityDeltaWindow.reduce((acc, entry) => { - const timeSinceNow = Date.now() - entry.time; - const recencyFactorScore = 1 - timeSinceNow / timeRange; - - return acc + entry.delta * recencyFactorScore; - }, 0); - - const closestImageIndex = currentClosestImageIndex(); - const activeDotIndex = currentActiveDotIndex(); - - // If the speed is fast enough, slide to the next or previous image in that direction - if (Math.abs(recentVelocity) > FLING_VELOCITY_THRESHOLD && !dropWithoutVelocity) { - // Positive velocity should go to the previous image - if (recentVelocity > 0) { - // Don't apply the velocity-based fling if we're already snapping to the next image - if (closestImageIndex >= activeDotIndex) { - slideDirection("prev", false, true); - return; - } - } - // Negative velocity should go to the next image - else { - // Don't apply the velocity-based fling if we're already snapping to the next image - // eslint-disable-next-line no-lonely-if - if (closestImageIndex <= activeDotIndex) { - slideDirection("next", false, true); - return; - } - } - } - - // If we didn't slide in a direction due to clear velocity, just snap to the closest image - // This can be reached either by not entering the if statement above, or by its inner if statements not returning early and exiting back to this scope - slideTo(clamp(closestImageIndex, 0, carouselDots.length - 1), true); -} - -function dragMove(event) { - if (carouselDragLastClientX === undefined) return; - - event.preventDefault(); - - const LEFT_MOUSE_BUTTON = 1; - if (!(event.buttons & LEFT_MOUSE_BUTTON)) { - dragEnd(false); - return; - } - - const deltaX = event.clientX - carouselDragLastClientX; - velocityDeltaWindow.shift(); - velocityDeltaWindow.push({ time: Date.now(), delta: deltaX }); - - const newTransformX = currentTransform() + deltaX; - setCurrentTransform(newTransformX, "px", false); - - carouselDragLastClientX = event.clientX; -} - -function clamp(value, min, max) { - return Math.min(Math.max(value, min), max); -} diff --git a/website/static/js/fundraising.js b/website/static/js/fundraising.js new file mode 100644 index 00000000..fdbf8224 --- /dev/null +++ b/website/static/js/fundraising.js @@ -0,0 +1,32 @@ +window.addEventListener("DOMContentLoaded", initializeFundraisingBar); + +function initializeFundraisingBar() { + const VISIBILITY_COVERAGE_FRACTION = 0.5; + + let loaded = false; + + const fundraising = document.querySelector("[data-fundraising]"); + const bar = fundraising.querySelector("[data-fundraising-bar]"); + const dynamicPercent = fundraising.querySelector("[data-fundraising-percent] [data-dynamic]") + const dynamicGoal = fundraising.querySelector("[data-fundraising-goal] [data-dynamic]") + if (!(fundraising instanceof HTMLElement && bar instanceof HTMLElement && dynamicPercent instanceof HTMLElement && dynamicGoal instanceof HTMLElement)) return; + + const setFundraisingGoal = async () => { + const request = await fetch("https://graphite.rs/fundraising-goal"); + /** @type {{ percentComplete: number, targetValue: number }} */ + const data = await request.json(); + + fundraising.classList.remove("loading"); + bar.style.setProperty("--fundraising-percent", `${data.percentComplete}%`); + dynamicPercent.textContent = data.percentComplete; + dynamicGoal.textContent = data.targetValue; + + loaded = true; + }; + new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (!loaded && entry.intersectionRatio > VISIBILITY_COVERAGE_FRACTION) setFundraisingGoal(); + }); + }, { threshold: VISIBILITY_COVERAGE_FRACTION }) + .observe(fundraising); +} diff --git a/website/static/js/image-interaction.js b/website/static/js/image-interaction.js new file mode 100644 index 00000000..3cdc1e72 --- /dev/null +++ b/website/static/js/image-interaction.js @@ -0,0 +1,279 @@ +// ================================================================================================ +// CAROUSEL +// ================================================================================================ + +const FLING_VELOCITY_THRESHOLD = 10; +const FLING_VELOCITY_WINDOW_SIZE = 20; + +window.addEventListener("DOMContentLoaded", initializeCarousel); +window.addEventListener("pointerup", () => dragEnd(false)); +window.addEventListener("scroll", () => dragEnd(true)); +window.addEventListener("pointermove", dragMove); + +const carousels = []; + +function initializeCarousel() { + const carouselContainers = document.querySelectorAll("[data-carousel]"); + + carouselContainers.forEach((carouselContainer) => { + const images = carouselContainer.querySelectorAll("[data-carousel-image]"); + const directionPrev = carouselContainer.querySelector("[data-carousel-prev]"); + const directionNext = carouselContainer.querySelector("[data-carousel-next]"); + const dots = carouselContainer.querySelectorAll("[data-carousel-dot]"); + const descriptions = carouselContainer.querySelectorAll("[data-carousel-description]"); + const dragLastClientX = undefined; + const velocityDeltaWindow = Array.from({ length: FLING_VELOCITY_WINDOW_SIZE }, () => ({ time: 0, delta: 0 })); + + const carousel = { + carouselContainer, + images, + directionPrev, + directionNext, + dots, + descriptions, + dragLastClientX, + velocityDeltaWindow, + }; + carousels.push(carousel); + + images.forEach((image) => { + image.addEventListener("pointerdown", dragBegin); + }); + directionPrev.addEventListener("click", () => slideDirection(carousel, "prev", true, false)); + directionNext.addEventListener("click", () => slideDirection(carousel, "next", true, false)); + Array.from(dots).forEach((dot) => + dot.addEventListener("click", (event) => { + const index = Array.from(dots).indexOf(event.target); + slideTo(carousel, index, true); + }) + ); + }); +} + +function slideDirection(carousel, direction, smooth, clamped = false) { + const directionIndexOffset = { prev: -1, next: 1 }[direction]; + const offsetDotIndex = currentClosestImageIndex(carousel) + directionIndexOffset; + + const nextDotIndex = (offsetDotIndex + carousel.dots.length) % carousel.dots.length; + const unwrappedNextDotIndex = clamp(offsetDotIndex, 0, carousel.dots.length - 1); + + if (clamped) slideTo(carousel, unwrappedNextDotIndex, smooth); + else slideTo(carousel, nextDotIndex, smooth); +} + +function slideTo(carousel, index, smooth) { + const activeDot = carousel.carouselContainer.querySelector("[data-carousel-dot].active"); + activeDot.classList.remove("active"); + carousel.dots[index].classList.add("active"); + + const activeDescription = carousel.carouselContainer.querySelector("[data-carousel-description].active"); + if (activeDescription) { + activeDescription.classList.remove("active"); + carousel.descriptions[index].classList.add("active"); + } + + setCurrentTransform(carousel, index * -100, "%", smooth); +} + +function currentTransform(carousel) { + const currentTransformMatrix = window.getComputedStyle(carousel.images[0]).transform; + // Grab the X value from the format that looks like: `matrix(1, 0, 0, 1, -1332.13, 0)` or `none` + return Number(currentTransformMatrix.split(",")[4] || "0"); +} + +function setCurrentTransform(carousel, x, unit, smooth) { + Array.from(carousel.images).forEach((image) => { + image.style.transitionTimingFunction = smooth ? "ease-in-out" : "cubic-bezier(0, 0, 0.2, 1)"; + image.style.transform = `translateX(${x}${unit})`; + }); +} + +function currentClosestImageIndex(carousel) { + const currentTransformX = -currentTransform(carousel); + + const imageWidth = carousel.images[0].getBoundingClientRect().width; + return Math.round(currentTransformX / imageWidth); +} + +function currentActiveDotIndex(carousel) { + const activeDot = carousel.carouselContainer.querySelector("[data-carousel-dot].active"); + return Array.from(carousel.dots).indexOf(activeDot); +} + +function dragBegin(event) { + const carouselContainer = event.target.closest("[data-carousel]"); + const carousel = carousels.find((carousel) => carousel.carouselContainer === carouselContainer); + if (!carousel) return; + + event.preventDefault(); + + carousel.dragLastClientX = event.clientX; + + setCurrentTransform(carousel, currentTransform(carousel), "px", false); + carouselContainer.classList.add("dragging"); +} + +function dragEnd(dropWithoutVelocity) { + const carousel = carousels.find((carousel) => carousel.dragLastClientX !== undefined); + if (!carousel) return; + + if (!carousel.images) return; + + carousel.dragLastClientX = undefined; + + carousel.carouselContainer.classList.remove("dragging"); + + const onlyRecentVelocityDeltaWindow = carousel.velocityDeltaWindow.filter((delta) => delta.time > Date.now() - 1000); + const timeRange = Date.now() - (onlyRecentVelocityDeltaWindow[0]?.time ?? NaN); + // Weighted (higher by recency) sum of velocity deltas from previous window of frames + const recentVelocity = onlyRecentVelocityDeltaWindow.reduce((acc, entry) => { + const timeSinceNow = Date.now() - entry.time; + const recencyFactorScore = 1 - timeSinceNow / timeRange; + + return acc + entry.delta * recencyFactorScore; + }, 0); + + const closestImageIndex = currentClosestImageIndex(carousel); + const activeDotIndex = currentActiveDotIndex(carousel); + + // If the speed is fast enough, slide to the next or previous image in that direction + if (Math.abs(recentVelocity) > FLING_VELOCITY_THRESHOLD && !dropWithoutVelocity) { + // Positive velocity should go to the previous image + if (recentVelocity > 0) { + // Don't apply the velocity-based fling if we're already snapping to the next image + if (closestImageIndex >= activeDotIndex) { + slideDirection(carousel, "prev", false, true); + return; + } + } + // Negative velocity should go to the next image + else { + // Don't apply the velocity-based fling if we're already snapping to the next image + // eslint-disable-next-line no-lonely-if + if (closestImageIndex <= activeDotIndex) { + slideDirection(carousel, "next", false, true); + return; + } + } + } + + // If we didn't slide in a direction due to clear velocity, just snap to the closest image + // This can be reached either by not entering the if statement above, or by its inner if statements not returning early and exiting back to this scope + slideTo(carousel, clamp(closestImageIndex, 0, carousel.dots.length - 1), true); +} + +function dragMove(event) { + const carouselContainer = event.target.closest("[data-carousel]"); + const carousel = carousels.find((carousel) => carousel.carouselContainer === carouselContainer); + if (!carousel) return; + + if (carousel.dragLastClientX === undefined) return; + + event.preventDefault(); + + const LEFT_MOUSE_BUTTON = 1; + if (!(event.buttons & LEFT_MOUSE_BUTTON)) { + dragEnd(false); + return; + } + + const deltaX = event.clientX - carousel.dragLastClientX; + carousel.velocityDeltaWindow.shift(); + carousel.velocityDeltaWindow.push({ time: Date.now(), delta: deltaX }); + + const newTransformX = currentTransform(carousel) + deltaX; + setCurrentTransform(carousel, newTransformX, "px", false); + + carousel.dragLastClientX = event.clientX; +} + +function clamp(value, min, max) { + return Math.min(Math.max(value, min), max); +} + +// ================================================================================================ +// IMAGE COMPARISON +// ================================================================================================ + +const RECENTER_DELAY = 1; +const RECENTER_ANIMATION_DURATION = 0.25; + +window.addEventListener("DOMContentLoaded", initializeImageComparison); + +function initializeImageComparison() { + Array.from(document.querySelectorAll("[data-image-comparison]")).forEach((element) => { + const moveHandler = (event) => { + const factor = (event.clientX - element.getBoundingClientRect().left) / element.getBoundingClientRect().width; + const capped = Math.max(0, Math.min(1, factor)); + + if (!(element instanceof HTMLElement)) return; + element.style.setProperty("--comparison-percent", `${capped * 100}%`); + element.dataset.lastInteraction = ""; + }; + + const leaveHandler = (event) => { + moveHandler(event); + + const randomCode = Math.random().toString().substring(2); + element.dataset.lastInteraction = randomCode; + + setTimeout(() => { + if (element.dataset.lastInteraction === randomCode) { + element.dataset.recenterStartTime = Date.now(); + element.dataset.recenterStartValue = parseFloat(element.style.getPropertyValue("--comparison-percent")); + + recenterAnimationStep(); + } + }, RECENTER_DELAY * 1000); + }; + + const recenterAnimationStep = () => { + if (element.dataset.lastInteraction === "") return; + + const completionFactor = (Date.now() - element.dataset.recenterStartTime) / (RECENTER_ANIMATION_DURATION * 1000); + if (completionFactor > 1) { + element.dataset.lastInteraction = ""; + return; + } + + const factor = smootherstep(completionFactor); + const newLocation = lerp(element.dataset.recenterStartValue, 50, factor); + element.style.setProperty("--comparison-percent", `${newLocation}%`); + + requestAnimationFrame(recenterAnimationStep); + }; + + const lerp = (a, b, t) => (1 - t) * a + t * b; + const smootherstep = (x) => x * x * x * (x * (x * 6 - 15) + 10); + + element.addEventListener("pointermove", moveHandler); + element.addEventListener("pointerenter", moveHandler); + element.addEventListener("pointerleave", leaveHandler); + element.addEventListener("dragstart", (event) => event.preventDefault()); + }); +} + +// ================================================================================================ +// AUTO-PLAYING VIDEO +// ================================================================================================ +window.addEventListener("DOMContentLoaded", initializeVideoAutoPlay); + +function initializeVideoAutoPlay() { + const VISIBILITY_COVERAGE_FRACTION = 0.25; + + let loaded = false; + + const player = document.querySelector("[data-auto-play]"); + if (!(player instanceof HTMLVideoElement)) return; + + new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (!loaded && entry.intersectionRatio > VISIBILITY_COVERAGE_FRACTION) { + player.play(); + + loaded = true; + }; + }); + }, { threshold: VISIBILITY_COVERAGE_FRACTION }) + .observe(player); +} diff --git a/website/static/js/navbar.js b/website/static/js/navbar.js index 463c0667..71a7ab08 100644 --- a/website/static/js/navbar.js +++ b/website/static/js/navbar.js @@ -1,6 +1,6 @@ const NAV_BUTTON_INITIAL_FONT_SIZE = 32; const RIPPLE_ANIMATION_MILLISECONDS = 100; -const RIPPLE_WIDTH = 140; +const RIPPLE_WIDTH = 120; const HANDLE_STRETCH = 0.4; let ripplesInitialized; @@ -29,7 +29,18 @@ function initializeRipples() { goingUp: false, })); - activeRippleIndex = ripples.findIndex((ripple) => ripple.element.getAttribute("href").replace(/\//g, "") === window.location.pathname.replace(/\//g, "")); + activeRippleIndex = ripples.findIndex((ripple) => { + let link = ripple.element.getAttribute("href"); + if (!link.endsWith("/")) link += "/"; + let location = window.location.pathname; + if (!location.endsWith("/")) location += "/"; + + // Special case for the root, which will otherwise match as the starting prefix of all pages + if (link === "/" && location === "/") return true; + if (link === "/") return false; + + return location.startsWith(link); + }); ripples.forEach((ripple) => { const updateTimings = (goingUp) => { @@ -51,7 +62,7 @@ function initializeRipples() { ripple.element.addEventListener("pointerleave", () => updateTimings(false)); }); - ripples[activeRippleIndex] = { + if (activeRippleIndex >= 0) ripples[activeRippleIndex] = { ...ripples[activeRippleIndex], animationStartTime: 1, animationEndTime: 1 + RIPPLE_ANIMATION_MILLISECONDS, diff --git a/website/templates/404.html b/website/templates/404.html index f3bb5211..a0a47944 100644 --- a/website/templates/404.html +++ b/website/templates/404.html @@ -1,12 +1,14 @@ {% extends "base.html" %} + {% block title %}Page not found{% endblock title %} {% block content %}
-

Page not found.

+

Page not found

+

Or as the machines like to say it: 404.


- Home Page + Home Page
{% endblock content %} diff --git a/website/templates/article.html b/website/templates/article.html index 55989b26..1d7419b9 100644 --- a/website/templates/article.html +++ b/website/templates/article.html @@ -1,36 +1,40 @@ {% extends "base.html" %} -{% block title %}Blog | {{ page.title }}{% endblock title %} + +{% block title %}Blog | {% set this = section | default(value = page) %}{{ this.title }}{% endblock title %} {% block head %} +{% set this = section | default(value = page) %} - + - + {% endblock head %} {% block content %} -
+{% set this = section | default(value = page) %} + +
-

{{ page.title }}.

- By {{ page.extra.author }}. {{ page.date | date(format="%B %d, %Y", timezone="America/Los_Angeles") }}. - +

{{ this.title }}

+ By {{ this.extra.author }}. {{ this.date | date(format = "%B %d, %Y", timezone="America/Los_Angeles") }}. +

- {{ page.content | safe }} + {{ this.content | safe }}
- {% if page.extra.reddit or page.extra.twitter %} + {% if this.extra.reddit or this.extra.twitter %}
@@ -63,20 +76,19 @@
- {% block content %} {% endblock %} + {% block content %}{% endblock %}
diff --git a/website/templates/blog.html b/website/templates/blog.html index 105a50c8..f473786c 100644 --- a/website/templates/blog.html +++ b/website/templates/blog.html @@ -1,4 +1,5 @@ {% extends "base.html" %} + {% block title %}{{ section.title }}{% endblock title %} {% block head %} @@ -10,25 +11,25 @@
{% for page in section.pages %} -
+
- By {{ page.extra.author }}. {{ page.date | date(format="%B %d, %Y", timezone="America/Los_Angeles") }}. + By {{ page.extra.author }}. {{ page.date | date(format = "%B %d, %Y", timezone = "America/Los_Angeles") }}.
{{ page.summary | safe }}
-
+
{% endfor %}
{% endblock content %} diff --git a/website/templates/book.html b/website/templates/book.html new file mode 100644 index 00000000..a800aca2 --- /dev/null +++ b/website/templates/book.html @@ -0,0 +1,150 @@ +{% extends "base.html" %} + +{% block title %}{% set this = section | default(value = page) %}{{ this.title }}{% endblock title %} + +{% block head %} + + +{% endblock head %} + +{% block content %} +{% set this = section | default(value = page) %} + +{# Search this page-or-section's ancestor tree for a section that identifies itself as a book, and save it to a `book` variable #} +{% for ancestor_path in this.ancestors | concat(with = this.relative_path) %} + {# Get the ancestor section from this ancestor path string #} + {% if ancestor_path is ending_with("/_index.md") %} + {% set potential_book = get_section(path = ancestor_path) %} + {% endif %} + + {# Check if the ancestor section is the root of a book, and if so, set it to a variable accessible outside the loop #} + {% if potential_book.extra.book %} + {% set_global book = get_section(path = potential_book.path ~ "_index.md" | trim_start_matches(pat="/")) %} + {% endif %} +{% endfor %} + +{# Map this book's chapter path strings to an array of sections #} +{% set chapters = [] %} +{% for chapter_path in book.subsections %} + {% set_global chapters = chapters | concat(with = get_section(path = chapter_path)) %} +{% endfor %} +{% set chapters = chapters | sort(attribute = "extra.order") %} + +{# A flat list of all pages in the ToC, initialized to just the book root section but updated when we generate the ToC #} +{% set flat_pages = [book] %} +{% set flat_index_of_this = 0 %} + +
+ + +
+
+

{{ this.title }}

+
+ {{ this.content | safe }} +
+ +
+ +
+ {% if flat_index_of_this >= 1 %} + {% set prev = flat_pages | nth(n = flat_index_of_this - 1) %} + {% endif %} + {% if prev %} + + + + + + {{ prev.title }} + + {% else %} + + {% endif %} + + {% if flat_index_of_this < flat_pages | length - 1 %} + {% set next = flat_pages | nth(n = flat_index_of_this + 1) %} + {% endif %} + {% if next %} + + {{ next.title }} + + + + + + {% endif %} +
+
+
+ + +
+{% endblock content %} diff --git a/website/templates/index.html b/website/templates/index.html deleted file mode 100644 index 4f8ffe42..00000000 --- a/website/templates/index.html +++ /dev/null @@ -1,340 +0,0 @@ -{% extends "base.html" %} -{% block title %}Redefining state-of-the-art graphics editing{% endblock title %} - -{% block head %} - -{% endblock head %} - -{% block content %} - - - - - - -
-

Redefining state-of-the-art graphics editing.

-

Graphite is an in-development raster and vector 2D graphics editor that is free and open source. It is powered by a node graph compositing engine that supercharges your - layer stack, providing a completely non-destructive editing experience.

-
- -
-
- - - - - - - -
-
- -
- - - -
- -
-

- "Valley of Spires" vector artwork made in Graphite, a web-based vector graphics editor. -

- -

- Near-future node graph mockup demonstrating how the the layer panel directly corresponds to the node graph. Thick vertical (upward) lines represent compositing stacks between layers and horizontal (rightward) links represent data flow connections between nodes. This workflow is coming soon. -

-
-
-
-
- -
-
-

A 2D creative tool made for everyone.

-

- With great power comes great accessibility. Graphite is built on the belief that the best creative tools can be powerful and within reach of all, from students to studios. -

-

- Graphite is designed with a friendly and intuitive interface where a delightful user experience is of first-class importance. It is available for free under an open source - license - and usable - instantly through a web browser - or an upcoming native client on Windows, Mac, and Linux. -

-

- It's easy to learn and teach, yet Graphite's accessible design does not sacrifice versatility for simplicity. The node-based workflow opens doors to an ecosystem of powerful capabilities catering to - casual and professional users alike. -

- -
-
- -
-
-
- -
- -
-
- -
-
-

One app to rule them all.

-

Stop jumping between programs. Planned developments will make Graphite a first-class design tool for these disciplines (listed by priority):

-
-
- - Graphic Design -
-
- - Image Editing -
-
- - Digital Painting -
-
- - Desktop Publishing -
-
- - VFX Compositing -
-
- - Motion Graphics -
-
-
-
- -
-
-
-

Stay in the loop.

-

- Subscribe to the newsletter for quarterly updates on major development progress. -

-
-

Thanks!

-

You'll receive your first newsletter email with the next major Graphite news.

-
-
-
-
- - -
-
- - -
- -
-
- -
-
-
-
-

Follow along.

-

- High-quality open source software is a community endeavor. Hang out with hundreds of friendly Graphite users and developers. -

- -
-
- -
-
-

Upcoming Tech / More details

-
-
-
-

Non-destructive editing, powered by nodes.

- -

While working in Graphite, your edits are saved into the Node Graph. Its nodes represent operations and effects like Magic Wand selection and Blur. Node parameters can be altered anytime, helping you iterate faster. The graph is organized into layers and folders, and a layer panel provides a simpler, compact view of the graph.

- -
-
-

Raster and vector art, crisp at any resolution.

- -

Just like vector artwork, which is based on curves instead of pixels to preserve quality at any scale, Graphite's raster paintbrushes, generators, and other tools - work the same way. A resolution-agnostic render engine lets you zoom infinitely and export at any size.

- -
-
-

Procedural superpowers, part of your art pipeline.

- -

Graphite aims to be the ultimate 2D tool for every technical artist. From procedural artwork to data viz and automation, it is designed from the ground up to fit into studio content - pipelines. You can also integrate Graphite's render engine into your game, app, or server.

- -
-
-
- -
-

More to come.

-

- RAW photo editing. Procedural texture generation. Advanced typesetting and desktop publishing. Motion graphics and animation. Physically-based digital painting. HDR and wide-gamut - color handling (ACES/OpenColorIO). Real-time collaboration. A rich ecosystem of custom nodes. -

-

- Learn more about the planned technology in forthcoming Graphite releases: -

- Features -
-
-
-
- -
-
- -
-
-

Built for the future, powered by Rust.

-

- Always on the bleeding edge and built to last— Graphite is written on a robust foundation with Rust, a modern programming language optimized for creating fast, reliable, future-proof - software. -

-

- The underlying node graph engine that computes and renders Graphite documents is called Graphene. The Graphene engine is an extension of the Rust language, acting as a system for chaining - together modular functions into useful pipelines with GPU and parallel computation. Artists can harness these powerful capabilities directly in the Graphite editor without touching code. - Technical artists and programmers can write reusable Rust functions to extend the capabilities of Graphite and create new nodes to share with the community. -

-
-
- -
-
- -
-
-

Get involved.

-

- The Graphite project could not exist without the community. Building its ambitious and diverse breadth of features will require contributions from developers, designers, technical experts, - creative professionals, and eagle-eyed bug hunters. Help build the future of digital art! Join the project Discord server and ask how you can - help. -

-

- Rust, web, and graphics programmers should check out the contribute page for a quick technical overview and resources for getting started. Or just ask where to - begin in the #development channel on Discord and the community will get you set up. -

-
-
- -
-
-

Recent News / More in the Blog

-
-
- {% set articles = get_section(path="blog/_index.md") %} - {% set latest = articles.pages | slice(end=2) %} - {% for article in latest %} -
- - By {{ article.extra.author }}. {{ article.date | date(format="%B %d, %Y", timezone="America/Los_Angeles") }}. -
-
- {{ article.summary | safe }} -
-
- -
- {% endfor %} -
-
-
- - -{% endblock content %} diff --git a/website/templates/page.html b/website/templates/page.html index b5fbe490..7c59f929 100644 --- a/website/templates/page.html +++ b/website/templates/page.html @@ -1,12 +1,7 @@ {% extends "base.html" %} -{% block title %}{{ section.title }}{% endblock title %} -{% block head %} -{% if section.extra.css %} - -{% endif %} -{% endblock head %} +{% block title %}{{ page.title }}{% endblock title %} {% block content %} -{{ section.content | safe }} +{{ page.content | safe }} {% endblock content %} diff --git a/website/templates/section.html b/website/templates/section.html new file mode 100644 index 00000000..ffd67f7a --- /dev/null +++ b/website/templates/section.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block title %}{{ section.title }}{% endblock title %} + +{% block content %} +{{ section.content | safe }} +{% endblock content %}