commit f931ca0423ff83463b66f68ceab2cb7f6453d7b6 Author: Thiemo Tiziani Date: Sun Oct 18 20:05:42 2026 +0200 first commit diff --git a/assets/bpm-mods.png b/assets/bpm-mods.png new file mode 100644 index 0000000..4bb411c Binary files /dev/null and b/assets/bpm-mods.png differ diff --git a/assets/bpm-stock.png b/assets/bpm-stock.png new file mode 100644 index 0000000..0faeef5 Binary files /dev/null and b/assets/bpm-stock.png differ diff --git a/assets/detail-mods.png b/assets/detail-mods.png new file mode 100644 index 0000000..2439f24 Binary files /dev/null and b/assets/detail-mods.png differ diff --git a/assets/detail-stock.png b/assets/detail-stock.png new file mode 100644 index 0000000..ba1ac25 Binary files /dev/null and b/assets/detail-stock.png differ diff --git a/assets/display-mods.png b/assets/display-mods.png new file mode 100644 index 0000000..752092e Binary files /dev/null and b/assets/display-mods.png differ diff --git a/assets/display-stock.png b/assets/display-stock.png new file mode 100644 index 0000000..25ccb82 Binary files /dev/null and b/assets/display-stock.png differ diff --git a/assets/hotcue-detail.png b/assets/hotcue-detail.png new file mode 100644 index 0000000..5ce704a Binary files /dev/null and b/assets/hotcue-detail.png differ diff --git a/assets/hotcue-overview.png b/assets/hotcue-overview.png new file mode 100644 index 0000000..856fc38 Binary files /dev/null and b/assets/hotcue-overview.png differ diff --git a/assets/mods-full.png b/assets/mods-full.png new file mode 100644 index 0000000..092d00b Binary files /dev/null and b/assets/mods-full.png differ diff --git a/assets/overview-mods.png b/assets/overview-mods.png new file mode 100644 index 0000000..fb530c7 Binary files /dev/null and b/assets/overview-mods.png differ diff --git a/assets/overview-stock.png b/assets/overview-stock.png new file mode 100644 index 0000000..c7563a3 Binary files /dev/null and b/assets/overview-stock.png differ diff --git a/assets/phase-mods.png b/assets/phase-mods.png new file mode 100644 index 0000000..eef21e4 Binary files /dev/null and b/assets/phase-mods.png differ diff --git a/assets/phase-stock.png b/assets/phase-stock.png new file mode 100644 index 0000000..9317297 Binary files /dev/null and b/assets/phase-stock.png differ diff --git a/assets/stock-full.png b/assets/stock-full.png new file mode 100644 index 0000000..46d655e Binary files /dev/null and b/assets/stock-full.png differ diff --git a/assets/utility-hotcue.png b/assets/utility-hotcue.png new file mode 100644 index 0000000..f45f39c Binary files /dev/null and b/assets/utility-hotcue.png differ diff --git a/assets/utility-phase.png b/assets/utility-phase.png new file mode 100644 index 0000000..0f35aab Binary files /dev/null and b/assets/utility-phase.png differ diff --git a/docs.html b/docs.html new file mode 100644 index 0000000..3e2b571 --- /dev/null +++ b/docs.html @@ -0,0 +1,231 @@ + + + + + + CDJ-900NXS Mods Docs + + + + + + + + + +
+
+ CDJ-900NXS Mods + + +
+
+ +
+ + +
+
Documentation
+

Using the mods

+

Everything you need to build the update, put it on your CDJ-900NXS and use each mod. The player must run the official firmware 1.31; the patcher only accepts that update.

+ + +

Install

+
    +
  1. Get the official C900NXS.UPD for firmware 1.31 from Pioneer DJ. The patcher checks its SHA-256 and refuses any other file.
  2. +
  3. Run the patcher with Python 3. Tick the mods with the arrow keys and Space, build with Enter. +
    $ python cdj900nxs_patcher.py C900NXS.UPD
    + Ticking a mod also ticks what it needs (utility needs code, hotcuekeys needs cuecolours); unticking one unticks what depends on it (beatjump needs hotcuekeys). credit (ticked at the start) makes the boot screen say Pionope Mods by Givou where it said Ver 1.31; untick it to keep the stock text. +
  4. +
  5. Read the disclaimer and type yes to accept it. Nothing is built without it.
  6. +
  7. The new file is written to out/<date>-<mods>/C900NXS.UPD, with a report.txt listing every check.
  8. +
  9. Copy C900NXS.UPD to the root of a FAT32 USB stick and run the update from the player as you would for an official update.
  10. +
