Contributing to ChroGPS Dash
First off, thank you for considering contributing to ChroGPS Dash! It’s people like you who make open-source tools better for the entire NTP/GNSS/precision time-keeping community.
Core Philosophy: “Zero Dependencies”
This project adheres to a strict no-dependency philosophy.
- No external CSS frameworks (Bootstrap, Tailwind, etc.).
- No external JS libraries (jQuery, Graph.js, Highgraphs, etc.).
- No package managers (npm, composer, pip).
- Single-file deployment: The entire dashboard logic resides in
index.php. - Native, available APT packages: ChroGPS Dash is designed to use programs native to Debian (& Debian-like) systems for ease of deployment and for portability/repeatability.
How Can I Contribute?
Pull Requests
- Fork the repository and create your branch from
master. - Test locally: Ensure your changes work on a standard Debian/Ubuntu stack with
chronyandgpsd. - Responsive Check: Verify the UI layout on both Desktop (2-column grid) and Mobile (stacked single column).
- Theme Check: Toggle the Light/Dark mode to ensure all new elements use the correct CSS variables.
- Submit a Pull Request with a clear description of your changes.
Reporting Bugs & Feature Requests
- Disabled and disregarded/dismissed: I only accept code contributions and bugfixes directly via Pull Requests.
- Did I make it clear that only code contributions and bugfixes are accepted? 😉
Development Guidelines
1. PHP & Graphing (The “Native SVG” Engine)
I do not use client-side graphing libraries. All graphs are generated server-side using native PHP to output raw SVG XML.
- Draw Function: Use the
drawGraphSVG()helper function for all graphs. - Tooltips: If you modify the graphs, ensure you preserve the invisible
<rect>“hit zones” that drive the JavaScript tooltips. - Log Parsing: The dashboard reads directly from
/var/log/chrony. Ensure any new parsers handle file permissions gracefully and fail silently if logs are missing.
2. JavaScript (Vanilla Only)
- No Frameworks: Use standard ES6+ JavaScript.
- AJAX Loop: The dashboard uses a single
fetch('?ajax=1')loop to update all data.- Do not add separate polling intervals for different components.
- All dynamic data should be returned in the single JSON response.
- DOM Manipulation: Use standard
document.getElementById()orquerySelector().
3. CSS & Theming
- Variables: Always use the defined CSS variables (e.g.,
--bg,--text,--accent,--green) to ensure full Dark/Light mode compatibility. - Grid Layout:
- Mobile First: The default layout is a single-column stack.
- Desktop: I use
@media (min-width: 900px)to switch to a 2-column grid. - Full Width Elements: Large graphs should use
grid-column: 1 / -1;in the desktop layout to span the full width.
- UI Styles: ChroGPS Dash has a specific and well-established set of styles, color palettes, etc. Included below is a comprehensive UI style guide developers should reference when contributing.
4. General Development Styles and Guidelines
- Code should be well-documented/commented
- No manual minification. The source file must remain fully human-readable. ChroGPS Dash includes a self-minifier that automatically strips comments, collapses whitespace, and removes redundant syntax from the HTML/CSS/JS output buffer at request time - the source is never modified. Never manually minify, uglify, or compress code you contribute; the minifier handles that transparently.
- PHP code follows PSR-12 guidelines
- JavaScript follows Modern Vanilla JS (ES6+) guidelines.
- Indentation should be 4 spaces to maintain consistency with the PHP file structure.
- CSS and HTML are v3 and v5, respectively.
- Indentation should be 4 spaces to maintain consistency with the PHP file structure.
5. Hardware Support
- GPS Types: When parsing
gpsddata, aim to support generic NMEA devices as well as specific drivers (u-blox, SiRF). - Baud Rates: Preserve the display format
@ /dev/path (nnnn baud).
6. Adding New Settings and Configuration Options
ChroGPS Dash uses a single canonical block - $defaultConfig near the very top of index.php - as the source of truth for every user-configurable setting. Understanding how this block works is essential before adding any new option.
How $defaultConfig Works
$defaultConfig is a PHP string that contains the complete, correctly-formatted default contents of cgpsd-settings.php. It serves three purposes simultaneously:
- Fresh install: If
cgpsd-settings.phpdoes not exist, the dashboard writes$defaultConfigdirectly to disk to create it. - Shell installer migration:
install.shreads$defaultConfigdirectly from the downloadedindex.phpand injects any blocks whose variable is absent from the user’s existing settings file. No separate per-setting maintenance in the installer is required. - Web updater migration (Step 6): The web updater parses
$defaultConfigat runtime, extracts every setting block, and injects any that are absent from the user’s livecgpsd-settings.php. This means adding a setting to$defaultConfigis all that is required - the web updater picks it up automatically on the next update for every existing installation worldwide.
The block lives here in index.php:
$defaultConfig = '<?php
// --- OPTIONAL CONFIGURATION ---
// CHECK FOR UPDATES (True/False)
// ...
$CHECK_UPDATES = true;
// DISPLAY RESOLUTION (Sampling Interval in Seconds)
// ...
$GRAPH_SAMPLE_SEC = 30;
// ... (one blank line between each setting block) ...
// --- END OPTIONAL CONFIGURATION ---
';
Rules for Adding a New Setting
Follow all of these rules precisely. The web updater’s parser depends on this structure.
1. Add the block to $defaultConfig.
Each setting is a self-contained block consisting of one or more comment lines immediately followed by a single assignment line. Blocks are separated by exactly one blank line.
// SETTING NAME (Type)
// Description line one.
// Description line two (optional).
$YOUR_NEW_VAR = <default_value>;
Add it before the // --- END OPTIONAL CONFIGURATION --- line. Increment the number to follow the existing sequence. Use the same escape style as the surrounding single-quoted string - single quotes inside the string must be escaped as \'.
2. No additional changes to install.sh are needed.
The installer’s write_default_config and migrate_settings_from_php functions both read $defaultConfig directly from the downloaded index.php at runtime. There is no separate settings list to maintain in the installer. Adding the block to $defaultConfig is the only step required for the installer to handle the new setting correctly on fresh installs and upgrades alike.
3. Consume the variable in index.php.
Use isset() before referencing your new variable anywhere in the dashboard logic. Settings files from older installations will not have it until the next web update or installer run, so always guard against it being undefined:
// Safe - works whether or not the setting has been injected yet
if (isset($YOUR_NEW_VAR) && $YOUR_NEW_VAR === true) {
// feature logic
}
4. Document it in your Pull Request.
In your Pull Request, you must create a markdown description of the new setting so that I can update the configuration section of the ChroGPS Dash web page. Follow the same structure as existing entries: variable name as the heading, default value, description, and function (true/false behavior or accepted values).
You also must include the new setting in the example default settings file. Follow the same structure as existing entries in the example.
The $skipVars Protected List
The web updater’s Step 6 migration maintains a small $skipVars array of variable names that are never injected during a web update, regardless of whether they are present in the user’s settings file:
$skipVars = ['ADMIN_TOKEN', 'UPDATE_TOKEN'];
ADMIN_TOKEN is protected because anyone running the web updater already has a valid token - injecting a new empty one would lock them out of the admin panel and break web updates entirely. UPDATE_TOKEN is the legacy name for the same credential and is kept in the list for backward compatibility with older installations. Do not add new settings to $skipVars unless there is a specific reason the web updater must never touch them. Variables that need special generation logic at install time (like tokens or secrets) are the only legitimate candidates.
Checklist: Adding a New Setting
- Block added to
$defaultConfiginindex.phpwith correct numbering and formatting - No installer changes needed -
install.shreads$defaultConfigfrom the downloadedindex.phpat runtime - Variable consumed with
isset()guard everywhere it is used in dashboard logic - Documented in the configuration reference
- Default value is the most conservative/safe option (features default to
false/off)
UI Style Guide
This section is the canonical reference for ChroGPS Dash’s visual design system. All contributors adding or modifying UI elements should follow it to keep the dashboard consistent and maintainable across all six themes.
Themes
ChroGPS Dash ships six themes, toggled via the data-theme attribute on <html>:
| Theme | data-theme value |
Character |
|---|---|---|
| Light | (default / no attribute) | Clean, high-contrast light UI |
| Dark | dark |
Deep navy backgrounds, bright accents |
| Terminal | terminal |
Pure black, phosphor-green, dimmed palette |
| Catppuccin Latte | ctp-latte |
Warm pastel light theme from the Catppuccin palette |
| Catppuccin Mocha | ctp-mocha |
Muted dark theme from the Catppuccin palette |
| Tokyo Night | tokyo-night |
Deep blue-black dark theme inspired by Tokyo Night |
Never hardcode a hex color value in CSS or HTML. Always reference a CSS variable. If the color you need is not yet in the palette, add it to all six theme blocks first (see Adding New Colors below).
Color Palette
The palette is structured in two layers inside index.php’s <style> block.
Layer 1 - Primitive Color Tokens
Hex color values are defined in two groups within each theme block. Named color primitives are pure hue definitions with no semantic meaning - they are the only tokens you should reference when defining new semantic tokens. A second group of structural tokens also carry direct hex values rather than aliasing a named primitive; these are documented in the second table below.
Named color primitives
All named primitives are defined in every theme block. In Catppuccin Latte, Mocha, and Tokyo Night, --brown and --yellow intentionally share the same value - this matches those palettes “Yellow” role serving both warm-tone purposes.
/* Defined in all six theme blocks */
--blue, --green, --amber, --red, --cyan, --pink, --purple, --indigo,
--orange, --brown, --yellow, --white, --gray
| Token | Light | Dark | Terminal | Ctp-Latte | Ctp-Mocha | Tokyo Night | Notes |
|---|---|---|---|---|---|---|---|
--blue |
#2563eb |
#38bdf8 |
#4c88ff |
#1e66f5 |
#89b4fa |
#7aa2f7 |
Primary accent hue |
--green |
#16a34a |
#4ade80 |
#00dd00 |
#40a02b |
#a6e3a1 |
#9ece6a |
Success / GPS |
--amber |
#d97706 |
#fbbf24 |
#e6ac00 |
#df8e1d |
#f9e2af |
#e0af68 |
Warning / SBAS |
--red |
#dc2626 |
#f87171 |
#d63232 |
#d20f39 |
#f38ba8 |
#f7768e |
Error / QZSS |
--cyan |
#0891b2 |
#00e5e5 |
#00dddd |
#179299 |
#94e2d5 |
#7dcfff |
Highlight |
--pink |
#db2777 |
#f472b6 |
#cc00cc |
#ea76cb |
#f5c2e7 |
#f78cb3 |
BeiDou |
--purple |
#9333ea |
#9333ea |
#8800cc |
#8839ef |
#cba6f7 |
#9d7cd8 |
Galileo / Time fix |
--indigo |
#6366f1 |
#818cf8 |
#6655ee |
#7287fd |
#b4befe |
#8b78f7 |
NavIC |
--orange |
#f59e0b |
#fbbf24 |
#dd8800 |
#fe640b |
#fab387 |
#ff9e64 |
Sun disc fill; sun disc border in Terminal |
--brown |
#b45309 |
#d97706 |
#aa6600 |
#df8e1d |
#f9e2af |
#e0af68 |
Sun disc border; maps to theme yellow in Latte / Mocha / Tokyo Night |
--yellow |
#eab308 |
#fde047 |
#dddd00 |
#df8e1d |
#f9e2af |
#e0af68 |
Sun disc fill in Terminal; warm yellow in other themes |
--white |
#ffffff |
#ffffff |
#888888 |
#eff1f5 |
#cdd6f4 |
#c0caf5 |
Text on dark bg; dimmed / desaturated per theme |
--gray |
#6b7280 |
#6b7280 |
#555555 |
#9ca0b0 |
#6c7086 |
#565f89 |
Muted / simulated state |
Global :root-only tokens
The following tokens are defined in :root. Most are not overridden in the per-theme blocks; any exceptions are noted under each section.
Shadow tokens
--shadow-popup /* per theme - active tab pill, small floating popups */
--shadow-lg /* per theme - modals, large overlays */
--shadow-sm /* per theme - minor card/element elevation, small drop shadows */
--shadow-inset /* per theme - inset depth effect (e.g. progress bar track) */
Always use the appropriate shadow token: --shadow-popup for compact floating elements (pill buttons, inline popups), --shadow-lg for full modals and large overlays, --shadow-sm for minor card or element elevation, and --shadow-inset for inset depth effects. Never hardcode a box-shadow value.
:root-only) - light themes use lighter rgba intensities; dark themes use deeper values. ctp-latte inherits the light theme shadow values since it has a light background.
Easing tokens
--ease-ui /* cubic-bezier(0.4, 0, 0.2, 1) - standard Material-style deceleration curve */
--ease-ping /* cubic-bezier(0, 0, 0.2, 1) - ping/ripple animation (fast deceleration) */
--ease-spring /* cubic-bezier(0.34, 1.56, 0.64, 1) - spring/overshoot curve for entrance animations */
Use var(--ease-ui) for all standard transition declarations. Use var(--ease-ping) for ripple/pulse animation keyframes. Use var(--ease-spring) for entrance-style transitions that intentionally overshoot their target (e.g. the modal slide-in). Never write a raw cubic-bezier(...) literal in a rule.
--ease-ping and --ease-spring are redefined in every theme block (values are identical across all themes). This is intentional - it ensures they remain available in the CSS cascade even when a theme block overrides other :root properties.
Badge and button text contrast tokens
These tokens provide per-theme WCAG-compliant text colors for colored badge backgrounds and accent-colored buttons. They are per-theme tokens - each theme block defines its own values to ensure accessible contrast over that theme’s specific badge and button background colors.
--badge-contrast /* text on standard colored badge backgrounds (fix-type, constellation) */
--badge-time-text /* text on time/purple badge backgrounds (varies per theme) */
--progress-bar-text /* text overlaid on progress bar fills (white for Terminal, --text for others) */
--btn-on-accent /* text on accent-colored buttons (Go, Save, Reload, etc.) */
| Token | Light | Dark | Terminal | Ctp-Latte | Ctp-Mocha | Tokyo Night |
|---|---|---|---|---|---|---|
--badge-contrast |
#000000 |
#000000 |
#000000 |
#000000 |
#000000 |
#1a1b26 |
--badge-time-text |
var(--text) |
#ffffff |
var(--text) |
var(--text) |
#1e1e2e |
#1a1b26 |
--progress-bar-text |
var(--text) |
var(--text) |
#ffffff |
var(--text) |
var(--text) |
var(--text) |
--btn-on-accent |
#000000 |
#000000 |
#000000 |
#000000 |
#000000 |
#000000 |
When adding a new badge type or accent-background button, always use color: var(--badge-contrast) or color: var(--btn-on-accent) - never hardcode #000 or #fff.
Semantic status, fix-type, and update tokens
These tokens are defined once in :root only. They alias the primitive color tokens and automatically inherit each theme’s hue values without needing per-theme overrides.
/* Status states */
--status-ok /* var(--green) - running, connected, healthy */
--status-err /* var(--red) - error, failed, not connected */
--status-warn /* var(--amber) - warning, degraded */
--warn /* var(--amber) - warning banners and caution panels */
/* GPS fix type */
--fix-3d /* var(--green) - 3D position fix (Normal) */
--fix-2d /* var(--amber) - 2D position fix only */
--fix-enhanced /* var(--cyan) - enhanced 3D fix (DGPS/RTK/DR) */
--fix-none /* var(--red) - no position fix */
/* Update availability */
--update-avail /* var(--green) - update-available pill indicator */
Use these semantic tokens - never use var(--green), var(--red), or var(--amber) directly in component rules. If you need to represent a new component state, add a semantic alias to this group in :root rather than reaching for a primitive token directly.
Logo brand tokens
These tokens drive the SVG header logo colorway for each display mode. Do not use them outside the logo SVG.
/* Dark-mode logo */
--logo-d-primary /* cyan - solar-panel wings, transmitter, "ChroGPS" text */
--logo-d-secondary /* cyan-green - orbits, strokes, antenna, "DASH" text */
--logo-d-accent /* green - blinking dot, orbit dot */
/* Light-mode logo */
--logo-l-primary /* blue - solar-panel wings, transmitter */
--logo-l-secondary /* teal - orbits, strokes, antenna */
--logo-l-accent /* green - blinking dot, orbit dot */
--logo-l-text1 /* blue - "ChroGPS" text */
--logo-l-text2 /* teal - "DASH" text */
Structural tokens with direct hex values
These tokens are semantic in role but carry direct hex values in the theme blocks rather than aliasing a named color primitive. They appear here for completeness - contributors must be aware of them so they do not accidentally hardcode the same values elsewhere. The same rule applies: never hardcode these hex values in CSS rules or HTML; always reference the token.
A “-” cell means the token is not redefined in that theme (it inherits the :root value). A “var(...)” cell means the token is an alias in that theme.
| Token | Light | Dark | Terminal | Ctp-Latte | Ctp-Mocha | Tokyo Night | Notes |
|---|---|---|---|---|---|---|---|
--bg |
#f4f4f9 |
#0f172a |
#000000 |
#eff1f5 |
#1e1e2e |
#1a1b26 |
Page background |
--card |
var(--white) |
#1e293b |
var(--bg) |
#e6e9ef |
#181825 |
#16161e |
Card / panel background |
--text |
#1a1a1a |
#f1f5f9 |
#b7b7b7 |
#4c4f69 |
#cdd6f4 |
#c0caf5 |
Body text |
--border |
#ddd |
#334155 |
#232323 |
#acb0be |
#45475a |
#292e42 |
Dividers, input borders |
--badge-text |
var(--white) |
var(--bg) |
#000000 |
#eff1f5 |
#1e1e2e |
#1a1b26 |
Text on colored badge backgrounds |
--progress-track |
#4b5563 |
#334155 |
#2a2a2a |
#5c5f77 |
#313244 |
#29355a |
Progress bar track - static dark so white text stays readable over any fill color |
--sky-bg |
#e2e8f0 |
#000000 |
var(--bg) |
#ccd0da |
#11111b |
#16161e |
Polar plot background |
--sky-lines |
#94a3b8 |
var(--border) |
#333333 |
#bcc0cc |
var(--border) |
var(--border) |
Elevation ring / crosshair stroke |
--sky-label |
#666666 |
#475569 |
#444444 |
#6c6f85 |
#585b70 |
#565f89 |
Elevation ring degree labels (15 deg, 30 deg...) |
--tooltip-border |
#ccc |
#475569 |
#333333 |
#acb0be |
#313244 |
#292e42 |
Tooltip border |
--tooltip-text |
#333 |
var(--text) |
var(--white) |
#4c4f69 |
var(--text) |
var(--text) |
Tooltip body text |
Adding New Colors
If your feature needs a color that has no existing token:
- Add the primitive to all six theme blocks (
:root,[data-theme="dark"],[data-theme="terminal"],[data-theme="ctp-latte"],[data-theme="ctp-mocha"],[data-theme="tokyo-night"]), choosing values appropriate to each theme’s character. - If the color has a semantic role (e.g., it represents a component state), create a semantic token that references the primitive.
- Use only the semantic token in your CSS and HTML.
/* ✅ Correct */
.my-element { color: var(--accent); }
/* ❌ Wrong - hardcoded hex breaks all other themes */
.my-element { color: #2563eb; }
/* ❌ Wrong - primitive used directly where a semantic token should exist */
.my-element { color: var(--blue); }
Typography
--font-sans /* UI text: system sans-serif stack */
--font-mono /* Code, coordinates, numeric readouts: monospace stack */
Font size everywhere derives from --base-size (default 14px). Use calc(var(--base-size) * N) to scale, never fixed px values for text.
/* ✅ Correct */
font-size: calc(var(--base-size) * 0.75); /* small label */
font-size: calc(var(--base-size) * 1.25); /* section heading */
/* ❌ Wrong */
font-size: 11px;
Monospace text for data values (coordinates, hashes, baud rates, offsets) should use the .font-mono utility class or font-family: var(--font-mono):
<span class="font-mono">2959743f63</span>
Graph SVG typography
PHP-generated SVG graphs follow this rule without exception:
| SVG element | Font | Examples |
|---|---|---|
| Axis labels / legend text | var(--font-sans) |
“PPS Offset”, “Sats Active”, Y-axis rotated label |
| Numeric tick marks, timestamps, value readouts | var(--font-mono) |
0.123, 14:30, axis min/max values |
// ✅ Axis label (sans)
$svg .= "<text ... font-family='var(--font-sans)' ...>Offset</text>";
// ✅ Numeric tick (mono)
$svg .= "<text ... font-family='var(--font-mono)' ...>0.123</text>";
SVG presentation-attribute font-size is exempt from the --base-size rule
SVG font-size attributes (e.g. font-size='12') are SVG user-unit coordinates, not CSS pixel values. They are part of the SVG coordinate system and scale proportionally with the SVG viewport - CSS custom properties cannot be used in SVG presentation attributes from PHP-generated strings. These values are therefore exempt from the calc(var(--base-size) * N) requirement.
When a CSS override is needed to make SVG text readable at a specific viewport size (e.g. in the 2-column graph view), apply it via a scoped CSS rule using calc(var(--base-size) * N):
/* ✅ CSS override on SVG text - must use --base-size */
#tab-panels-wrap.panels-grid .graph-svg text {
font-size: calc(var(--base-size) * 1.15);
}
// ✅ SVG presentation attribute - user-unit coordinate, exempt from --base-size rule
$svg .= "<text font-size='12' ...>0.123</text>";
// ❌ Wrong - CSS px in a CSS rule, even inside a PHP string
$svg .= "<text style='font-size: 12px' ...>0.123</text>";
Badges
Badges are used for fix-type labels and constellation counts. Always use the .badge base class plus one modifier.
<span class="badge badge-fix">3D Fix</span>
<span class="badge badge-time">Time</span>
<span class="badge badge-amber">2D Fix</span>
<span class="badge badge-nofix">No Fix</span>
<span class="badge badge-cyan">DGPS/RTK</span>
<span class="badge badge-simulated">Simulated</span>
| Modifier | Color | Represents |
|---|---|---|
badge-fix |
--fix-3d |
Normal 3D fix |
badge-amber |
--fix-2d |
2D fix only |
badge-cyan |
--fix-enhanced |
Enhanced 3D (DGPS/RTK/DR) |
badge-time |
--purple |
Time-only precision fix |
badge-simulated |
--gray |
Simulated / NMEA replay mode |
badge-nofix |
--fix-none |
No position fix |
Do not add style="..." to badge elements. Spacing (margin-right) is handled by the base .badge rule. Text color is handled by --badge-text as the default, with per-theme overrides using --badge-contrast and --badge-time-text for high-contrast cases already in place. If you add a new badge type that needs a contrast override on a specific theme, use color: var(--badge-contrast) in the theme-specific rule block.
Constellation Badges
Constellation count badges use .const-badge plus a .c-* modifier. Both classes are required.
<span class="const-badge c-gps">GPS: 9</span>
<span class="const-badge c-glo">GLONASS: 6</span>
<span class="const-badge c-gal">Galileo: 9</span>
<span class="const-badge c-bds">BeiDou: 5</span>
| Modifier | Token | Constellation |
|---|---|---|
c-gps |
--sat-gps |
GPS |
c-glo |
--sat-glo |
GLONASS |
c-gal |
--sat-gal |
Galileo |
c-bds |
--sat-bds |
BeiDou |
c-sbas |
--sat-sbas |
SBAS |
c-qzss |
--sat-qzss |
QZSS |
c-navic |
--sat-navic |
NavIC |
Info Tags
Info tags are small colored label chips used in data tables and info panels.
<span class="info-tag tag-perfect">Excellent</span>
<span class="info-tag tag-good">Good</span>
<span class="info-tag tag-marginal">Marginal</span>
<span class="info-tag tag-poor">Poor</span>
<span class="info-tag tag-sun">Phenomenon</span>
| Modifier | Background | Use |
|---|---|---|
tag-perfect |
--green |
Best-case value |
tag-good |
--accent |
Normal/acceptable value |
tag-marginal |
--amber |
Degraded but functional |
tag-poor |
--red |
Out of spec |
tag-sun |
--sun-fill |
Solar/equinox event context |
Utility Classes
These small single-purpose classes exist to keep inline styles out of HTML. Use them instead of style="..." wherever they apply.
Quality / DOP indicator colors
<span class="clr-good">●</span> <!-- color: var(--green) -->
<span class="clr-ok">●</span> <!-- color: var(--accent) -->
<span class="clr-fair">●</span> <!-- color: var(--amber) -->
<span class="clr-poor">●</span> <!-- color: var(--red) -->
Opacity
<span class="op-full">●</span> <!-- opacity: 1 -->
<span class="op-dim">●</span> <!-- opacity: 0.5 -->
<span class="op-muted">●</span> <!-- opacity: 0.7 -->
Layout & typography
<svg class="icon-inline" ...></svg> <!-- vertical-align: -3px; margin-right: 6px -->
<span class="font-mono">689e9c61e3</span> <!-- font-family: var(--font-mono) -->
<span class="font-bold">value</span> <!-- font-weight: bold -->
<p class="info-note">Contextual note.</p> <!-- margin-top: 10px; opacity: 0.8 -->
Component-specific helpers
These classes exist to eliminate inline styles in specific contexts. Use them rather than adding style="..." to the element.
<!-- Sets tooltip width when used for table-header data-tip hints -->
<div class="tooltip-th">...</div>
<!-- Positions a textarea off-screen for clipboard read; replaces position/opacity/pointer-events inline styles -->
<textarea class="clip-helper"></textarea>
<!-- Bottom-margin spacing for update warning messages -->
<span class="upd-warn-msg">...</span>
<!-- SNR histogram bar state (applied by JS; replaces runtime style.border / style.opacity assignments) -->
<div class="snr-bar snr-bar-used">...</div> <!-- solid fill, full opacity - used in fix -->
<div class="snr-bar snr-bar-seen">...</div> <!-- hollow / bordered, slightly dimmed - visible but unused -->
PHP-Generated Colors
PHP cannot read CSS variable values at render time, but SVG attributes do accept var(--token) references, and the browser resolves them correctly. Prefer CSS variable references in SVG attributes over hardcoded hex wherever possible:
// ✅ Preferred - CSS variable resolves correctly in SVG
$svg .= "<polyline stroke='var(--accent)' .../>";
$svg .= "<text fill='var(--purple)' ...>Sats Active</text>";
// ✅ Required for <stop> elements (SVG spec mandates style attribute)
$svg .= "<stop offset='0%' style='stop-color:#16a34a'/>";
// ❌ Avoid - hardcoded hex breaks all non-light themes
$svg .= "<polyline stroke='#38bdf8' .../>";
For colors that must be computed numerically in PHP (e.g., progress-bar fills derived from a calculated percentage), use the predefined PHP constants rather than raw hex values:
// ✅ Use these constants - they mirror the light-theme palette values
PBAR_GREEN // #16a34a (--green)
PBAR_AMBER // #d97706 (--amber)
PBAR_RED // #dc2626 (--red)
For new PHP-generated colors that cannot use a var() reference, define a const near the top of the file alongside the existing ones, with a comment referencing its CSS counterpart.
CSS Authoring Rules
-
No inline
style="..."in HTML unless the value is truly runtime-dynamic (e.g., a bar width or color computed from live data values). Layout, color, spacing, and typography all belong in the<style>block. JavaScript.style.*assignments are also only acceptable when the value is computed at runtime from data (e.g., DOP color coding based on a parsed numeric value); static values must use CSS classes instead. -
No hardcoded hex values outside the primitive color token definitions. This applies to CSS rules, HTML attributes, and PHP-generated SVG attributes - use CSS variable references (
var(--token)) or PHP constants as described above. -
Use semantic tokens, not primitives, in rules. Write
color: var(--accent)notcolor: var(--blue). The primitive exists to define the value; the semantic token expresses the intent. -
Modifiers over duplication. If a component needs a variant, add a modifier class (e.g.,
.info-p.mt-14) rather than copying the base rule with one property changed. -
New components need CSS rules in all six themes. Test every new element by cycling through Light → Dark → Terminal → Catppuccin Latte → Catppuccin Mocha → Tokyo Night before submitting.
-
SVG
<stop>elements are the only legitimate exception to the no-inline-style rule - the SVG specification requiresstop-colorto be declared as astyleattribute. SVG elements that are not<stop>should usestroke="var(--sky-lines)"and similar CSS variable references directly in the attribute. -
Use the appropriate easing token for all transitions and animations.
var(--ease-ui)for standard UI transitions;var(--ease-ping)for ripple/pulse animation keyframes;var(--ease-spring)for entrance-style spring animations that intentionally overshoot. Never write a rawcubic-bezier(...)literal in a rule. -
Use the appropriate shadow token for all shadows. Compact floating elements (pill buttons, inline popups) use
--shadow-popup; full modals and large overlays use--shadow-lg; minor card/element elevation uses--shadow-sm; inset depth effects (e.g. progress bar track) use--shadow-inset. Never hardcode abox-shadowvalue. -
No consecutive blank lines in the
<style>block. A single blank line between blocks is the maximum. This keeps the source readable and consistent. -
Tab buttons share a common base rule. The selectors
.legend-tab-btn,.adm-tab-btn, and.tab-btnare merged into a single grouped rule. Any new tab-style button must use one of these existing classes rather than introducing a new selector with duplicated properties. -
Write minifier-safe code. The self-minifier processes the output buffer using a character-by-character state machine. A few patterns require care:
calc()is fully protected - the minifier stashes allcalc()expressions before any whitespace collapse and restores them verbatim, socalc(var(--base-size) * 0.75)is always safe.- JS template literals are fully protected - anything inside backticks is passed through verbatim. Multi-line template strings that build HTML are safe.
- JS
//single-line comments - the minifier preserves the terminating newline, so the next line of code is never merged into the comment. Standard comment style is safe. - JS regex literals - genuine regex literals (e.g.
/pattern/flags) are preserved by the state machine. Avoid constructing regex-like strings in contexts where the parser may misread them; prefernew RegExp(...)for dynamic patterns. - Never use
<?php ... ?>open/close tags inside an already-open PHP block - the minifier functions are injected into the PHP execution context. Spurious<?phptags within that context will cause a parse error.
Attribution is required under the MIT License. Happy coding!