Skip to content
memo.d.foundation / handbook / memo-media-standardConnect

Handbook

Memo media and styling standard

One rule set for images, figures, alt text, dark-theme safety and imported styling on Dwarves Memo, with a test for every rule and the checklist editors run before approving a post.

Use with AIOpen in ClaudeOpen in ChatGPT

This page is the single rule set for images and figures on memo. It applies to every post, whichever door it came through. Contribute to memo summarizes it in chapters 2 and 5, and the contributor skills link here instead of copying it.

Each rule names who it applies to and how to test it.

  • Everyone covers repo posts and guest imports.
  • Repo posts are posts written in dwarvesf/brainery by members and fellows, through a branch or a fork.
  • Guest imports are posts memo converts from a writer's own site.

A rule that says target is a budget the editors check by hand. No automatic check blocks a post on a target today.

1Formats

  1. Everyone. Draw diagrams and charts as SVG, never as a screenshot of a diagram. Test: no raster file in the post holds a diagram.
  2. Everyone. Use a raster only for photos, screenshots and scans. Use webp at quality 80, or PNG after pngquant, for screenshots, and jpeg or webp for photos. Test: file <f> reports png, webp or jpeg.
  3. Repo posts. Reference one file per image. Do not commit a .webp twin beside a .png. Test: no two files in assets/ share a basename. Reason: the worker does not negotiate formats, so the twin is never served.
  4. Everyone. Use GIF or video only when motion carries meaning that a still cannot. Prefer a short mp4 to a GIF. Test: the post says in text what the motion shows.

2Byte budgets and dimensions

  1. Repo posts, target. A single-card screenshot is about 14 KB. A dense multi-panel shot may reach 50 KB. A real terminal window capture lands at 30 to 60 KB. Past that the shot does too much, so crop it.
  2. Everyone, target. No raster over 200 KB. Test: stat -f%z <f> prints 204800 or less. Reason: the compression step caps pixels and not bytes, and it passes an oversized file through. After compression, media-sync prints a warning for any raster still over 200 KB. The warning never fails the run.
  3. Everyone. No raster over 1600 pixels on the long edge. Test: identify -format "%w x %h" <f>.
  4. Everyone, target. A full-width raster is at least 704 pixels wide and at most 1600. Reason: the reading column is about 704 pixels, so a narrower image upscales and blurs.
  5. Everyone, target. An SVG is 60 KB or less. Over 150 KB needs a written reason in the pull request. Reason: across the 99 SVGs in the vault, the median is 4,735 bytes and the 90th percentile is 55,684.
  6. Repo posts. Author each SVG at a viewBox width of 720 to 760 with no fixed width or height, and add style="width:100%;height:auto;max-width:<W>px;display:block". Test: rg '<svg[^>]*\swidth="[0-9]+"' finds nothing. Reason: a fixed-width SVG wider than the column overflows into the sidebar.
  7. Guest imports. An inline SVG inside a figure.fig keeps a viewBox and sets no pixel width. Memo owns the figure box and ignores width, margin and transform rules from the writer. Test: the figure renders inside its 60rem cap with no page-level horizontal scroll.
  8. Guest imports. Wide content scrolls inside a <div class="fk-scroll">. Test: the rest of the figure keeps the plate width.
  9. Everyone. Name files <slug>-figN-<name>.<ext> for figures and <slug>-<what>.<ext> for screenshots. Test: ls assets/ matches the pattern.

3Dark-theme safety

  1. Repo posts. A standalone SVG embedded as an image carries an opaque background rect sized to its viewBox. Test: the first drawing element is a rect that covers the viewBox. Reason: a transparent SVG floats on the dark theme.
  2. Repo posts. A standalone SVG uses no prefers-color-scheme block. Reason, not yet tested: an SVG loaded through <img> follows the operating system setting and not memo's theme toggle, so it can disagree with the page.
  3. Guest imports. A figure.fig colors everything through --fk-* variables or currentColor. Test: rg 'fill="#|stroke="#' inside the figure finds nothing, or each hit is a deliberate data color that reads on both themes.
  4. Guest imports. A prefers-color-scheme block in figure CSS is allowed. Memo maps it to its own toggle.
  5. Everyone, target. A raster screenshot has an opaque background. Do not commit a transparent PNG of a UI. Test: identify -format %A <f> prints False, or the image is a deliberate cutout checked on both themes.
  6. Everyone. No device chrome, browser frame or cursor in a screenshot. Test: visual check.