+
What the patcher guarantees. Only the main application is replaced. The boot loader and recovery loader (flash 0x0–0x3FFFF), the S-record header and entry record, and the DRIV and PANL sections are copied from the original byte for byte and checked again on the finished file. The file is labelled 1.32 so that the player does not skip it as the version it already has; the player still reports 1.31.
+

Without a terminal that can read single keys, name the mods instead and accept the disclaimer with --accept:

+
$ python cdj900nxs_patcher.py C900NXS.UPD --mods code,utility,overview,cuecolours,hotcuekeys --accept
+ +

Back to stock

+

Build the original application, relabelled so the player accepts it, and update as above:

+
$ python cdj900nxs_patcher.py C900NXS.UPD --stock
+

Keep this file on a stick before you try any mod.

+ + +

code arnoldasr

+

Three display changes tested together as one build: the detail waveform in RGB colours, the BPM shown with two decimals (stacked beside the tempo) and a two-line phase meter.

+
RGB detail waveform
RGB detail waveform.
+ +

utility arnoldasr

+

UTILITY › SLIP FLASHING becomes PHASE METER; its value chooses which phase meter is shown. Needs code.

+
UTILITY with PHASE METER
UTILITY › PHASE METER.
+ +

overview arnoldasr

+

The overview waveform's colour table becomes a red-to-white gradient. Data only, no code.

+ +

cuecolours ours

+

