Revamp the Graphite website (#1265)
Revamp the website with more content
This commit is contained in:
parent
f2b0abc164
commit
f429db6369
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<h1 align="center">Redefining state-of-the-art graphics editing</h1>
|
<h1 align="center">Redefining state-of-the-art graphics editing</h1>
|
||||||
|
|
||||||
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.
|
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.
|
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
|
## Feature and roadmap
|
||||||
|
|
||||||
[See the web page for this information.](https://graphite.rs/features/)
|
[See the web page for this information.](https://graphite.rs/features/)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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 (<kbd>Ctrl</kbd>, <kbd>Shift</kbd>, <kbd>Alt</kbd>)
|
|
||||||
- Letter keys (<kbd>A</kbd>–<kbd>Z</kbd>)
|
|
||||||
- Number keys (<kbd>0</kbd>–<kbd>9</kbd>)
|
|
||||||
- Left edge keys (<kbd>Escape</kbd>, <kbd>\`</kbd>, <kbd>Tab</kbd>)
|
|
||||||
- Right edge keys (<kbd>Backspace</kbd>, <kbd>\\</kbd>, <kbd>Return</kbd>, <kbd>/</kbd>)
|
|
||||||
- Space bar (<kbd>⎵</kbd>)
|
|
||||||
- Symbol pair keys (<kbd>-</kbd>/<kbd>=</kbd>, <kbd>\[</kbd>/<kbd>\]</kbd>, <kbd>;</kbd>/<kbd>'</kbd>, <kbd>,</kbd>/<kbd>.</kbd>)
|
|
||||||
- Function keys (<kbd>F1</kbd>–<kbd>F12</kbd>)
|
|
||||||
- Navigation keys (<kbd>Insert</kbd>/<kbd>Delete</kbd>, <kbd>Home</kbd>/<kbd>End</kbd>, <kbd>Page Up</kbd>/<kbd>Page Down</kbd>)
|
|
||||||
- Arrow keys (<kbd>↑</kbd>, <kbd>→</kbd>, <kbd>↓</kbd>, <kbd>←</kbd>)
|
|
||||||
- Numpad <kbd>Enter</kbd> should be equivalent to <kbd>Ctrl</kbd><kbd>Return</kbd> (<kbd>Enter</kbd>)
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- <kbd>Ctrl</kbd><kbd>S</kbd> Save document.
|
|
||||||
- <kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>S</kbd> Save as.
|
|
||||||
- <kbd>Ctrl</kbd><kbd>E</kbd> Export.
|
|
||||||
|
|
||||||
## Panel-specific commands
|
|
||||||
|
|
||||||
### Document Panel
|
|
||||||
|
|
||||||
#### Viewport navigation
|
|
||||||
|
|
||||||
<kbd>Ctrl</kbd><kbd>-</kbd> Zoom out to the next discrete increment.
|
|
||||||
<kbd>Ctrl</kbd><kbd>=</kbd>/<kbd>Ctrl</kbd><kbd>+</kbd> Zoom in to the next discrete increment.
|
|
||||||
<kbd>Ctrl</kbd><kbd>0</kbd> Zoom to show the entire canvas.
|
|
||||||
<kbd>Ctrl</kbd><kbd>`</kbd> Zoom to show the entire selection.
|
|
||||||
<kbd>Ctrl</kbd><kbd>1</kbd> Zoom to 100% scale.
|
|
||||||
<kbd>Ctrl</kbd><kbd>2</kbd> Zoom to 200% scale.
|
|
||||||
|
|
||||||
#### Selection-specific
|
|
||||||
|
|
||||||
- <kbd>H</kbd> Hide/show selection, equivalent to turning off the eye icon on every selected layer
|
|
||||||
- <kbd>Alt</kbd><kbd>H</kbd> Show hidden direct children of the selection, equivalent to turning on the eye icon on every direct child layer of the selected layers
|
|
||||||
- <kbd>X</kbd> Delete selection (with confirmation)
|
|
||||||
- <kbd>Ctrl</kbd><kbd>I</kbd> Invert selected, by applying an Invert node.
|
|
||||||
|
|
||||||
#### Masking
|
|
||||||
|
|
||||||
- <kbd>Tab</kbd> Enter/exit Mask Mode.
|
|
||||||
|
|
||||||
#### Working colors
|
|
||||||
|
|
||||||
- <kbd>Shift</kbd><kbd>X</kbd> Swap the primary and secondary working colors.
|
|
||||||
- <kbd>Ctrl</kbd><kbd>Shift</kbd><kbd>X</kbd> Reset the primary and secondary working colors to black and white.
|
|
||||||
|
|
||||||
#### Tool shelf
|
|
||||||
|
|
||||||
| Tool | Graphite | Photoshop | Illustrator | XD | Affinity Designer | Inkscape | Gimp |
|
|
||||||
| --------------- | ------------ | ------------------------------------------ | ----------------------------- | ---------------- | ----------------------------- | -------- | -------- |
|
|
||||||
Select Tool | <kbd>V</kbd> | **<kbd>V</kbd>** | <kbd>V</kbd> | <kbd>V</kbd> | <kbd>V</kbd> | | |
|
|
||||||
Artboard Tool | <kbd> </kbd> | <kbd>C</kbd> | <kbd>Shift</kbd><kbd>O</kbd> | <kbd>A</kbd> | | | |
|
|
||||||
Navigate Tool | <kbd>Z</kbd> | **<kbd>Z</kbd>**/<kbd>H</kbd>/<kbd>R</kbd> | **<kbd>Z</kbd>**/<kbd>H</kbd> | **<kbd>Z</kbd>** | **<kbd>Z</kbd>**/<kbd>H</kbd> | | |
|
|
||||||
Eyedropper Tool | <kbd>I</kbd> | **<kbd>I</kbd>** | **<kbd>I</kbd>** | | **<kbd>I</kbd>** | | |
|
|
||||||
Fill Tool | <kbd>F</kbd> | <kbd>G</kbd> | | | <kbd>G</kbd> | | |
|
|
||||||
Gradient Tool | <kbd>H</kbd> | <kbd>G</kbd> | <kbd>G</kbd> | | <kbd>G</kbd> | | |
|
|
||||||
Path Tool | <kbd>A</kbd> | **<kbd>A</kbd>** | **<kbd>A</kbd>** | | **<kbd>A</kbd>** | | |
|
|
||||||
Pen Tool | <kbd>P</kbd> | **<kbd>P</kbd>** | **<kbd>P</kbd>** | **<kbd>P</kbd>** | **<kbd>P</kbd>** | | |
|
|
||||||
Freehand Tool | <kbd>N</kbd> | <kbd>P</kbd> | **<kbd>N</kbd>** | | **<kbd>N</kbd>** | | |
|
|
||||||
Spline Tool | <kbd> </kbd> | <kbd>P</kbd> | <kbd>Shift</kbd><kbd>~</kbd> | | <kbd>P</kbd> | | |
|
|
||||||
Line Tool | <kbd>L</kbd> | <kbd>U</kbd> | <kbd>\\</kbd> | **<kbd>L</kbd>** | <kbd>P</kbd> | | |
|
|
||||||
Rectangle Tool | <kbd>M</kbd> | <kbd>U</kbd>/**<kbd>M</kbd>** | **<kbd>M</kbd>** | <kbd>R</kbd> | **<kbd>M</kbd>** | | |
|
|
||||||
Ellipse Tool | <kbd>E</kbd> | <kbd>U</kbd>/<kbd>M</kbd> | <kbd>L</kbd> | **<kbd>E</kbd>** | <kbd>M</kbd> | | |
|
|
||||||
Polygon Tool | <kbd>Y</kbd> | <kbd>U</kbd> | | **<kbd>Y</kbd>** | | | |
|
|
||||||
Text Tool | <kbd>T</kbd> | **<kbd>T</kbd>** | **<kbd>T</kbd>** | **<kbd>T</kbd>** | **<kbd>T</kbd>** | | |
|
|
||||||
Brush Tool | <kbd>B</kbd> | **<kbd>B</kbd>** | **<kbd>B</kbd>** | | **<kbd>B</kbd>** | | |
|
|
||||||
Heal Tool | <kbd>J</kbd> | **<kbd>J</kbd>** | | | | | |
|
|
||||||
Clone Tool | <kbd>C</kbd> | <kbd>S</kbd> | | | | | |
|
|
||||||
Patch Tool | <kbd> </kbd> | <kbd>J</kbd> | | | | | |
|
|
||||||
Relight Tool | <kbd>O</kbd> | **<kbd>O</kbd>** | | | | | |
|
|
||||||
Detail Tool | <kbd>D</kbd> | | | | | | |
|
|
||||||
|
|
||||||
#### Tool-specific keys
|
|
||||||
|
|
||||||
Excluding mouse inputs and modifier keys.
|
|
||||||
|
|
||||||
##### Select Tool
|
|
||||||
|
|
||||||
- <kbd>G</kbd> 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.
|
|
||||||
- <kbd>R</kbd> 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`).
|
|
||||||
- <kbd>S</kbd> 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
|
|
||||||
|
|
@ -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.
|
|
||||||
<!-- - CSG - Constructive Solid Geometry. What's inside and outside of a shape. -->
|
|
||||||
<!-- - SDF - Signed Distance Field. Distance from the surface of a CSG. -->
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
# Tool behavior
|
|
||||||
|
|
||||||
# Select Tool
|
|
||||||
|
|
||||||
<kbd>LMB Click</kbd> selects the outermost group.
|
|
||||||
|
|
||||||
<kbd>LMB Double Click</kbd> "digs down" the layer tree and selects the layer or group in the currently selected group.
|
|
||||||
|
|
||||||
<kbd>Ctrl</kbd><kbd>LMB Click</kbd> 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 <kbd>Ctrl</kbd> displays vector points and they can be <kbd>LMB Click</kbd>ed 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.
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
# Panels
|
|
||||||
|
|
||||||
## Document
|
|
||||||
|
|
||||||
### Canvas and frames
|
|
||||||
|
|
||||||
### Rulers
|
|
||||||
|
|
||||||
### Tool menu
|
|
||||||
|
|
||||||
### Options bar
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
# Masking
|
|
||||||
|
|
||||||
## Mask mode
|
|
||||||
|
|
||||||
At any time while in the viewport, <kbd>Tab</kbd> 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. <kbd>Tab</kbd> 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. <kbd>Ctrl</kbd><kbd>D</kbd> will discard the working mask.
|
|
||||||
|
|
@ -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.
|
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.
|
The author and rightsholder, Keavon Chambers, may be reached through the email address listed at https://graphite.rs/contact/ or https://keavon.com.
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
|
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,400;0,700;1,400;1,700&family=Inconsolata:wght@400;700">
|
|
||||||
<title>Graphite</title>
|
<title>Graphite</title>
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,400;0,700;1,400;1,700&family=Inconsolata:wght@400;700">
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="public/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="public/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="public/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="public/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="public/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="public/favicon-16x16.png">
|
||||||
<link rel="manifest" href="/public/site.webmanifest">
|
<link rel="manifest" href="/public/site.webmanifest">
|
||||||
<link rel="mask-icon" href="/public/safari-pinned-tab.svg" color="#473a3a">
|
<link rel="mask-icon" href="/public/safari-pinned-tab.svg" color="#473a3a">
|
||||||
|
<script defer data-domain="dev.graphite.rs" data-api="https://graphite.rs/visit/event" src="https://graphite.rs/visit/script.outbound-links.file-downloads.js"></script>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5">
|
||||||
<meta name="apple-mobile-web-app-title" content="Graphite">
|
<meta name="apple-mobile-web-app-title" content="Graphite">
|
||||||
<meta name="application-name" content="Graphite">
|
<meta name="application-name" content="Graphite">
|
||||||
<meta name="msapplication-TileColor" content="#ffffff">
|
<meta name="msapplication-TileColor" content="#ffffff">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Overview of `/frontend/src/components/`
|
# 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/`
|
## Floating Menus: `floating-menus/`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ function githubUrl(panicDetails: string): string {
|
||||||
|
|
||||||
**Steps To Reproduce**
|
**Steps To Reproduce**
|
||||||
Describe precisely how the crash occurred, step by step, starting with a new editor window.
|
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.
|
2.
|
||||||
3.
|
3.
|
||||||
4.
|
4.
|
||||||
|
|
@ -79,7 +79,7 @@ function githubUrl(panicDetails: string): string {
|
||||||
${browserVersion()}, ${operatingSystem(true).replace("Unknown", "YOUR OPERATING SYSTEM")}
|
${browserVersion()}, ${operatingSystem(true).replace("Unknown", "YOUR OPERATING SYSTEM")}
|
||||||
|
|
||||||
**Stack Trace**
|
**Stack Trace**
|
||||||
Copied from the crash dialog in the Graphite Editor:
|
Copied from the crash dialog in the Graphite editor:
|
||||||
`;
|
`;
|
||||||
|
|
||||||
body += "\n\n```\n";
|
body += "\n\n```\n";
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
public/
|
public/
|
||||||
|
static/syntax-highlighting.css
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ feed_filename = "rss.xml"
|
||||||
# Theme can be customized by setting the `highlight_theme` variable to a theme supported by Zola
|
# Theme can be customized by setting the `highlight_theme` variable to a theme supported by Zola
|
||||||
highlight_code = true
|
highlight_code = true
|
||||||
highlight_theme = "css"
|
highlight_theme = "css"
|
||||||
|
highlight_themes_css = [{ theme = "kronuz", filename = "syntax-highlighting.css" }]
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
# Put all your custom variables here
|
# Put all your custom variables here
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
+++
|
||||||
|
|
||||||
|
<section id="logo">
|
||||||
|
<img src="https://static.graphite.rs/logos/graphite-logotype-color.svg" alt="Graphite Logo" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<img class="pencil-texture" src="https://static.graphite.rs/textures/pencil-texture.png" alt="" />
|
||||||
|
|
||||||
|
<section id="quick-links">
|
||||||
|
<a href="#community" class="button arrow">Subscribe to the newsletter</a>
|
||||||
|
<a href="/donate" class="button arrow">♥ Support the mission</a>
|
||||||
|
<div>
|
||||||
|
<a href="https://github.com/GraphiteEditor/Graphite" target="_blank">
|
||||||
|
<img src="https://static.graphite.rs/icons/github.svg" alt="GitHub" />
|
||||||
|
</a>
|
||||||
|
<a href="https://www.reddit.com/r/graphite/" target="_blank">
|
||||||
|
<img src="https://static.graphite.rs/icons/reddit.svg" alt="Reddit" />
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/graphiteeditor" target="_blank">
|
||||||
|
<img src="https://static.graphite.rs/icons/twitter.svg" alt="Twitter" />
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.graphite.rs" target="_blank">
|
||||||
|
<img src="https://static.graphite.rs/icons/discord.svg" alt="Discord" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="hero-message">
|
||||||
|
|
||||||
|
# Redefining state-of-the-art graphics editing
|
||||||
|
|
||||||
|
<p class="balance-text"><strong>Graphite</strong> 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.</p>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="hexagons">
|
||||||
|
<div>
|
||||||
|
<svg viewBox="0 0 1400 1215.42" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<polygon points="1049.43,0.99 350.57,0.99 1.14,607.71 350.57,1214.44 1049.43,1214.44 1398.86,607.71" />
|
||||||
|
<polygon points="1016.39,57.57 383.61,57.57 67.22,607.71 383.61,1157.85 1016.39,1157.85 1332.78,607.71" />
|
||||||
|
<polygon points="964.49,149.01 435.51,149.01 171.02,607.71 435.51,1066.41 964.49,1066.41 1228.98,607.71" />
|
||||||
|
<polygon points="875.52,304.71 524.48,304.71 348.96,607.71 524.48,910.71 875.52,910.71 1051.04,607.71" />
|
||||||
|
<polygon points="768.12,490.96 631.88,490.96 563.78,607.71 631.88,724.47 768.12,724.47 836.22,607.71" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="screenshots" class="carousel window-size-1" data-carousel>
|
||||||
|
<div class="carousel-slide">
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-demo-valley-of-spires.png" alt="Graphite UI image #1" data-carousel-image />
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-mockup-nodes__2.png" alt="Graphite UI image #3" data-carousel-image />
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-mockup-viewport__2.png" alt="Graphite UI image #2" data-carousel-image />
|
||||||
|
</div>
|
||||||
|
<div class="carousel-slide torn left">
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-demo-valley-of-spires.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-mockup-nodes__2.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-mockup-viewport__2.png" alt="" data-carousel-image />
|
||||||
|
</div>
|
||||||
|
<div class="carousel-slide torn right">
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-demo-valley-of-spires.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-mockup-nodes__2.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://static.graphite.rs/content/index/gui-mockup-viewport__2.png" alt="" data-carousel-image />
|
||||||
|
</div>
|
||||||
|
<div class="screenshot-details">
|
||||||
|
<div class="carousel-controls">
|
||||||
|
<button class="direction prev" data-carousel-prev>
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
|
||||||
|
<polygon points="24.71,10.71 23.29,9.29 12.59,20 23.29,30.71 24.71,29.29 15.41,20" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="dot active" data-carousel-dot></button>
|
||||||
|
<button class="dot" data-carousel-dot></button>
|
||||||
|
<button class="dot" data-carousel-dot></button>
|
||||||
|
<button class="direction next" data-carousel-next>
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
|
||||||
|
<polygon points="16.71,9.29 15.29,10.71 24.59,20 15.29,29.29 16.71,30.71 27.41,20" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="screenshot-description">
|
||||||
|
<p class="active" data-carousel-description>
|
||||||
|
<em>Valley of Spires</em> — <a href="https://static.graphite.rs/graphite-files/valley-of-spires.graphite">Download</a> the artwork and open it in <a href="https://79dda0fc.graphite.pages.dev/">this previous version</a> of the Graphite editor.
|
||||||
|
</p>
|
||||||
|
<p data-carousel-description>
|
||||||
|
Design mockup for the work-in-progress node graph raster editing pipeline. Some raster nodes shown here are not implemented yet.
|
||||||
|
</p>
|
||||||
|
<p data-carousel-description>
|
||||||
|
Design mockup for the work-in-progress viewport raster editing workflow. Some features shown here are not implemented yet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div id="graphite-today" class="section">
|
||||||
|
|
||||||
|
# Graphite today
|
||||||
|
|
||||||
|
<div class="informational-group features">
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 0" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Vector editing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 1" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Node graph image effects</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 2" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>AI-assisted art creation</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 3" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Open source and free forever</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="graphite-tomorrow" class="section">
|
||||||
|
|
||||||
|
# Graphite tomorrow
|
||||||
|
|
||||||
|
<div class="informational-group features">
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 4" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Looks and feels like traditional <span style="text-decoration: underline dotted; text-decoration-color: #16323f77;" title=""what you see is what you get"">WYSIWYG</span> design apps</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 5" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Clean, intuitive interface built by designers, for designers</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 6" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Real-time collaborative editing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 7" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Windows/Mac/Linux/Web/iPad</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/features#roadmap" class="button arrow">Roadmap</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="community" class="feature-box">
|
||||||
|
<div class="box">
|
||||||
|
<section class="section-row">
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div id="newsletter" class="section">
|
||||||
|
|
||||||
|
# Stay in the loop
|
||||||
|
|
||||||
|
Subscribe to the newsletter for quarterly updates on major development progress.
|
||||||
|
|
||||||
|
<div id="newsletter-success">
|
||||||
|
|
||||||
|
## Thanks!
|
||||||
|
|
||||||
|
You'll receive your first newsletter email with the next major Graphite news.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="https://graphite.rs/newsletter-signup" method="post">
|
||||||
|
<div class="same-line">
|
||||||
|
<div class="column name">
|
||||||
|
<label for="newsletter-name">First + last name:</label>
|
||||||
|
<input id="newsletter-name" name="name" type="text" required />
|
||||||
|
</div>
|
||||||
|
<div class="column phone">
|
||||||
|
<label for="newsletter-phone">Phone:</label>
|
||||||
|
<input id="newsletter-phone" name="phone" type="text" tabindex="-1" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<div class="column email">
|
||||||
|
<label for="newsletter-email">Email address:</label>
|
||||||
|
<input id="newsletter-email" name="email" type="email" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column submit">
|
||||||
|
<input type="submit" value="Subscribe" class="button" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="social" class="section">
|
||||||
|
|
||||||
|
# Follow along
|
||||||
|
|
||||||
|
<!-- High-quality open source software is a community endeavor. Hang out with hundreds of friendly Graphite users and developers. -->
|
||||||
|
|
||||||
|
<div class="social-links">
|
||||||
|
<div class="column">
|
||||||
|
<a href="https://discord.graphite.rs" target="_blank">
|
||||||
|
<img src="https://static.graphite.rs/icons/discord.svg" alt="Discord" />
|
||||||
|
<span class="link arrow">Join on Discord</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://www.reddit.com/r/graphite/" target="_blank">
|
||||||
|
<img src="https://static.graphite.rs/icons/reddit.svg" alt="Reddit" />
|
||||||
|
<span class="link not-uppercase arrow">/r/Graphite</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<a href="https://github.com/GraphiteEditor/Graphite" target="_blank">
|
||||||
|
<img src="https://static.graphite.rs/icons/github.svg" alt="GitHub" />
|
||||||
|
<span class="link arrow">Star on GitHub</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://twitter.com/graphiteeditor" target="_blank">
|
||||||
|
<img src="https://static.graphite.rs/icons/twitter.svg" alt="Twitter" />
|
||||||
|
<span class="link not-uppercase arrow">@GraphiteEditor</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="vector-art" class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
<div class="background-video">
|
||||||
|
<video loop muted disablepictureinpicture disableremoteplayback data-auto-play>
|
||||||
|
<source src="https://static.graphite.rs/content/index/just-a-potted-cactus-timelapse.mp4" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="download-artwork">
|
||||||
|
<img src="https://static.graphite.rs/content/index/just-a-potted-cactus-thumbnail.png" alt="Vector art of Just of Potted Cactus" />
|
||||||
|
<span>
|
||||||
|
<em>Just a Potted Cactus</em>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<a href="https://static.graphite.rs/graphite-files/just-a-potted-cactus.graphite">Download</a>
|
||||||
|
the artwork and
|
||||||
|
<br />
|
||||||
|
open it in the <a href="https://editor.graphite.rs">Graphite editor</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- <section id="node-graph">
|
||||||
|
|
||||||
|
<section id="node-graph-intro" class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# The power of nodes
|
||||||
|
|
||||||
|
At Graphite's core is its **node graph**, a compositing engine and artist-friendly visual scripting environment that simplifies laborious steps in your design process.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="node-graph-adjustment-layers" class="section-row feature-explainer">
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## Adjust layers with<br />nondestructive effects
|
||||||
|
|
||||||
|
Apply effects directly in the layer stack to modify the artwork underneath. Combine them in unique ways by connecting the effect nodes to one another.
|
||||||
|
|
||||||
|
- Per-pixel color adjustments: levels, curves, exposure, contrast, saturation
|
||||||
|
- Image-wide creative filters: blur/sharpen, high pass, flood fill, warp, fresco
|
||||||
|
- Alpha-aware image styles: color overlay, drop shadow, inner/outer glow
|
||||||
|
- Effort-saving modifiers: transform, mirror, tile, scatter, linear/radial repeat
|
||||||
|
- Procedural generators: solid color, gradient, pattern, coherent/white noise
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</section> -->
|
||||||
|
|
||||||
|
<section id="imaginate">
|
||||||
|
|
||||||
|
<section id="imaginate-intro" class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
<h1><span class="alternating-text"><span>Co-create</span><span>Ideate</span><span>Illustrate</span><span>Generate</span><span>Iterate</span></span> with Imaginate</h1>
|
||||||
|
|
||||||
|
**Imaginate** is a node powered by <a href="https://en.wikipedia.org/wiki/Stable_Diffusion" target="_blank">Stable Diffusion</a> that makes AI-assisted art creation an easy, nondestructive process.
|
||||||
|
<!-- [Learn how](/learn/node-graph/imaginate) it works. -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="imaginate-vector-art" class="section-row feature-explainer">
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
<h2 class="balance-text">Make it stylish</h2>
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
<div class="image-comparison" data-image-comparison style="--comparison-percent: 50%">
|
||||||
|
<div class="crop-container">
|
||||||
|
<img src="https://static.graphite.rs/content/index/light-bulb-before.png" alt="Vector illustration of a light bulb" />
|
||||||
|
</div>
|
||||||
|
<div class="crop-container">
|
||||||
|
<img src="https://static.graphite.rs/content/index/light-bulb-after.png" alt="Watercolor painting of a light bulb" />
|
||||||
|
</div>
|
||||||
|
<div class="slide-bar">
|
||||||
|
<div class="arrows">
|
||||||
|
<div></div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 22">
|
||||||
|
<path d="M12.71 1.71 11.29.29.59 11l10.7 10.71 1.42-1.42L3.41 11Z" />
|
||||||
|
</svg>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 22">
|
||||||
|
<path d="M12.71 1.71 11.29.29.59 11l10.7 10.71 1.42-1.42L3.41 11Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<blockquote class="balance-text require-polyfill"><strong>Watercolor painting</strong> of a light bulb gleaming with an exclamation mark inside</blockquote>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
<div class="image-comparison" data-image-comparison style="--comparison-percent: 50%">
|
||||||
|
<div class="crop-container">
|
||||||
|
<img src="https://static.graphite.rs/content/index/california-poppies-before.png" alt="Sloppy poppy: vector doodle of California poppy flowers wrapped around a circle" />
|
||||||
|
</div>
|
||||||
|
<div class="crop-container">
|
||||||
|
<img src="https://static.graphite.rs/content/index/california-poppies-after.png" alt="Polished poppy: artistic, high-quality illustration of California poppy flowers wrapped around a circle" />
|
||||||
|
</div>
|
||||||
|
<div class="slide-bar">
|
||||||
|
<div class="arrows">
|
||||||
|
<div></div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 22">
|
||||||
|
<path d="M12.71 1.71 11.29.29.59 11l10.7 10.71 1.42-1.42L3.41 11Z" />
|
||||||
|
</svg>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 22">
|
||||||
|
<path d="M12.71 1.71 11.29.29.59 11l10.7 10.71 1.42-1.42L3.41 11Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<blockquote class="balance-text require-polyfill"><strong>Botanical illustration</strong> of California poppies wrapped around a circle</blockquote>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="fundraising" class="feature-box">
|
||||||
|
<div class="box">
|
||||||
|
<div class="section-row">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# Support the mission
|
||||||
|
|
||||||
|
<p class="balance-text">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Summer 2023 fundraising goal:
|
||||||
|
|
||||||
|
<div class="fundraising loading" data-fundraising>
|
||||||
|
<div class="fundraising-bar" data-fundraising-bar style="--fundraising-percent: 0%">
|
||||||
|
<div class="fundraising-bar-progress"></div>
|
||||||
|
</div>
|
||||||
|
<div class="goal-metrics">
|
||||||
|
<span data-fundraising-percent>Progress: <span data-dynamic>0</span>%</span>
|
||||||
|
<span data-fundraising-goal>Goal: $<span data-dynamic>0</span>/month</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[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.
|
||||||
|
|
||||||
|
<a href="https://github.com/sponsors/Keavon" class="button arrow">Donate</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="graphic">
|
||||||
|
<a href="https://github.com/sponsors/Keavon"><img src="https://files.keavon.com/-/OtherDroopyBoto/Spring_Heart.png" /></a>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
<div id="disciplines" class="section">
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
|
||||||
|
<div class="informational-group concepts">
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 12" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Graphic Design</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 13" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Image Editing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Digital Painting</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 15" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Desktop Publishing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 16" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>VFX Compositing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 17" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span>Motion Graphics</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="vector-art" class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# Powerful proceduralism
|
||||||
|
|
||||||
|
The data-driven approach to design affords unique capabilities that are presently in-development.
|
||||||
|
|
||||||
|
<div class="informational-group features four-wide">
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 8" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span class="balance-text">Fully nondestructive editing with node-driven layers</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 9" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span class="balance-text">Infinitely scalable raster content with no pixelation</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 10" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span class="balance-text">Integrates generative AI models and graphics algorithms</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 11" src="https://static.graphite.rs/icons/icon-atlas-features.png" alt="" />
|
||||||
|
<span class="balance-text">Procedural pipelines for studio production environments</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="demo-video" class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
<div class="video-embed aspect-16x9">
|
||||||
|
<iframe width="1280" height="720" src="https://www.youtube.com/embed/JgJvAHQLnXA" title="Graphite Vector Editing: "Commander Basstronaut" Artwork (25x Timelapse)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
<!-- <a href="/blog/mission-statement" class="link arrow">Mission Statement</a> -->
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="get-involved-box" class="feature-box">
|
||||||
|
<div class="box">
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# Get involved
|
||||||
|
|
||||||
|
<p class="balance-text">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.</p>
|
||||||
|
|
||||||
|
<a href="/volunteer" class="button arrow">Volunteer</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="graphic">
|
||||||
|
<img src="https://static.graphite.rs/content/index/volunteer.svg" alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
+++
|
||||||
|
title = "About Graphite"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
css = ["/about.css"]
|
||||||
|
+++
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# 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).
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- A batteries-included creative app for every kind of digital artist where -->
|
||||||
|
|
||||||
|
<!-- ## Statistics
|
||||||
|
|
||||||
|
- [GitHub stars](https://github.com/GraphiteEditor/Graphite/stargazers): <span class="loading-data" data-github-stars></span>
|
||||||
|
- [Contributors](https://github.com/GraphiteEditor/Graphite/graphs/contributors): <span class="loading-data" data-contributors></span>
|
||||||
|
- [Code commits](https://github.com/GraphiteEditor/Graphite/commits/master): <span class="loading-data" data-code-commits></span>
|
||||||
|
- [First line of code](https://github.com/GraphiteEditor/Graphite/commit/bca97cbeff8e38b426cfb410159cb21132062fba): Feb. 14, 2021
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(async () => {
|
||||||
|
const response = await fetch("https://api.github.com/repos/graphiteeditor/graphite?per_page=1");
|
||||||
|
const json = await response.json();
|
||||||
|
const stars = parseInt(json.stargazers_count);
|
||||||
|
if (!stars) return;
|
||||||
|
|
||||||
|
document.querySelector("[data-github-stars]").innerText = `${Math.round(stars / 100) / 10}k ⭐`;
|
||||||
|
})();
|
||||||
|
(async () => {
|
||||||
|
const response = await fetch("https://api.github.com/repos/graphiteeditor/graphite/contributors?per_page=1");
|
||||||
|
const link = [...response.headers].find(([header, _]) => header === "link")[1];
|
||||||
|
if (!link) return;
|
||||||
|
// With one page per contributor, the last past number is the contributor count
|
||||||
|
const contributors = parseInt(link.match(/page=(\d+)>; rel="last"/)[1]);
|
||||||
|
if (!contributors) return;
|
||||||
|
|
||||||
|
document.querySelector("[data-contributors]").innerText = contributors;
|
||||||
|
})();
|
||||||
|
(async () => {
|
||||||
|
const response = await fetch("https://api.github.com/repos/graphiteeditor/graphite/commits?per_page=1");
|
||||||
|
const link = [...response.headers].find(([header, _]) => header === "link")[1];
|
||||||
|
if (!link) return;
|
||||||
|
// With one page per commit, the last past number is the commit count
|
||||||
|
const commits = parseInt(link.match(/page=(\d+)>; rel="last"/)[1]);
|
||||||
|
if (!commits) return;
|
||||||
|
|
||||||
|
document.querySelector("[data-code-commits]").innerText = commits;
|
||||||
|
})();
|
||||||
|
</script> -->
|
||||||
|
|
||||||
|
<!-- <section id="opener-message" class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## 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](/license) and usable [instantly through a web browser](https://editor.graphite.rs) 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="graphic">
|
||||||
|
<img src="https://static.graphite.rs/content/index/brush__2.svg" alt="" />
|
||||||
|
</div>
|
||||||
|
</section> -->
|
||||||
|
|
||||||
|
<section id="core-team" class="feature-box">
|
||||||
|
<div class="box">
|
||||||
|
|
||||||
|
<h1 class="box-header">Meet the core team</h1>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="triptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Keavon Chambers <span class="flag" title="American">🇺🇸</span>
|
||||||
|
|
||||||
|
*@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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Dennis Kobert <span class="flag" title="German">🇩🇪</span>
|
||||||
|
|
||||||
|
*@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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## "Hypercube" <span class="flag" title="British">🇬🇧</span>
|
||||||
|
|
||||||
|
*@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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
|
||||||
|
<div class="triptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
<a href="https://github.com/GraphiteEditor/Graphite/graphs/contributors" class="button arrow">Credits</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The Graphite editor and source code are provided under the Apache License 2.0 terms. See below for details and exclusions:
|
||||||
|
|
||||||
|
<a href="/license" class="button arrow">License</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## Logo
|
||||||
|
|
||||||
|
More information about the Graphite logo, including its community-focused usage policy and downloadable assets, is available on the logo page:
|
||||||
|
|
||||||
|
<a href="/logo" class="button arrow">Logo</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
@ -9,7 +9,7 @@ generate_feed = true
|
||||||
<section id="intro" class="section-row">
|
<section id="intro" class="section-row">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
|
||||||
# Blog.
|
# Blog
|
||||||
|
|
||||||
<div class="left-right-split">
|
<div class="left-right-split">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
+++
|
+++
|
||||||
title = "Contact the team"
|
title = "Contact the team"
|
||||||
template = "page.html"
|
|
||||||
+++
|
+++
|
||||||
|
|
||||||
<section class="section-row">
|
<section class="section-row reading-material">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
|
||||||
# Contact the team.
|
# Contact the team
|
||||||
|
|
||||||
* Members of the press, please see the [press resources](/press) page.
|
* 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/).
|
* 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.
|
* 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 <contact@graphite.rs>.
|
* For other inquiries, get in touch by email at <contact@graphite.rs>.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -1,149 +0,0 @@
|
||||||
+++
|
|
||||||
title = "Contribute to Graphite"
|
|
||||||
template = "page.html"
|
|
||||||
+++
|
|
||||||
|
|
||||||
<section class="section-row">
|
|
||||||
<div class="section">
|
|
||||||
|
|
||||||
# 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 <http://localhost:8080> 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 <kbd>Ctrl</kbd><kbd>C</kbd> 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.
|
|
||||||
|
|
||||||
<details><summary><b>Additional technical details (click to show)</b></summary>
|
|
||||||
|
|
||||||
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<LayerId>, 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))`.
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Debugging.
|
|
||||||
|
|
||||||
Use the browser console (<kbd>F12</kbd>) 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:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
+++
|
||||||
|
title = "Donate"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
css = ["/donate.css"]
|
||||||
|
js = ["/fundraising.js"]
|
||||||
|
+++
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# Donate
|
||||||
|
|
||||||
|
**Safeguard the sustainable, independent future of Graphite.** Your monthly support (or one-off contribution) is needed for the speedy growth of the project.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="fundraising" class="feature-box">
|
||||||
|
<div class="box">
|
||||||
|
<div class="section-row">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# Support the mission
|
||||||
|
|
||||||
|
<p class="balance-text">
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Summer 2023 fundraising goal:
|
||||||
|
|
||||||
|
<div class="fundraising loading" data-fundraising>
|
||||||
|
<div class="fundraising-bar" data-fundraising-bar style="--fundraising-percent: 0%">
|
||||||
|
<div class="fundraising-bar-progress"></div>
|
||||||
|
</div>
|
||||||
|
<div class="goal-metrics">
|
||||||
|
<span data-fundraising-percent>Progress: <span data-dynamic>0</span>%</span>
|
||||||
|
<span data-fundraising-goal>Goal: $<span data-dynamic>0</span>/month</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[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.
|
||||||
|
|
||||||
|
<a href="https://github.com/sponsors/Keavon" class="button arrow">Donate</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <div class="graphic">
|
||||||
|
<a href="https://github.com/sponsors/Keavon"><img src="https://files.keavon.com/-/OtherDroopyBoto/Spring_Heart.png" /></a>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
@ -0,0 +1,304 @@
|
||||||
|
+++
|
||||||
|
title = "Graphite features"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
css = ["/features.css"]
|
||||||
|
+++
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
<!-- Rust programmers may find the following technical details to be of interest. Graphene node graphs are programs built out of reusable Rust functions using Graphite as a visual "code" editor. New nodes and data types can be implemented by writing custom Rust code with a built-in text editor. `no_std` code also gets compiled to GPU compute shaders using [`rust-gpu`](https://github.com/EmbarkStudios/rust-gpu). Each node is independently pre-compiled by `rustc` into portable WASM binaries and linked at runtime. Groups of nodes may be compiled into one unit of execution, utilizing Rust's zero-cost abstractions and optimizations to run with less overhead. And whole node graphs can be compiled into standalone executables for use outside Graphite. -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
<!-- ## Proudly written in Rust -->
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
<!-- 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. -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## Layers & nodes: hybrid compositing<br /><span style="white-space: nowrap; background: var(--color-flamingo); color: white; font-size: 0.5em; padding: 0.2em 0.4em">work-in-progress</span>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## Raster & vector: sharp at all sizes<br /><span style="white-space: nowrap; background: var(--color-flamingo); color: white; font-size: 0.5em; padding: 0.2em 0.4em">1–2 months away</span>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
<div class="roadmap">
|
||||||
|
<div class="informational-group features">
|
||||||
|
<div class="informational complete heading" title="Development Complete">
|
||||||
|
<h3>— Pre-Alpha —</h3>
|
||||||
|
</div>
|
||||||
|
<div class="informational complete" title="Development Complete">
|
||||||
|
<img class="atlas" style="--atlas-index: 0" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Editor systems; basic vector art tools</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational complete heading" title="Development Complete">
|
||||||
|
<h3>— Alpha Milestone 1 —</h3>
|
||||||
|
</div>
|
||||||
|
<div class="informational complete" title="Development Complete">
|
||||||
|
<img class="atlas" style="--atlas-index: 1" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Tool upgrades; Graphene prototyping</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational ongoing heading" title="Development Ongoing">
|
||||||
|
<h3>— Alpha Milestone 2 —</h3>
|
||||||
|
</div>
|
||||||
|
<div class="informational complete" title="Development Complete">
|
||||||
|
<img class="atlas" style="--atlas-index: 4" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Brush tool</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational ongoing" title="Development Ongoing">
|
||||||
|
<img class="atlas" style="--atlas-index: 11" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>WebGPU accelerated rendering</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational ongoing" title="Development Ongoing">
|
||||||
|
<img class="atlas" style="--atlas-index: 2" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Layer stack compositing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational ongoing">
|
||||||
|
<img class="atlas" style="--atlas-index: 5" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Graph-based documents</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 3" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Node-based layer tree</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Resolution-agnostic raster rendering</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 19" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Document history system</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 18" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Stable document format</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 7" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Custom subgraph document nodes</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 22" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Select mode and masking</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 13" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>New viewport overlays system</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 6" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Self-updating desktop app</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational heading">
|
||||||
|
<h3>— Alpha Milestone 3 —</h3>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 8" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Graph data attributes</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 9" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Spreadsheet-based vector data</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 10" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Editable SVG import</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 12" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Rust-based vector renderer</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 20" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>RAW photo import and processing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 15" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Powerful snapping and grid system</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 16" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Remote compile/render server</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 17" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Code editor for custom nodes</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational heading">
|
||||||
|
<h3>— Beta —</h3>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 21" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Procedural paint brush styling</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 23" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Frozen history references</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 24" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Internationalization and accessability</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 25" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Reconfigurable workspace panels</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 26" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Liquify and non-affine rendering</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 27" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Interactive graph auto-layout</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 28" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Automation and batch processing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 29" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Guide mode</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 30" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>CAD-like constraint solver</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 31" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Constraint models for UI layouts</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 32" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Advanced typography and typesetting</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 33" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>PDF export</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 34" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>HDR and WCG color handling</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 35" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Node manager and marketplace</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 36" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Predictive graph rendering/caching</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 37" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Distributed graph rendering</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 38" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Cloud document storage</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 39" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Multiplayer collaborative editing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 40" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Offline edit resolution with CRDTs</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 41" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Native UI rewrite from HTML frontend</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational heading">
|
||||||
|
<h3>— 1.0 Release —</h3>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 42" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Timeline and renderer for animation</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 43" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Live video compositing</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 44" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Pen and touch-only interaction</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 45" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>iPad app</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 46" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Portable render engine</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 48" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>SVG animation</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational">
|
||||||
|
<img class="atlas" style="--atlas-index: 49" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span><em>…and that's all just the beginning…</em></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
+++
|
|
||||||
title = "Features and roadmap"
|
|
||||||
template = "page.html"
|
|
||||||
|
|
||||||
[extra]
|
|
||||||
css = "/features.css"
|
|
||||||
+++
|
|
||||||
|
|
||||||
<section class="section-row">
|
|
||||||
<div class="section">
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-row">
|
|
||||||
<div class="diptych">
|
|
||||||
|
|
||||||
<div id="graphite-today" class="section">
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
<div class="informational-group features">
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 0" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Vector editing</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 1" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Node graph image effects</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 2" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>AI-assisted art creation</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 3" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Open source and free forever</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a href="https://editor.graphite.rs" class="link arrow">Launch Graphite</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div id="graphite-tomorrow" class="section">
|
|
||||||
|
|
||||||
## Graphite tomorrow.
|
|
||||||
|
|
||||||
All the digital content creation tools a professional needs— in one streamlined package:
|
|
||||||
|
|
||||||
<div class="informational-group features">
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 4" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Looks and feels like traditional <span style="text-decoration: underline dotted; text-decoration-color: #16323f77;" title=""what you see is what you get"">WYSIWYG</span> design apps</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 5" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Sleek, intuitive interface built by designers, for designers</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 6" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Real-time collaborative editing</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 7" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Windows/Mac/Linux/Web/iPad</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
The full Graphite vision wholly embraces procedural workflows:
|
|
||||||
|
|
||||||
<div class="informational-group features">
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 8" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Fully non-destructive editing with node-driven layers</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 9" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Infinitely scalable raster content with no pixelation</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 10" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Integrates leading-edge AI models and graphics algorithms</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 11" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Procedural pipelines for studio production environments</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="upcoming-tech" class="feature-box">
|
|
||||||
<div class="box">
|
|
||||||
|
|
||||||
<h1 class="box-header">Upcoming Tech: How it works</h1>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Graphite's concept is unique among graphics editors and requires some explanation. Here's a glimpse at that secret sauce. -->
|
|
||||||
|
|
||||||
<div class="diptych">
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="section">
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section-row">
|
|
||||||
<div class="section">
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
<h3>— Pre-Alpha (complete) —</h3>
|
|
||||||
<ul>
|
|
||||||
<li>First year of development (complete, details omitted)</li>
|
|
||||||
</ul>
|
|
||||||
<h3>— Alpha Milestone 1 (ongoing) —</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Second year of development (complete, details omitted)</li>
|
|
||||||
<li>Brush tool (in-progress)</li>
|
|
||||||
<li>WebGPU in supported browsers (in-progress)</li>
|
|
||||||
<li>Vertical compositing of nodes</li>
|
|
||||||
<li>Node-based layer tree</li>
|
|
||||||
<li>Graph-based documents</li>
|
|
||||||
<li>Self-updating desktop app</li>
|
|
||||||
<li>Custom subgraph document nodes</li>
|
|
||||||
</ul>
|
|
||||||
<h3>— Alpha Milestone 2 —</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Graph data attributes</li>
|
|
||||||
<li>Spreadsheet-based vector data</li>
|
|
||||||
<li>Editable SVG import</li>
|
|
||||||
<li>Rust-based vector renderer</li>
|
|
||||||
<li>Select mode and masking</li>
|
|
||||||
<li>New viewport overlays system</li>
|
|
||||||
<li>Resolution-agnostic raster rendering</li>
|
|
||||||
<li>Powerful snapping and grid system</li>
|
|
||||||
<li>Remote compile/render server</li>
|
|
||||||
<li>Code editor for custom nodes</li>
|
|
||||||
<li>Document history system</li>
|
|
||||||
<li>Stable document format</li>
|
|
||||||
</ul>
|
|
||||||
<h3>— Alpha Milestone 3 —</h3>
|
|
||||||
<ul>
|
|
||||||
<li>RAW photo import and processing</li>
|
|
||||||
<li>Procedural paint brush styling</li>
|
|
||||||
<li>Frozen history references</li>
|
|
||||||
<li>Internationalization and accessability</li>
|
|
||||||
<li>Reconfigurable workspace panels</li>
|
|
||||||
<li>Liquify and non-affine rendering</li>
|
|
||||||
<li>Interactive graph auto-layout</li>
|
|
||||||
<li>Automation and batch processing</li>
|
|
||||||
</ul>
|
|
||||||
<h3>— Beta —</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Guide mode</li>
|
|
||||||
<li>CAD-like constraint solver</li>
|
|
||||||
<li>Constraint models for UI layouts</li>
|
|
||||||
<li>Advanced typography and typesetting</li>
|
|
||||||
<li>PDF export</li>
|
|
||||||
<li>HDR and WCG color handling</li>
|
|
||||||
<li>Node manager and marketplace</li>
|
|
||||||
<li>Predictive graph rendering/caching</li>
|
|
||||||
<li>Distributed graph rendering</li>
|
|
||||||
<li>Cloud document storage</li>
|
|
||||||
<li>Multiplayer collaborative editing</li>
|
|
||||||
<li>Offline edit resolution with CRDTs</li>
|
|
||||||
<li>Native UI rewrite from HTML frontend</li>
|
|
||||||
</ul>
|
|
||||||
<h3>— 1.0 Release —</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Timeline and renderer for animation</li>
|
|
||||||
<li>Live video compositing</li>
|
|
||||||
<li>Pen and touch-only interaction</li>
|
|
||||||
<li>iPad app</li>
|
|
||||||
<li>Portable render engine</li>
|
|
||||||
<li>SVG animation</li>
|
|
||||||
</ul>
|
|
||||||
<h3>— Future Releases —</h3>
|
|
||||||
<ul>
|
|
||||||
<li><em>…and that's just the beginning…</em></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
<!-- Learn how to use Graphite like a pro. -->
|
||||||
|
|
||||||
|
<!--  -->
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
+++
|
||||||
|
title = "Introduction"
|
||||||
|
template = "book.html"
|
||||||
|
page_template = "book.html"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 1
|
||||||
|
+++
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Artboards"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 4
|
||||||
|
+++
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Interface"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 1
|
||||||
|
+++
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Layers"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 3
|
||||||
|
+++
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Tools"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 2
|
||||||
|
+++
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
+++
|
||||||
|
title = "Node graph"
|
||||||
|
template = "book.html"
|
||||||
|
page_template = "book.html"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 2
|
||||||
|
+++
|
||||||
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
+++
|
||||||
|
title = "Imaginate"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 2
|
||||||
|
+++
|
||||||
|
|
||||||
|
<!-- <section id="imaginate-creative-concepts" class="section-row feature-explainer">
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
## Explore new creative concepts
|
||||||
|
|
||||||
|
**Get inspired** by generating endless variations of concepts and references to work from, such as:
|
||||||
|
|
||||||
|
<section id="imaginate-creative-concepts-carousel" class="carousel window-size-3" data-carousel>
|
||||||
|
<div class="carousel-slide">
|
||||||
|
<img src="https://files.keavon.com/-/PerfumedNiceRhinoceros/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/UnwrittenFrankHeterodontosaurus/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/BlindBiodegradableAlaskajingle/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/DigitalElatedVicuna/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/ThreadbareIncredibleFlycatcher/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/NaughtyGracefulSquirrel/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/BrownSuburbanMacaw/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/AbsoluteAwkwardGrouse/capture.png" alt="" data-carousel-image />
|
||||||
|
</div>
|
||||||
|
<div class="carousel-slide torn left">
|
||||||
|
<img src="https://files.keavon.com/-/PerfumedNiceRhinoceros/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/UnwrittenFrankHeterodontosaurus/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/BlindBiodegradableAlaskajingle/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/DigitalElatedVicuna/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/ThreadbareIncredibleFlycatcher/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/NaughtyGracefulSquirrel/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/BrownSuburbanMacaw/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/AbsoluteAwkwardGrouse/capture.png" alt="" data-carousel-image />
|
||||||
|
</div>
|
||||||
|
<div class="carousel-slide torn right">
|
||||||
|
<img src="https://files.keavon.com/-/PerfumedNiceRhinoceros/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/UnwrittenFrankHeterodontosaurus/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/BlindBiodegradableAlaskajingle/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/DigitalElatedVicuna/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/ThreadbareIncredibleFlycatcher/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/NaughtyGracefulSquirrel/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/BrownSuburbanMacaw/capture.png" alt="" data-carousel-image />
|
||||||
|
<img src="https://files.keavon.com/-/AbsoluteAwkwardGrouse/capture.png" alt="" data-carousel-image />
|
||||||
|
</div>
|
||||||
|
<div class="screenshot-details">
|
||||||
|
<div class="carousel-controls">
|
||||||
|
<button class="direction prev" data-carousel-prev>
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
|
||||||
|
<polygon points="24.71,10.71 23.29,9.29 12.59,20 23.29,30.71 24.71,29.29 15.41,20" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="dot active" data-carousel-dot></button>
|
||||||
|
<button class="dot" data-carousel-dot></button>
|
||||||
|
<button class="dot" data-carousel-dot></button>
|
||||||
|
<button class="dot" data-carousel-dot></button>
|
||||||
|
<button class="dot" data-carousel-dot></button>
|
||||||
|
<button class="dot" data-carousel-dot></button>
|
||||||
|
<button class="direction next" data-carousel-next>
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
|
||||||
|
<polygon points="16.71,9.29 15.29,10.71 24.59,20 15.29,29.29 16.71,30.71 27.41,20" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<blockquote class="balance-text">Lighthouse built on a rock outcropping in stormy high seas</blockquote>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section> -->
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
+++
|
||||||
|
title = "Nodes"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 1
|
||||||
|
+++
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
+++
|
+++
|
||||||
title = "Graphite license"
|
title = "Graphite license"
|
||||||
template = "page.html"
|
|
||||||
+++
|
+++
|
||||||
|
|
||||||
<section class="section-row">
|
<section class="section-row reading-material">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
|
||||||
# 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.
|
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.
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
@ -1,30 +1,35 @@
|
||||||
+++
|
+++
|
||||||
title = "Graphite logo"
|
title = "Graphite logo"
|
||||||
template = "page.html"
|
|
||||||
|
|
||||||
[extra]
|
[extra]
|
||||||
css = "/logo.css"
|
css = ["/logo.css"]
|
||||||
+++
|
+++
|
||||||
|
|
||||||
<section class="section-row">
|
<section class="section-row reading-material">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
|
|
||||||
# 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.
|
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.
|
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:
|
||||||
|
|
||||||
The Graphite 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.
|
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 <https://graphite.rs>.
|
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 <https://graphite.rs>.
|
||||||
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.
|
4. Commercial use of the logo (for example, merchandise sales) is not allowed without permission.
|
||||||
|
|
||||||
If in doubt, please send an email to <contact@graphite.rs> for permission.
|
If in doubt, please <a href="/contact">get in touch</a> 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.
|
Download the complete [logo kit](https://static.graphite.rs/logos/graphite-logo-kit.zip) or a specific version in PNG or SVG format below.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
+++
|
||||||
|
title = "Press resources"
|
||||||
|
+++
|
||||||
|
|
||||||
|
<section class="section-row reading-material">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# 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 <contact@graphite.rs> 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
+++
|
|
||||||
title = "Press resources"
|
|
||||||
template = "page.html"
|
|
||||||
+++
|
|
||||||
|
|
||||||
<section class="section-row">
|
|
||||||
<div class="section">
|
|
||||||
|
|
||||||
# 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 <contact@graphite.rs> 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.
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
+++
|
||||||
|
title = "Volunteer"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
css = ["/volunteer.css"]
|
||||||
|
+++
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
# Volunteer
|
||||||
|
|
||||||
|
Graphite is built by volunteers. Join the effort to bring great, free creative software to the world.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section-row">
|
||||||
|
|
||||||
|
<div class="diptych">
|
||||||
|
|
||||||
|
<div class="section creative-contributions">
|
||||||
|
|
||||||
|
## Creative contributions
|
||||||
|
|
||||||
|
<a href="https://discord.graphite.rs" class="button arrow">Ask how to begin (Discord)</a>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="info-box">
|
||||||
|
|
||||||
|
### PUBLICITY TEAM
|
||||||
|
|
||||||
|
Become the author of feature announcements, [blog](/blog) posts, website content, the user manual, press releases, social media posts, and industry outreach.
|
||||||
|
<!-- Become the author of feature announcements, [blog](/blog) posts, website content, the [user manual](/learn), press releases, social media posts, and industry outreach. -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="section code-contributions">
|
||||||
|
|
||||||
|
## Code contributions
|
||||||
|
|
||||||
|
<a href="/volunteer/guide" class="button arrow">Contributor guide</a>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="info-box">
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
+++
|
||||||
|
title = "Codebase overview"
|
||||||
|
template = "book.html"
|
||||||
|
page_template = "book.html"
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
order = 2 # Chapter number
|
||||||
|
+++
|
||||||
|
|
||||||
|
<div class="video-embed aspect-16x9">
|
||||||
|
<iframe width="1280" height="720" src="https://www.youtube.com/embed/vUzIeg8frh4" title="Workshop: Intro to Coding for Graphite" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
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<LayerId>, 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))
|
||||||
|
```
|
||||||
|
|
@ -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:
|
||||||
|
|
||||||
|

|
||||||
|
|
@ -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 (<kbd>F12</kbd>) 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.
|
||||||
|
|
@ -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)
|
||||||
|
<!-- - Vello: GPU-accelerated vector graphics renderer -->
|
||||||
|
<!-- - COSMIC Text: Text shaping and typesetting -->
|
||||||
|
<!-- - Wasmer or Wasmtime: Portable, sandboxed runtime for custom nodes -->
|
||||||
|
<!-- - Tokio: parallelized job execution in the node graph pipeline -->
|
||||||
|
<!-- - Xilem: High-performance native UI framework, to replace Tauri when ready -->
|
||||||
|
|
@ -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 <http://localhost:8080> 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 <kbd>Ctrl</kbd><kbd>C</kbd> 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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
- Interface
|
||||||
- Title bar
|
- Title bar
|
||||||
- Menu bar
|
- Menu bar
|
||||||
|
|
@ -1,43 +1,67 @@
|
||||||
# Feature goals
|
+++
|
||||||
|
title = "Uses and workflows"
|
||||||
|
|
||||||
## Possible use cases
|
[extra]
|
||||||
|
order = 2 # Page number after chapter intro
|
||||||
|
+++
|
||||||
|
|
||||||
- Photography
|
**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
|
- RAW photo editing/processing
|
||||||
- Batch processing pipeline
|
- Batch processing pipeline
|
||||||
- Motion graphics
|
|
||||||
|
### Motion graphics
|
||||||
- Title sequences/kinetic typography/etc.
|
- Title sequences/kinetic typography/etc.
|
||||||
- Live broadcast/streaming
|
|
||||||
|
### Live broadcast/streaming
|
||||||
- Live video compositing/overlays
|
- Live video compositing/overlays
|
||||||
- Interactive exhibits (rendering live content for museum/art/festival exhibits)
|
- Interactive exhibits (rendering live content for museum/art/festival exhibits)
|
||||||
- Web
|
|
||||||
|
### Web
|
||||||
- SVG design
|
- SVG design
|
||||||
- Animated or interactive SVG design
|
- Animated or interactive SVG design
|
||||||
- Automation
|
|
||||||
|
### Automation
|
||||||
- Batch multimedia editing/conversion
|
- Batch multimedia editing/conversion
|
||||||
- Graphic design
|
|
||||||
|
### Graphic design
|
||||||
- Print design
|
- Print design
|
||||||
- Web/digital-focused graphics (marketing, branding, infographics, ads, etc.)
|
- 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)
|
- Templates filled with file/spreadsheet data (e.g. prototyping/iterating components of a board/card game)
|
||||||
- Illustration
|
|
||||||
|
### Illustration
|
||||||
- Digital painting
|
- Digital painting
|
||||||
- Logo and icon design
|
- Logo and icon design
|
||||||
- Desktop publishing
|
|
||||||
|
### Desktop publishing
|
||||||
- Templates filled with Markdown/HTML content with export to PDF
|
- Templates filled with Markdown/HTML content with export to PDF
|
||||||
- Video compositing
|
|
||||||
- Data Visualization
|
### Video compositing
|
||||||
|
|
||||||
|
### Data Visualization
|
||||||
- Data-powered graphs/charts/etc.
|
- Data-powered graphs/charts/etc.
|
||||||
- Automated rendering with live/often-updated data
|
- Automated rendering with live/often-updated data
|
||||||
- 3D/Gamedev
|
|
||||||
|
### 3D/Gamedev
|
||||||
- PBR procedural material authorship
|
- PBR procedural material authorship
|
||||||
- 3D model UV map texturing
|
- 3D model UV map texturing
|
||||||
- AI-assisted tools
|
|
||||||
- HDR processing
|
### AI-assisted tools
|
||||||
- 360° and panoramic stitching and spherical editing
|
|
||||||
|
### HDR processing
|
||||||
|
|
||||||
|
### 360° and panoramic stitching and spherical editing
|
||||||
|
|
||||||
## User stories
|
## 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
|
### Photography
|
||||||
- Using a face detection node to sort photos into the correct folders upon export
|
- 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
|
- Creating a chart from a CSV
|
||||||
- Rendering an always-up-to-date chart powered by real-time updates from a database
|
- 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
|
- 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
|
### Digital painting
|
||||||
- Creating a digital acrylic or oil painting using various brushes
|
- 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
|
- 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
|
- 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
|
### Graphic design
|
||||||
- Prototyping cards for board games fed with data in a spreadsheet which generates the cards from a template
|
- 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
|
- 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.
|
- 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
|
- 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
|
- 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
|
- 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
|
- 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
|
### Automation
|
||||||
- Laser cutter artwork processing for automating custom Etsy orders
|
- 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)
|
- 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
|
### 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)
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#article {
|
.reading-material {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
display: flex;
|
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 {
|
.social {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -28,6 +28,7 @@
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -37,9 +38,8 @@
|
||||||
#articles {
|
#articles {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 80px;
|
|
||||||
|
|
||||||
article {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px 80px;
|
gap: 20px 80px;
|
||||||
|
|
||||||
|
|
@ -58,10 +58,14 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|
||||||
.headline a {
|
.headline {
|
||||||
|
margin-top: -0.5em;
|
||||||
|
|
||||||
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--color-navy);
|
color: var(--color-navy);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.publication {
|
.publication {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
#fundraising {
|
||||||
|
background-color: var(--color-seaside);
|
||||||
|
color: rgba(0, 0, 0, 0.9);
|
||||||
|
|
||||||
|
.graphic {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,79 @@
|
||||||
#upcoming-tech {
|
#roadmap {
|
||||||
background-color: var(--color-navy);
|
width: 100%;
|
||||||
color: var(--color-fog);
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
.roadmap {
|
||||||
color: var(--color-mustard);
|
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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="%2316323f"><polygon points="6.69 14.57 0 9 1.8 6.84 6.32 10.61 13.83 1.43 16 3.2 6.69 14.57" /></svg>');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ongoing::after {
|
||||||
|
background: var(--color-lemon);
|
||||||
|
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="%2316323f"><path d="m8,2c3.31,0,6,2.69,6,6s-2.69,6-6,6-6-2.69-6-6,2.69-6,6-6m0-2C3.58,0,0,3.58,0,8s3.58,8,8,8,8-3.58,8-8S12.42,0,8,0h0Zm2.83,9.41l-1.83-1.83v-2.59c0-.55-.45-1-1-1s-1,.45-1,1v3c0,.13.03.26.08.38.05.12.12.23.22.33l2.12,2.12c.2.2.45.29.71.29s.51-.1.71-.29c.39-.39.39-1.02,0-1.41Z" /></svg>');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.heading {
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
.page {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
@ -23,7 +27,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#quick-links {
|
#quick-links {
|
||||||
margin-bottom: calc(120 * var(--variable-px));
|
margin-top: calc(80 * var(--variable-px));
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: calc(var(--font-size-link) * 0.8);
|
gap: calc(var(--font-size-link) * 0.8);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
@ -41,8 +45,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#hero-message {
|
#hero-message {
|
||||||
@media screen and (max-width: 1400px) {
|
h1 {
|
||||||
|
font-size: var(--font-size-intro-heading);
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
font-size: var(--font-size-intro-body);
|
||||||
|
|
||||||
|
@media screen and (max-width: 1400px) {
|
||||||
max-width: unset !important;
|
max-width: unset !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -80,162 +90,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#screenshots {
|
#graphite-tomorrow > img {
|
||||||
transform: translate(0);
|
margin: 16px 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#disciplines {
|
#disciplines {
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.informational-group {
|
.informational-group {
|
||||||
margin-top: 0;
|
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 {
|
#community {
|
||||||
|
background-color: var(--color-lime);
|
||||||
|
|
||||||
#newsletter {
|
#newsletter {
|
||||||
#newsletter-success {
|
#newsletter-success {
|
||||||
background: var(--color-crimson);
|
background: var(--color-crimson);
|
||||||
|
|
@ -276,6 +275,10 @@
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
flex: 100000 1 0;
|
flex: 100000 1 0;
|
||||||
|
|
||||||
|
div {
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1200px) {
|
@media screen and (max-width: 1200px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
|
|
@ -291,6 +294,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
--input-focus-color: var(--color-ale);
|
||||||
|
|
||||||
&.name {
|
&.name {
|
||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
|
|
@ -314,7 +318,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.submit {
|
&.submit {
|
||||||
flex: 1 0 0;
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
label,
|
label,
|
||||||
|
|
@ -344,7 +348,7 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: var(--color-mustard);
|
border-color: var(--input-focus-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -354,8 +358,8 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: var(--color-mustard);
|
border-color: var(--input-focus-color);
|
||||||
color: var(--color-mustard);
|
color: var(--input-focus-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -384,6 +388,7 @@
|
||||||
|
|
||||||
span {
|
span {
|
||||||
line-height: 48px;
|
line-height: 48px;
|
||||||
|
margin: 0;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -391,40 +396,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#upcoming-tech {
|
#demo-video {
|
||||||
background-color: var(--color-navy);
|
max-width: 1000px;
|
||||||
color: var(--color-fog);
|
}
|
||||||
|
|
||||||
a {
|
#fundraising {
|
||||||
color: var(--color-mustard);
|
background-color: var(--color-seaside);
|
||||||
|
color: rgba(0, 0, 0, 0.9);
|
||||||
|
|
||||||
|
.graphic {
|
||||||
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#get-involved-box {
|
||||||
#recent-news {
|
background-color: var(--color-lemon);
|
||||||
background-color: var(--color-mustard);
|
background-blend-mode: color-burn;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const NAV_BUTTON_INITIAL_FONT_SIZE = 32;
|
const NAV_BUTTON_INITIAL_FONT_SIZE = 32;
|
||||||
const RIPPLE_ANIMATION_MILLISECONDS = 100;
|
const RIPPLE_ANIMATION_MILLISECONDS = 100;
|
||||||
const RIPPLE_WIDTH = 140;
|
const RIPPLE_WIDTH = 120;
|
||||||
const HANDLE_STRETCH = 0.4;
|
const HANDLE_STRETCH = 0.4;
|
||||||
|
|
||||||
let ripplesInitialized;
|
let ripplesInitialized;
|
||||||
|
|
@ -29,7 +29,18 @@ function initializeRipples() {
|
||||||
goingUp: false,
|
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) => {
|
ripples.forEach((ripple) => {
|
||||||
const updateTimings = (goingUp) => {
|
const updateTimings = (goingUp) => {
|
||||||
|
|
@ -51,7 +62,7 @@ function initializeRipples() {
|
||||||
ripple.element.addEventListener("pointerleave", () => updateTimings(false));
|
ripple.element.addEventListener("pointerleave", () => updateTimings(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
ripples[activeRippleIndex] = {
|
if (activeRippleIndex >= 0) ripples[activeRippleIndex] = {
|
||||||
...ripples[activeRippleIndex],
|
...ripples[activeRippleIndex],
|
||||||
animationStartTime: 1,
|
animationStartTime: 1,
|
||||||
animationEndTime: 1 + RIPPLE_ANIMATION_MILLISECONDS,
|
animationEndTime: 1 + RIPPLE_ANIMATION_MILLISECONDS,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}Page not found{% endblock title %}
|
{% block title %}Page not found{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section id="404" class="section-row">
|
<section id="404" class="section-row">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h1>Page not found.</h1>
|
<h1>Page not found</h1>
|
||||||
|
<p>Or as the machines like to say it: <code>404</code>.</p>
|
||||||
<br />
|
<br />
|
||||||
<a href="/" class="link arrow">Home Page</a>
|
<a href="/" class="button arrow">Home Page</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,40 @@
|
||||||
{% extends "base.html" %}
|
{% 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 %}
|
{% block head %}
|
||||||
|
{% set this = section | default(value = page) %}
|
||||||
<link rel="stylesheet" href="/article.css">
|
<link rel="stylesheet" href="/article.css">
|
||||||
<meta property="og:title" content="{{ page.title }}.">
|
<meta property="og:title" content="{{ this.title }}.">
|
||||||
<meta property="og:type" content="article" />
|
<meta property="og:type" content="article" />
|
||||||
<meta property="og:image" content="{{ page.extra.banner | safe }}">
|
<meta property="og:image" content="{{ this.extra.banner | safe }}">
|
||||||
<meta property="og:url" content="{{ current_url | safe }}">
|
<meta property="og:url" content="{{ current_url | safe }}">
|
||||||
{% endblock head %}
|
{% endblock head %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section id="article" class="section-row">
|
{% set this = section | default(value = page) %}
|
||||||
|
|
||||||
|
<section class="section-row reading-material">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<h2 class="headline">{{ page.title }}.</h2>
|
<h1 class="headline">{{ this.title }}</h2>
|
||||||
<span class="publication">By {{ page.extra.author }}. {{ page.date | date(format="%B %d, %Y", timezone="America/Los_Angeles") }}.</span>
|
<span class="publication">By {{ this.extra.author }}. {{ this.date | date(format = "%B %d, %Y", timezone="America/Los_Angeles") }}.</span>
|
||||||
<img class="banner" src="{{ page.extra.banner | safe }}" />
|
<img class="banner" src="{{ this.extra.banner | safe }}" />
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<article>
|
<article>
|
||||||
{{ page.content | safe }}
|
{{ this.content | safe }}
|
||||||
</article>
|
</article>
|
||||||
{% if page.extra.reddit or page.extra.twitter %}
|
{% if this.extra.reddit or this.extra.twitter %}
|
||||||
<hr />
|
<hr />
|
||||||
<div class="social">
|
<div class="social">
|
||||||
{% if page.extra.reddit %}
|
{% if this.extra.reddit %}
|
||||||
<a href="{{ page.extra.reddit | safe }}" target="_blank" class="button arrow">
|
<a href="{{ this.extra.reddit | safe }}" target="_blank" class="button arrow">
|
||||||
<img src="https://static.graphite.rs/icons/reddit.svg" /><span>Comment on Reddit</span>
|
<img src="https://static.graphite.rs/icons/reddit.svg" /><span>Comment on Reddit</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if page.extra.twitter %}
|
{% if this.extra.twitter %}
|
||||||
<a href="{{ page.extra.twitter | safe }}" target="_blank" class="button arrow">
|
<a href="{{ this.extra.twitter | safe }}" target="_blank" class="button arrow">
|
||||||
<img src="https://static.graphite.rs/icons/twitter.svg" /><span>Comment on Twitter</span>
|
<img src="https://static.graphite.rs/icons/twitter.svg" /><span>Comment on Twitter</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,32 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
|
{% set this = section | default(value = page | default(value = false)) %}
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, minimum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, minimum-scale=1">
|
||||||
<title>Graphite | {% block title %}{% endblock title %}.</title>
|
<title>Graphite | {% block title %}{% endblock title %}</title>
|
||||||
{% block rss %}
|
{% block rss %}
|
||||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="{{ get_url(path = 'blog/rss.xml', trailing_slash = false) }}">
|
<link rel="alternate" type="application/rss+xml" title="RSS" href="{{ get_url(path = 'blog/rss.xml', trailing_slash = false) }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block head %}{% endblock head %}
|
|
||||||
<link rel="stylesheet" href="/base.css">
|
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bona+Nova:wght@400;700">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Bona+Nova:wght@700&family=EB+Garamond:ital,wght@0,500;1,500&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/base.css">
|
||||||
|
<link rel="stylesheet" href="/syntax-highlighting.css">
|
||||||
|
{% if this and this.extra.css %}
|
||||||
|
{% for css in this.extra.css %}
|
||||||
|
<link rel="stylesheet" href="{{ css | safe }}">
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% block head %}{% endblock head %}
|
||||||
|
{% if this and this.extra.js %}
|
||||||
|
{% for js in this.extra.js %}
|
||||||
|
<script src="/js/{{ js | safe }}"></script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
<script src="/js/navbar.js"></script>
|
<script src="/js/navbar.js"></script>
|
||||||
|
<script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.outbound-links.file-downloads.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.balance-text {
|
.balance-text {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
@ -19,24 +34,19 @@
|
||||||
|
|
||||||
@media (scripting: none) {
|
@media (scripting: none) {
|
||||||
.balance-text {
|
.balance-text {
|
||||||
visibility: visible;
|
visibility: visible !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (text-wrap: balance) {
|
@supports (text-wrap: balance) {
|
||||||
.balance-text {
|
.balance-text,
|
||||||
|
.balanced-text {
|
||||||
|
text-align: left;
|
||||||
text-wrap: balance;
|
text-wrap: balance;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<noscript>
|
|
||||||
<style>
|
|
||||||
.balance-text {
|
|
||||||
visibility: visible !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</noscript>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="page">
|
<div class="page">
|
||||||
|
|
@ -49,9 +59,12 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<a href="/blog">Blog</a>
|
<!-- <a href="/learn">Learn</a> -->
|
||||||
<a href="/features">Features</a>
|
<a href="/features">Features</a>
|
||||||
<a href="/contribute">Contribute</a>
|
<a href="/about">About</a>
|
||||||
|
<a href="/blog">Blog</a>
|
||||||
|
<a href="/volunteer">Volunteer</a>
|
||||||
|
<a href="/donate">Donate</a>
|
||||||
<a href="https://editor.graphite.rs" class="button arrow">Launch</a>
|
<a href="https://editor.graphite.rs" class="button arrow">Launch</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -69,14 +82,13 @@
|
||||||
<footer>
|
<footer>
|
||||||
<hr />
|
<hr />
|
||||||
<nav>
|
<nav>
|
||||||
<a href="https://github.com/GraphiteEditor/Graphite/tree/master/docs" class="link not-uppercase">Documentation</a>
|
|
||||||
<a href="https://github.com/GraphiteEditor/Graphite/graphs/contributors" class="link not-uppercase">Credits</a>
|
<a href="https://github.com/GraphiteEditor/Graphite/graphs/contributors" class="link not-uppercase">Credits</a>
|
||||||
<a href="/license" class="link not-uppercase">License</a>
|
<a href="/license" class="link not-uppercase">License</a>
|
||||||
<a href="/logo" class="link not-uppercase">Logo</a>
|
<a href="/logo" class="link not-uppercase">Logo</a>
|
||||||
<a href="/press" class="link not-uppercase">Press</a>
|
<a href="/press" class="link not-uppercase">Press</a>
|
||||||
<a href="/contact" class="link not-uppercase">Contact</a>
|
<a href="/contact" class="link not-uppercase">Contact</a>
|
||||||
</nav>
|
</nav>
|
||||||
<span>Copyright © {{ now() | date(format="%Y") }} Graphite contributors.</span>
|
<span>Copyright © {{ now() | date(format = "%Y") }} Graphite contributors</span>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://static.graphite.rs/text-balancer/text-balancer.js"></script>
|
<script src="https://static.graphite.rs/text-balancer/text-balancer.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block title %}{{ section.title }}{% endblock title %}
|
{% block title %}{{ section.title }}{% endblock title %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
|
|
@ -10,13 +11,13 @@
|
||||||
|
|
||||||
<section id="articles" class="section">
|
<section id="articles" class="section">
|
||||||
{% for page in section.pages %}
|
{% for page in section.pages %}
|
||||||
<article>
|
<section>
|
||||||
<div class="banner">
|
<div class="banner">
|
||||||
<a href="{{ page.permalink | safe }}"><img src="{{ page.extra.banner | safe }}" /></a>
|
<a href="{{ page.path }}"><img src="{{ page.extra.banner | safe }}" /></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<div class="headline">
|
<div class="headline">
|
||||||
<h2><a href="{{ page.permalink | safe }}">{{ page.title }}.</a></h2>
|
<h2><a href="{{ page.path }}">{{ page.title }}</a></h2>
|
||||||
</div>
|
</div>
|
||||||
<span class="publication">By {{ page.extra.author }}. {{ page.date | date(format = "%B %d, %Y", timezone = "America/Los_Angeles") }}.</span>
|
<span class="publication">By {{ page.extra.author }}. {{ page.date | date(format = "%B %d, %Y", timezone = "America/Los_Angeles") }}.</span>
|
||||||
<div class="summary">
|
<div class="summary">
|
||||||
|
|
@ -25,10 +26,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="keep-reading">
|
<div class="keep-reading">
|
||||||
<a href="{{ page.permalink | safe }}" class="link arrow">Keep Reading</a>
|
<a href="{{ page.path }}" class="link arrow">Keep Reading</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</section>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{% set this = section | default(value = page) %}{{ this.title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="/book.css">
|
||||||
|
<script src="/js/book.js"></script>
|
||||||
|
{% 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 %}
|
||||||
|
|
||||||
|
<section class="three-column-layout">
|
||||||
|
<aside class="chapters">
|
||||||
|
<ul>
|
||||||
|
<li class="title {% if current_path == book.path %}active{% endif %}"><a href="{{ book.path }}">{{ book.title }}</a></li>
|
||||||
|
</ul>
|
||||||
|
{% for chapter in chapters %}
|
||||||
|
<ul>
|
||||||
|
<li class="chapter {% if current_path == chapter.path %}active{% endif %}"><a href="{{ chapter.path }}">» {{ chapter.title }}</a></li>
|
||||||
|
|
||||||
|
{% set_global flat_pages = flat_pages | concat(with = chapter) %}
|
||||||
|
{% if chapter == this %}{% set_global flat_index_of_this = flat_pages | length - 1 %}{% endif %}
|
||||||
|
|
||||||
|
{% if chapter.pages %}
|
||||||
|
|
||||||
|
{% for page in chapter.pages | sort(attribute = "extra.order") %}
|
||||||
|
|
||||||
|
{% set_global flat_pages = flat_pages | concat(with = page) %}
|
||||||
|
{% if page == this %}{% set_global flat_index_of_this = flat_pages | length - 1 %}{% endif %}
|
||||||
|
|
||||||
|
<li {% if current_path == page.path %}class="active"{% endif %}><a href="{{ page.path }}">» {{ page.title }}</a></li>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="section-row reading-material">
|
||||||
|
<div class="section">
|
||||||
|
<h1>{{ this.title }}</h1>
|
||||||
|
<article>
|
||||||
|
{{ this.content | safe }}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="prev-next">
|
||||||
|
{% if flat_index_of_this >= 1 %}
|
||||||
|
{% set prev = flat_pages | nth(n = flat_index_of_this - 1) %}
|
||||||
|
{% endif %}
|
||||||
|
{% if prev %}
|
||||||
|
<a href="{{ prev.path }}">
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
|
||||||
|
<polygon points="24.71,10.71 23.29,9.29 12.59,20 23.29,30.71 24.71,29.29 15.41,20" />
|
||||||
|
</svg>
|
||||||
|
{{ prev.title }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a><!-- Spacer --></a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if flat_index_of_this < flat_pages | length - 1 %}
|
||||||
|
{% set next = flat_pages | nth(n = flat_index_of_this + 1) %}
|
||||||
|
{% endif %}
|
||||||
|
{% if next %}
|
||||||
|
<a href="{{ next.path }}">
|
||||||
|
{{ next.title }}
|
||||||
|
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
|
||||||
|
<polygon points="16.71,9.29 15.29,10.71 24.59,20 15.29,29.29 16.71,30.71 27.41,20" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<aside class="contents">
|
||||||
|
<ul>
|
||||||
|
<li class="title">
|
||||||
|
<a href="#">
|
||||||
|
{% if this.toc | length > 0 %}
|
||||||
|
Contents<span> (top 🡑)</span>
|
||||||
|
{% else %}
|
||||||
|
Back to top 🡑
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
{% for depth_1 in this.toc %}
|
||||||
|
<li><a href="#{{ depth_1.id }}">{{ depth_1.title }}</a></li>
|
||||||
|
{% for depth_2 in depth_1.children %}
|
||||||
|
<ul>
|
||||||
|
<li><a href="#{{ depth_2.id }}">{{ depth_2.title }}</a></li>
|
||||||
|
{% for depth_3 in depth_2.children %}
|
||||||
|
<ul>
|
||||||
|
<li><a href="#{{ depth_3.id }}">{{ depth_3.title }}</a></li>
|
||||||
|
{% for depth_4 in depth_3.children %}
|
||||||
|
<ul>
|
||||||
|
<li><a href="#{{ depth_4.id }}">{{ depth_4.title }}</a></li>
|
||||||
|
{% for depth_5 in depth_4.children %}
|
||||||
|
<ul>
|
||||||
|
<li><a href="#{{ depth_5.id }}">{{ depth_5.title }}</a></li>
|
||||||
|
{% for depth_6 in depth_5.children %}
|
||||||
|
<ul>
|
||||||
|
<li><a href="#{{ depth_6.id }}">{{ depth_6.title }}</a></li>
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
</section>
|
||||||
|
{% endblock content %}
|
||||||
|
|
@ -1,340 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}Redefining state-of-the-art graphics editing{% endblock title %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<link rel="stylesheet" href="/index.css">
|
|
||||||
{% endblock head %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<section id="logo">
|
|
||||||
<img src="https://static.graphite.rs/logos/graphite-logotype-color.svg" alt="Graphite Logo" />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<img class="pencil-texture" src="https://static.graphite.rs/textures/pencil-texture.png"></img>
|
|
||||||
|
|
||||||
<section id="quick-links">
|
|
||||||
<a href="#newsletter" class="button arrow">Subscribe to the newsletter</a>
|
|
||||||
<a href="https://editor.graphite.rs" class="button arrow">Instantly launch the web editor</a>
|
|
||||||
<div>
|
|
||||||
<a href="https://github.com/GraphiteEditor/Graphite" target="_blank">
|
|
||||||
<img src="https://static.graphite.rs/icons/github.svg" alt="GitHub" />
|
|
||||||
</a>
|
|
||||||
<a href="https://www.reddit.com/r/graphite/" target="_blank">
|
|
||||||
<img src="https://static.graphite.rs/icons/reddit.svg" alt="Reddit" />
|
|
||||||
</a>
|
|
||||||
<a href="https://discord.graphite.rs" target="_blank">
|
|
||||||
<img src="https://static.graphite.rs/icons/discord.svg" alt="Discord" />
|
|
||||||
</a>
|
|
||||||
<a href="https://twitter.com/graphiteeditor" target="_blank">
|
|
||||||
<img src="https://static.graphite.rs/icons/twitter.svg" alt="Twitter" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="hero-message">
|
|
||||||
<h1 class="hero">Redefining state-of-the-art graphics editing.</h1>
|
|
||||||
<p class="balance-text"><strong>Graphite</strong> 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.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="hexagons">
|
|
||||||
<div>
|
|
||||||
<svg viewBox="0 0 1400 1215.42" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<polygon points="1049.43,0.99 350.57,0.99 1.14,607.71 350.57,1214.44 1049.43,1214.44 1398.86,607.71" />
|
|
||||||
<polygon points="1016.39,57.57 383.61,57.57 67.22,607.71 383.61,1157.85 1016.39,1157.85 1332.78,607.71" />
|
|
||||||
<polygon points="964.49,149.01 435.51,149.01 171.02,607.71 435.51,1066.41 964.49,1066.41 1228.98,607.71" />
|
|
||||||
<polygon points="875.52,304.71 524.48,304.71 348.96,607.71 524.48,910.71 875.52,910.71 1051.04,607.71" />
|
|
||||||
<polygon points="768.12,490.96 631.88,490.96 563.78,607.71 631.88,724.47 768.12,724.47 836.22,607.71" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section id="screenshots">
|
|
||||||
<div class="carousel">
|
|
||||||
<img src="https://static.graphite.rs/content/index/gui-demo-valley-of-spires.png" />
|
|
||||||
<!-- <img src="https://static.graphite.rs/content/index/gui-mockup-viewport__2.png" /> -->
|
|
||||||
<img src="https://static.graphite.rs/content/index/gui-mockup-nodes__2.png" />
|
|
||||||
</div>
|
|
||||||
<div class="carousel torn left">
|
|
||||||
<img src="https://static.graphite.rs/content/index/gui-demo-valley-of-spires.png" />
|
|
||||||
<!-- <img src="https://static.graphite.rs/content/index/gui-mockup-viewport__2.png" /> -->
|
|
||||||
<img src="https://static.graphite.rs/content/index/gui-mockup-nodes__2.png" />
|
|
||||||
</div>
|
|
||||||
<div class="carousel torn right">
|
|
||||||
<img src="https://static.graphite.rs/content/index/gui-demo-valley-of-spires.png" />
|
|
||||||
<!-- <img src="https://static.graphite.rs/content/index/gui-mockup-viewport__2.png" /> -->
|
|
||||||
<img src="https://static.graphite.rs/content/index/gui-mockup-nodes__2.png" />
|
|
||||||
</div>
|
|
||||||
<div class="screenshot-details">
|
|
||||||
<div class="carousel-controls">
|
|
||||||
<button class="direction prev">
|
|
||||||
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
|
|
||||||
<polygon points="24.71,10.71 23.29,9.29 12.59,20 23.29,30.71 24.71,29.29 15.41,20" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="dot active"></button>
|
|
||||||
<!-- <button class="dot"></button> -->
|
|
||||||
<button class="dot"></button>
|
|
||||||
<button class="direction next">
|
|
||||||
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M20,0C8.95,0,0,8.95,0,20c0,11.05,8.95,20,20,20c11.05,0,20-8.95,20-20C40,8.95,31.05,0,20,0z M20,38c-9.93,0-18-8.07-18-18S10.07,2,20,2s18,8.07,18,18S29.93,38,20,38z" />
|
|
||||||
<polygon points="16.71,9.29 15.29,10.71 24.59,20 15.29,29.29 16.71,30.71 27.41,20" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="screenshot-description">
|
|
||||||
<p class="balance-text active">
|
|
||||||
"Valley of Spires" vector artwork made in Graphite, a web-based vector graphics editor.
|
|
||||||
</p>
|
|
||||||
<!-- <p class="balance-text">
|
|
||||||
Viewport interface mockup showcasing a photo editing project that utilizes Graphite's raster graphics pipeline, one of the upcoming roadmap milestones. Photo editing is not yet supported.
|
|
||||||
</p> -->
|
|
||||||
<p class="balance-text">
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="opener-message" class="section-row">
|
|
||||||
<div class="section">
|
|
||||||
<h1>A 2D creative tool made for everyone.</h1>
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
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
|
|
||||||
<a href="/license" target="_blank">license</a>
|
|
||||||
and usable
|
|
||||||
<a href="https://editor.graphite.rs">instantly through a web browser</a>
|
|
||||||
or an upcoming native client on Windows, Mac, and Linux.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
<!-- <a href="/blog/mission-statement" class="link arrow">Mission Statement</a> -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="demo-video" class="section-row">
|
|
||||||
<div class="section">
|
|
||||||
<div class="video-embed aspect-16x9">
|
|
||||||
<iframe width="1280" height="720" src="https://www.youtube.com/embed/JgJvAHQLnXA" title="Graphite Vector Editing: "Commander Basstronaut" Artwork (25x Timelapse)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
|
||||||
</div>
|
|
||||||
<!-- <a href="/blog/mission-statement" class="link arrow">Mission Statement</a> -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="disciplines" class="section-row">
|
|
||||||
<div class="section">
|
|
||||||
<h1>One app to rule them all.</h1>
|
|
||||||
<p>Stop jumping between programs. Planned developments will make Graphite a first-class design tool for these disciplines (listed by priority):</p>
|
|
||||||
<div class="informational-group concepts">
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 12" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Graphic Design</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 13" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Image Editing</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Digital Painting</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 15" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Desktop Publishing</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 16" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>VFX Compositing</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 17" src="https://static.graphite.rs/content/index/icon-atlas-features-preview.png" alt="" />
|
|
||||||
<span>Motion Graphics</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="community" class="section-row">
|
|
||||||
<div class="diptych">
|
|
||||||
<div id="newsletter" class="section">
|
|
||||||
<h1>Stay in the loop.</h1>
|
|
||||||
<p>
|
|
||||||
Subscribe to the newsletter for quarterly updates on major development progress.
|
|
||||||
</p>
|
|
||||||
<div id="newsletter-success">
|
|
||||||
<h2>Thanks!</h2>
|
|
||||||
<p>You'll receive your first newsletter email with the next major Graphite news.</p>
|
|
||||||
</div>
|
|
||||||
<form action="https://graphite.rs/newsletter-signup" method="post">
|
|
||||||
<div class="same-line">
|
|
||||||
<div class="column name">
|
|
||||||
<label for="newsletter-name">First + last name:</label>
|
|
||||||
<input id="newsletter-name" name="name" type="text" required />
|
|
||||||
</div>
|
|
||||||
<div class="column phone">
|
|
||||||
<label for="newsletter-phone">Phone:</label>
|
|
||||||
<input id="newsletter-phone" name="phone" type="text" tabindex="-1" autocomplete="off" />
|
|
||||||
</div>
|
|
||||||
<div class="column email">
|
|
||||||
<label for="newsletter-email">Email address:</label>
|
|
||||||
<input id="newsletter-email" name="email" type="email" required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="column submit">
|
|
||||||
<input type="submit" value="Subscribe" class="button" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div id="social" class="section">
|
|
||||||
<h1>Follow along.</h1>
|
|
||||||
<p>
|
|
||||||
High-quality open source software is a community endeavor. Hang out with hundreds of friendly Graphite users and developers.
|
|
||||||
</p>
|
|
||||||
<div class="social-links">
|
|
||||||
<div class="column">
|
|
||||||
<a href="https://discord.graphite.rs" target="_blank">
|
|
||||||
<img src="https://static.graphite.rs/icons/discord.svg" alt="Discord" />
|
|
||||||
<span class="link arrow">Join on Discord</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://www.reddit.com/r/graphite/" target="_blank">
|
|
||||||
<img src="https://static.graphite.rs/icons/reddit.svg" alt="Reddit" />
|
|
||||||
<span class="link not-uppercase arrow">/r/Graphite</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
<a href="https://github.com/GraphiteEditor/Graphite" target="_blank">
|
|
||||||
<img src="https://static.graphite.rs/icons/github.svg" alt="GitHub" />
|
|
||||||
<span class="link arrow">Star on GitHub</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://twitter.com/graphiteeditor" target="_blank">
|
|
||||||
<img src="https://static.graphite.rs/icons/twitter.svg" alt="Twitter" />
|
|
||||||
<span class="link not-uppercase arrow">@GraphiteEditor</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="upcoming-tech" class="feature-box">
|
|
||||||
<div class="box">
|
|
||||||
<h1 class="box-header">Upcoming Tech <span> / </span> <a href="/features" class="link arrow">More details</a></h1>
|
|
||||||
<hr />
|
|
||||||
<div class="triptych">
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="balance-text">Non-destructive editing, powered by nodes.</h2>
|
|
||||||
<!-- <div class="graphic">
|
|
||||||
<img src="graphic.svg" alt="" />
|
|
||||||
</div> -->
|
|
||||||
<p>While working in Graphite, your edits are saved into the <em>Node Graph</em>. Its <em>nodes</em> represent operations and effects like Magic Wand selection and Blur. Node <em>parameters</em> 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.</p>
|
|
||||||
<!-- <a href="/blog/node-graph-explained" class="link arrow">Node Graph</a> -->
|
|
||||||
</div>
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="balance-text">Raster and vector art, crisp at any resolution.</h2>
|
|
||||||
<!-- <div class="graphic">
|
|
||||||
<img src="graphic.svg" alt="" />
|
|
||||||
</div> -->
|
|
||||||
<p>Just like <em>vector</em> artwork, which is based on curves instead of pixels to preserve quality at any scale, Graphite's <em>raster</em> paintbrushes, generators, and other tools
|
|
||||||
work the same way. A <em>resolution-agnostic</em> render engine lets you zoom infinitely and export at any size.</p>
|
|
||||||
<!-- <a href="/blog/rendering-pipeline-explained" class="link arrow">Rendering</a> -->
|
|
||||||
</div>
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="balance-text">Procedural superpowers, part of your art pipeline.</h2>
|
|
||||||
<!-- <div class="graphic">
|
|
||||||
<img src="graphic.svg" alt="" />
|
|
||||||
</div> -->
|
|
||||||
<p>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.</p>
|
|
||||||
<!-- <a href="/blog/graphene-explained" class="link arrow">Graphene</a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="more-features section-row right">
|
|
||||||
<!-- <div class="graphic">
|
|
||||||
<img src="graphic.svg" alt="" />
|
|
||||||
</div> -->
|
|
||||||
<div class="section">
|
|
||||||
<h2>More to come.</h2>
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Learn more about the planned technology in forthcoming Graphite releases:
|
|
||||||
</p>
|
|
||||||
<a href="/features" class="link arrow">Features</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="powered-by-rust" class="section-row left">
|
|
||||||
<div class="graphic">
|
|
||||||
<img src="https://static.graphite.rs/content/index/rust.svg" alt="" />
|
|
||||||
</div>
|
|
||||||
<div class="section">
|
|
||||||
<h1>Built for the future, powered by Rust.</h1>
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
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.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="get-involved" class="section-row right">
|
|
||||||
<div class="graphic">
|
|
||||||
<img src="https://static.graphite.rs/content/index/volunteer.svg" alt="" />
|
|
||||||
</div>
|
|
||||||
<div class="section">
|
|
||||||
<h1>Get involved.</h1>
|
|
||||||
<p>
|
|
||||||
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! <a href="https://discord.graphite.rs">Join the project Discord server</a> and ask how you can
|
|
||||||
help.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Rust, web, and graphics programmers should check out the <a href="/contribute">contribute</a> 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.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="recent-news" class="feature-box">
|
|
||||||
<div class="box">
|
|
||||||
<h1 class="box-header">Recent News <span> / </span> <a href="/blog" class="link arrow">More in the Blog</a></h1>
|
|
||||||
<hr />
|
|
||||||
<div class="diptych">
|
|
||||||
{% set articles = get_section(path="blog/_index.md") %}
|
|
||||||
{% set latest = articles.pages | slice(end=2) %}
|
|
||||||
{% for article in latest %}
|
|
||||||
<article class="section">
|
|
||||||
<div class="headline">
|
|
||||||
<h2><a href="{{ article.permalink | safe }}">{{ article.title }}.</a></h2>
|
|
||||||
</div>
|
|
||||||
<span class="publication">By {{ article.extra.author }}. {{ article.date | date(format="%B %d, %Y", timezone="America/Los_Angeles") }}.</span>
|
|
||||||
<div class="summary">
|
|
||||||
<div>
|
|
||||||
{{ article.summary | safe }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="keep-reading">
|
|
||||||
<a href="{{ article.permalink | safe }}" class="link arrow">Keep Reading</a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<script src="/js/carousel.js"></script>
|
|
||||||
{% endblock content %}
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}{{ section.title }}{% endblock title %}
|
|
||||||
|
|
||||||
{% block head %}
|
{% block title %}{{ page.title }}{% endblock title %}
|
||||||
{% if section.extra.css %}
|
|
||||||
<link rel="stylesheet" href="{{ section.extra.css | safe }}">
|
|
||||||
{% endif %}
|
|
||||||
{% endblock head %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ section.content | safe }}
|
{{ page.content | safe }}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ section.title }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{{ section.content | safe }}
|
||||||
|
{% endblock content %}
|
||||||
Loading…
Reference in New Issue