code wiki / _hdl_build / nx_media_theme.nx
nx_media_theme.nx
buildroot/runtime/_hdl_build/nx_media_theme.nx
about
nx_media_theme.nx -- SOVEREIGN READING-SURFACE THEME ENGINE for /gallery + /library.
Closes the TEN media.matrix rows that all name this organ (operator buy-off 2026-07-14: harsh plain
white, no dark theme, clunky): light+dark theme, sunlight/outdoor readability, ambient adaptation,
mobile legibility, anti-glare surface, warm night shift, flicker/refresh comfort, paper-like feel,
reading typography, blue-light reduction.
NOT A STUB. media.matrix's own axis says a symbol on disk is NECESSARY-NOT-SUFFICIENT, so this organ is
built to be MEASURED, not greppable: every palette is a token row that mt_contrast() scores through the
estate's WCAG kernel, and every claim it makes is a number its gate can refuse.
DRY (rule 15) -- this organ INVENTS NOTHING THE ESTATE ALREADY OWNS:
* WCAG math -> nx_uiq_color.nx (uiq_parse_color / uiq_ratio100 / uiq_lum). That file's header
declares itself THE ONE WCAG 2.x relative-luminance + contrast-ratio implementation
and carries a D001 migrate-on-touch note. A second luminance kernel here would be
the exact duplication that note exists to prevent -- and would have been the
FOURTH copy in this estate.
* data format +
parser + safety -> nx_brand_tokens.nx (bt_slice_eq / bt_value_safe / bt_app / bt_emit_root_buf).
* mode vocabulary
+ hex values -> nx_night_grade.nx 6-theme SSOT (migrated from night_themes.tsv). light/sepia/
dark/night bg+fg are BYTE-FAITHFUL to it, so its already-measured blue-emission
assertions still hold here instead of new ones being asserted.
* override idiom -> data-theme, matching nishi_ds.css and nx_papers_index.nx. Not a new attribute.
DATA-DRIVEN (rule 11): every palette value, threshold and floor is a named const or a row in MT_PALETTE_S.
OVERRIDE SEMANTICS -- the user wins in BOTH directions. The OS default rides prefers-color-scheme, but it
is scoped :root:not([data-theme]), so once a user picks a mode the media query CANNOT win. A bare @media
block cannot express OS-dark + user-picks-light.
HONEST LIMITS -- stated, not papered over:
* Reflective/paper-like and matte/anti-glare are PHYSICAL display properties the web cannot set. This
ships the controllable proxy: a paper palette and a capped peak luminance (sepia bg #f4ecd8, not
#ffffff). It does not make glass matte.
* Flicker-free is a panel property (PWM / e-ink). The controllable parts are motion and the first-paint
flash; both are handled (prefers-reduced-motion, and color-scheme so the UA paints the correct base
colour BEFORE first paint instead of flashing white).
* Ambient-light auto-adaptation is DELEGATED to the OS signal. A real AmbientLightSensor lane is NOT
implemented and is NOT claimed -- that matrix row stays _ABSENT_.
Usage: nx_media_theme [--css | --table | --contrast <mode> | --kat] (default --css)
dependencies 3 imports · 0 importers
imports: nx_syscalls.nxnx_brand_tokens.nxnx_uiq_color.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 46 | const MT_MODE_N: i64 = 5 |
| 47 | const MT_COLOR_N: i64 = 6 |
| 48 | const MT_SCRATCH: i64 = 8192 |
| 49 | const MT_CSSCAP: i64 = 32768 |
| 52 | const MT_FLOOR_READ: i64 = 700 |
| 53 | const MT_FLOOR_UI: i64 = 450 |
| 54 | const MT_FLOOR_SUN: i64 = 2000 |
| 55 | const MT_KAT_BW: i64 = 2100 |
| 58 | const MT_BG_W: i64 = 9 |
| 59 | const MT_FG_W: i64 = 1 |
| 60 | const MT_PERMILLE: i64 = 1000 |
| 61 | const MT_BLUE_NIGHT_MAX: i64 = 250 |
| 62 | const MT_BLUE_CTRL_MIN: i64 = 330 |
| 64 | const MT_ASCII_NL: i64 = 10 |
| 65 | const MT_ASCII_PIPE: i64 = 124 |
| 66 | const MT_ASCII_DASH: i64 = 45 |
| 68 | const MT_PALETTE_S: *u8 = "mode|light|bg|#fbfbf9\nmode|light|surface|#ffffff\nmode|light|text|#1d1d1b\nmode|light|muted|#586274\nmode|light|accent|#2f5fd0\nmode|light|border|#dfe4ec\nmode|light|scheme|light\nmode|dark|bg|#15161a\nmode|dark|surface|#1b1d23\nmode|dark|text|#d8d8d2\nmode|dark|muted|#8b929c\nmode|dark|accent|#5b9dff\nmode|dark|border|#2a2f3a\nmode|dark|scheme|dark\nmode|sepia|bg|#f4ecd8\nmode|sepia|surface|#fbf4e3\nmode|sepia|text|#43391f\nmode|sepia|muted|#6b5d3a\nmode|sepia|accent|#7a3b12\nmode|sepia|border|#e0d4b6\nmode|sepia|scheme|light\nmode|sunlight|bg|#ffffff\nmode|sunlight|surface|#f0f0f0\nmode|sunlight|text|#000000\nmode|sunlight|muted|#262626\nmode|sunlight|accent|#0b3d91\nmode|sunlight|border|#767676\nmode|sunlight|scheme|light\nmode|night|bg|#1a1206\nmode|night|surface|#241809\nmode|night|text|#e8c890\nmode|night|muted|#b39760\nmode|night|accent|#f0a94b\nmode|night|border|#3d2a10\nmode|night|scheme|dark\n" as *u8 |
| 70 | const MT_BASE_S: *u8 = "token|font|body|system-ui, -apple-system, Segoe UI, Roboto, sans-serif\ntoken|font|read|Iowan Old Style, Charter, Palatino, Georgia, serif\ntoken|size|base|16px\ntoken|size|leading|1.65\ntoken|size|measure|68ch\ntoken|size|tap|44px\ntoken|radius|md|10px\n" as *u8 |
| 72 | const MT_ALIAS_S: *u8 = "--bg:var(--nx-color-bg);--fg:var(--nx-color-text);--mut:var(--nx-color-muted);--card:var(--nx-color-surface);--acc:var(--nx-color-accent);--bd:var(--nx-color-border);" |
| 74 | const MT_SCAFFOLD_S: *u8 = "html{-webkit-text-size-adjust:100%;text-size-adjust:100%}\nbody{margin:0;background:var(--nx-color-bg);color:var(--nx-color-text);font-family:var(--nx-font-body);font-size:16px;font-size:var(--nx-size-base);line-height:1.65;line-height:var(--nx-size-leading)}\n.nx-read{max-width:68ch;max-width:var(--nx-size-measure);margin:0 auto;padding:0 1rem;font-family:var(--nx-font-read);font-size:1.0625rem;line-height:1.7;hyphens:auto;overflow-wrap:break-word}\n.nx-read p{margin:0 0 1.15em}\na{color:var(--nx-color-accent)}\n.nx-muted{color:var(--nx-color-muted)}\n.nx-surface{background:var(--nx-color-surface);border:1px solid var(--nx-color-border);border-radius:10px;border-radius:var(--nx-radius-md)}\nbutton,select,input,textarea,[role=button],.nx-tap{min-height:44px;min-width:44px;min-height:var(--nx-size-tap);min-width:var(--nx-size-tap);font-size:16px;font-size:var(--nx-size-base)}\nimg,video,canvas{max-width:100%;height:auto}\n:focus-visible{outline:3px solid var(--nx-color-accent);outline-offset:2px}\n@media (max-width:600px){.nx-read{font-size:1.125rem;line-height:1.75;padding:0 1.1rem}}\n@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}}\n" |
functions
| 76 | func mt_w(s: *u8) -> i64 { sys_write(1, s, bt_len(s)); return 0 } |
| 77 | func mt_n(v: i64) -> i64 |
| 91 | func mt_mode_name(i: i64) -> *u8 |
| 98 | func mt_color_name(i: i64) -> *u8 |
| 106 | func mt_palette() -> *u8 { return MT_PALETTE_S } |
| 107 | func mt_palette_len() -> i64 { return bt_len(MT_PALETTE_S) } |
| 109 | func mt_slice(text: *u8, n: i64, mode: *u8, token: *u8, box: *i64) -> i64 |
| 136 | func mt_rgb(text: *u8, n: i64, mode: *u8, token: *u8, out: *i64) -> i64 |
| 143 | func mt_contrast(text: *u8, n: i64, mode: *u8, fg: *u8, bg: *u8) -> i64 |
| 151 | func mt_blue_permille(text: *u8, n: i64, mode: *u8) -> i64 |
| 162 | func mt_mode_complete(text: *u8, n: i64, mode: *u8) -> i64 |
| 175 | func mt_distinct(text: *u8, n: i64, mode: *u8, ta: *u8, tb: *u8) -> i64 |
| 186 | func mt_emit_vars(text: *u8, n: i64, mode: *u8, out: *u8, pos: i64, cap: i64) -> i64 |
| 213 | func mt_emit_css(text: *u8, n: i64, out: *u8, cap: i64) -> i64 |
| 246 | func mt_emit_css_fd(text: *u8, n: i64, fd: i64) -> i64 |
| 254 | func mt_report_mode(text: *u8, n: i64, mode: *u8) -> i64 |
| 271 | func mt_selftest() -> i64 |
| 297 | func main(argc: i64, argv: *i64) -> i64 |