4Alt text, captions and the hero image

  1. Everyone, target. Every image embed has non-empty alt text that says what the figure shows. Test: rg '!\[\]\(' finds nothing in the post. Today 383 files in brainery contain an empty-alt image, so this is a standard to move toward.
  2. Repo posts. Put an italic _Fig. N: caption._ line under each figure. Test: the line follows every figure embed.
  3. Guest imports. Use figure.fig with a figcaption, or the figure head block, for the caption. A plain <figure><img> loses its structure, and the caption becomes a plain paragraph. Test: the figure appears as one block on the live page.
  4. Guest imports. An inline SVG figure has role="img" plus an aria-label, or a <title> and <desc>. Every data-tip states a fact already in the text and adds no new number.
  5. Everyone. Use one hero image, and make it the first image in the body. It must still read at thumbnail size. Test: the first embed in the body is the intended hero.
  6. Repo posts. Set image: only when the hero is not the first image. Use an absolute site path such as /content/<vault-path>/assets/<file>.png. Test: curl -s https://memo.d.foundation/<slug> | grep og:image shows that path and not home_cover.webp.
  7. Everyone, target. A raster share image is 1200 by 630 and 300 KB or less. Never use an SVG for a third-party hotlink. Reason: Facebook, LinkedIn and X reject an SVG share image. A vault SVG is safe because the worker renders a PNG twin, and a hotlinked SVG gets none.

5Hotlinked versus re-hosted media

  1. Repo posts. Commit all media next to the post, under assets/. Raster files move to the storage bucket after the merge, and SVG stays in git. Test: relative-link-exists is green, and gh run list -R dwarvesf/brainery --workflow media-sync.yml --limit 1 shows success after the merge.
  2. Guest imports. Memo hotlinks your image address and does not copy the file. The image lives as long as you keep the same address online. Test: the editor opens each image address during review and confirms status 200 and a matching content type.
  3. Guest imports. Serve every image over https from a stable address with no version in it. Memo drops http: and data: images with no warning, so a post that relies on them loses them silently. Test: rg '<img[^>]*src="(http:|data:)' on your source page finds nothing.
  4. Guest imports, target. Keep each hotlinked image within the raster targets in rules 6 to 8. Memo does not compress it. Test: curl -sI <url> shows a content-length of 204800 or less.
  5. Guest imports. Remove a post through the removal flow before you move or delete its images. A moved image breaks the memo copy until the next re-check.

6Third-party images and attribution

  1. Everyone. Use only images you made, own or hold a licence for. Credit any other image on the line under the caption: Image: <author>, <source link>, <licence>. Do not scrape images. Test: every image whose origin is not the author carries a credit line.
  2. Guest imports. The consent line on a guest note covers the text. It does not clearly cover copying images, so memo does not copy them today. Do not ask for a third-party image to be re-hosted without that image's licence. Whether consent should cover images is an open question for the editors.

7Interactive figures in guest posts

