Skip to main content...

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

  1. Fork the repository and create your branch from master.
  2. Test locally: Ensure your changes work on a standard Debian/Ubuntu stack with chrony and gpsd.
  3. Responsive Check: Verify the UI layout on both Desktop (2-column grid) and Mobile (stacked single column).
  4. Theme Check: Toggle the Light/Dark mode to ensure all new elements use the correct CSS variables.
  5. 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() or querySelector().

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 gpsd data, 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:

  1. Fresh install: If cgpsd-settings.php does not exist, the dashboard writes $defaultConfig directly to disk to create it.
  2. Shell installer migration: install.sh reads $defaultConfig directly from the downloaded index.php and injects any blocks whose variable is absent from the user’s existing settings file. No separate per-setting maintenance in the installer is required.
  3. Web updater migration (Step 6): The web updater parses $defaultConfig at runtime, extracts every setting block, and injects any that are absent from the user’s live cgpsd-settings.php. This means adding a setting to $defaultConfig is 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.

Warning
If you do not include documentation for the new setting(s), your Pull Request will be silently deleted.

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 $defaultConfig in index.php with correct numbering and formatting
  • No installer changes needed - install.sh reads $defaultConfig from the downloaded index.php at 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.

Note
All four shadow tokens are per-theme (not :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.

Note
--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:

  1. 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.
  2. If the color has a semantic role (e.g., it represents a component state), create a semantic token that references the primitive.
  3. 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

  1. 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.

  2. 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.

  3. Use semantic tokens, not primitives, in rules. Write color: var(--accent) not color: var(--blue). The primitive exists to define the value; the semantic token expresses the intent.

  4. 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.

  5. 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.

  6. SVG <stop> elements are the only legitimate exception to the no-inline-style rule - the SVG specification requires stop-color to be declared as a style attribute. SVG elements that are not <stop> should use stroke="var(--sky-lines)" and similar CSS variable references directly in the attribute.

  7. 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 raw cubic-bezier(...) literal in a rule.

  8. 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 a box-shadow value.

  9. No consecutive blank lines in the <style> block. A single blank line between blocks is the maximum. This keeps the source readable and consistent.

  10. Tab buttons share a common base rule. The selectors .legend-tab-btn, .adm-tab-btn, and .tab-btn are 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.

  11. 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 all calc() expressions before any whitespace collapse and restores them verbatim, so calc(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; prefer new 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 <?php tags within that context will cause a parse error.

Attribution is required under the MIT License. Happy coding!

Document Version: b19f79d -- Last Revision: 2026-03-16
Permanent Link: <https://w0chp.radio/chrogps-dash/contributing/>