When a track is loaded the player asks its database for the cues. This mod also asks for the extended cue list (the one newer players read from rekordbox's export), which carries the colours and the hot cues.

+
    +
  • Memory cues are drawn in the colour set in rekordbox, on the overview and on the detail waveform. A memory cue without a colour, or one stored on the player, stays red.
  • +
  • Hot cues A–H are drawn as small boxes with their letter, in their rekordbox colour (green when none was set).
  • +
+
Overview with coloured memory cues and hot cues A to F
Coloured memory cues and hot cues A–F on the overview.
+ + +

hotcuekeys ours

+

The CDJ-900NXS has no hot cue pads. This mod lends it three: the top three BEAT DIVIDE keys left of the jog. It needs cuecolours, which reads the hot cues from your USB.

+
UTILITY with HOT CUE KEYS
UTILITY › HOT CUE KEYS, the new last item.
+ +

The hot key mode

+
    +
  1. Open UTILITY (hold MENU) and set HOT CUE KEYS to ON. The player remembers it.
  2. +
  3. Press QUANTIZE briefly: the hot key mode switches on, and a red HOT KEYS box appears beside the QUANTIZE label. Press briefly again to switch it off (the box turns grey).
  4. +
  5. Hold QUANTIZE for about a second to switch quantize as usual.
  6. +
+
HOT KEYS box beside QUANTIZE
Hot key mode on: the red HOT KEYS box.
+

In the mode the BEAT DIVIDE keys are hot cues, top to bottom:

+
+
A
3/4
+
B
1/3
+
C
1/2
+
–
1/4
+
–
1/8
+
+

1/4 and 1/8 do nothing in the mode and stay dark: the player has three hot cue buffers, and A–C keep them, so each of the three starts at once.

+

With the mode off, or HOT CUE KEYS set to OFF, the keys do what they always did.

+ +

Play a hot cue

+

Press the key briefly. The deck jumps to the hot cue and plays — also when it was paused.

+ +

Gate

+

Press the key and keep holding it. The deck jumps and starts playing at once, exactly as with a short press; nothing waits. When you let go:

+
    +
  • held less than 0.5 s: it simply plays on (a normal hot cue);
  • +
  • held 0.5 s or longer: it jumps back to the hot cue and stands there, ready to go again.
  • +
+ +

Set a hot cue

+

On a key without a hot cue (LED off):

+
    +
  1. Pause the deck at the place you want.
  2. +
  3. Hold the key for 0.8 s. The hot cue is set there in green, its LED blinks twice and then stays dim, and the box appears on the waveforms.
  4. +
+

Keys that already have a hot cue are not overwritten — delete the old one first.

+ +

Delete a hot cue

+
    +
  1. Hold DELETE (beside MEMORY under the screen).
  2. +
  3. While holding it, press the hot cue key. Its LED blinks twice and goes off, and the box disappears from the waveforms. You can delete several keys while holding DELETE.
  4. +
  5. Let go of DELETE.
  6. +
+
While DELETE is held in the hot key mode, the deck does not see it, so no memory cue is lost. If you let go of DELETE without pressing a hot cue key, the press is passed on and DELETE deletes the called memory cue as always.
+ +

The LEDs

+

In the hot key mode the LEDs of 3/4, 1/3 and 1/2 show hot cues A–C; 1/4 and 1/8 are off (SLIP no longer changes them):

+
+ off — no hot cue + dim — hot cue there + two bright blinks — pressed, set or deleted +
+ +

Good to know

+
    +
  • Set and deleted hot cues are saved on the USB stick, in the track's rekordbox analysis files, the same way MEMORY and DELETE keep memory cues. Like MEMORY, this works once the track has settled, a few seconds after loading.
  • +
  • A–C start at once. After a track is loaded, once the deck stands at its CUE point for a second in the hot key mode, hot cues A, B and C are loaded silently into the player's three hot cue buffers (about three seconds) and the deck goes back to its CUE point. If you press PLAY or CUE meanwhile, it waits for that (PLAY then plays); a hot cue key pressed meanwhile cuts it short and plays.
  • +
  • A hot cue you set later loads into its own buffer on its first press (about a second) and starts at once after that.
  • +
  • Hot cues D–H from rekordbox are shown on the waveforms but have no key.
  • +
  • A hot cue played from a buffer leaves the CUE point where it is. A gate's release stands at the hot cue; PLAY then plays from there.
  • +
  • A key pressed while a hot cue is still loading waits for it (the last key pressed wins).
  • +
  • No slip hot cues yet: with SLIP on, a hot cue plays as without SLIP.
  • +
+ +

beatjump emulator only

+

With UTILITY › BEAT JUMP set to 1, 2, 4 or 8 (the item replaces SCREEN SAVER; the screen saver stays on), TRACK SEARCH ⏮ ⏭ jumps that many beats back or forward along the beat grid, keeping the position within the beat. OFF restores TRACK SEARCH.

+
    +
  • While playing, the jump target is loaded into a buffer of the player that only beat jumps use, so hot cues A–C stay ready. There is a short silence while it loads, but the deck comes back in phase, as if it had played on through the jump: the load time is measured on every jump and allowed for, at the deck's tempo. The CUE point stays.
  • +
  • Paused, the jump is a CALL: the deck and its CUE point move by that many beats.
  • +
  • A press while a jump or a hot cue is still loading is ignored, so loads never overlap.
  • +
  • A jump while a loop plays ends the loop; LOOP IN / OUT work again right after a jump.
  • +
  • Needs hotcuekeys (its engine makes the jumps); the HOT CUE KEYS item may stay OFF.
  • +
+
Rebuilt: the first version jumped off the grid on a player and, after quick presses, reported a corrupted file. The new one has only run in the emulator so far; please report how it behaves on a player.
+ + +

ledsaver emulator only

+

UTILITY › LED ANIMATION (the last item) is ON from the first start; set it to OFF to switch it off (the player keeps the choice). When the player's own screen saver dims the display (5 minutes without a track loaded, or 100 minutes paused or at the cue), the LEDs play a loop of about three seconds:

+
    +
  • the BEAT DIVIDE keys fade in a wave from top to bottom and back up,
  • +
  • SYNC and MASTER, MASTER TEMPO and DISC EJECT fade in and out against each other,
  • +
  • CUE, PLAY, REV, SLIP, the SLIP LOOP bar and RELOOP/EXIT light one after another,
  • +
  • the jog's three rings light in turn: inner, middle, outer, middle, inner …
  • +
+

Any key ends it, as it ends the screen saver. The keys without levels (CUE, PLAY …) can only switch on and off; the rings too.

+ +

Test status

+
+ + + + + + + + + + + + +
ModByStatus
codearnoldasrran on a player
utilityarnoldasrran on a player
overviewarnoldasrran on a player
cuecoloursoursran on a player
hotcuekeysourspartly tested mode, HOT KEYS box and LEDs on a player; the hot cue buffers (A–C at once), gate, set and delete in the emulator
beatjumpoursemulator only rebuilt on the hot cue engine
ledsaveroursemulator only LED map from the LED tests on a player
creditoursemulator only boot screen text (ticked at the start, can be unticked)
+
+ +

FAQ

+

Does this touch the boot loader?

+

No. The patcher copies the boot loader and recovery loader from the official file byte for byte and checks them again before writing. Only the main application changes.

+

Can I go back?

+

Yes, build --stock and run the update. Keep that file ready before flashing mods.

+

Do I need rekordbox?

+

Cue colours and hot cues come from a USB stick exported with rekordbox. Without them the mods show what the stock player shows.

+

Why is the version still 1.31?

+

The update file says 1.32 so the player installs it; the application inside still reports 1.31 (in UTILITY and on the LINK screen). Only the boot screen says Pionope Mods by Givou instead of Ver 1.31.

+
+
+ + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..0f43aef --- /dev/null +++ b/index.html @@ -0,0 +1,250 @@ + + + + + + CDJ-900NXS Mods + + + + + + + + + +
+
+ CDJ-900NXS Mods + + +
+
+ +
+
+
+
Pioneer CDJ-900NXS · firmware 1.31
+

New tricks for an old favourite.

+

+ A set of firmware mods that bring features of newer players to the CDJ-900NXS: + an RGB waveform, rekordbox cue colours, visible hot cues and + hot cues you can play from the deck itself. Built into the player's own + update file by a patcher that leaves the boot loader untouched. +

+ + +
+ Stock CDJ-900NXS screen: blue waveform, red memory cues, no hot cues + Modded screen: RGB waveform, coloured cues, hot cues A to F, HOT KEYS box + Stock 1.31 + Modded + + +
+
Same track, same position (2:13 into “Webra – Jumbled”). Drag to compare. Screens taken in the CDJ-900NXS emulator the mods were developed in.
+
+
+ +
+
+
+
6
mods, pick the ones you want
+
A–C
hot cues on the BEAT DIVIDE keys, at once
+
0 bytes
changed in the boot loader and recovery
+
1 file
a normal C900NXS.UPD for the player's USB update
+
+
+
+ +
+
+

The mods

+

Each one compared with stock. Three are by arnoldasr, three are ours. Choose any combination in the patcher.

+
+ ran on a player + emulator only + partly tested + known faults +
+ +
+ + +
+
+ +
+

RGB waveform, BPM & phase meter code

+
by arnoldasr
+
+
ran on a player
+
+

The detail waveform in RGB colours, as on newer Pioneer players, instead of plain blue. Plus the BPM with two decimals and a two-line phase meter.

+
+
Stock blue detail waveform
Stock
+
RGB detail waveform
Modded
+
+
+
Stock BPM 145.0
BPM · stock
+
BPM with two decimals
BPM · two decimals
+
+
    +
  • StockBlue waveform, BPM to one decimal, phase meter as four boxes.
  • +
  • ModdedRGB waveform, BPM to two decimals (stacked), phase meter on two lines.
  • +
+
+ + +
+
+ +
+

Phase meter switch utility

+
by arnoldasr · needs code
+
+
ran on a player
+
+

Choose the phase meter in UTILITY. The rarely used SLIP FLASHING item becomes PHASE METER.

+
+
Stock phase meter: four boxes
Phase meter · stock
+
Two-line phase meter
Phase meter · two lines
+
+
+
UTILITY menu with PHASE METER item
UTILITY › PHASE METER
+
+
    +
  • StockUTILITY › SLIP FLASHING.
  • +
  • ModdedUTILITY › PHASE METER picks the meter.
  • +
+
+ + +
+
+ +
+

Overview colour gradient overview

+
by arnoldasr
+
+
ran on a player
+
+

The whole-track overview at the bottom of the screen gets a warm red-to-white colour gradient instead of flat blue, so drops and breaks stand out.

+
+
Stock blue overview waveform
Stock
+
Gradient overview waveform with coloured cues and hot cues
Modded (with cue colours and hot cues)
+
+
    +
  • StockFlat blue overview.
  • +
  • ModdedRed-to-white gradient.
  • +
+
+ + +
+
+ +
+

Cue colours & hot cues shown cuecolours

+
ours · made with Claude Code
+
+
ran on a player
+
+

Memory cues take the colour you gave them in rekordbox. The hot cues stored on the USB (A–H) appear as lettered boxes in their rekordbox colour on the overview and the detail waveform — the stock 900NXS ignores them completely.

+
+
Stock: red memory triangles only
Stock
+
Hot cue A box on the detail waveform
Hot cue A on the waveform
+
+
    +
  • StockEvery memory cue a red triangle; hot cues not shown.
  • +
  • ModdedMemory cues in rekordbox colours; hot cues as coloured A–H boxes.
  • +
+ Read more → +
+ + +
+
+ +
+

Hot cue keys hotcuekeys

+
ours · made with Claude Code · needs cuecolours
+
+
partly tested
+
+

Play your rekordbox hot cues on the deck. A short press of QUANTIZE switches the hot key mode; the top three BEAT DIVIDE keys become hot cues A–C, ready to start at once. Press to jump and play, hold for a gate, set new ones while paused and delete them with DELETE. The key LEDs show which hot cues exist.

+
+
Stock: QUANTIZE label only
Stock
+
HOT KEYS box beside QUANTIZE
HOT KEYS mode on
+
+
+
UTILITY menu with BEAT JUMP and HOT CUE KEYS items
UTILITY › HOT CUE KEYS
+
+
    +
  • StockNo hot cues on the 900NXS; BEAT DIVIDE keys only for loops.
  • +
  • ModdedHot cue mode: play, gate, set, delete on the BEAT DIVIDE keys; QUANTIZE still works when held.
  • +
+ Hot cue guide → +
+ + +
+
+ +
+

Beat jump beatjump

+
ours · made with Claude Code
+
+
emulator only
+
+

TRACK SEARCH jumps 1, 2, 4 or 8 beats along the beat grid instead of changing the track, set in UTILITY › BEAT JUMP (which replaces SCREEN SAVER).

+
    +
  • StockTRACK SEARCH changes the track; UTILITY › SCREEN SAVER.
  • +
  • ModdedTRACK SEARCH jumps beats; UTILITY › BEAT JUMP OFF / 1 / 2 / 4 / 8.
  • +
+
Rebuilt. Jumps while playing go through a buffer of their own and come back in phase; the CUE point and hot cues A–C stay. Needs hotcuekeys. Tested in the emulator only.
+
+ +
+
+
+ +
+
+

One patcher, your choice

+

The patcher takes the official 1.31 update, adds the mods you tick and writes a new C900NXS.UPD. Only the main application changes; the boot loader, the recovery loader and the other sections are copied byte for byte and checked again before the file is written.

+
# pick the mods in a list +$ python cdj900nxs_patcher.py C900NXS.UPD + +# or name them +$ python cdj900nxs_patcher.py C900NXS.UPD --mods code,utility,overview,cuecolours,hotcuekeys + +# back to stock +$ python cdj900nxs_patcher.py C900NXS.UPD --stock
+

Before it builds, the patcher shows a disclaimer that you must accept. Full install guide →

+
+
+
+ + + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..3972132 --- /dev/null +++ b/script.js @@ -0,0 +1,52 @@ +// CDJ-900NXS Mods — small helpers: theme toggle, before/after slider, docs TOC highlight. +(function () { + var root = document.documentElement; + + function storedTheme() { + try { return localStorage.getItem('theme'); } catch (e) { return null; } + } + var t = storedTheme(); + if (t === 'light' || t === 'dark') root.setAttribute('data-theme', t); + + document.addEventListener('DOMContentLoaded', function () { + var btn = document.querySelector('.theme-btn'); + function label() { + if (btn) btn.textContent = root.getAttribute('data-theme') === 'light' ? '☾' : '☀'; + } + label(); + if (btn) btn.addEventListener('click', function () { + var next = root.getAttribute('data-theme') === 'light' ? 'dark' : 'light'; + root.setAttribute('data-theme', next); + try { localStorage.setItem('theme', next); } catch (e) { /* private mode */ } + label(); + }); + + // before / after + document.querySelectorAll('.compare').forEach(function (box) { + var range = box.querySelector('input[type="range"]'); + if (!range) return; + function set() { box.style.setProperty('--pos', range.value + '%'); } + range.addEventListener('input', set); + set(); + }); + + // docs: highlight the section in view + var links = Array.prototype.slice.call(document.querySelectorAll('.toc a[href^="#"]')); + if (links.length && 'IntersectionObserver' in window) { + var byId = {}; + links.forEach(function (a) { byId[a.getAttribute('href').slice(1)] = a; }); + var obs = new IntersectionObserver(function (entries) { + entries.forEach(function (e) { + if (e.isIntersecting && byId[e.target.id]) { + links.forEach(function (a) { a.classList.remove('active'); }); + byId[e.target.id].classList.add('active'); + } + }); + }, { rootMargin: '-80px 0px -70% 0px' }); + Object.keys(byId).forEach(function (id) { + var el = document.getElementById(id); + if (el) obs.observe(el); + }); + } + }); +})(); diff --git a/style.css b/style.css new file mode 100644 index 0000000..3ae0a85 --- /dev/null +++ b/style.css @@ -0,0 +1,289 @@ +/* CDJ-900NXS Mods — shared styles */ +:root { + --bg: #0b0d12; + --bg-2: #11141b; + --card: #151922; + --card-2: #1b2030; + --line: #262c3b; + --text: #e8ebf2; + --muted: #98a1b6; + --faint: #6b7489; + --accent: #ff5a1f; /* the deck's orange */ + --accent-2: #2fd3ff; /* the deck's cyan */ + --ok: #3ddc84; + --warn: #ffb020; + --bad: #ff4d5e; + --radius: 16px; + --shadow: 0 10px 40px rgba(0, 0, 0, .35); + --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + --sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + color-scheme: dark; +} + +:root[data-theme="light"] { + --bg: #f5f6fa; + --bg-2: #eceef4; + --card: #ffffff; + --card-2: #f3f4f8; + --line: #dde1ea; + --text: #141821; + --muted: #545d70; + --faint: #7c8598; + --shadow: 0 10px 30px rgba(20, 24, 33, .08); + color-scheme: light; +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } +body { + margin: 0; + background: var(--bg); + color: var(--text); + font: 16px/1.6 var(--sans); + -webkit-font-smoothing: antialiased; +} +img { max-width: 100%; display: block; } +a { color: var(--accent-2); text-decoration: none; } +a:hover { text-decoration: underline; } +code, kbd { font-family: var(--mono); font-size: .88em; } +code { background: var(--card-2); border: 1px solid var(--line); border-radius: 6px; padding: .08em .4em; } +kbd { + display: inline-block; min-width: 1.6em; text-align: center; + background: var(--card-2); border: 1px solid var(--line); border-bottom-width: 3px; + border-radius: 7px; padding: .05em .45em; font-weight: 600; +} + +.wrap { width: min(1180px, 100% - 32px); margin-inline: auto; } + +/* ---- header ---- */ +.top { + position: sticky; top: 0; z-index: 20; + backdrop-filter: blur(14px); + background: color-mix(in srgb, var(--bg) 80%, transparent); + border-bottom: 1px solid var(--line); +} +.top .wrap { display: flex; align-items: center; gap: 16px; height: 64px; } +.brand { display: flex; align-items: center; gap: 10px; color: var(--text); font-weight: 800; letter-spacing: -.01em; } +.brand:hover { text-decoration: none; } +.brand .disc { + width: 30px; height: 30px; border-radius: 50%; + background: conic-gradient(from 0deg, var(--accent), #ffb020, var(--accent-2), #7a5cff, var(--accent)); + box-shadow: inset 0 0 0 7px var(--bg), inset 0 0 0 9px var(--line); +} +.tabs { display: flex; gap: 4px; margin-left: auto; background: var(--card); border: 1px solid var(--line); border-radius: 999px; padding: 4px; } +.tabs a { + color: var(--muted); padding: 6px 16px; border-radius: 999px; font-weight: 600; font-size: .95rem; +} +.tabs a:hover { color: var(--text); text-decoration: none; } +.tabs a[aria-current="page"] { background: var(--accent); color: #fff; } +.theme-btn { + border: 1px solid var(--line); background: var(--card); color: var(--muted); + width: 38px; height: 38px; border-radius: 50%; cursor: pointer; font-size: 1rem; +} +.theme-btn:hover { color: var(--text); } + +/* ---- hero ---- */ +.hero { padding: 72px 0 40px; position: relative; overflow: hidden; } +.hero::before { + content: ""; position: absolute; inset: -40% -10% auto; height: 520px; z-index: -1; + background: + radial-gradient(closest-side at 25% 50%, color-mix(in srgb, var(--accent) 28%, transparent), transparent), + radial-gradient(closest-side at 75% 40%, color-mix(in srgb, var(--accent-2) 22%, transparent), transparent); + filter: blur(20px); +} +.eyebrow { font: 600 .8rem/1 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--accent); } +.hero h1 { font-size: clamp(2.2rem, 5vw, 3.8rem); line-height: 1.05; letter-spacing: -.03em; margin: 14px 0 16px; } +.hero h1 .grad { + background: linear-gradient(90deg, var(--accent), #ffb020 45%, var(--accent-2)); + -webkit-background-clip: text; background-clip: text; color: transparent; +} +.hero p.lead { font-size: 1.15rem; color: var(--muted); max-width: 720px; margin: 0 0 24px; } +.cta { display: flex; flex-wrap: wrap; gap: 12px; } +.btn { + display: inline-flex; align-items: center; gap: 8px; padding: 11px 20px; border-radius: 12px; + font-weight: 700; border: 1px solid var(--line); color: var(--text); background: var(--card); +} +.btn:hover { text-decoration: none; border-color: var(--faint); } +.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; } +.btn.primary:hover { filter: brightness(1.08); } + +/* ---- before / after slider ---- */ +.compare { + margin-top: 40px; position: relative; border-radius: var(--radius); overflow: hidden; + border: 1px solid var(--line); box-shadow: var(--shadow); background: #000; + aspect-ratio: 800 / 480; user-select: none; touch-action: pan-y; +} +.compare img { position: absolute; inset: 0; width: 100%; height: 100%; image-rendering: pixelated; } +.compare .after { clip-path: inset(0 0 0 var(--pos, 50%)); } +.compare .handle { + position: absolute; top: 0; bottom: 0; left: var(--pos, 50%); width: 2px; background: #fff; + transform: translateX(-1px); pointer-events: none; +} +.compare .handle::after { + content: "◂ ▸"; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); + background: #fff; color: #111; font: 700 .8rem/1 var(--sans); padding: 9px 10px; border-radius: 999px; + box-shadow: 0 4px 16px rgba(0,0,0,.4); white-space: nowrap; +} +.compare .tag { + position: absolute; top: 12px; font: 700 .72rem/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; + padding: 6px 9px; border-radius: 8px; background: rgba(0,0,0,.65); color: #fff; pointer-events: none; +} +.compare .tag.l { left: 12px; } +.compare .tag.r { right: 12px; background: var(--accent); } +.compare input[type="range"] { + position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: ew-resize; margin: 0; +} +.caption { color: var(--faint); font-size: .85rem; margin-top: 10px; } + +/* ---- sections ---- */ +section { padding: 56px 0; } +h2.section-title { font-size: clamp(1.6rem, 3vw, 2.2rem); letter-spacing: -.02em; margin: 0 0 8px; } +p.section-sub { color: var(--muted); margin: 0 0 32px; max-width: 760px; } + +.legend { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 26px; } + +/* ---- mod cards ---- */ +.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 520px), 1fr)); gap: 22px; } +.card { + background: linear-gradient(180deg, var(--card), var(--bg-2)); + border: 1px solid var(--line); border-radius: var(--radius); padding: 22px; + box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 16px; + transition: transform .2s ease, border-color .2s ease; +} +.card:hover { transform: translateY(-3px); border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); } +.card-head { display: flex; align-items: flex-start; gap: 14px; } +.card-head .icon { + flex: none; width: 44px; height: 44px; border-radius: 12px; display: grid; place-items: center; font-size: 1.3rem; + background: color-mix(in srgb, var(--accent) 16%, var(--card-2)); border: 1px solid var(--line); +} +.card h3 { margin: 0; font-size: 1.25rem; letter-spacing: -.01em; } +.card h3 code { font-size: .8rem; margin-left: 6px; vertical-align: middle; color: var(--muted); } +.card .by { color: var(--faint); font-size: .85rem; margin-top: 2px; } +.badges { display: flex; flex-wrap: wrap; gap: 6px; margin-left: auto; justify-content: flex-end; } +.badge { + font: 700 .68rem/1 var(--mono); letter-spacing: .06em; text-transform: uppercase; + padding: 6px 8px; border-radius: 7px; border: 1px solid currentColor; white-space: nowrap; +} +.badge.ok { color: var(--ok); } +.badge.emu { color: var(--accent-2); } +.badge.warn { color: var(--warn); } +.badge.bad { color: var(--bad); } +.badge.author { color: var(--muted); } +.card p { margin: 0; color: var(--muted); } + +.vs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } +.vs.stack { grid-template-columns: 1fr; } +.vs figure { margin: 0; background: #000; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; } +.vs figure img { width: 100%; image-rendering: pixelated; } +.vs figcaption { + font: 700 .68rem/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; + padding: 8px 10px; color: #c9cfdb; background: #0d1017; border-top: 1px solid #1f2533; +} +.vs figure.mod figcaption { color: #fff; background: color-mix(in srgb, var(--accent) 70%, #000); } + +.diff { list-style: none; padding: 0; margin: 0; display: grid; gap: 8px; } +.diff li { display: grid; grid-template-columns: 74px 1fr; gap: 10px; align-items: baseline; font-size: .95rem; } +.diff .k { font: 700 .68rem/1.9 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--faint); } +.diff .k.new { color: var(--accent); } +.card .more { margin-top: auto; font-weight: 700; font-size: .92rem; } + +/* ---- feature strip ---- */ +.strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; } +.tile { background: var(--card); border: 1px solid var(--line); border-radius: 14px; padding: 18px; } +.tile .n { font: 800 1.8rem/1 var(--sans); letter-spacing: -.02em; } +.tile .t { color: var(--muted); font-size: .92rem; margin-top: 6px; } + +/* ---- terminal ---- */ +.term { + background: #07090d; border: 1px solid var(--line); border-radius: 12px; padding: 16px 18px; + font: 14px/1.7 var(--mono); color: #d7deea; overflow-x: auto; white-space: pre; +} +.term .c { color: #6b7489; } +.term .p { color: var(--accent); } + +/* ---- docs layout ---- */ +.docs { display: grid; grid-template-columns: 250px 1fr; gap: 40px; padding: 40px 0 72px; } +.toc { position: sticky; top: 88px; align-self: start; max-height: calc(100vh - 110px); overflow: auto; } +.toc .h { font: 700 .72rem/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--faint); margin: 18px 0 8px; } +.toc a { display: block; color: var(--muted); padding: 5px 12px; border-left: 2px solid var(--line); font-size: .93rem; } +.toc a:hover { color: var(--text); text-decoration: none; } +.toc a.active { color: var(--text); border-left-color: var(--accent); } +.prose { min-width: 0; } +.prose h1 { font-size: clamp(2rem, 4vw, 2.8rem); letter-spacing: -.03em; margin: 0 0 10px; } +.prose h2 { + font-size: 1.6rem; letter-spacing: -.02em; margin: 56px 0 12px; padding-top: 8px; + scroll-margin-top: 84px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; +} +.prose h3 { font-size: 1.15rem; margin: 30px 0 8px; scroll-margin-top: 84px; } +.prose p, .prose li { color: var(--muted); } +.prose strong { color: var(--text); } +.prose ul, .prose ol { padding-left: 1.3em; } +.prose li { margin: 4px 0; } +.prose .shot { margin: 18px 0; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: #000; } +.prose .shot img { width: 100%; image-rendering: pixelated; } +.prose .shot figcaption { font-size: .82rem; color: var(--faint); padding: 8px 12px; background: var(--card); border-top: 1px solid var(--line); } + +.table-wrap { overflow-x: auto; margin: 16px 0; border: 1px solid var(--line); border-radius: 12px; } +table { border-collapse: collapse; width: 100%; min-width: 520px; font-size: .94rem; } +th, td { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--line); vertical-align: top; } +th { font: 700 .72rem/1.4 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--faint); background: var(--card); } +tr:last-child td { border-bottom: 0; } +td { color: var(--muted); } +td:first-child { color: var(--text); white-space: nowrap; } + +.callout { + border: 1px solid var(--line); border-left: 4px solid var(--accent-2); background: var(--card); + border-radius: 12px; padding: 14px 16px; margin: 18px 0; color: var(--muted); +} +.callout.warn { border-left-color: var(--warn); } +.callout.bad { border-left-color: var(--bad); } +.callout strong { color: var(--text); } + +.keys { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin: 16px 0; max-width: 520px; } +.keys > div { + background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 12px 6px; text-align: center; +} +.keys .b { font: 800 1.4rem/1 var(--sans); color: var(--accent); } +.keys .f { font: 600 .78rem/1 var(--mono); color: var(--faint); margin-top: 6px; } + +.leds { display: flex; gap: 18px; flex-wrap: wrap; margin: 14px 0; } +.led { display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: .92rem; } +.led i { width: 26px; height: 12px; border-radius: 4px; background: #2a2f3a; border: 1px solid #3a4150; display: inline-block; } +.led i.dim { background: #6b3a12; box-shadow: 0 0 6px rgba(255, 120, 30, .25); } +.led i.bright { background: #ff8a2a; box-shadow: 0 0 14px rgba(255, 138, 42, .8); } +.led i.blink { animation: blink 1.2s steps(1) infinite; } +@keyframes blink { + 0%, 16% { background: #ff8a2a; box-shadow: 0 0 14px rgba(255,138,42,.8); } + 17%, 33% { background: #6b3a12; box-shadow: none; } + 34%, 50% { background: #ff8a2a; box-shadow: 0 0 14px rgba(255,138,42,.8); } + 51%, 100% { background: #6b3a12; box-shadow: none; } +} + +/* ---- footer ---- */ +footer { border-top: 1px solid var(--line); background: var(--bg-2); padding: 36px 0 44px; margin-top: 20px; } +footer .cols { display: grid; grid-template-columns: 1.4fr 1fr; gap: 30px; } +footer h4 { margin: 0 0 8px; font-size: .78rem; letter-spacing: .12em; text-transform: uppercase; color: var(--faint); font-family: var(--mono); } +footer p { margin: 0 0 10px; color: var(--muted); font-size: .9rem; } +footer .ai { + display: inline-flex; align-items: center; gap: 8px; font-weight: 700; color: var(--text); + border: 1px solid var(--line); border-radius: 999px; padding: 6px 12px; margin-bottom: 12px; background: var(--card); +} +footer .ai .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); } + +@media (max-width: 860px) { + .docs { grid-template-columns: 1fr; gap: 10px; } + .toc { position: static; max-height: none; display: none; } + footer .cols { grid-template-columns: 1fr; } +} +@media (max-width: 560px) { + .top .wrap { gap: 10px; } + .brand span:not(.disc) { display: none; } + .tabs a { padding: 6px 12px; } + .hero { padding: 44px 0 24px; } + .vs { grid-template-columns: 1fr; } + .card-head { flex-wrap: wrap; } + .badges { margin-left: 0; justify-content: flex-start; width: 100%; } + .keys { grid-template-columns: repeat(5, 1fr); gap: 4px; } + .keys .b { font-size: 1.1rem; } + .diff li { grid-template-columns: 1fr; gap: 0; } +}