diff --git a/.memory/state.md b/.memory/state.md
index e95da20..0ad015f 100644
--- a/.memory/state.md
+++ b/.memory/state.md
@@ -1,6 +1,7 @@
# State
- Core engine: `src/rpn-calculator.js`
-- Active demo: `samples/dev/` (HP48-style UI)
+- Active demo: `samples/calc-02/` responsive HP48GX layout driven by portrait/landscape text mockups; display-adjacent button row stays in 4 columns
+- Mode button shows the current angle mode only; selecting a mode uses a popup menu
- Public API: `push`, `pop`, `clear`, `swap`, `remove`, `edit`, `isValidIndex`, `input`, `command`, `getOperationsByCategory`, `getConstants`
- Config: `maxSize`, `base`, `angleMode`, `enabledCommands`
- Commands: arithmetic, stack, trigonometry, constants `pi` and `e`
diff --git a/README.md b/README.md
index 703033d..45a926b 100644
--- a/README.md
+++ b/README.md
@@ -22,9 +22,9 @@ The main class is `RpnCalculator`.
- `samples/calc-01/index.html`: active browser demo entry point
- `samples/calc-01/index.css`: demo styles
- `samples/calc-01/index.js`: demo UI and keyboard logic
-- `samples/calc-01/index.html`: alternate browser demo entry point
-- `samples/calc-01/index.css`: alternate demo styles
-- `samples/calc-01/index.js`: alternate demo UI and keyboard logic
+- `samples/calc-02/index.html`: new responsive HP48GX-style demo entry point
+- `samples/calc-02/index.css`: new responsive demo styles
+- `samples/calc-02/index.js`: new demo UI and keyboard logic
- `samples/calc-XX/`: placeholder name for future demo variants
## Public API
@@ -268,6 +268,11 @@ The current demo supports:
The demo also implements stack selection and stack-item move mode in its UI layer using the public calculator methods.
It keeps the calculator screen focused and updates the visible stack window as the selection moves.
+## Calc 02 demo
+
+`samples/calc-02/` is a new responsive HP48GX-inspired demo.
+It adapts its layout to the browser window and switches between the supplied portrait and landscape arrangements.
+
## Exports
`RpnCalculator` is exposed in both environments:
diff --git a/samples/calc-02/index.css b/samples/calc-02/index.css
new file mode 100644
index 0000000..1c5978e
--- /dev/null
+++ b/samples/calc-02/index.css
@@ -0,0 +1,367 @@
+:root {
+ --bg0: #10151e;
+ --bg1: #1b2432;
+ --panel: #2c3442;
+ --panel2: #394354;
+ --edge: #0c1118;
+ --display: #cfe0ae;
+ --display2: #b9cd8a;
+ --displayText: #1f2a12;
+ --buttonText: #f4f7fb;
+ --shadow: rgba(0, 0, 0, 0.35);
+ --btnTop: #444c58;
+ --btnBottom: #2f3640;
+ --btnAccentTop: #3f526b;
+ --btnAccentBottom: #2b394c;
+ --btnAltTop: #525c69;
+ --btnAltBottom: #3a434f;
+ --btnDangerTop: #584042;
+ --btnDangerBottom: #402d2f;
+ --btnEnterTop: #465349;
+ --btnEnterBottom: #303a31;
+ --btnText: #eef2f7;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html, body {
+ margin: 0;
+ min-height: 100%;
+}
+
+body {
+ min-height: 100vh;
+ font-family: Arial, sans-serif;
+ color: var(--buttonText);
+ background:
+ radial-gradient(circle at top, rgba(255, 255, 255, 0.08), transparent 32%),
+ linear-gradient(180deg, var(--bg1), var(--bg0));
+}
+
+.app-shell {
+ min-height: 100vh;
+ display: grid;
+ place-items: center;
+ padding: clamp(12px, 2vw, 28px);
+}
+
+.calculator {
+ width: min(100vw - 24px, 1120px);
+ height: min(100vh - 24px, 900px);
+ display: grid;
+ gap: clamp(10px, 1.4vw, 18px);
+ padding: clamp(12px, 1.8vw, 18px);
+ border-radius: 28px;
+ background: linear-gradient(180deg, var(--panel2), var(--panel));
+ border: 1px solid var(--edge);
+ box-shadow: 0 26px 70px var(--shadow), inset 0 1px 0 rgba(255, 255, 255, 0.08);
+ grid-template-columns: 1.3fr 0.9fr;
+ grid-template-rows: minmax(0, 0.62fr) min-content minmax(180px, 1fr) minmax(180px, 1fr);
+ align-content: start;
+ grid-template-areas:
+ "display functions"
+ "buttons functions"
+ "keypad functions"
+ "keypad trigo";
+}
+
+.display-panel,
+.display-buttons-panel,
+.keypad-panel,
+.functions-panel,
+.trigo-panel,
+.status-line {
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.06);
+ background: rgba(6, 10, 16, 0.16);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
+}
+
+.display-panel {
+ grid-area: display;
+ padding: clamp(12px, 1.5vw, 16px);
+ background: linear-gradient(180deg, var(--display), var(--display2));
+ color: var(--displayText);
+ font-family: "Courier New", monospace;
+ overflow: hidden;
+ height: clamp(112px, 18vw, 160px);
+ max-height: 140px;
+ align-self: start;
+ margin-bottom: 0;
+ min-height: 0;
+}
+
+.display-grid {
+ height: 100%;
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-template-rows: repeat(4, minmax(0, 1fr));
+ gap: 2px;
+}
+
+.stack-cell {
+ display: grid;
+ grid-template-columns: 2.2ch 1fr;
+ align-items: center;
+ gap: 12px;
+ font-size: clamp(18px, 3vw, 30px);
+ line-height: 1;
+ min-height: 0;
+ padding-block: 0;
+}
+
+.stack-label {
+ text-align: right;
+ opacity: 0.78;
+}
+
+.stack-value {
+ min-height: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.display-buttons-panel {
+ grid-area: buttons;
+ padding: 8px;
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ grid-template-rows: repeat(2, auto);
+ gap: 8px;
+ align-content: start;
+ align-items: stretch;
+ grid-auto-flow: row;
+ grid-auto-rows: auto;
+ background: linear-gradient(180deg, #242a33, #1a1f27);
+ border-color: rgba(255, 255, 255, 0.04);
+ margin-top: 0;
+}
+
+.display-button {
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 3px 0 rgba(0, 0, 0, 0.34);
+ background-clip: padding-box;
+ background: linear-gradient(180deg, #3a414c, #252b34);
+ color: #e8edf3;
+ border-color: rgba(255, 255, 255, 0.05);
+}
+
+.display-button-offset {
+ grid-column-start: 2;
+}
+
+.display-buttons-panel > button {
+ width: 100%;
+}
+
+.mode-menu {
+ position: fixed;
+ z-index: 20;
+ display: grid;
+ gap: 6px;
+ padding: 10px;
+ border-radius: 14px;
+ background: rgba(18, 24, 33, 0.98);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ box-shadow: 0 14px 30px rgba(0, 0, 0, 0.35);
+}
+
+.mode-menu-item {
+ min-width: 120px;
+ background: linear-gradient(180deg, var(--btnAltTop), var(--btnAltBottom));
+}
+
+.mode-menu-item.is-active {
+ outline: 2px solid rgba(207, 224, 174, 0.7);
+}
+
+.display-button:nth-child(6),
+.display-button:nth-child(7) {
+ background: linear-gradient(180deg, #343b46, #20262e);
+}
+
+.keypad-panel {
+ grid-area: keypad;
+ padding: 10px;
+}
+
+.functions-panel {
+ grid-area: functions;
+ padding: 10px;
+ align-self: start;
+ min-height: 0;
+ padding-top: 10px;
+}
+
+.trigo-panel {
+ grid-area: trigo;
+ padding: 10px;
+ align-self: start;
+ min-height: 0;
+ padding-top: 10px;
+}
+
+.status-line {
+ grid-area: status;
+ padding: 10px 14px;
+ display: flex;
+ align-items: center;
+ min-height: 42px;
+ font-size: 14px;
+ color: rgba(255, 255, 255, 0.85);
+}
+
+.keypad-grid,
+.functions-grid,
+.trigo-grid {
+ display: grid;
+ gap: 8px;
+ grid-auto-rows: minmax(0, 1fr);
+}
+
+.keypad-grid {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ grid-template-rows: repeat(5, minmax(0, 1fr));
+}
+
+.functions-grid,
+.trigo-grid {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.functions-grid,
+.trigo-grid {
+ grid-template-rows: repeat(2, minmax(0, 1fr));
+}
+
+button {
+ border: 1px solid rgba(14, 18, 25, 0.85);
+ border-radius: 12px;
+ padding: 10px 8px;
+ font: inherit;
+ font-weight: 700;
+ color: var(--btnText);
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.35);
+ cursor: pointer;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 3px 0 rgba(0, 0, 0, 0.28);
+ transition: transform 120ms ease, filter 120ms ease, box-shadow 120ms ease;
+}
+
+button:hover {
+ filter: brightness(1.06);
+}
+
+.display-button:hover {
+ filter: brightness(1.08);
+}
+
+button:active {
+ transform: translateY(2px);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08), 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.display-button:active {
+ transform: translateY(2px);
+}
+
+.key-default {
+ background: linear-gradient(180deg, var(--btnTop), var(--btnBottom));
+ color: #eef2f7;
+}
+
+.key-accent {
+ background: linear-gradient(180deg, var(--btnAccentTop), var(--btnAccentBottom));
+ color: #eef2f7;
+}
+
+.key-alt {
+ background: linear-gradient(180deg, var(--btnAltTop), var(--btnAltBottom));
+ color: #eef2f7;
+}
+
+.key-danger {
+ background: linear-gradient(180deg, var(--btnDangerTop), var(--btnDangerBottom));
+ color: #eef2f7;
+}
+
+.key-enter {
+ background: linear-gradient(180deg, var(--btnEnterTop), var(--btnEnterBottom));
+ color: #eef2f7;
+}
+
+.hidden-input {
+ position: absolute;
+ left: -9999px;
+ width: 1px;
+ height: 1px;
+ opacity: 0;
+ pointer-events: none;
+}
+
+@media (orientation: portrait), (max-width: 860px) {
+ .calculator {
+ width: min(100vw - 16px, 760px);
+ height: auto;
+ min-height: calc(100vh - 16px);
+ grid-template-columns: 1fr;
+ grid-template-rows: minmax(160px, auto) auto minmax(220px, auto) auto auto;
+ grid-template-areas:
+ "display"
+ "buttons"
+ "keypad"
+ "functions"
+ "trigo";
+ }
+
+ .display-buttons-panel {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ grid-template-rows: repeat(2, auto);
+ margin-top: 0;
+ }
+
+ .keypad-grid {
+ grid-template-rows: repeat(5, minmax(42px, 1fr));
+ }
+
+ .functions-grid,
+ .trigo-grid {
+ grid-auto-rows: minmax(0, 1fr);
+ grid-template-rows: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 520px) {
+ .app-shell {
+ padding: 8px;
+ }
+
+ .calculator {
+ width: 100%;
+ min-height: calc(100vh - 16px);
+ border-radius: 20px;
+ padding: 10px;
+ gap: 10px;
+ }
+
+ .display-panel {
+ padding: 10px;
+ }
+
+ .stack-cell {
+ font-size: clamp(16px, 5.2vw, 22px);
+ gap: 8px;
+ }
+
+ button {
+ border-radius: 10px;
+ padding: 8px 6px;
+ font-size: 13px;
+ }
+
+ .display-buttons-panel {
+ gap: 6px;
+ }
+
+}
diff --git a/samples/calc-02/index.html b/samples/calc-02/index.html
new file mode 100644
index 0000000..e69cdf3
--- /dev/null
+++ b/samples/calc-02/index.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ HP48GX RPN Calculator
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/calc-02/index.js b/samples/calc-02/index.js
new file mode 100644
index 0000000..b5baea9
--- /dev/null
+++ b/samples/calc-02/index.js
@@ -0,0 +1,310 @@
+const calc = new RpnCalculator({ angleMode: 'deg' });
+
+const hiddenInput = document.getElementById('hiddenInput');
+const modeButton = document.getElementById('modeButton');
+const pasteButton = document.getElementById('pasteButton');
+const upButton = document.getElementById('upButton');
+const constButton = document.getElementById('constButton');
+const rightButton = document.getElementById('rightButton');
+const downButton = document.getElementById('downButton');
+const constRightButton = document.getElementById('constRightButton');
+
+const stackEls = {
+ T: document.getElementById('stackT'),
+ Z: document.getElementById('stackZ'),
+ Y: document.getElementById('stackY'),
+ X: document.getElementById('stackX'),
+};
+
+const keypadGrid = document.getElementById('keypadGrid');
+const functionsGrid = document.getElementById('functionsGrid');
+const trigoGrid = document.getElementById('trigoGrid');
+const calculatorEl = document.querySelector('.calculator');
+
+const keypadKeys = [
+ { label: '±', action: 'neg', className: 'key-default' },
+ { label: 'C', action: 'clear', className: 'key-danger' },
+ { label: '⎋', action: 'escape', className: 'key-danger' },
+ { label: '⌫', action: 'backspace', className: 'key-danger' },
+ { label: '7', input: '7', className: 'key-default' },
+ { label: '8', input: '8', className: 'key-default' },
+ { label: '9', input: '9', className: 'key-default' },
+ { label: '/', action: 'div', className: 'key-accent' },
+ { label: '4', input: '4', className: 'key-default' },
+ { label: '5', input: '5', className: 'key-default' },
+ { label: '6', input: '6', className: 'key-default' },
+ { label: '*', action: 'mul', className: 'key-accent' },
+ { label: '1', input: '1', className: 'key-default' },
+ { label: '2', input: '2', className: 'key-default' },
+ { label: '3', input: '3', className: 'key-default' },
+ { label: '-', action: 'sub', className: 'key-accent' },
+ { label: '0', input: '0', className: 'key-default' },
+ { label: '.', input: '.', className: 'key-default' },
+ { label: 'Enter', action: 'enter', className: 'key-enter' },
+ { label: '+', action: 'add', className: 'key-accent' },
+];
+
+const functionKeys = [
+ { label: 'x²', action: 'sqr', className: 'key-default' },
+ { label: 'yˣ', action: 'pow', className: 'key-default' },
+ { label: '1/x', action: 'recip', className: 'key-default' },
+ { label: '%', action: 'mod', className: 'key-default' },
+ { label: '√x', action: 'sqrt', className: 'key-default' },
+ { label: 'y√x', action: 'pow', className: 'key-default' },
+ { label: '10ˣ', action: 'pow10', className: 'key-default' },
+ { label: '', spacer: true },
+ { label: 'log', action: 'log', className: 'key-default' },
+ { label: 'ln', action: 'ln', className: 'key-default' },
+ { label: '', spacer: true },
+ { label: '', spacer: true },
+];
+
+const trigoKeys = [
+ { label: 'sin', action: 'sin', className: 'key-default' },
+ { label: 'cos', action: 'cos', className: 'key-default' },
+ { label: 'tan', action: 'tan', className: 'key-default' },
+ { label: '', spacer: true },
+ { label: 'asin', action: 'asin', className: 'key-default' },
+ { label: 'acos', action: 'acos', className: 'key-default' },
+ { label: 'atan', action: 'atan', className: 'key-default' },
+ { label: '', spacer: true },
+];
+
+
+function focusInput() {
+ hiddenInput.focus();
+}
+
+function setStatus(message) {
+ console.log(message);
+}
+
+function normalizeStack() {
+ while (calc.stack.length > 4) {
+ calc.stack.shift();
+ }
+}
+
+function getStackLine(indexFromTop) {
+ const index = calc.stack.length - 1 - indexFromTop;
+ return index >= 0 ? calc.stack[index] : '';
+}
+
+function render() {
+ normalizeStack();
+ const isPortrait = window.matchMedia('(orientation: portrait)').matches || window.innerWidth <= 860;
+ calculatorEl?.classList.toggle('portrait', isPortrait);
+ calculatorEl?.classList.toggle('landscape', !isPortrait);
+ stackEls.T.textContent = calc.formatNumber(getStackLine(3)) || '';
+ stackEls.Z.textContent = calc.formatNumber(getStackLine(2)) || '';
+ stackEls.Y.textContent = calc.formatNumber(getStackLine(1)) || '';
+ stackEls.X.textContent = calc.formatNumber(getStackLine(0)) || (calc.isEditing ? calc.inputValue : '');
+ modeButton.textContent = calc.angleMode;
+}
+
+function pushEditingValueIfNeeded() {
+ if (!calc.isEditing) return;
+ if (calc.inputValue !== '') {
+ calc.push(calc.parseInputValue(calc.inputValue));
+ }
+ calc.inputValue = '';
+ calc.isEditing = false;
+}
+
+function inputToX(value) {
+ if (!calc.isEditing) {
+ calc.isEditing = true;
+ calc.inputValue = '';
+ }
+ if (value === 'Backspace') {
+ calc.inputValue = calc.inputValue.slice(0, -1);
+ } else {
+ calc.inputValue += value;
+ }
+ if (calc.inputValue === '') {
+ calc.isEditing = false;
+ }
+}
+
+function execute(name) {
+ try {
+ if (name === 'enter') {
+ pushEditingValueIfNeeded();
+ } else if (name === 'clear') {
+ calc.clear();
+ calc.inputValue = '';
+ calc.isEditing = false;
+ } else if (name === 'escape') {
+ calc.inputValue = '';
+ calc.isEditing = false;
+ } else if (name === 'backspace') {
+ if (calc.isEditing) {
+ inputToX('Backspace');
+ } else {
+ calc.remove(0);
+ }
+ } else if (name === 'neg') {
+ if (calc.isEditing) {
+ calc.inputValue = calc.inputValue.startsWith('-') ? calc.inputValue.slice(1) : `-${calc.inputValue}`;
+ } else {
+ calc.push(calc.pop() * -1);
+ }
+ } else if (name === 'pow10') {
+ pushEditingValueIfNeeded();
+ calc.push(10);
+ calc.command('pow');
+ } else {
+ pushEditingValueIfNeeded();
+ calc.command(name);
+ }
+ render();
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+function createKeyButton({ label, input, action, spacer, className }) {
+ if (spacer) {
+ const div = document.createElement('div');
+ return div;
+ }
+ const button = document.createElement('button');
+ button.type = 'button';
+ button.textContent = label;
+ button.className = className;
+ button.addEventListener('click', () => {
+ focusInput();
+ if (input) {
+ inputToX(input);
+ render();
+ return;
+ }
+ execute(action);
+ });
+ return button;
+}
+
+function buildGrid(container, keys) {
+ container.innerHTML = '';
+ keys.forEach((key) => container.appendChild(createKeyButton(key)));
+}
+
+function handleKeyboard(event) {
+ if (event.target === hiddenInput) return;
+ const key = event.key;
+ if (/^[0-9.]$/.test(key)) {
+ event.preventDefault();
+ inputToX(key);
+ render();
+ return;
+ }
+ const map = {
+ Enter: 'enter',
+ Backspace: 'backspace',
+ Escape: 'escape',
+ Delete: 'clear',
+ '+': 'add',
+ '-': 'sub',
+ '*': 'mul',
+ '/': 'div',
+ '%': 'mod',
+ '^': 'pow',
+ };
+ if (map[key]) {
+ event.preventDefault();
+ execute(map[key]);
+ }
+}
+
+const modeOptions = ['deg', 'rad', 'grad'];
+let modeMenuEl = null;
+
+function closeModeMenu() {
+ if (modeMenuEl) {
+ modeMenuEl.remove();
+ modeMenuEl = null;
+ }
+}
+
+function openModeMenu() {
+ closeModeMenu();
+ const rect = modeButton.getBoundingClientRect();
+ modeMenuEl = document.createElement('div');
+ modeMenuEl.className = 'mode-menu';
+ modeMenuEl.style.top = `${rect.bottom + 6 + window.scrollY}px`;
+ modeMenuEl.style.left = `${rect.left + window.scrollX}px`;
+ modeOptions.forEach((mode) => {
+ const button = document.createElement('button');
+ button.type = 'button';
+ button.className = `mode-menu-item${mode === calc.angleMode ? ' is-active' : ''}`;
+ button.textContent = mode;
+ button.addEventListener('click', () => {
+ calc.angleMode = mode;
+ render();
+ closeModeMenu();
+ });
+ modeMenuEl.appendChild(button);
+ });
+ document.body.appendChild(modeMenuEl);
+ const menuRect = modeMenuEl.getBoundingClientRect();
+ const maxLeft = Math.max(8, window.innerWidth - menuRect.width - 8);
+ modeMenuEl.style.left = `${Math.max(8, Math.min(maxLeft, rect.left + window.scrollX))}px`;
+}
+
+modeButton.addEventListener('click', (event) => {
+ event.stopPropagation();
+ openModeMenu();
+});
+
+window.addEventListener('resize', closeModeMenu);
+window.addEventListener('scroll', closeModeMenu, true);
+
+window.addEventListener('click', (event) => {
+ if (modeMenuEl && !event.target.closest('.mode-menu') && event.target !== modeButton) {
+ closeModeMenu();
+ }
+});
+
+pasteButton.addEventListener('click', async () => {
+ try {
+ const text = await navigator.clipboard.readText();
+ if (!text) {
+ setStatus('Clipboard empty');
+ return;
+ }
+ if (calc.isEditing) {
+ calc.inputValue += text;
+ } else {
+ calc.isEditing = true;
+ calc.inputValue = text;
+ }
+ setStatus('Pasted');
+ render();
+ } catch (error) {
+ setStatus('Paste unavailable');
+ }
+});
+
+upButton.addEventListener('click', () => {});
+
+constButton.addEventListener('click', () => {});
+
+rightButton.addEventListener('click', () => {});
+
+downButton.addEventListener('click', () => {});
+
+constRightButton.addEventListener('click', () => {});
+
+window.addEventListener('keydown', handleKeyboard);
+window.addEventListener('load', focusInput);
+
+document.addEventListener('click', (event) => {
+ if (!event.target.closest('.calculator')) {
+ focusInput();
+ }
+});
+
+buildGrid(keypadGrid, keypadKeys);
+buildGrid(functionsGrid, functionKeys);
+buildGrid(trigoGrid, trigoKeys);
+render();
diff --git a/samples/calc-02/visual-landscape.txt b/samples/calc-02/visual-landscape.txt
new file mode 100644
index 0000000..02f6667
--- /dev/null
+++ b/samples/calc-02/visual-landscape.txt
@@ -0,0 +1,10 @@
+┌──────────── Display ────────────┐ ┌────── Functions ─────┐ ┌──────────── Keypad ─────────────┐
+| T: | | x^2 | y^x | 1/x | % | | +/- | Clear | Esc | backspace |
+| Z: | | √x | y√x | 10^x | | | 7 | 8 | 9 | / |
+| Y: | | log | ln | | | | 4 | 5 | 6 | * |
+| X: | └──────────────────────┘ | 1 | 2 | 3 | - |
+└─────────────────────────────────┘ ┌─────── Trigo ────────┐ | 0 | . | Enter | + |
+┌──────── Display Buttons ────────┐ | sin | cos | tan | └─────────────────────────────────┘
+| Mode | Paste | Up | Const | | asin | acos | atan |
+| | Right | Down | Right | └──────────────────────┘
+└─────────────────────────────────┘
diff --git a/samples/calc-02/visual-portrait.txt b/samples/calc-02/visual-portrait.txt
new file mode 100644
index 0000000..ccfcb6f
--- /dev/null
+++ b/samples/calc-02/visual-portrait.txt
@@ -0,0 +1,26 @@
+┌──────────── Display ────────────┐
+| T: |
+| Z: |
+| Y: |
+| X: |
+└─────────────────────────────────┘
+┌──────── Display Buttons ────────┐
+| Mode | Paste | Up | Const |
+| | Right | Down | Right |
+└─────────────────────────────────┘
+┌──────────── Keypad ─────────────┐
+| +/- | Clear | Esc | backspace |
+| 7 | 8 | 9 | / |
+| 4 | 5 | 6 | * |
+| 1 | 2 | 3 | - |
+| 0 | . | Enter | + |
+└─────────────────────────────────┘
+┌─────────── Functions ───────────┐
+| x^2 | y^x | 1/x | % |
+| √x | y√x | 10^x | |
+| log | ln | | |
+└─────────────────────────────────┘
+┌───────────── Trigo ─────────────┐
+| sin | cos | tan | |
+| asin | acos | atan | |
+└─────────────────────────────────┘