The contract lives at memo.d.foundation/figures.md. These rules are the editor's side of it.

  1. Guest imports. The static figure is the figure. It must read fully with no script, in RSS and with reduced motion. Test: view the page with scripts off.
  2. Guest imports. Use figure.fig blocks only. Memo ignores inline <script> and <style> and every stylesheet except up to three same-host https sheets. Each sheet's path contains fig, or its tag carries data-figkit, and the total is at most 512 KB. Test: the figure stylesheet path matches fig.
  3. Guest imports. Use only the elements and attributes that figures.md allows. Do not use img, foreignObject, iframe, mask, textPath, SMIL, @import, @font-face, any url() other than url(#id), or position: fixed. Test: the editor preview of the converted note shows every figure intact.
  4. Guest imports. Use data-animate plus data-step for motion. Nothing plays on its own, and the Play button appears by itself. Test: no autoplay in the figure.
  5. Guest imports. Add data-fk-zoom="touch" to a dense diagram that fits a tablet plate but reads better full screen.

8Animation

  1. Repo posts. Most figures get no animation. Animate only when the motion carries meaning the static frame cannot. Gate all motion behind @media (prefers-reduced-motion: no-preference), and let the base attributes show the finished figure. Test: xmllint --noout <f>.svg, then a static render shows the complete figure.
  2. Guest imports. Use figkit motion only (rule 37). Memo drops SMIL, so an SMIL animation silently becomes a static frame or nothing.

9Styling memo will not carry over

  1. Guest imports. Memo loads the figure stylesheets and drops the rest of your site's styling. Plan for the cases below before you submit.
Your site does thisOn memoDo this instead
Custom web fonts (@font-face)Dropped. Fonts map to memo's sans and monoSet type with --font-body, --fk-font and --fk-mono
Page-wide CSS, theme, layoutNot loaded. Only figure stylesheets loadPut figure styles in a figures.css (the path contains fig)
Inline <script> or <style>IgnoredUse figkit attributes (data-tip, data-animate, data-fk-*)
Breakout widths, negative marginsOverridden by memo's figure boxDesign for the column and the 60rem cap
<iframe> embeds (video, tweets, sandboxes)RemovedLink out, and add a still image with alt text
<img> inside a figure.figDropped silentlyDraw it as inline SVG, or put the image in a plain paragraph
<picture> with srcset and sourcesOnly the fallback <img> is keptMake the fallback <img> the best single file
<svg> outside figure.figRemoved, with a dropped_figures warningWrap it in <figure class="fig">
Inline style= on proseRemovedStyle in figure CSS only, or accept memo's prose style
http: or data: imagesRemoved silentlyUse https image addresses
Your own share imageNot carried. The card uses the site defaultAsk the editors to set a hero
Header, nav, footer, asideDroppedKeep the post in one <article>

10House palette

  1. Repo posts by Dwarves authors, required. Use the house palette and figure header: background #12395f, box fill #21466a, box stroke #68839d, one amber accent #e8c56a, text #e8f1fa, #a7c0d9, #c3d7ea and #6f8fb0, arrow #c3d7ea, a monospace font, and a header rule at y=36. Test: the figure uses no color outside this set.
  2. Repo posts by Dwarves authors, required. One accent only, two line weights at most, labels that never crowd, and whitespace as structure. Split a busy mechanism into a short series of numbered figures.
  3. Guest imports, recommended. Follow the limits in rule 43. A guest may keep their own palette, and still meets rules 1 to 41.

11Editor checklist

Run this before approving any post, repo or imported.

  • Open every image address. Status 200, correct type, not an error page.
  • Check bytes: raster 200 KB or less, SVG 60 KB or less (150 KB with a reason).
  • Check raster width: 704 to 1600 pixels, with no blurry upscale.
  • Open the page in light and dark. No transparent figure floats on the dark theme and no edge is clipped.
  • Resize to a phone width. No page-level horizontal scroll, and wide parts scroll inside their own box.
  • Every image has real alt text, and every figure has a caption.
  • Check the hero. The first image reads at thumbnail size, and the share card is not the site default by accident.
  • Confirm credit for every image the author did not make, with no scraped images.
  • Guest import: list what the importer dropped in the preview warnings, and ask the writer to fix http:, data: and <img>-in-figure cases.
  • Guest import: confirm the writer knows images are hotlinked and must stay online.
  • Interactive figure: view it with scripts off. The static figure must be complete.
  • Repo post: run xmllint --noout on each SVG and ./pre-commit-hook.sh install, and read the formatting bot's diff.