Bladerunner (#67)

* Introduce bladerunner theme

* Light neon glow

* Fix build error

* Different logo font and glow

* Neon glow on hover

* Make the theme to be easter egg

* Toggle lights via header

* Minor fixes

* Fix lint errors
This commit is contained in:
Borys Levytskyi
2025-11-07 21:24:02 -05:00
committed by GitHub
parent ac83c1efeb
commit c4877ac376
16 changed files with 20206 additions and 120 deletions

21
AGENTS.md Normal file
View File

@@ -0,0 +1,21 @@
Agent Guidelines for This Repository
Scope
- These instructions apply to the entire repository.
Theme Styling Policy
- Centralize all theme-related CSS (e.g., `.light`, `.dark`, `.midnight`, `.bladerunner`, and future themes) in `src/index.css` only.
- Do NOT place theme-specific rules in component-scoped stylesheets (e.g., files under `src/shell/components/*.css`). Component CSS must remain theme-agnostic.
- If a component needs theme-dependent styling, add/adjust the selectors in `src/index.css` that target the components markup (e.g., `.app-root.<theme> .component-selector { ... }`).
- Prefer grouping theme rules together by theme block in `src/index.css` for readability and consistency.
Layout Rules
- Global layout modifiers that affect multiple views (e.g., centered vs. stretched layout) should also live in `src/index.css`.
Examples
- Good: `src/index.css``.bladerunner .top-links button { color: #ff7fb0 }`
- Avoid: `src/shell/components/TopLinks.css``.bladerunner .top-links button { ... }`
Testing
- After changing theme styles, verify all themes (Light/Dark/Midnight/Bladerunner) render legibly and that component CSS contains no theme-specific selectors.

90
TEMP_whats.tsx Normal file
View File

@@ -0,0 +1,90 @@
import React from 'react';
import CommandLink from '../../core/components/CommandLink';
import './WhatsNewResultView.css';
function WhatsnewResultView() {
return <div className="changelog">
<h3>Changelog</h3>
<div className='item item-new'>
<p>
<span className="soft date">Nov 6th, 2025</span> <br/>
<p>
Added a new <CommandLink text="bladerunner" /> theme inspired by neon cyberpunk aesthetics.
</p>
</p>
</div>
<div className='item item-new'>
<p>
<span className="soft date">May 10th, 2023</span> <br/>
<p>
Behold! After a long time of inactivity, BitwiseCmd is getting an update. Here is what changed:
</p>
<ul>
<li>Browser's JavaScript engine is no longer used for the execution of bitwise operations.
BitwiseCmd has its own shiny custom-built bitwise calculator that supports operations integer of different sizes (8,16,32, and 64 bits) as well as their signed and unsigned versions. <CommandLink text='Check it out!' command='-1b 255ub -1 4294967295u -1l 18446744073709551615u' />.
This calculator tries to follow the same behavior of bitwise operations as implemented in C.
This includes shifting an integer by the number of bytes equal to its size (spoiler: you get the same number, this is undefined behavior in C. Don't believe me? Check this <a href="https://codeyarns.com/tech/2004-12-20-c-shift-operator-mayhem.html#gsc.tab=0">link</a>).</li>
<li>A slightly improved UI</li>
</ul>
<p>I'm sure there will be some bugs following such a big update. I will do my best to fix them as they are found.</p>
<p>Many thanks to all people that submitted issues on GitHub. Your feedback is greatly appreciated. </p>
</p>
</div>
<div className='item'>
<span className="soft date">May 5th, 2023</span> <br/>
<p>
Fixed <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues/13">bug</a> with incorrect binary representation of 64-bit numbers.
</p>
</div>
<div className="item">
<p><span className="soft date">Jul 24th, 2021</span> <br/>
<ul>
<li>Added support of <code>vpc</code> command to see how VPC network address is divided between VPC, Subnets, and Hosts. Try it out: <CommandLink text="vpc 192.168.24.1/24" /></li>
<li>Added ability to remove individual results</li>
</ul>
</p>
</div>
<div className="item">
<span className="soft date">Jun 16th, 2021</span>
<p>
Added support of <code>subnet</code> command to display information about subnet IP address such. Try it out: <CommandLink text="subnet 192.168.24.1/14" />
</p>
</div>
<div className="item">
<span className="soft date">Jun 14th, 2021</span>
<p>
Added support of IP addresses and subnet mask notations. Try them out:
</p>
<ul>
<li>A single IP address <CommandLink text="127.0.0.1" /></li>
<li>Multiple IP addresses and subnet mask notations <CommandLink text="127.0.0.1 192.168.0.0/24" /></li>
</ul>
</div>
<div className="item">
<span className="soft date">Jun 6th, 2017</span>
<p>
Added <code><CommandLink text="guid" /></code> command. Use it for generating v4 GUIDs </p>
</div>
<div className="item">
<span className="soft date">May 27th, 2017</span>
<p>
Added support of binary number notation (e.g. <code><CommandLink text="0b10101" /></code>). </p>
</div>
<div className="item">
<span className="soft">May 20th, 2017</span>
<p>
A new <CommandLink text="Midnight" /> theme was added.
</p>
</div>
<div className="item">
<span className="soft">May 16th, 2017</span>
<p>
Complete rewrite using React. Please let me know if you have problems with this release by <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues">creating an issue</a> in Github Repo.
</p>
</div>
</div>;
}
export default WhatsnewResultView;

19662
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -126,7 +126,7 @@ class ExpressionElementTableRow extends React.Component<ExpressionElementRowProp
return <tr className={"row-with-bits " + css}> return <tr className={"row-with-bits " + css}>
<td className="sign">{sign}</td> <td className="sign">{sign}</td>
<td className="label"> <td className="label">
{this.getLabel()} <span>{this.getLabel()}</span>
</td> </td>
<td className="bin"> <td className="bin">
<BinaryStringView <BinaryStringView

View File

@@ -11,7 +11,26 @@ html { height: 100% }
position: relative; position: relative;
} }
code { font-size: 1.2em; font-weight: bold; } /* Centered vs stretched layout */
.app-root.layout-centered { }
/* Constrain and center top-level sections while keeping full-width theme background */
.app-root.layout-centered > * {
max-width: clamp(70rem, 92vw, 100rem);
margin-left: auto;
margin-right: auto;
}
/* Blade Runner theme overrides for Settings pane (centralized here per AGENTS.md) */
.app-root.bladerunner #settings button { color: #ffbf69 }
.app-root.layout-stretched {
max-width: none;
margin: 0;
}
/* Theme-scoped code styling to avoid cross-theme overrides */
.light code, .dark code, .midnight code, .bladerunner code { font-size: 1.2em; font-weight: bold; }
.icon { margin-right: 5px; vertical-align: middle; } .icon { margin-right: 5px; vertical-align: middle; }
.header-cmd { color: #c5c5c5 } .header-cmd { color: #c5c5c5 }
@@ -30,8 +49,6 @@ code { font-size: 1.2em; font-weight: bold; }
.button svg { margin: 0; margin-right: 5px; } .button svg { margin: 0; margin-right: 5px; }
.button-large { padding: 10px; } .button-large { padding: 10px; }
.button:hover { background: rgba(255, 255, 255, 0.2);}
.expressionInput { .expressionInput {
padding: 3px; padding: 3px;
outline: none; outline: none;
@@ -98,6 +115,14 @@ button.link-button {text-decoration: underline;}
margin-left: -20px; margin-left: -20px;
} }
/* When Settings is active (no .soft), remove any button border/outline */
.settings-button button,
.settings-button .command-link {
border: none;
outline: none;
box-shadow: none;
}
.undo button { .undo button {
opacity: 0.4; opacity: 0.4;
padding: 0; padding: 0;
@@ -123,7 +148,7 @@ button.link-button {text-decoration: underline;}
.light .hashLink, .light .hashLink:visited { color: #aaa; } .light .hashLink, .light .hashLink:visited { color: #aaa; }
.light .hashLink:hover { color: #888 } .light .hashLink:hover { color: #888 }
.light ul.top-links li:hover { background: #ddd } .light ul.top-links li:hover { background: #ddd }
.light .error { color: #da586d } .light .error { color: #d83e8f }
.light button.btn { color: black} .light button.btn { color: black}
.light button.btn:hover { background: #ddd} .light button.btn:hover { background: #ddd}
.light button.btn:disabled { color: #888; background-color: inherit; } .light button.btn:disabled { color: #888; background-color: inherit; }
@@ -133,6 +158,8 @@ button.link-button {text-decoration: underline;}
.light .button:hover { background: rgba(0, 0, 0, 0.2);} .light .button:hover { background: rgba(0, 0, 0, 0.2);}
.light .solid-border { border: solid 1px gray;} .light .solid-border { border: solid 1px gray;}
.light .accent1-border { border-color:green} .light .accent1-border { border-color:green}
.light .help code, .light .help code a { color:green}
.light .button:hover { background: rgba(255, 255, 255, 0.2);}
/* Dark */ /* Dark */
.dark { background: #121212; color: white;} .dark { background: #121212; color: white;}
@@ -147,12 +174,14 @@ button.link-button {text-decoration: underline;}
.dark .hashLink, .dark .hashLink:visited { color: #555 } .dark .hashLink, .dark .hashLink:visited { color: #555 }
.dark .hashLink:hover { color: #999 } .dark .hashLink:hover { color: #999 }
.dark ul.top-links li:hover { background: #333 } .dark ul.top-links li:hover { background: #333 }
.dark .error { color: #da586d} .dark .error { color: #d83e8f}
.dark button.btn { color: white} .dark button.btn { color: white}
.dark button.btn:hover { background: #333} .dark button.btn:hover { background: #333}
.dark button.btn:disabled { color: #999; background-color: inherit; } .dark button.btn:disabled { color: #999; background-color: inherit; }
.dark .accent1 { color:mediumseagreen} .dark .accent1 { color:mediumseagreen}
.dark .accent1-border { border-color:mediumseagreen} .dark .accent1-border { border-color:mediumseagreen}
.dark .help code, .dark .help code a { color:mediumseagreen}
.dark .button:hover { background: rgba(255, 255, 255, 0.2);}
/* /*
Midnight Theme Midnight Theme
@@ -171,13 +200,139 @@ button.link-button {text-decoration: underline;}
.midnight .hashLink, .midnight .hashLink:visited { color: #85a0ad } .midnight .hashLink, .midnight .hashLink:visited { color: #85a0ad }
.midnight .hashLink:hover { color: #9FBAC7 } .midnight .hashLink:hover { color: #9FBAC7 }
.midnight ul.top-links li:hover { background: #132537 } .midnight ul.top-links li:hover { background: #132537 }
.midnight .error { color:#da586d} .midnight .error { color:#d83e8f}
.midnight .changelog .item-new .date { font-weight: bold } .midnight .changelog .item-new .date { font-weight: bold }
.midnight button.btn { color: white} .midnight button.btn { color: white}
.midnight button.btn:hover { background: #132537} .midnight button.btn:hover { background: #132537}
.midnight button.btn:disabled { color: #85a0ad; background-color: inherit; } .midnight button.btn:disabled { color: #85a0ad; background-color: inherit; }
.midnight .accent1 { color:mediumseagreen} .midnight .accent1 { color:mediumseagreen}
.midnight .accent1-border { border-color:mediumseagreen} .midnight .accent1-border { border-color:mediumseagreen}
.midnight .help code, .midnight .help code a { color:mediumseagreen}
.midnight .button:hover { background: rgba(255, 255, 255, 0.2);}
/*
Blade Runner Theme
*/
.bladerunner { background: #0b0f14; color: white }
.bladerunner .solid-background { background: #0b0f14; }
.bladerunner .header-cmd { color: #ff7fb0a8 !important; }
.bladerunner .lights-on .header-cmd { color: #ff7fb0 !important; }
/* Rule for element when it has both .header and .on */
.bladerunner .header.lights-on { text-shadow: 0 0 4px rgba(255, 127, 176, 0.35), 0 0 9px rgba(255, 127, 176, 0.22); }
.bladerunner .header h1 { text-transform: uppercase; color: #66d9e8a8; font-family: Impact, Verdana, sans-serif; cursor: pointer; position: relative; z-index: 1; letter-spacing: 0.02em; }
.bladerunner .header h1.lights-on { color: #66d9e8; text-shadow: 0 0 4px rgba(102, 217, 232, 0.35), 0 0 9px rgba(102, 217, 232, 0.22); }
.bladerunner .header h1.lights-on::after {
/* Neon-style halo: layered cyan + magenta radial glows */
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translate(-30%, -50%);
width: 20%;
height: 120%;
pointer-events: none;
z-index: 0;
background:
radial-gradient(ellipse at center,
rgba(102, 217, 232, 0.70) 0%,
rgba(102, 217, 232, 0.45) 22%,
rgba(0, 0, 0, 0) 60%),
radial-gradient(ellipse at center,
rgba(255, 127, 176, 0.55) 0%,
rgba(255, 127, 176, 0.35) 35%,
rgba(0, 0, 0, 0) 72%);
filter: blur(35px);
}
.bladerunner {color: #e6f0ff;}
.bladerunner .expression { color: white; }
.bladerunner .expressionInput { color: white; }
.bladerunner a, .bladerunner a:visited { color: #00d1f2; }
.bladerunner button { color: #00eaff; }
.bladerunner .indicator { color: #0e6e7e; }
.bladerunner .on { color: white; }
.bladerunner .prefix { color: #00d1f2 }
.bladerunner .other { color: #6c8497}
.bladerunner .label { color: #7ac9d6 }
.bladerunner .accent-background { background-color: #131a22; }
.bladerunner .hashLink:hover { color: #33e1ff }
.bladerunner .error { color:#d83e8f }
.bladerunner .changelog .item-new .date { font-weight: bold }
.bladerunner button.btn { color: #00eaff }
.bladerunner button.btn:hover { background: #11222c }
.bladerunner button.btn:disabled { color: #0e6e7e; background-color: inherit; }
.bladerunner .accent1 { color: mediumseagreen }
.bladerunner .accent1-border { border-color: mediumseagreen }
.bladerunner .button { border-color: #00eaff }
.bladerunner code { color: #e3a600 }
.bladerunner code a, .bladerunner code a:visited { color: #e3a600 }
.bladerunner .command-link { color: #5fc2e9 }
.bladerunner .command-link:hover,
.bladerunner .command-link:focus {
/* very dim magenta glow */
text-shadow: 0 0 2px rgba(255, 127, 176, 0.5);
}
.bladerunner .soft { color: #aebed0 }
.bladerunner .solid-border { background-color: #0e161d; border-color: #1f3b4a;}
.bladerunner .expressionInput {border-color: #1f3b4a;}
.bladerunner .expressionInput::placeholder {color: #7e95a7;}
.bladerunner .zero {color: #b9cfe2; }
.bladerunner .expression {color: #e6f0ff;}
.bladerunner .settings-button button {color: #b9cfe2;}
.bladerunner button.hashLink, .bladerunner a.hashLink, .bladerunner a.hashLink:visited { color: #52687b; }
.bladerunner button.hashLink:hover, .bladerunner a.hashLink:hover {
/* subtle glow derived from current hover color */
--text-shadow: 0 0 2px currentColor;
text-shadow:0 0 2px rgb(229, 33, 0.5);
color: #5fc2e9;
}
.bladerunner .cur { color: #6c8497; }
/* Blade Runner: subtle glow for SVG icons inside hash links on hover/focus */
.bladerunner .hashLink:hover .icon,
.bladerunner .hashLink:focus .icon,
.bladerunner .hashLink:hover svg,
.bladerunner .hashLink:focus svg {
filter: drop-shadow(0 0 2px currentColor);
}
/* Blade Runner: neon hover glow for top-links controls */
.bladerunner .top-links a, .bladerunner .top-links a:visited { color: #dce7f5 }
.bladerunner .top-links button { color: #dce7f5 }
.bladerunner .top-links a:hover,
.bladerunner .top-links a:focus,
.bladerunner .top-links button:hover,
.bladerunner .top-links button:focus {
/* very dim magenta glow */
text-shadow:0 0 2px rgb(229, 33, 0.5);
color: #FFBF00;
}
/* Blade Runner: add matching glow to SVG icons inside top-links on hover/focus */
.bladerunner .top-links a:hover .icon,
.bladerunner .top-links a:focus .icon,
.bladerunner .top-links button:hover .icon,
.bladerunner .top-links button:focus .icon,
.bladerunner .top-links a:hover svg,
.bladerunner .top-links a:focus svg,
.bladerunner .top-links button:hover svg,
.bladerunner .top-links button:focus svg {
color: amber !important;
filter: drop-shadow(0 0 2px rgba(255, 127, 176, 0.5));
}
.bladerunner .bladerunner-easter-egg {display: none;}
.bladerunner-easter-egg {
position: absolute;
right: 1em;
bottom: 1em;
opacity: 0.3;
}
.bladerunner-easter-egg:hover {
opacity: 1;
}
button { button {
border: none; border: none;
@@ -185,8 +340,25 @@ button {
cursor: pointer; cursor: pointer;
} }
button:focus {outline:0;} button:focus {outline:0;}
/* Remove blue focus ring from the Blade Runner easter egg button */
.bladerunner-easter-egg button:focus,
.bladerunner-easter-egg button:focus-visible {
outline: none !important;
box-shadow: none !important;
}
/* Anchor the Blade Runner easter egg to bottom-right regardless of scrolling */
.bladerunner-easter-egg {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 1000;
}
/* Top Links Shrink */ /* Top Links Shrink */
@media (max-width: 800px) { @media (max-width: 800px) {
.top-links .link-text { display: none } .top-links .link-text { display: none }

View File

@@ -11,7 +11,8 @@ export type PersistedAppData = {
donationClicked: boolean; donationClicked: boolean;
annotateTypes: boolean; annotateTypes: boolean;
dimExtrBits: boolean; dimExtrBits: boolean;
cookieDisclaimerHidden: boolean cookieDisclaimerHidden: boolean,
centeredLayout?: boolean
} }
export type CommandResultView = { export type CommandResultView = {
@@ -41,6 +42,7 @@ export default class AppState {
annotateTypes: boolean = false; annotateTypes: boolean = false;
dimExtraBits: boolean = false; dimExtraBits: boolean = false;
cookieDisclaimerHidden: boolean = false; cookieDisclaimerHidden: boolean = false;
centeredLayout: boolean = true;
constructor(persistData: PersistedAppData, env: string) { constructor(persistData: PersistedAppData, env: string) {
@@ -56,6 +58,7 @@ export default class AppState {
this.annotateTypes = !!persistData.annotateTypes; this.annotateTypes = !!persistData.annotateTypes;
this.dimExtraBits = !!persistData.dimExtrBits; this.dimExtraBits = !!persistData.dimExtrBits;
this.cookieDisclaimerHidden = !!persistData.cookieDisclaimerHidden this.cookieDisclaimerHidden = !!persistData.cookieDisclaimerHidden
this.centeredLayout = (persistData.centeredLayout === undefined) ? true : !!persistData.centeredLayout;
} }
addCommandResult(input: string, view: ViewFactory) { addCommandResult(input: string, view: ViewFactory) {
@@ -116,6 +119,11 @@ export default class AppState {
this.triggerChanged(); this.triggerChanged();
} }
toggleCenteredLayout(value?: boolean) {
this.centeredLayout = value !== null && value !== undefined ? value : !this.centeredLayout;
this.triggerChanged();
}
registerVisit() { registerVisit() {
this.pageVisitsCount++; this.pageVisitsCount++;
this.triggerChanged(); this.triggerChanged();
@@ -144,7 +152,8 @@ export default class AppState {
donationClicked: this.donationClicked, donationClicked: this.donationClicked,
annotateTypes: this.annotateTypes, annotateTypes: this.annotateTypes,
dimExtrBits: this.dimExtraBits, dimExtrBits: this.dimExtraBits,
cookieDisclaimerHidden: this.cookieDisclaimerHidden cookieDisclaimerHidden: this.cookieDisclaimerHidden,
centeredLayout: this.centeredLayout
} }
} }
}; };

80
src/shell/Bladerunner.ts Normal file
View File

@@ -0,0 +1,80 @@
import AppState from "./AppState";
import log from 'loglevel';
const LIGHTS_ON_KEY = 'lightsOn';
const LIGHTS_ON_CLASS = 'lights-on';
const turnLightsOff = (): void => (getHeader()).classList.remove('lights-on')
const getLightIsOn = () : boolean => localStorage.getItem(LIGHTS_ON_KEY) !== 'false';
const setLightIsOn = (value: boolean) => localStorage.setItem(LIGHTS_ON_KEY, value.toString());
const getHeader = (): HTMLHeadElement => document.querySelector('.header h1')!;
const toggleLights = (): void => {
const header = getHeader();
const lightIsOn = header.classList.contains(LIGHTS_ON_CLASS);
setLightIsOn(!lightIsOn);
if (lightIsOn)
turnLightsOff();
else
turnLightsOnWithFlickering();
};
const turnLightsOnWithFlickering = (): void => {
const header = getHeader();
const flickers = [true, false, true, false, true, false, true];
log.info("Turning lights on with flickering");
flickers.forEach((flicker, index) => {
setTimeout(() => {
if (flicker)
header.classList.add(LIGHTS_ON_CLASS);
else
header.classList.remove(LIGHTS_ON_CLASS);
}, index * 100);
});
};
const startLightsIfWereOnAfterDelay = (): void => {
if (getLightIsOn())
setTimeout(turnLightsOnWithFlickering, 500);
else
log.info("Lights are off by user preference");
};
const addStateListener = (appState: AppState) => {
var oldTheme = appState.uiTheme;
appState.onChange(() => {
log.info("App state changed, checking for lights theme. Current theme: " + appState.uiTheme + ", old theme: " + oldTheme);
if(appState.uiTheme === 'bladerunner' && oldTheme !== 'bladerunner') {
log.info("Starting lights because bladerunner theme is active");
startLightsIfWereOnAfterDelay();
}
else if(oldTheme === "bladerunner" && appState.uiTheme !== 'bladerunner') {
turnLightsOff();
}
oldTheme = appState.uiTheme;
});
};
const start = (appState: AppState): void => {
if(appState.uiTheme === 'bladerunner')
startLightsIfWereOnAfterDelay();
else
turnLightsOff();
addStateListener(appState);
};
const BladerunnerApi = { start, toggleLights };
export default BladerunnerApi;

View File

@@ -11,7 +11,8 @@ const DEFAULT_DATA : PersistedAppData = {
donationClicked: false, donationClicked: false,
annotateTypes: false, annotateTypes: false,
dimExtrBits: false, dimExtrBits: false,
cookieDisclaimerHidden: false cookieDisclaimerHidden: false,
centeredLayout: true
} }
const appStateStore = { const appStateStore = {

View File

@@ -7,9 +7,10 @@ import DebugIndicators from './DebugIndicators';
import hash from '../../core/hash'; import hash from '../../core/hash';
import TopLinks from './TopLinks'; import TopLinks from './TopLinks';
import SettingsPane from './SettingsPane'; import SettingsPane from './SettingsPane';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import CommandLink from '../../core/components/CommandLink';
import { faGear } from '@fortawesome/free-solid-svg-icons'; import { faGear, faPersonRunning } from '@fortawesome/free-solid-svg-icons';
import CookieDisclaimerFooter from './CookieDisclaimerFooter'; import CookieDisclaimerFooter from './CookieDisclaimerFooter';
import bladerunner from '../Bladerunner';
type AppRootProps = { type AppRootProps = {
@@ -19,7 +20,8 @@ type AppRootProps = {
type AppRootState = { type AppRootState = {
uiTheme: string, uiTheme: string,
emphasizeBytes: boolean, emphasizeBytes: boolean,
commandResults: CommandResultView[] commandResults: CommandResultView[],
centeredLayout: boolean
} }
export default class AppRoot extends React.Component<AppRootProps, AppRootState> { export default class AppRoot extends React.Component<AppRootProps, AppRootState> {
@@ -46,16 +48,23 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
return results; return results;
} }
componentDidMount(): void {
bladerunner.start(this.props.appState);
}
render() { render() {
const enableNewUi = this.props.appState.env !== 'prod' || true; const enableNewUi = this.props.appState.env !== 'prod' || true;
const newUi = enableNewUi ? 'new-ui' : ''; const newUi = enableNewUi ? 'new-ui' : '';
const settingsCss = "settings-button" + (this.props.appState.showSettings ? '' : ' soft'); const settingsCss = "settings-button" + (this.props.appState.showSettings ? '' : ' soft');
return <div className={`app-root ${this.state.uiTheme} ${newUi}`}> const layoutClass = this.state.centeredLayout ? 'layout-centered' : 'layout-stretched';
return <div className={`app-root ${this.state.uiTheme} ${newUi} ${layoutClass}`}>
<div className="header-pane">
<DebugIndicators appState={this.props.appState} /> <DebugIndicators appState={this.props.appState} />
<div className="header"> <div className="header">
<h1>Bitwise<span className="header-cmd">Cmd</span> <h1 onClick={() => bladerunner.toggleLights()}>
Bitwise<span className="header-cmd">Cmd</span>
</h1> </h1>
<TopLinks /> <TopLinks />
</div> </div>
@@ -63,16 +72,24 @@ export default class AppRoot extends React.Component<AppRootProps, AppRootState>
<div className="expressionInput-container"> <div className="expressionInput-container">
<InputBox onCommandEntered={(input) => cmd.execute(input)} /> <InputBox onCommandEntered={(input) => cmd.execute(input)} />
<button className={settingsCss} title='Toggle Settings' type="button" onClick={() => this.props.appState.toggleShowSettings()}> <span className={settingsCss}>
<FontAwesomeIcon icon={faGear} /> <CommandLink text="" command='settings' icon={faGear} />
</button> </span>
</div> </div>
</div>
<div className="content-pane">
{this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null} {this.props.appState.showSettings ? <SettingsPane appState={this.props.appState} /> : null}
<div id="output"> <div id="output">
{this.getResultViews()} {this.getResultViews()}
</div> </div>
<CookieDisclaimerFooter appSate={this.props.appState} /> </div>
<CookieDisclaimerFooter appState={this.props.appState} />
<div className="bladerunner-easter-egg">
<CommandLink text="" command='bladerunner-easter' icon={faPersonRunning} />
</div>
</div>; </div>;
} }
} }

View File

@@ -5,14 +5,14 @@ import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCaretDown, faCaretUp, faCircleXmark } from "@fortawesome/free-solid-svg-icons"; import { faCaretDown, faCaretUp, faCircleXmark } from "@fortawesome/free-solid-svg-icons";
function CookieDisclaimerFooter(props: { appSate: AppState }): JSX.Element { function CookieDisclaimerFooter(props: { appState: AppState }): JSX.Element {
const [expanded, setShowMore] = useState(false); const [expanded, setShowMore] = useState(false);
const longCss = expanded ? " expanded" : " collapsed"; const longCss = expanded ? " expanded" : " collapsed";
const shortCss = expanded ? " collapsed" : " expanded"; const shortCss = expanded ? " collapsed" : " expanded";
const buttonText = expanded ? "Read Less" : "Read More"; const buttonText = expanded ? "Read Less" : "Read More";
if (props.appSate.cookieDisclaimerHidden) if (props.appState.cookieDisclaimerHidden)
return <React.Fragment></React.Fragment>; return <React.Fragment></React.Fragment>;
return <div className="cookie-disclaimer"> return <div className="cookie-disclaimer">
@@ -34,7 +34,7 @@ function CookieDisclaimerFooter(props: { appSate: AppState }): JSX.Element {
</div> </div>
<div> <div>
<p> <p>
<button className="button" onClick={() => setShowMore(!expanded)}><FontAwesomeIcon icon={expanded ? faCaretDown : faCaretUp} />{buttonText}</button> <button className="button" onClick={() => props.appSate.setCookieDisclaimerHidden(true)}><FontAwesomeIcon icon={faCircleXmark} />Hide</button> <button className="button" onClick={() => setShowMore(!expanded)}><FontAwesomeIcon icon={expanded ? faCaretDown : faCaretUp} />{buttonText}</button> <button className="button" onClick={() => props.appState.setCookieDisclaimerHidden(true)}><FontAwesomeIcon icon={faCircleXmark} />Hide</button>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,5 @@
.help ul { list-style-type: none; margin: 0; margin-left: 10px; margin-top: 0.2em; padding: 0; } .help ul { list-style-type: none; margin: 0; margin-left: 10px; margin-top: 0.2em; padding: 0; }
.help li { padding: 1px; margin-bottom: 5px;} .help li { padding: 1px; margin-bottom: 5px;}
.help code, .help code a { color:mediumseagreen}
.light .help code, .light .help code a { color:green} .light .help code, .light .help code a { color:green}
.help p { margin-top: 0.5em } .help p { margin-top: 0.5em }
.help .section {padding: 1em;} .help .section {padding: 1em;}

View File

@@ -63,7 +63,7 @@ function HelpResultView() {
</div> </div>
</div> </div>
<div className="section soft-border"> <div className="section soft-border">
<div className="section-title soft">Supported Number Types <sup className='accent1'>NEW</sup></div> <div className="section-title soft">Supported Number Types</div>
<p> <p>
BitiwseCmd no longer uses the browser's JavaScript engine for the execution of bitwise operations. It has its own calculator implementation which brings supports bitwise operations on the following <i>signed</i> and <i>unsigned</i> data types: BitiwseCmd no longer uses the browser's JavaScript engine for the execution of bitwise operations. It has its own calculator implementation which brings supports bitwise operations on the following <i>signed</i> and <i>unsigned</i> data types:
</p> </p>

View File

@@ -32,3 +32,7 @@
.settings-container .setting { .settings-container .setting {
margin-bottom: 10px; margin-bottom: 10px;
} }
/* Theme-specific overrides belong in src/index.css per AGENTS.md */
/* Ensure Settings aligns with centered layout */
.app-root.layout-centered #settings.settings-container { display: block; }

View File

@@ -44,6 +44,16 @@ function SettingsPane(props : SettingsPaneProps) {
: "Information about the size of integers is hidden."} : "Information about the size of integers is hidden."}
</p> </p>
</div> </div>
<div className='setting'>
<button type="button" onClick={() => appState.toggleCenteredLayout()}>
<FontAwesomeIcon size='xl' icon={appState.centeredLayout ? faToggleOn : faToggleOff} /> Centered Layout
</button>
<p className='description'>
{appState.centeredLayout
? "Content is centered with wide side margins on large screens."
: "Content stretches to full width as before."}
</p>
</div>
</div> </div>
</div> </div>
} }

View File

@@ -2,51 +2,60 @@ import React from 'react';
import CommandLink from '../../core/components/CommandLink'; import CommandLink from '../../core/components/CommandLink';
import './WhatsNewResultView.css'; import './WhatsNewResultView.css';
function WhatsnewResultView() { function WhatsNewResultView() {
return <div className="changelog"> return <div className="changelog">
<h3>Changelog</h3> <h3>Changelog</h3>
<div className='item item-new'> <div className='item item-new'>
<p>
<span className="soft date">Nov 6th, 2025</span> <br />
</p>
<ul>
<li>Introduced a Centered Layout option that centers content on wide monitors; you can revert to the stretched layout in <CommandLink text="settings" />.</li>
</ul>
</div>
<div className='item'>
<p> <p>
<span className="soft date">May 10th, 2023</span> <br /> <span className="soft date">May 10th, 2023</span> <br />
<p> <p>
Behold! After a long time of inactivity, BitwiseCmd is getting an update. Here is what changed: Behold! After a long time of inactivity, BitwiseCmd is getting an update. Here is what changed:
</p> </p>
<ul> <ul>
<li>Browser's JavaScript engine is no longer used for the execution of bitwise operations. <li>The browser's JavaScript engine is no longer used for executing bitwise operations.
BitwiseCmd has its own shiny custom-built bitwise calculator that supports operations integer of different sizes (8,16,32, and 64 bits) as well as their signed and unsigned versions. <CommandLink text='Check it out!' command='-1b 255ub -1 4294967295u -1l 18446744073709551615u' />. BitwiseCmd now has its own shiny, custom-built bitwise calculator that supports bitwise operations on integers of different sizes (8, 16, 32, and 64 bits), in both signed and unsigned variants. <CommandLink text='Check it out!' command='-1b 255ub -1 4294967295u -1l 18446744073709551615u' />&nbsp;
This calculator tries to follow the same behavior of bitwise operations as implemented in C. This calculator follows the behavior of bitwise operations as implemented in C, including edge cases.
This includes shifting an integer by the number of bytes equal to its size (spoiler: you get the same number, this is undefined behavior in C. Don't believe me? Check this <a href="https://codeyarns.com/tech/2004-12-20-c-shift-operator-mayhem.html#gsc.tab=0">link</a>).</li> For example, shifting an integer by a number of bits equal to its width is undefined behavior in C (see this <a href="https://codeyarns.com/tech/2004-12-20-c-shift-operator-mayhem.html#gsc.tab=0">link</a>).
</li>
<li>A slightly improved UI</li> <li>A slightly improved UI</li>
</ul> </ul>
<p>I'm sure there will be some bugs following such a big update. I will do my best to fix them as they are found.</p> <p>I'm sure there will be some bugs following such a big update. I will do my best to fix them as they are found.</p>
<p>Many thanks to all people that submitted issues on GitHub. Your feedback is greatly appreciated. </p> <p>Many thanks to everyone who submitted issues on GitHub. Your feedback is greatly appreciated.</p>
</p> </p>
</div> </div>
<div className='item'> <div className='item'>
<span className="soft date">May 5th, 2023</span> <br /> <span className="soft date">May 5th, 2023</span> <br />
<p> <p>
Fixed <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues/13">bug</a> with incorrect binary representation of 64-bit numbers. Fixed a <a href="https://github.com/BorysLevytskyi/BitwiseCmd/issues/13">bug</a> with the binary representation of 64-bit numbers.
</p> </p>
</div> </div>
<div className="item"> <div className="item">
<p><span className="soft date">Jul 24th, 2021</span> <br /> <p><span className="soft date">Jul 24th, 2021</span> <br />
<ul> <ul>
<li>Added support of <code>vpc</code> command to see how VPC network address is divided between VPC, Subnets, and Hosts. Try it out: <CommandLink text="vpc 192.168.24.1/24" /></li> <li>Added support for the <code>vpc</code> command to see how the VPC network address bits are divided between the VPC, subnets, and hosts. Try it out: <CommandLink text="vpc 192.168.24.1/24" /></li>
<li>Added ability to remove individual results</li> <li>Added the ability to remove individual results</li>
</ul> </ul>
</p> </p>
</div> </div>
<div className="item"> <div className="item">
<span className="soft date">Jun 16th, 2021</span> <span className="soft date">Jun 16th, 2021</span>
<p> <p>
Added support of <code>subnet</code> command to display information about subnet IP address such. Try it out: <CommandLink text="subnet 192.168.24.1/14" /> Added support for the <code>subnet</code> command to display subnet information (network address, broadcast address, etc.). Try it out: <CommandLink text="subnet 192.168.24.1/14" />
</p> </p>
</div> </div>
<div className="item"> <div className="item">
<span className="soft date">Jun 14th, 2021</span> <span className="soft date">Jun 14th, 2021</span>
<p> <p>
Added support of IP addresses and subnet mask notations. Try them out: Added support for IP addresses and subnet mask notation. Try them out:
</p> </p>
<ul> <ul>
<li>A single IP address <CommandLink text="127.0.0.1" /></li> <li>A single IP address <CommandLink text="127.0.0.1" /></li>
@@ -57,7 +66,7 @@ function WhatsnewResultView() {
<div className="item"> <div className="item">
<span className="soft date">Jun 6th, 2017</span> <span className="soft date">Jun 6th, 2017</span>
<p> <p>
Added <code><CommandLink text="guid" /></code> command. Use it for generating v4 GUIDs </p> Added the <code><CommandLink text="guid" /></code> command. Use it to generate v4 GUIDs.</p>
</div> </div>
<div className="item"> <div className="item">
<span className="soft date">May 27th, 2017</span> <span className="soft date">May 27th, 2017</span>
@@ -79,4 +88,4 @@ function WhatsnewResultView() {
</div>; </div>;
} }
export default WhatsnewResultView; export default WhatsNewResultView;

View File

@@ -24,6 +24,16 @@ const shellModule = {
cmd.command("dark", () => appState.setUiTheme('dark')); cmd.command("dark", () => appState.setUiTheme('dark'));
cmd.command("light", () => appState.setUiTheme('light')); cmd.command("light", () => appState.setUiTheme('light'));
cmd.command("midnight", () => appState.setUiTheme('midnight')); cmd.command("midnight", () => appState.setUiTheme('midnight'));
cmd.command("settings", () => appState.toggleShowSettings());
cmd.command("bladerunner", () => {
appState.setUiTheme('bladerunner');
sendAnalyticsEvent({eventCategory: "UI", eventAction: "ThemeChanged", eventLabel: "bladerunner"});
});
cmd.command("bladerunner-easter", (c: CommandInput) => {
document.querySelector('.app-root')!.scrollTo(0, 0);
cmd.execute("bladerunner");
appState.addCommandResult(c.input, () => <TextResultView text="You've discovered the hidden Blade Runner theme. Next time, to activate this theme, use the command 'bladerunner'." />);
});
cmd.command("about", (c: CommandInput) => appState.addCommandResult(c.input, () => <AboutResultView />)); cmd.command("about", (c: CommandInput) => appState.addCommandResult(c.input, () => <AboutResultView />));
cmd.command("whatsnew", (c: CommandInput) => appState.addCommandResult(c.input, () => <WhatsnewResultView />)); cmd.command("whatsnew", (c: CommandInput) => appState.addCommandResult(c.input, () => <WhatsnewResultView />));
cmd.command("guid", (c: CommandInput) => appState.addCommandResult(c.input, () => <TextResultView text={uuid()} />)); cmd.command("guid", (c: CommandInput) => appState.addCommandResult(c.input, () => <TextResultView text={uuid()} />));
@@ -93,3 +103,5 @@ const shellModule = {
} }
export default shellModule; export default shellModule;