Night Grid cyberpunk style guide
Spec, stylesheet, glitch script, Claude Code skill and the design canvas source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
94832d861b
8 files changed
+845
No files matched your search
+100
@@ -0,0 +1,100 @@
|
|||||||
|
# Night Grid style guide
|
||||||
|
|
||||||
|
Instructions for building UIs (dashboards, tools, pages) in the Night Grid style: a dark neon HUD look in the spirit of near-future noir games. Dark ground, neon signal, hard edges.
|
||||||
|
|
||||||
|
- Stylesheet: `night-grid.css` (same folder). Use its classes and `--ng-*` variables; don't redefine them.
|
||||||
|
- Script: `night-grid.js` (same folder, optional, `defer`). Drives the random glitch; nothing else needs JS.
|
||||||
|
- Visual reference: https://claude.ai/artifact/AT2mCxMkeLFzkife1rAuLk (private; the spec below is complete without it).
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
1. **Signal over noise.** About 70% of the screen is near-black void. Data glows; the background doesn't.
|
||||||
|
2. **Hard edges.** No rounded corners, soft shadows or gradients. Panels get one 45° clipped corner (top-right) and 1px hairline borders.
|
||||||
|
3. **Color is meaning, never decoration.** Yellow = act/focus. Cyan = info and live data. Red = danger. Green = OK.
|
||||||
|
4. **Diegetic data.** Labels read like machine output: uppercase mono, IDs, timestamps, `//` separators (`01 // NETWORK`, `ALERT // DISK FULL`).
|
||||||
|
|
||||||
|
## Colors
|
||||||
|
|
||||||
|
| Token | Hex | Use |
|
||||||
|
|---|---|---|
|
||||||
|
| `--ng-void` | `#050508` | Page background |
|
||||||
|
| `--ng-panel` | `#0E1016` | Cards, panels |
|
||||||
|
| `--ng-raise` | `#1C2029` | Meter tracks, gridlines, hover |
|
||||||
|
| `--ng-line` | `#2A2F3A` | 1px borders, dividers |
|
||||||
|
| `--ng-text` | `#E6E8EC` | Primary text (never pure white) |
|
||||||
|
| `--ng-soft` | `#C3C8D1` | Secondary body text |
|
||||||
|
| `--ng-dim` | `#8A93A3` | Labels, axis text |
|
||||||
|
| `--ng-yellow` | `#FCEE0A` | Primary action, focus, page title |
|
||||||
|
| `--ng-cyan` | `#02D7F2` | Info, links, live data, chart series 1 |
|
||||||
|
| `--ng-red` | `#FF003C` | Danger fills/borders, thresholds |
|
||||||
|
| `--ng-red-txt` | `#FF3B5C` | Danger text (better contrast than `--ng-red`) |
|
||||||
|
| `--ng-green` | `#00FF9F` | Nominal/success, sparingly |
|
||||||
|
|
||||||
|
Screen budget, roughly: void 70%, panel 15%, yellow 8%, cyan 4%, red 3%.
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
- **UI and display:** Rajdhani 500/600/700. Headings and buttons uppercase; anything longer than one line in sentence case.
|
||||||
|
- **Data and labels:** JetBrains Mono 400/600, `font-variant-numeric: tabular-nums` for every metric.
|
||||||
|
- **Labels:** mono 12–13px, uppercase, `letter-spacing: 0.15em`, `--ng-dim`.
|
||||||
|
- **Scale (px):** 13, 15, 18 (body), 24, 32, 48, 72, 120. Body text at least 16px.
|
||||||
|
- Fonts: `https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=JetBrains+Mono:wght@400;600&display=swap`
|
||||||
|
|
||||||
|
## Shape and line
|
||||||
|
|
||||||
|
- Clipped corner: `clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%)`. 16px for cards, 24px for big sections, 12px for buttons. One cut per shape.
|
||||||
|
- Borders 1px `--ng-line`. Live/active dividers can be cyan.
|
||||||
|
- Selection or focus: yellow 2px corner brackets or a 2px yellow outline.
|
||||||
|
- Section headings can end in a broken yellow dash: a long line, then 8px, then 4px segments.
|
||||||
|
- **Diagonal structure:** edges step at 45°. Use `.ng-rule` under headings (thick bar stepping down into a hairline), `.ng-edge` for a stepped band along a section's bottom (`.ng-edge--hot` makes it yellow), and `.ng-panel--notched` for a panel with several cuts. One or two of these per screen; they frame, they don't fill.
|
||||||
|
|
||||||
|
## Components (classes in `night-grid.css`)
|
||||||
|
|
||||||
|
| Component | Class | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Panel | `.ng-panel` | Clipped card |
|
||||||
|
| Button | `.ng-btn` + `--primary` / `--info` / `--danger` | Min height 48px. One `--primary` per view |
|
||||||
|
| Tag | `.ng-tag` + `--priority` / `--live` / `--ok` / `--breach` | Status chips |
|
||||||
|
| Meter | `.ng-meter` with 10 `<i>`, lit ones get `.on` | Add `.ng-meter--danger` past a threshold |
|
||||||
|
| Alert | `.ng-alert` | Red border, 8% red fill. Say the thing, the number and the time |
|
||||||
|
| Input | `.ng-input` | Yellow underline on focus, red when `aria-invalid="true"` |
|
||||||
|
| Label | `.ng-label` | Mono machine label |
|
||||||
|
| Data | `.ng-data` | Mono, tabular numbers |
|
||||||
|
| Glow | `.ng-glow` | Only on the single most important value per screen |
|
||||||
|
| Fold panel | `<details class="ng-fold"><summary>Title</summary>…</details>` | Yellow corner chip with `»`: points down when folded, up when open. Native, keyboard-accessible, no JS |
|
||||||
|
| Glitch | `.ng-glitch` | Marks a heading or value as a glitch candidate; `night-grid.js` glitches one at random every 6–14s |
|
||||||
|
| Scanlines | `.ng-scanlines` on `<body>` | Optional; disabled with reduced motion |
|
||||||
|
|
||||||
|
A stat tile is a `.ng-panel` holding a `.ng-label`, a big `.ng-data` value (about 40px, unit at 18px in `--ng-dim`), a `.ng-meter`, and a short status line.
|
||||||
|
|
||||||
|
## Charts
|
||||||
|
|
||||||
|
- Series 1 cyan, series 2 yellow, threshold/alert red. At most three series.
|
||||||
|
- Gridlines `--ng-raise`, axis labels mono 12px `--ng-dim`. No chart background.
|
||||||
|
- Square bars with 2px gaps. Lines 2px, no smoothing.
|
||||||
|
|
||||||
|
## Motion
|
||||||
|
|
||||||
|
- Fast and stepped: 120ms, `linear` or `steps(4)`. No easing bounces.
|
||||||
|
- Glitch (skew, RGB split, sliced, 360ms) on a state change, plus an ambient glitch: one random `.ng-glitch` element every ~10s. Put `.ng-glitch` on 3–6 headings or key values, never on body text or controls.
|
||||||
|
- Numbers tick up when they change; panels wipe in left to right.
|
||||||
|
- Respect `prefers-reduced-motion`: no animation, no scanlines.
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
- Text contrast at least 4.5:1 (all text tokens above pass on `--ng-void` and `--ng-panel`).
|
||||||
|
- Never use color alone: every state also has a label or icon.
|
||||||
|
- Real `<button>`, `<a>`, `<label>` elements; touch targets at least 44px.
|
||||||
|
|
||||||
|
## Do / Don't
|
||||||
|
|
||||||
|
**Do**
|
||||||
|
- One yellow call to action per view.
|
||||||
|
- Separate groups with empty space before adding lines.
|
||||||
|
- Keep everything dark: this style has no light theme.
|
||||||
|
|
||||||
|
**Don't**
|
||||||
|
- Rounded corners, soft shadows, gradients, emoji.
|
||||||
|
- Yellow text on cyan or red.
|
||||||
|
- Glow everything.
|
||||||
|
- Use any game's logos, fonts or UI assets. Inspired by, never copied.
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Night Grid — Cyberpunk style guide
|
||||||
|
|
||||||
|
A dark neon HUD style for dashboards and tools: yellow, cyan and red neon on near-black, clipped corners, 45° stepped edges, mono machine labels and a rare random glitch.
|
||||||
|
|
||||||
|
| File | What |
|
||||||
|
|---|---|
|
||||||
|
| [`NIGHT-GRID.md`](NIGHT-GRID.md) | The full spec. Hand this to any agent or designer. |
|
||||||
|
| [`night-grid.css`](night-grid.css) | Tokens (`--ng-*`) and components (`.ng-*`). |
|
||||||
|
| [`night-grid.js`](night-grid.js) | Optional: glitches one random `.ng-glitch` element every 6–14s. |
|
||||||
|
| [`SKILL.md`](SKILL.md) | Claude Code skill wrapper. |
|
||||||
|
| [`canvas/`](canvas) | Source of the visual style guide and sample dashboard (Claude Design canvas). |
|
||||||
|
|
||||||
|
## Use it
|
||||||
|
|
||||||
|
```html
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="night-grid.css">
|
||||||
|
<script src="night-grid.js" defer></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
As a Claude Code skill:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ln -s "$PWD" ~/.claude/skills/night-grid
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspired by the look of near-future noir games; no game logos, fonts or assets are used.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: night-grid
|
||||||
|
description: Night Grid, Bas's cyberpunk / dark neon HUD style for dashboards, tools and pages (yellow, cyan and red neon on near-black, clipped corners, mono labels). Use when asked for a cyberpunk, neon, HUD or "Night Grid" look, or when building a dashboard or UI with no other style given.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Night Grid
|
||||||
|
|
||||||
|
1. Read `NIGHT-GRID.md` in this folder: the full spec (principles, colors, type, shapes, components, charts, motion, do/don't). Follow it.
|
||||||
|
2. Use `night-grid.css` from this folder: copy it into the project (or inline it in a single-file page) and build with its `ng-*` classes and `--ng-*` variables. Don't redefine the tokens.
|
||||||
|
3. Load the two Google Fonts listed at the top of the CSS.
|
||||||
|
4. For the ambient glitch, include `night-grid.js` (copy or inline it, `defer`) and mark a few headings or values `.ng-glitch`.
|
||||||
|
|
||||||
|
Dark theme only: this style has no light mode.
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Night Grid sample dashboard</title>
|
||||||
|
<script src="./support.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<x-dc>
|
||||||
|
<helmet>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body{margin:0;background:#050508}
|
||||||
|
a{color:#02D7F2}a:hover{color:#FCEE0A}
|
||||||
|
</style>
|
||||||
|
</helmet>
|
||||||
|
<div style="min-height: 100vh; background: #050508; color: #E6E8EC; font-family: 'Rajdhani', sans-serif; font-size: 18px; line-height: 1.5">
|
||||||
|
<div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px; padding: 16px 24px; border-bottom: 1px solid #2A2F3A">
|
||||||
|
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap">
|
||||||
|
<span style="background: #FCEE0A; color: #050508; padding: 4px 12px; font-size: 22px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase">Homelab</span>
|
||||||
|
<span style="font-family: 'JetBrains Mono', monospace; font-size: 13px; letter-spacing: 0.12em; color: #8A93A3">SYS.MONITOR // SAMPLE DATA</span>
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Sections" style="display: flex; flex-wrap: wrap; gap: 4px">
|
||||||
|
<a href="#" style="min-height: 44px; display: inline-flex; align-items: center; padding: 0 16px; color: #FCEE0A; text-decoration: none; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; border-bottom: 2px solid #FCEE0A">Overview</a>
|
||||||
|
<a href="#" style="min-height: 44px; display: inline-flex; align-items: center; padding: 0 16px; color: #8A93A3; text-decoration: none; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase">Nodes</a>
|
||||||
|
<a href="#" style="min-height: 44px; display: inline-flex; align-items: center; padding: 0 16px; color: #8A93A3; text-decoration: none; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase">Network</a>
|
||||||
|
<a href="#" style="min-height: 44px; display: inline-flex; align-items: center; padding: 0 16px; color: #8A93A3; text-decoration: none; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase">Logs</a>
|
||||||
|
</nav>
|
||||||
|
<span style="font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #02D7F2">● LIVE 22:41:07</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="max-width: 1360px; margin: 0 auto; padding: 32px 24px 64px; display: flex; flex-direction: column; gap: 24px">
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px">
|
||||||
|
<sc-for list="{{stats}}" as="s" hint-placeholder-count="4">
|
||||||
|
<div style="background: #0E1016; border: 1px solid #2A2F3A; padding: 20px 24px; display: flex; flex-direction: column; gap: 8px; clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%)">
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 0.15em; color: #8A93A3">{{s.label}}</div>
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 40px; font-weight: 600; line-height: 1.1; color: {{s.color}}">{{s.value}}<span style="font-size: 18px; color: #8A93A3">{{s.unit}}</span></div>
|
||||||
|
<div style="display: flex; gap: 3px">
|
||||||
|
<sc-for list="{{s.segs}}" as="g" hint-placeholder-count="10"><div style="flex: 1; height: 6px; background: {{g}}"></div></sc-for>
|
||||||
|
</div>
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 12px; color: {{s.noteColor}}">{{s.note}}</div>
|
||||||
|
</div>
|
||||||
|
</sc-for>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 16px">
|
||||||
|
<div style="flex: 999 1 560px; min-width: 0; background: #0E1016; border: 1px solid #2A2F3A; padding: 24px; display: flex; flex-direction: column; gap: 16px; clip-path: polygon(0 0, calc(100% - 24px) 0, 100% 24px, 100% 100%, 0 100%)">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: baseline; flex-wrap: wrap; gap: 8px">
|
||||||
|
<h2 style="margin: 0; font-size: 24px; font-weight: 700; text-transform: uppercase">Network throughput</h2>
|
||||||
|
<div style="display: flex; gap: 16px; font-family: 'JetBrains Mono', monospace; font-size: 12px; color: #8A93A3">
|
||||||
|
<span><span style="display: inline-block; width: 10px; height: 10px; background: #02D7F2; margin-right: 6px"></span>RX</span>
|
||||||
|
<span><span style="display: inline-block; width: 10px; height: 10px; background: #FCEE0A; margin-right: 6px"></span>TX</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div role="img" aria-label="Bar chart of received and transmitted traffic over the last 12 hours, sample data" style="display: flex; align-items: flex-end; gap: 6px; height: 220px; border-bottom: 1px solid #2A2F3A; background: repeating-linear-gradient(0deg, #1C2029 0px, #1C2029 1px, transparent 1px, transparent 55px)">
|
||||||
|
<sc-for list="{{bars}}" as="b" hint-placeholder-count="12">
|
||||||
|
<div style="flex: 1; display: flex; align-items: flex-end; gap: 2px; height: 100%">
|
||||||
|
<div style="flex: 1; height: {{b.rx}}; background: #02D7F2"></div>
|
||||||
|
<div style="flex: 1; height: {{b.tx}}; background: #FCEE0A"></div>
|
||||||
|
</div>
|
||||||
|
</sc-for>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: space-between; font-family: 'JetBrains Mono', monospace; font-size: 12px; color: #8A93A3"><span>-12H</span><span>-6H</span><span>NOW</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="flex: 1 1 320px; min-width: 0; background: #0E1016; border: 1px solid #2A2F3A; padding: 24px; display: flex; flex-direction: column; gap: 16px">
|
||||||
|
<h2 style="margin: 0; font-size: 24px; font-weight: 700; text-transform: uppercase">Nodes</h2>
|
||||||
|
<sc-for list="{{nodes}}" as="n" hint-placeholder-count="4">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid #1C2029">
|
||||||
|
<div style="display: flex; flex-direction: column">
|
||||||
|
<span style="font-weight: 700; text-transform: uppercase">{{n.name}}</span>
|
||||||
|
<span style="font-family: 'JetBrains Mono', monospace; font-size: 12px; color: #8A93A3">{{n.ip}}</span>
|
||||||
|
</div>
|
||||||
|
<span style="font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 0.12em; padding: 4px 10px; border: 1px solid {{n.color}}; color: {{n.color}}">{{n.status}}</span>
|
||||||
|
</div>
|
||||||
|
</sc-for>
|
||||||
|
<button type="button" style="min-height: 48px; background: #FCEE0A; color: #050508; border: 0; font-family: 'Rajdhani', sans-serif; font-size: 18px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%)">Run diagnostics</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="background: #0E1016; border: 1px solid #2A2F3A; padding: 24px; display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px">
|
||||||
|
<h2 style="margin: 0; font-size: 24px; font-weight: 700; text-transform: uppercase">Event log</h2>
|
||||||
|
<div style="flex: 1; height: 2px; background: #FCEE0A; max-width: 120px"></div><div style="width: 8px; height: 2px; background: #FCEE0A"></div><div style="width: 4px; height: 2px; background: #FCEE0A"></div>
|
||||||
|
</div>
|
||||||
|
<div style="overflow-x: auto">
|
||||||
|
<table style="width: 100%; min-width: 640px; border-collapse: collapse; font-family: 'JetBrains Mono', monospace; font-size: 14px">
|
||||||
|
<thead>
|
||||||
|
<tr style="text-align: left; color: #8A93A3; font-size: 12px; letter-spacing: 0.15em">
|
||||||
|
<th style="padding: 8px 12px 8px 0; font-weight: 400; border-bottom: 1px solid #2A2F3A">TIME</th>
|
||||||
|
<th style="padding: 8px 12px; font-weight: 400; border-bottom: 1px solid #2A2F3A">LEVEL</th>
|
||||||
|
<th style="padding: 8px 12px; font-weight: 400; border-bottom: 1px solid #2A2F3A">SOURCE</th>
|
||||||
|
<th style="padding: 8px 12px; font-weight: 400; border-bottom: 1px solid #2A2F3A">MESSAGE</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<sc-for list="{{logs}}" as="l" hint-placeholder-count="5">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 10px 12px 10px 0; color: #8A93A3; border-bottom: 1px solid #1C2029; white-space: nowrap">{{l.t}}</td>
|
||||||
|
<td style="padding: 10px 12px; color: {{l.color}}; border-bottom: 1px solid #1C2029; font-weight: 600">{{l.level}}</td>
|
||||||
|
<td style="padding: 10px 12px; color: #C3C8D1; border-bottom: 1px solid #1C2029">{{l.src}}</td>
|
||||||
|
<td style="padding: 10px 12px; color: #E6E8EC; border-bottom: 1px solid #1C2029">{{l.msg}}</td>
|
||||||
|
</tr>
|
||||||
|
</sc-for>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-dc>
|
||||||
|
<script type="text/x-dc" data-dc-script data-props='{"$preview":{"width":1440,"height":1100}}'>
|
||||||
|
class Component extends DCLogic {
|
||||||
|
renderVals() {
|
||||||
|
const meter = (n, on) => Array.from({ length: 10 }, (_, i) => (i < n ? on : '#1C2029'));
|
||||||
|
const rx = [30, 42, 38, 55, 61, 48, 72, 66, 80, 58, 64, 88];
|
||||||
|
const tx = [12, 18, 15, 22, 30, 20, 35, 28, 41, 26, 33, 46];
|
||||||
|
return {
|
||||||
|
stats: [
|
||||||
|
{ label: 'CPU LOAD', value: '42', unit: '%', color: '#E6E8EC', segs: meter(4, '#02D7F2'), note: 'NOMINAL', noteColor: '#00FF9F' },
|
||||||
|
{ label: 'MEMORY', value: '27.1', unit: 'GB', color: '#E6E8EC', segs: meter(7, '#02D7F2'), note: '68% OF 40 GB', noteColor: '#8A93A3' },
|
||||||
|
{ label: 'DISK /DEV/NVME0', value: '94', unit: '%', color: '#FF3B5C', segs: meter(9, '#FF003C'), note: '▲ ALERT // THRESHOLD 90%', noteColor: '#FF3B5C' },
|
||||||
|
{ label: 'UPTIME', value: '18', unit: 'D', color: '#FCEE0A', segs: meter(10, '#FCEE0A'), note: 'SINCE 2026-10-13', noteColor: '#8A93A3' }
|
||||||
|
],
|
||||||
|
bars: rx.map((r, i) => ({ rx: r + '%', tx: tx[i] + '%' })),
|
||||||
|
nodes: [
|
||||||
|
{ name: 'desktop', ip: '10.0.0.10', status: 'ONLINE', color: '#00FF9F' },
|
||||||
|
{ name: 'nas-01', ip: '10.0.0.20', status: 'WARN', color: '#FF3B5C' },
|
||||||
|
{ name: 'pi-dns', ip: '10.0.0.53', status: 'ONLINE', color: '#00FF9F' },
|
||||||
|
{ name: 'laptop', ip: '10.0.0.31', status: 'IDLE', color: '#8A93A3' }
|
||||||
|
],
|
||||||
|
logs: [
|
||||||
|
{ t: '22:41:07', level: 'INFO', color: '#02D7F2', src: 'pi-dns', msg: 'Blocklist refreshed' },
|
||||||
|
{ t: '22:38:52', level: 'ALERT', color: '#FF3B5C', src: 'nas-01', msg: 'Disk usage crossed 90%' },
|
||||||
|
{ t: '22:30:00', level: 'OK', color: '#00FF9F', src: 'desktop', msg: 'Nightly backup complete' },
|
||||||
|
{ t: '22:12:44', level: 'INFO', color: '#02D7F2', src: 'laptop', msg: 'Entered sleep' },
|
||||||
|
{ t: '21:58:19', level: 'ACTION', color: '#FCEE0A', src: 'desktop', msg: 'Updates ready to install' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Night Grid style guide</title>
|
||||||
|
<script src="./support.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<x-dc>
|
||||||
|
<helmet>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body{margin:0;background:#050508}
|
||||||
|
a{color:#02D7F2}a:hover{color:#FCEE0A}
|
||||||
|
.is-glitching{animation:ng-glitch 360ms steps(1,end)}
|
||||||
|
@keyframes ng-glitch{20%{transform:translateX(-3px) skewX(-12deg);text-shadow:2px 0 #FF003C,-2px 0 #02D7F2;clip-path:inset(10% 0 55% 0)}40%{transform:translateX(3px) skewX(8deg);clip-path:inset(60% 0 8% 0)}60%{transform:translateX(-1px);text-shadow:-2px 0 #FF003C,2px 0 #02D7F2;clip-path:inset(30% 0 35% 0)}80%{transform:none;text-shadow:1px 0 #FF003C,-1px 0 #02D7F2}}
|
||||||
|
.ng-fold>summary{list-style:none;cursor:pointer}
|
||||||
|
.ng-fold>summary::-webkit-details-marker{display:none}
|
||||||
|
.ng-fold>summary:focus-visible{outline:2px solid #FCEE0A;outline-offset:-2px}
|
||||||
|
.ng-fold>summary::before{content:"";position:absolute;top:0;right:0;width:44px;height:44px;background:#FCEE0A;clip-path:polygon(0 0,100% 0,100% 100%,12px 100%,0 calc(100% - 12px))}
|
||||||
|
.ng-fold>summary::after{content:"»";position:absolute;top:0;right:0;width:44px;height:44px;display:grid;place-items:center;color:#050508;font:700 24px/1 'JetBrains Mono',monospace;transform:rotate(90deg);transition:transform 120ms steps(2)}
|
||||||
|
.ng-fold[open]>summary::after{transform:rotate(-90deg)}
|
||||||
|
@media (prefers-reduced-motion:reduce){.is-glitching{animation:none}.ng-fold>summary::after{transition:none}}
|
||||||
|
</style>
|
||||||
|
</helmet>
|
||||||
|
<div style="position: relative; min-height: 100vh; background: #050508; color: #E6E8EC; font-family: 'Rajdhani', sans-serif; font-size: 18px; line-height: 1.5; overflow: hidden">
|
||||||
|
<sc-if value="{{scanlines}}" hint-placeholder-val="{{ true }}">
|
||||||
|
<div aria-hidden="true" style="position: absolute; inset: 0; pointer-events: none; background: repeating-linear-gradient(0deg, rgba(255,255,255,0.025) 0px, rgba(255,255,255,0.025) 1px, transparent 1px, transparent 3px); z-index: 2"></div>
|
||||||
|
</sc-if>
|
||||||
|
<div style="max-width: 1200px; margin: 0 auto; padding: 64px 24px 96px; display: flex; flex-direction: column; gap: 96px; position: relative; z-index: 1">
|
||||||
|
|
||||||
|
<header style="display: flex; flex-direction: column; gap: 20px; border-bottom: 1px solid #2A2F3A; padding-bottom: 48px">
|
||||||
|
<div style="display: flex; gap: 16px; align-items: center; flex-wrap: wrap; font-family: 'JetBrains Mono', monospace; font-size: 13px; letter-spacing: 0.12em; color: #8A93A3">
|
||||||
|
<span style="background: #FCEE0A; color: #050508; padding: 4px 10px; font-weight: 600">SYS.STYLE</span>
|
||||||
|
<span>REV 1.0 // 2026-10-31</span>
|
||||||
|
<span style="color: #02D7F2">STATUS: ONLINE</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="{{g0}}" style="margin: 0; font-size: clamp(56px, 9vw, 120px); line-height: 0.9; font-weight: 700; letter-spacing: -0.01em; text-transform: uppercase; color: #FCEE0A">Night Grid</h1>
|
||||||
|
<p style="margin: 0; max-width: 680px; font-size: 22px; color: #C3C8D1">A visual language for dashboards and tools: dark streets, neon signal, hard edges. Inspired by the HUD look of near-future noir games, built to stay readable at 3 a.m.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section style="display: flex; flex-direction: column; gap: 32px">
|
||||||
|
<h2 style="margin: 0; font-size: 14px; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.2em; color: #FF003C; font-weight: 600">01 // PRINCIPLES</h2>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px">
|
||||||
|
<div style="background: #0E1016; border: 1px solid #2A2F3A; padding: 24px; clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%)">
|
||||||
|
<div class="{{g1}}" style="font-size: 26px; font-weight: 700; text-transform: uppercase; color: #FCEE0A">Signal over noise</div>
|
||||||
|
<p style="margin: 8px 0 0; color: #C3C8D1">The city is dark so the data can glow. 70% of any screen is near-black void.</p>
|
||||||
|
</div>
|
||||||
|
<div style="background: #0E1016; border: 1px solid #2A2F3A; padding: 24px; clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%)">
|
||||||
|
<div class="{{g2}}" style="font-size: 26px; font-weight: 700; text-transform: uppercase; color: #FCEE0A">Hard edges</div>
|
||||||
|
<p style="margin: 8px 0 0; color: #C3C8D1">No rounded corners. Panels get one 45° clipped corner and 1px hairlines.</p>
|
||||||
|
</div>
|
||||||
|
<div style="background: #0E1016; border: 1px solid #2A2F3A; padding: 24px; clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%)">
|
||||||
|
<div class="{{g3}}" style="font-size: 26px; font-weight: 700; text-transform: uppercase; color: #FCEE0A">Color is meaning</div>
|
||||||
|
<p style="margin: 8px 0 0; color: #C3C8D1">Yellow = act or focus. Cyan = info and live data. Red = danger. Never decorative.</p>
|
||||||
|
</div>
|
||||||
|
<div style="background: #0E1016; border: 1px solid #2A2F3A; padding: 24px; clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%)">
|
||||||
|
<div class="{{g4}}" style="font-size: 26px; font-weight: 700; text-transform: uppercase; color: #FCEE0A">Diegetic data</div>
|
||||||
|
<p style="margin: 8px 0 0; color: #C3C8D1">Labels read like machine output: uppercase mono, IDs, timestamps, // separators.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="display: flex; flex-direction: column; gap: 32px">
|
||||||
|
<h2 style="margin: 0; font-size: 14px; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.2em; color: #FF003C; font-weight: 600">02 // COLOR</h2>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px">
|
||||||
|
<sc-for list="{{colors}}" as="c" hint-placeholder-count="9">
|
||||||
|
<div style="display: flex; flex-direction: column; border: 1px solid #2A2F3A; background: #0E1016">
|
||||||
|
<div style="height: 96px; background: {{c.hex}}; border-bottom: 1px solid #2A2F3A"></div>
|
||||||
|
<div style="padding: 14px 16px; display: flex; flex-direction: column; gap: 2px">
|
||||||
|
<div style="font-size: 20px; font-weight: 700; text-transform: uppercase">{{c.name}}</div>
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #02D7F2">{{c.hex}} · {{c.token}}</div>
|
||||||
|
<div style="font-size: 15px; color: #8A93A3">{{c.role}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</sc-for>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 10px">
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 13px; letter-spacing: 0.12em; color: #8A93A3">SCREEN BUDGET</div>
|
||||||
|
<div style="display: flex; height: 28px; border: 1px solid #2A2F3A">
|
||||||
|
<div style="flex: 70; background: #050508"></div>
|
||||||
|
<div style="flex: 15; background: #0E1016; border-left: 1px solid #2A2F3A"></div>
|
||||||
|
<div style="flex: 8; background: #FCEE0A"></div>
|
||||||
|
<div style="flex: 4; background: #02D7F2"></div>
|
||||||
|
<div style="flex: 3; background: #FF003C"></div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 8px 24px; font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #C3C8D1">
|
||||||
|
<span>VOID 70%</span><span>PANEL 15%</span><span style="color: #FCEE0A">YELLOW 8%</span><span style="color: #02D7F2">CYAN 4%</span><span style="color: #FF3B5C">RED 3%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="display: flex; flex-direction: column; gap: 32px">
|
||||||
|
<h2 style="margin: 0; font-size: 14px; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.2em; color: #FF003C; font-weight: 600">03 // TYPE</h2>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 24px">
|
||||||
|
<div style="border: 1px solid #2A2F3A; padding: 28px; display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #8A93A3; letter-spacing: 0.12em">DISPLAY + UI — RAJDHANI 500/600/700</div>
|
||||||
|
<div style="font-size: 72px; font-weight: 700; line-height: 1; text-transform: uppercase; color: #FCEE0A">Wake up</div>
|
||||||
|
<div style="font-size: 32px; font-weight: 600; text-transform: uppercase">Section heading 32</div>
|
||||||
|
<div style="font-size: 18px; font-weight: 500; color: #C3C8D1">Body 18/1.5. Condensed, squared shapes; uppercase for headings and buttons, sentence case for anything longer than a line.</div>
|
||||||
|
</div>
|
||||||
|
<div style="border: 1px solid #2A2F3A; padding: 28px; display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #8A93A3; letter-spacing: 0.12em">DATA + LABELS — JETBRAINS MONO 400/600</div>
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 56px; font-weight: 600; line-height: 1; color: #02D7F2">98.6<span style="font-size: 24px; color: #8A93A3">%</span></div>
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 13px; letter-spacing: 0.15em; color: #8A93A3">LABEL 13 · +0.15EM · UPPERCASE</div>
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 14px; color: #C3C8D1">[22:41:07] node-03 > sync complete</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 8px 32px; font-family: 'JetBrains Mono', monospace; font-size: 13px; color: #8A93A3">
|
||||||
|
<span>SCALE 13 · 15 · 18 · 24 · 32 · 48 · 72 · 120</span><span>TABULAR NUMBERS FOR ALL METRICS</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="display: flex; flex-direction: column; gap: 32px">
|
||||||
|
<h2 style="margin: 0; font-size: 14px; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.2em; color: #FF003C; font-weight: 600">04 // SHAPE + LINE</h2>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 24px">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="height: 140px; background: #0E1016; border: 1px solid #2A2F3A; clip-path: polygon(0 0, calc(100% - 20px) 0, 100% 20px, 100% 100%, 0 100%)"></div>
|
||||||
|
<div style="font-weight: 700; text-transform: uppercase">Clipped panel</div>
|
||||||
|
<div style="font-size: 15px; color: #8A93A3">Top-right cut, 16px (cards) or 24px (sections). One cut per shape.</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="height: 140px; position: relative; background: #0E1016">
|
||||||
|
<div style="position: absolute; top: 0; left: 0; width: 18px; height: 18px; border-top: 2px solid #FCEE0A; border-left: 2px solid #FCEE0A"></div>
|
||||||
|
<div style="position: absolute; top: 0; right: 0; width: 18px; height: 18px; border-top: 2px solid #FCEE0A; border-right: 2px solid #FCEE0A"></div>
|
||||||
|
<div style="position: absolute; bottom: 0; left: 0; width: 18px; height: 18px; border-bottom: 2px solid #FCEE0A; border-left: 2px solid #FCEE0A"></div>
|
||||||
|
<div style="position: absolute; bottom: 0; right: 0; width: 18px; height: 18px; border-bottom: 2px solid #FCEE0A; border-right: 2px solid #FCEE0A"></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: 700; text-transform: uppercase">Target brackets</div>
|
||||||
|
<div style="font-size: 15px; color: #8A93A3">Marks focus or selection. Yellow only, 2px.</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="height: 140px; background: #0E1016; display: flex; flex-direction: column; justify-content: center; gap: 18px; padding: 0 20px">
|
||||||
|
<div style="height: 1px; background: #2A2F3A"></div>
|
||||||
|
<div style="height: 1px; background: #02D7F2"></div>
|
||||||
|
<div style="display: flex; gap: 4px"><div style="flex: 1; height: 2px; background: #FCEE0A"></div><div style="width: 8px; height: 2px; background: #FCEE0A"></div><div style="width: 4px; height: 2px; background: #FCEE0A"></div></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: 700; text-transform: uppercase">Hairlines</div>
|
||||||
|
<div style="font-size: 15px; color: #8A93A3">1px #2A2F3A dividers; cyan for live; broken yellow dash as a header tail.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 24px">
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="height: 140px; background: #0E1016; display: flex; flex-direction: column; justify-content: center; padding: 0 20px">
|
||||||
|
<div style="position: relative; padding-bottom: 16px; font-size: 24px; font-weight: 700; text-transform: uppercase">Network
|
||||||
|
<div aria-hidden="true" style="position: absolute; left: 0; bottom: 0; width: 100%; height: 8px; background: #FCEE0A; clip-path: polygon(0 0, 72px 0, 78px 5px, 100% 5px, 100% 6px, 76px 6px, 70px 8px, 0 8px)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: 700; text-transform: uppercase">Stepped rule <span style="font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 400; color: #02D7F2">.ng-rule</span></div>
|
||||||
|
<div style="font-size: 15px; color: #8A93A3">Under headings: a thick bar that steps down at 45° into a hairline.</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="height: 140px; position: relative; background: #0E1016">
|
||||||
|
<div aria-hidden="true" style="position: absolute; left: 0; right: 0; bottom: 0; height: 14px; background: #FCEE0A; clip-path: polygon(0 100%, 0 0, 22% 0, calc(22% + 14px) 100%, 52% 100%, calc(52% + 7px) 50%, 70% 50%, calc(70% + 7px) 100%, 90% 100%, calc(90% + 14px) 0, 100% 0, 100% 100%)"></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-weight: 700; text-transform: uppercase">Edge band <span style="font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 400; color: #02D7F2">.ng-edge</span></div>
|
||||||
|
<div style="font-size: 15px; color: #8A93A3">Stepped band along a section's bottom. Line grey by default, yellow with --hot.</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px">
|
||||||
|
<div style="height: 140px; background: #0E1016; border: 1px solid #2A2F3A; clip-path: polygon(0 16px, 16px 0, 55% 0, calc(55% + 12px) 12px, 100% 12px, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%)"></div>
|
||||||
|
<div style="font-weight: 700; text-transform: uppercase">Notched panel <span style="font-family: 'JetBrains Mono', monospace; font-size: 13px; font-weight: 400; color: #02D7F2">.ng-panel--notched</span></div>
|
||||||
|
<div style="font-size: 15px; color: #8A93A3">Several cuts for a hero panel. One or two diagonal pieces per screen.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="display: flex; flex-direction: column; gap: 32px">
|
||||||
|
<h2 style="margin: 0; font-size: 14px; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.2em; color: #FF003C; font-weight: 600">05 // COMPONENTS</h2>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 16px; align-items: center">
|
||||||
|
<button type="button" style="min-height: 48px; padding: 0 28px; background: #FCEE0A; color: #050508; border: 0; font-family: 'Rajdhani', sans-serif; font-size: 18px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer; clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%)">Execute</button>
|
||||||
|
<button type="button" style="min-height: 48px; padding: 0 28px; background: transparent; color: #02D7F2; border: 1px solid #02D7F2; font-family: 'Rajdhani', sans-serif; font-size: 18px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer">Scan</button>
|
||||||
|
<button type="button" style="min-height: 48px; padding: 0 28px; background: transparent; color: #FF3B5C; border: 1px solid #FF003C; font-family: 'Rajdhani', sans-serif; font-size: 18px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer">Abort</button>
|
||||||
|
<button type="button" style="min-height: 48px; padding: 0 20px; background: transparent; color: #8A93A3; border: 1px solid #2A2F3A; font-family: 'Rajdhani', sans-serif; font-size: 18px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; cursor: pointer">Cancel</button>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 10px; font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 0.12em">
|
||||||
|
<span style="padding: 4px 10px; background: #FCEE0A; color: #050508; font-weight: 600">PRIORITY</span>
|
||||||
|
<span style="padding: 4px 10px; border: 1px solid #02D7F2; color: #02D7F2">LIVE</span>
|
||||||
|
<span style="padding: 4px 10px; border: 1px solid #00FF9F; color: #00FF9F">NOMINAL</span>
|
||||||
|
<span style="padding: 4px 10px; background: #FF003C; color: #FFFFFF; font-weight: 600">BREACH</span>
|
||||||
|
<span style="padding: 4px 10px; border: 1px solid #2A2F3A; color: #8A93A3">IDLE</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px">
|
||||||
|
<div style="background: #0E1016; border: 1px solid #2A2F3A; padding: 20px 24px; display: flex; flex-direction: column; gap: 8px; clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 0 100%)">
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 0.15em; color: #8A93A3">STAT TILE // CPU LOAD</div>
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 44px; font-weight: 600; color: #E6E8EC; line-height: 1.1">42<span style="font-size: 20px; color: #8A93A3">%</span></div>
|
||||||
|
<div style="display: flex; gap: 3px">
|
||||||
|
<div style="flex: 1; height: 6px; background: #02D7F2"></div><div style="flex: 1; height: 6px; background: #02D7F2"></div><div style="flex: 1; height: 6px; background: #02D7F2"></div><div style="flex: 1; height: 6px; background: #02D7F2"></div><div style="flex: 1; height: 6px; background: #1C2029"></div><div style="flex: 1; height: 6px; background: #1C2029"></div><div style="flex: 1; height: 6px; background: #1C2029"></div><div style="flex: 1; height: 6px; background: #1C2029"></div><div style="flex: 1; height: 6px; background: #1C2029"></div><div style="flex: 1; height: 6px; background: #1C2029"></div>
|
||||||
|
</div>
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 12px; color: #00FF9F">▲ SEGMENTED METER · 10 STEPS</div>
|
||||||
|
</div>
|
||||||
|
<div style="background: rgba(255,0,60,0.08); border: 1px solid #FF003C; padding: 20px 24px; display: flex; flex-direction: column; gap: 6px">
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px; font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 0.15em; color: #FF3B5C; font-weight: 600">
|
||||||
|
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="square"><path d="M12 3 2 21h20L12 3z"></path><path d="M12 10v5"></path><path d="M12 18v.5"></path></svg>
|
||||||
|
ALERT // ICE BREACH
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 22px; font-weight: 700; text-transform: uppercase">Disk /dev/nvme0 at 94%</div>
|
||||||
|
<div style="font-size: 15px; color: #C3C8D1">Alerts state the thing, the number and the time. Red border, 8% red fill.</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 8px">
|
||||||
|
<label for="ng-input" style="font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 0.15em; color: #8A93A3">INPUT // HOST</label>
|
||||||
|
<input id="ng-input" type="text" value="night-city.local" style="min-height: 48px; padding: 0 16px; background: #050508; color: #E6E8EC; border: 1px solid #2A2F3A; border-bottom: 2px solid #FCEE0A; font-family: 'JetBrains Mono', monospace; font-size: 16px; outline: none">
|
||||||
|
<div style="font-size: 15px; color: #8A93A3">Focus = yellow underline. Errors swap it to red with a mono message.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; align-items: start">
|
||||||
|
<details class="ng-fold" open="" style="position: relative; background: #0E1016; border: 1px solid #2A2F3A">
|
||||||
|
<summary style="min-height: 44px; padding: 10px 64px 10px 24px; display: flex; align-items: center; font-size: 24px; font-weight: 700; line-height: 1.1; text-transform: uppercase">Fold panel · open</summary>
|
||||||
|
<p style="margin: 0; padding: 0 24px 20px; color: #C3C8D1">Corner chip » points up: click to fold. Native <details>, works with the keyboard, no script.</p>
|
||||||
|
</details>
|
||||||
|
<details class="ng-fold" style="position: relative; background: #0E1016; border: 1px solid #2A2F3A">
|
||||||
|
<summary style="min-height: 44px; padding: 10px 64px 10px 24px; display: flex; align-items: center; font-size: 24px; font-weight: 700; line-height: 1.1; text-transform: uppercase">Fold panel · folded</summary>
|
||||||
|
<p style="margin: 0; padding: 0 24px 20px; color: #C3C8D1">Folded, the chip points down: click to unfold.</p>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="display: flex; flex-direction: column; gap: 32px">
|
||||||
|
<h2 style="margin: 0; font-size: 14px; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.2em; color: #FF003C; font-weight: 600">06 // CHARTS + MOTION</h2>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 24px">
|
||||||
|
<ul style="margin: 0; padding: 0 0 0 20px; display: flex; flex-direction: column; gap: 10px; color: #C3C8D1">
|
||||||
|
<li>Series 1 cyan, series 2 yellow, threshold/alert red. Max three series per chart.</li>
|
||||||
|
<li>Gridlines #1C2029, axis labels mono 12px #8A93A3. No chart backgrounds.</li>
|
||||||
|
<li>Bars squared, 2px gaps. Lines 2px, no smoothing: the data is jagged, show it.</li>
|
||||||
|
<li>Glow only on the single most important value: <span style="font-family: 'JetBrains Mono', monospace">text-shadow 0 0 12px</span> at 60%.</li>
|
||||||
|
</ul>
|
||||||
|
<ul style="margin: 0; padding: 0 0 0 20px; display: flex; flex-direction: column; gap: 10px; color: #C3C8D1">
|
||||||
|
<li>Motion is fast and stepped: 120ms, <span style="font-family: 'JetBrains Mono', monospace">steps(4)</span> or linear. No bounces.</li>
|
||||||
|
<li>Glitch (skew, RGB split, slices, 360ms) on state change, plus one random marked heading every 6–14s. Watch this page's title and principles.</li>
|
||||||
|
<li>Numbers tick up when they change; panels wipe in left to right.</li>
|
||||||
|
<li>Scanlines at 2.5% white, toggleable, off under <span style="font-family: 'JetBrains Mono', monospace">prefers-reduced-motion</span>.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="display: flex; flex-direction: column; gap: 32px">
|
||||||
|
<h2 style="margin: 0; font-size: 14px; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.2em; color: #FF003C; font-weight: 600">07 // TOKENS</h2>
|
||||||
|
<pre style="margin: 0; padding: 24px; background: #0E1016; border: 1px solid #2A2F3A; overflow-x: auto; font-family: 'JetBrains Mono', monospace; font-size: 14px; line-height: 1.7; color: #C3C8D1">:root {
|
||||||
|
--ng-void: #050508; /* page ground */
|
||||||
|
--ng-panel: #0E1016; /* cards, panels */
|
||||||
|
--ng-raise: #1C2029; /* tracks, grid, hover */
|
||||||
|
--ng-line: #2A2F3A; /* 1px borders */
|
||||||
|
--ng-text: #E6E8EC;
|
||||||
|
--ng-dim: #8A93A3; /* labels, 6.5:1 on void */
|
||||||
|
--ng-yellow: #FCEE0A; /* action, focus */
|
||||||
|
--ng-cyan: #02D7F2; /* info, live data */
|
||||||
|
--ng-red: #FF003C; /* danger fill/border */
|
||||||
|
--ng-red-txt: #FF3B5C; /* danger text */
|
||||||
|
--ng-green: #00FF9F; /* ok */
|
||||||
|
--ng-font-ui: 'Rajdhani', sans-serif;
|
||||||
|
--ng-font-data: 'JetBrains Mono', monospace;
|
||||||
|
--ng-cut: 16px; /* clip-path corner */
|
||||||
|
}</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="display: flex; flex-direction: column; gap: 32px">
|
||||||
|
<h2 style="margin: 0; font-size: 14px; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.2em; color: #FF003C; font-weight: 600">08 // DO + DON'T</h2>
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 24px">
|
||||||
|
<div style="border-top: 2px solid #00FF9F; padding-top: 16px; display: flex; flex-direction: column; gap: 8px; color: #C3C8D1">
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 13px; letter-spacing: 0.15em; color: #00FF9F">DO</div>
|
||||||
|
<div>One yellow call to action per view.</div>
|
||||||
|
<div>Pair every color state with a label or icon.</div>
|
||||||
|
<div>Keep body text sentence case and ≥16px.</div>
|
||||||
|
<div>Let empty void separate groups before adding lines.</div>
|
||||||
|
</div>
|
||||||
|
<div style="border-top: 2px solid #FF003C; padding-top: 16px; display: flex; flex-direction: column; gap: 8px; color: #C3C8D1">
|
||||||
|
<div style="font-family: 'JetBrains Mono', monospace; font-size: 13px; letter-spacing: 0.15em; color: #FF3B5C">DON'T</div>
|
||||||
|
<div>Use rounded corners, soft shadows or gradients.</div>
|
||||||
|
<div>Put yellow text on cyan or red (it vibrates).</div>
|
||||||
|
<div>Glow everything: if all of it shines, nothing does.</div>
|
||||||
|
<div>Copy game logos, fonts or UI assets: inspired by, never lifted.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-dc>
|
||||||
|
<script type="text/x-dc" data-dc-script data-props='{"scanlines":{"editor":"boolean","default":true},"$preview":{"width":1440,"height":3600}}'>
|
||||||
|
class Component extends DCLogic {
|
||||||
|
componentDidMount() {
|
||||||
|
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
const next = () => { this.timer = setTimeout(tick, 6000 + Math.random() * 8000); };
|
||||||
|
const tick = () => {
|
||||||
|
this.setState({ glitch: Math.floor(Math.random() * 5) });
|
||||||
|
this.clear = setTimeout(() => this.setState({ glitch: -1 }), 400);
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
clearTimeout(this.clear);
|
||||||
|
}
|
||||||
|
renderVals() {
|
||||||
|
const g = (i) => (this.state && this.state.glitch === i ? 'is-glitching' : '');
|
||||||
|
return {
|
||||||
|
g0: g(0), g1: g(1), g2: g(2), g3: g(3), g4: g(4),
|
||||||
|
scanlines: this.props.scanlines ?? true,
|
||||||
|
colors: [
|
||||||
|
{ name: 'Void', hex: '#050508', token: '--ng-void', role: 'Page ground. Almost black, slightly blue.' },
|
||||||
|
{ name: 'Panel', hex: '#0E1016', token: '--ng-panel', role: 'Cards and panels.' },
|
||||||
|
{ name: 'Raise', hex: '#1C2029', token: '--ng-raise', role: 'Meter tracks, gridlines, hover.' },
|
||||||
|
{ name: 'Line', hex: '#2A2F3A', token: '--ng-line', role: '1px borders and dividers.' },
|
||||||
|
{ name: 'Yellow', hex: '#FCEE0A', token: '--ng-yellow', role: 'Action, focus, headline. Primary.' },
|
||||||
|
{ name: 'Cyan', hex: '#02D7F2', token: '--ng-cyan', role: 'Info, links, live data series.' },
|
||||||
|
{ name: 'Red', hex: '#FF003C', token: '--ng-red', role: 'Danger, alerts, thresholds.' },
|
||||||
|
{ name: 'Green', hex: '#00FF9F', token: '--ng-green', role: 'Nominal, success. Use sparingly.' },
|
||||||
|
{ name: 'Text', hex: '#E6E8EC', token: '--ng-text', role: 'Primary text. Never pure white.' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"v":3,"createdOnFiles":{"v":1,"at":"2026-10-31T12:00:00Z"},"title":"Night Grid Style Guide","launch":{"view":"canvas"},"pages":[],"boards":{"Main.dc.html":{"x":0,"y":0,"w":1440,"h":3600,"title":"Style guide","expand":"fill","is_interactive":true},"Dashboard.dc.html":{"x":1520,"y":0,"w":1440,"h":1100,"title":"Sample dashboard","expand":"fill"}},"order":["Main.dc.html","Dashboard.dc.html"],"notes":{},"designSystems":[]}
|
||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
/* Night Grid — dark neon HUD style for dashboards.
|
||||||
|
Guide: https://claude.ai/artifact/AT2mCxMkeLFzkife1rAuLk
|
||||||
|
Fonts: <link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||||
|
Random glitch: <script src="night-grid.js" defer></script> */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--ng-void: #050508; /* page ground */
|
||||||
|
--ng-panel: #0E1016; /* cards, panels */
|
||||||
|
--ng-raise: #1C2029; /* tracks, grid, hover */
|
||||||
|
--ng-line: #2A2F3A; /* 1px borders */
|
||||||
|
--ng-text: #E6E8EC;
|
||||||
|
--ng-soft: #C3C8D1; /* secondary body text */
|
||||||
|
--ng-dim: #8A93A3; /* labels */
|
||||||
|
--ng-yellow: #FCEE0A; /* action, focus */
|
||||||
|
--ng-cyan: #02D7F2; /* info, live data */
|
||||||
|
--ng-red: #FF003C; /* danger fill/border */
|
||||||
|
--ng-red-txt: #FF3B5C; /* danger text */
|
||||||
|
--ng-green: #00FF9F; /* ok */
|
||||||
|
--ng-font-ui: 'Rajdhani', sans-serif;
|
||||||
|
--ng-font-data: 'JetBrains Mono', monospace;
|
||||||
|
--ng-cut: 16px;
|
||||||
|
--ng-speed: 120ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--ng-void);
|
||||||
|
color: var(--ng-text);
|
||||||
|
font: 500 18px/1.5 var(--ng-font-ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
a { color: var(--ng-cyan); }
|
||||||
|
a:hover { color: var(--ng-yellow); }
|
||||||
|
h1, h2, h3 { font-weight: 700; text-transform: uppercase; line-height: 1.1; }
|
||||||
|
h1 { color: var(--ng-yellow); }
|
||||||
|
|
||||||
|
/* Mono machine-style label: "CPU LOAD", "01 // COLOR" */
|
||||||
|
.ng-label {
|
||||||
|
font: 600 12px/1.4 var(--ng-font-data);
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ng-dim);
|
||||||
|
}
|
||||||
|
.ng-data { font-family: var(--ng-font-data); font-variant-numeric: tabular-nums; }
|
||||||
|
|
||||||
|
/* Panel with one clipped top-right corner */
|
||||||
|
.ng-panel {
|
||||||
|
background: var(--ng-panel);
|
||||||
|
border: 1px solid var(--ng-line);
|
||||||
|
padding: 20px 24px;
|
||||||
|
clip-path: polygon(0 0, calc(100% - var(--ng-cut)) 0, 100% var(--ng-cut), 100% 100%, 0 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons: one primary (yellow) per view */
|
||||||
|
.ng-btn {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 28px;
|
||||||
|
border: 1px solid var(--ng-line);
|
||||||
|
background: transparent;
|
||||||
|
color: var(--ng-dim);
|
||||||
|
font: 700 18px var(--ng-font-ui);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: filter var(--ng-speed) linear;
|
||||||
|
}
|
||||||
|
.ng-btn:hover { filter: brightness(1.2); }
|
||||||
|
.ng-btn:focus-visible { outline: 2px solid var(--ng-yellow); outline-offset: 3px; }
|
||||||
|
.ng-btn--primary {
|
||||||
|
background: var(--ng-yellow);
|
||||||
|
color: var(--ng-void);
|
||||||
|
border: 0;
|
||||||
|
clip-path: polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%);
|
||||||
|
}
|
||||||
|
.ng-btn--info { color: var(--ng-cyan); border-color: var(--ng-cyan); }
|
||||||
|
.ng-btn--danger { color: var(--ng-red-txt); border-color: var(--ng-red); }
|
||||||
|
|
||||||
|
/* Tags */
|
||||||
|
.ng-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: 1px solid var(--ng-line);
|
||||||
|
color: var(--ng-dim);
|
||||||
|
font: 600 12px var(--ng-font-data);
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.ng-tag--priority { background: var(--ng-yellow); color: var(--ng-void); border-color: var(--ng-yellow); }
|
||||||
|
.ng-tag--live { color: var(--ng-cyan); border-color: var(--ng-cyan); }
|
||||||
|
.ng-tag--ok { color: var(--ng-green); border-color: var(--ng-green); }
|
||||||
|
.ng-tag--breach { background: var(--ng-red); color: #fff; border-color: var(--ng-red); }
|
||||||
|
|
||||||
|
/* Segmented meter: <div class="ng-meter"><i class="on"></i>…10×</div> */
|
||||||
|
.ng-meter { display: flex; gap: 3px; }
|
||||||
|
.ng-meter > i { flex: 1; height: 6px; background: var(--ng-raise); }
|
||||||
|
.ng-meter > i.on { background: var(--ng-cyan); }
|
||||||
|
.ng-meter--danger > i.on { background: var(--ng-red); }
|
||||||
|
|
||||||
|
/* Alert banner */
|
||||||
|
.ng-alert {
|
||||||
|
background: rgb(255 0 60 / 0.08);
|
||||||
|
border: 1px solid var(--ng-red);
|
||||||
|
padding: 20px 24px;
|
||||||
|
}
|
||||||
|
.ng-alert .ng-label { color: var(--ng-red-txt); }
|
||||||
|
|
||||||
|
/* Input: yellow underline on focus */
|
||||||
|
.ng-input {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 16px;
|
||||||
|
background: var(--ng-void);
|
||||||
|
color: var(--ng-text);
|
||||||
|
border: 1px solid var(--ng-line);
|
||||||
|
font: 16px var(--ng-font-data);
|
||||||
|
}
|
||||||
|
.ng-input:focus { outline: none; border-bottom: 2px solid var(--ng-yellow); }
|
||||||
|
.ng-input[aria-invalid="true"] { border-bottom: 2px solid var(--ng-red); }
|
||||||
|
|
||||||
|
/* Glow: the single most important value only */
|
||||||
|
.ng-glow { text-shadow: 0 0 12px rgb(252 238 10 / 0.6); }
|
||||||
|
|
||||||
|
/* --- Diagonal structure --- */
|
||||||
|
|
||||||
|
/* Multi-notch panel: cut top-left, step in the top edge, cut bottom-right */
|
||||||
|
.ng-panel--notched {
|
||||||
|
clip-path: polygon(0 16px, 16px 0, 55% 0, calc(55% + 12px) 12px, 100% 12px,
|
||||||
|
100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%);
|
||||||
|
padding-top: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Heading underline: thick bar that steps down diagonally into a hairline */
|
||||||
|
.ng-rule { position: relative; padding-bottom: 16px; }
|
||||||
|
.ng-rule::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--ng-rule, var(--ng-yellow));
|
||||||
|
clip-path: polygon(0 0, 72px 0, 78px 5px, 100% 5px, 100% 6px, 76px 6px, 70px 8px, 0 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stepped edge band along the bottom of a section */
|
||||||
|
.ng-edge { position: relative; padding-bottom: 40px; }
|
||||||
|
.ng-edge::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 14px;
|
||||||
|
background: var(--ng-edge, var(--ng-line));
|
||||||
|
clip-path: polygon(0 100%, 0 0, 22% 0, calc(22% + 14px) 100%, 52% 100%, calc(52% + 7px) 50%,
|
||||||
|
70% 50%, calc(70% + 7px) 100%, 90% 100%, calc(90% + 14px) 0, 100% 0, 100% 100%);
|
||||||
|
}
|
||||||
|
.ng-edge--hot { --ng-edge: var(--ng-yellow); }
|
||||||
|
|
||||||
|
/* --- Glitch --- */
|
||||||
|
/* .ng-glitch marks candidates; night-grid.js adds .is-glitching to one at random every 6–14s */
|
||||||
|
.ng-glitch.is-glitching { animation: ng-glitch 360ms steps(1, end); }
|
||||||
|
@keyframes ng-glitch {
|
||||||
|
20% { transform: translateX(-3px) skewX(-12deg); text-shadow: 2px 0 var(--ng-red), -2px 0 var(--ng-cyan); clip-path: inset(10% 0 55% 0); }
|
||||||
|
40% { transform: translateX(3px) skewX(8deg); clip-path: inset(60% 0 8% 0); }
|
||||||
|
60% { transform: translateX(-1px); text-shadow: -2px 0 var(--ng-red), 2px 0 var(--ng-cyan); clip-path: inset(30% 0 35% 0); }
|
||||||
|
80% { transform: none; text-shadow: 1px 0 var(--ng-red), -1px 0 var(--ng-cyan); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Fold / unfold ---
|
||||||
|
<details class="ng-fold" open><summary>Title</summary>…content…</details>
|
||||||
|
Corner chip shows » pointing down (unfold) when closed, up (fold) when open. */
|
||||||
|
.ng-fold {
|
||||||
|
position: relative;
|
||||||
|
background: var(--ng-panel);
|
||||||
|
border: 1px solid var(--ng-line);
|
||||||
|
}
|
||||||
|
.ng-fold > summary {
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 10px 64px 10px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font: 700 24px/1.1 var(--ng-font-ui);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.ng-fold > summary::-webkit-details-marker { display: none; }
|
||||||
|
.ng-fold > summary:focus-visible { outline: 2px solid var(--ng-yellow); outline-offset: -2px; }
|
||||||
|
.ng-fold > summary::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
background: var(--ng-yellow);
|
||||||
|
clip-path: polygon(0 0, 100% 0, 100% 100%, 12px 100%, 0 calc(100% - 12px));
|
||||||
|
}
|
||||||
|
.ng-fold > summary::after {
|
||||||
|
content: "»";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
color: var(--ng-void);
|
||||||
|
font: 700 24px/1 var(--ng-font-data);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transition: transform var(--ng-speed) steps(2);
|
||||||
|
}
|
||||||
|
.ng-fold[open] > summary::after { transform: rotate(-90deg); }
|
||||||
|
.ng-fold > :not(summary) { margin: 0; padding: 0 24px 20px; }
|
||||||
|
|
||||||
|
/* Scanline overlay: add class to <body> */
|
||||||
|
.ng-scanlines::after {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background: repeating-linear-gradient(0deg, rgb(255 255 255 / 0.025) 0 1px, transparent 1px 3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ng-scanlines::after { display: none; }
|
||||||
|
* { transition: none !important; animation: none !important; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Night Grid: glitch one random .ng-glitch element every 6–14s (~10s average).
|
||||||
|
(() => {
|
||||||
|
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||||
|
const next = () => setTimeout(tick, 6000 + Math.random() * 8000);
|
||||||
|
const tick = () => {
|
||||||
|
const els = document.querySelectorAll('.ng-glitch');
|
||||||
|
const el = els[Math.floor(Math.random() * els.length)];
|
||||||
|
if (el) {
|
||||||
|
el.classList.add('is-glitching');
|
||||||
|
el.addEventListener('animationend', () => el.classList.remove('is-glitching'), { once: true });
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
next();
|
||||||
|
})();
|
||||||
Reference in new issue
Block a user