From 0e2d5bea5517cb79fffca36ea1578303eec1656d Mon Sep 17 00:00:00 2001 From: Kojib Date: Fri, 30 Jan 2026 23:32:55 -0500 Subject: [PATCH 01/77] 42.7.1 --- public/sw.js | 2 +- src/components/ExhaleNote.css | 188 +++++++++++++++++----------------- src/version.ts | 2 +- 3 files changed, 95 insertions(+), 97 deletions(-) diff --git a/public/sw.js b/public/sw.js index b69e8085..a419dfaf 100644 --- a/public/sw.js +++ b/public/sw.js @@ -14,7 +14,7 @@ // Update this version string manually to keep the app + cache versions in sync. // The value is forwarded to the UI via the service worker "SW_ACTIVATED" message. -const APP_VERSION = "42.7.0"; // update on release +const APP_VERSION = "42.7.1"; // update on release const VERSION = new URL(self.location.href).searchParams.get("v") || APP_VERSION; // derived from build const PREFIX = "PHINETWORK"; diff --git a/src/components/ExhaleNote.css b/src/components/ExhaleNote.css index a8505daf..6d190fee 100644 --- a/src/components/ExhaleNote.css +++ b/src/components/ExhaleNote.css @@ -1,12 +1,10 @@ /* ───────────────────────────────────────────────────────────────────────────── - ExhaleNote.css — Atlantean Glass Note Composer (v26.3) - FINAL PRODUCTION STYLES — UNIT TOGGLE EDITION - - Premium one-row header (kk-headbar) with crystalline icon pills - - Guided step composer (top answer box beside Send Amount) - - Send Amount unit toggle (Φ / $) — polished segmented control - - Chat shows only past Q/A + current question (no future prompts) - - Preview card + classic form compatibility - - Scoped tokens to .kk-note (no global bleed) + ExhaleNote.css — Atlantean Glass Note Composer (v26.3.1) + FINAL PRODUCTION STYLES — UNIT TOGGLE + CENTERED AMOUNTS FIX + - FIX: Send Amount area now uses a grid layout so nothing clips + - USD/Φ display is centered + always visible (no right-edge cut-off) + - Unit toggle + input stay aligned and responsive + - Everything remains scoped to .kk-note ───────────────────────────────────────────────────────────────────────────── */ /* ───────────────────────────────────────────────────────────────────────────── @@ -73,7 +71,6 @@ position: relative; isolation: isolate; - /* nicer text rendering */ text-rendering: geometricPrecision; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -105,13 +102,9 @@ mix-blend-mode: screen; } -/* Selection */ .kk-note ::selection { background: rgba(86,255,227,0.22); } - -/* Mono helper */ .kk-mono { font-family: var(--kk-font-mono); } -/* Utility: subtle separators without adding elements */ .kk-note hr { border: 0; height: 1px; @@ -163,11 +156,8 @@ mask-image: linear-gradient(90deg, #000 80%, transparent 100%); } -.kk-headbar__right { - flex: 0 0 auto; -} +.kk-headbar__right { flex: 0 0 auto; } -/* Pill base (header) */ .kk-pill { display: inline-flex; align-items: center; @@ -178,11 +168,8 @@ border-radius: 999px; border: 1px solid rgba(255,255,255,0.12); - background: - linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02)); - box-shadow: - 0 10px 28px rgba(0,0,0,0.18), - inset 0 1px 0 rgba(255,255,255,0.04); + background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02)); + box-shadow: 0 10px 28px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.04); color: var(--kk-dim); font-size: 12px; @@ -191,7 +178,6 @@ .kk-pill:hover { transform: translateY(-1px); border-color: rgba(255,255,255,0.20); } -/* Brand pill */ .kk-pill--brand { padding: 7px 9px; border-color: rgba(86,255,227,0.18); @@ -214,7 +200,6 @@ filter: drop-shadow(0 6px 18px rgba(86,255,227,0.22)); } -/* State pill */ .kk-pill--state { padding: 7px 9px; border-color: rgba(255,255,255,0.14); @@ -242,29 +227,19 @@ 100% { box-shadow: 0 10px 28px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.04), 0 0 0 0 rgba(61,225,167,0.00); } } -/* Pulse / value pills */ .kk-pill--pulse, .kk-pill--value, .kk-pill--usd, -.kk-pill--progress { - font-variant-numeric: tabular-nums; -} +.kk-pill--progress { font-variant-numeric: tabular-nums; } .kk-pill--pulse { max-width: 160px; overflow: hidden; } .kk-pill--value { max-width: 180px; overflow: hidden; } .kk-pill--usd { max-width: 140px; overflow: hidden; } .kk-pill--progress { padding: 7px 9px; } -.kk-pillPhi { - font-weight: 900; - color: rgba(158,247,255,0.92); -} +.kk-pillPhi { font-weight: 900; color: rgba(158,247,255,0.92); } -/* Mode pill (two icon buttons inside) */ -.kk-pill--mode { - padding: 6px; - gap: 6px; -} +.kk-pill--mode { padding: 6px; gap: 6px; } .kk-iconBtn { appearance: none; @@ -295,12 +270,8 @@ box-shadow: 0 10px 22px rgba(86,255,227,0.14); } -.kk-iconBtn:focus-visible { - outline: none; - box-shadow: var(--kk-focus); -} +.kk-iconBtn:focus-visible { outline: none; box-shadow: var(--kk-focus); } -/* Shield pill toggle */ .kk-pill--shield { width: 38px; height: 34px; @@ -319,7 +290,6 @@ box-shadow: 0 12px 26px rgba(245,217,141,0.08); } -/* Mobile tightening */ @media (max-width: 560px) { .kk-note { padding: 12px; } .kk-headbar { padding: 8px; gap: 8px; } @@ -473,7 +443,7 @@ } /* ───────────────────────────────────────────────────────────────────────────── - Buttons (kept compatible) + Buttons ───────────────────────────────────────────────────────────────────────────── */ .kk-btn { @@ -510,7 +480,6 @@ .kk-btn:focus-visible { outline: none; box-shadow: var(--kk-focus); } -/* Icon-only button sizing (used for top step controls) */ .kk-iconOnly { width: 44px; height: 42px; @@ -530,10 +499,7 @@ padding: 10px 12px; } -.kk-lockcard__t { - font-weight: 950; - color: var(--kk-gold); -} +.kk-lockcard__t { font-weight: 950; color: var(--kk-gold); } .kk-lockcard__s { margin-top: 4px; @@ -543,7 +509,7 @@ } /* ───────────────────────────────────────────────────────────────────────────── - Dual bar: Step answer (left) + Send Amount (right) + Dual bar: Step answer + Send Amount ───────────────────────────────────────────────────────────────────────────── */ .kk-dualbar { @@ -632,10 +598,7 @@ transform: translateY(-1px); } -.kk-qaInput:disabled { - opacity: 0.70; - cursor: not-allowed; -} +.kk-qaInput:disabled { opacity: 0.70; cursor: not-allowed; } .kk-qaBtns { display: inline-flex; @@ -645,7 +608,6 @@ flex-wrap: wrap; } -/* Suggestions */ .kk-suggest { margin-top: 10px; display: flex; @@ -667,23 +629,20 @@ .kk-suggest__chip:hover { border-color: rgba(255,255,255,0.24); transform: translateY(-1px); } .kk-suggest__chip:active { transform: translateY(0); } - .kk-suggest__chip:focus-visible { outline: none; box-shadow: var(--kk-focus); } /* ───────────────────────────────────────────────────────────────────────────── - Send Amount bar + Unit toggle + Send Amount bar — FIXED LAYOUT (CENTERED + NO CLIP) ───────────────────────────────────────────────────────────────────────────── */ .kk-sendbar { border: 1px solid var(--kk-border); border-radius: var(--kk-radius); background: rgba(255,255,255,0.03); - padding: 12px; - display: flex; - gap: 12px; - align-items: center; - justify-content: space-between; + padding: 12px 12px 10px; position: relative; + + /* Keep glow inside the card but stop clipping content by preventing overflow */ overflow: hidden; } @@ -698,9 +657,7 @@ pointer-events: none; } -@media (max-width: 720px) { - .kk-sendbar { flex-direction: column; align-items: stretch; } -} +.kk-sendbar__left { min-width: 0; position: relative; z-index: 1; } .kk-sendbar__label { font-weight: 950; @@ -713,16 +670,55 @@ margin-top: 2px; } -.kk-sendbar__right { - display: flex; +/* ✅ Key fix: + - Use GRID so meta never overflows off the right edge. + - Meta is centered under the controls (always visible). +*/ +.kk-sendbar { + display: grid; + grid-template-columns: minmax(180px, 1fr) minmax(0, 1.35fr); gap: 12px; align-items: center; +} + +.kk-sendbar__right { position: relative; - z-index: 1; /* above sendbar::before */ + z-index: 1; + + display: grid; + grid-template-columns: auto minmax(0, 1fr); + grid-template-areas: + "unit input" + "meta meta"; + column-gap: 12px; + row-gap: 8px; + align-items: center; + + /* Prevent edge clipping inside narrow modals */ + min-width: 0; } +.kk-sendbar__unit { grid-area: unit; } +.kk-sendbar__inputWrap { grid-area: input; } +.kk-sendbar__meta { grid-area: meta; } + @media (max-width: 720px) { - .kk-sendbar__right { justify-content: space-between; } + .kk-sendbar { + grid-template-columns: 1fr; + gap: 10px; + align-items: start; + } + + .kk-sendbar__right { + grid-template-columns: 1fr; + grid-template-areas: + "unit" + "input" + "meta"; + justify-items: center; + } + + .kk-sendbar__unit { justify-self: center; } } /* Unit segmented control */ @@ -733,8 +729,7 @@ padding: 6px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.14); - background: - linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02)); + background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02)); box-shadow: 0 10px 22px rgba(0,0,0,0.16), inset 0 1px 0 rgba(255,255,255,0.04); backdrop-filter: blur(8px); } @@ -779,6 +774,7 @@ .kk-sendbar__unitBtn:focus-visible { outline: none; box-shadow: var(--kk-focus); } +/* Input wrap + input */ .kk-sendbar__inputWrap { display: inline-flex; align-items: center; @@ -789,6 +785,11 @@ background: rgba(24, 30, 40, 0.65); box-shadow: inset 0 1px 0 rgba(255,255,255,0.03); min-height: 42px; + + /* ✅ prevents overflow on narrow layouts */ + width: min(340px, 100%); + min-width: 0; + justify-self: center; } .kk-sendbar__prefix { @@ -797,7 +798,8 @@ } .kk-sendbar__input { - width: 170px; + width: 100%; + min-width: 120px; border: 0; outline: none; background: transparent; @@ -810,32 +812,38 @@ .kk-sendbar__input:disabled { opacity: 0.70; cursor: not-allowed; } .kk-sendbar__input.is-error { color: #ffd3dc; } -.kk-sendbar__inputWrap:has(.kk-sendbar__input:focus) { +/* Focus ring without relying on :has (Safari/Firefox safe) */ +.kk-sendbar__inputWrap:focus-within { border-color: rgba(86,255,227,0.60); box-shadow: var(--kk-focus); } +/* Meta (centered + never clipped) */ .kk-sendbar__meta { display: grid; - gap: 2px; - justify-items: end; - min-width: 140px; + gap: 3px; + justify-items: center; + text-align: center; + + min-width: 0; + max-width: 100%; } .kk-sendbar__usd { font-weight: 950; font-variant-numeric: tabular-nums; + + /* ✅ allow wrap instead of clipping */ + max-width: 100%; + overflow-wrap: anywhere; + line-height: 1.15; } .kk-sendbar__hint { font-size: 12px; color: var(--kk-mute); -} - -/* Error hint emphasis */ -.kk-sendbar__input.is-error ~ .kk-sendbar__meta, -.kk-sendbar__inputWrap:has(.kk-sendbar__input.is-error) + .kk-sendbar__meta { - filter: saturate(1.05); + max-width: 100%; + overflow-wrap: anywhere; } /* ───────────────────────────────────────────────────────────────────────────── @@ -889,7 +897,6 @@ overscroll-behavior: contain; } -/* Crisp scrollbars (webkit) */ .kk-chatpanel__body::-webkit-scrollbar { width: 10px; height: 10px; } .kk-chatpanel__body::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.10); @@ -899,11 +906,7 @@ .kk-chatpanel__body::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.16); } .kk-chatpanel__body::-webkit-scrollbar-corner { background: transparent; } -.kk-bubbleRow { - display: flex; - margin: 10px 0; -} - +.kk-bubbleRow { display: flex; margin: 10px 0; } .kk-bubbleRow.is-sys { justify-content: flex-start; } .kk-bubbleRow.is-you { justify-content: flex-end; } @@ -1058,10 +1061,7 @@ } .kk-row input:disabled, -.kk-row textarea:disabled { - opacity: 0.75; - cursor: not-allowed; -} +.kk-row textarea:disabled { opacity: 0.75; cursor: not-allowed; } .kk-out { font-family: var(--kk-font-mono); } @@ -1082,9 +1082,7 @@ .kk-headbar, .kk-hero2, .kk-chatpanel, - .kk-formpanel { - display: none !important; - } + .kk-formpanel { display: none !important; } #print-root { display: block !important; } body { background: #fff !important; } diff --git a/src/version.ts b/src/version.ts index aba4ea29..bc4edd1d 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,7 +1,7 @@ // src/version.ts // Shared PWA version constants so the app shell, SW registration, and UI stay in sync. -export const BASE_APP_VERSION = "42.7.0"; // Canonical offline/PWA version +export const BASE_APP_VERSION = "42.7.1"; // Canonical offline/PWA version export const SW_VERSION_EVENT = "kairos:sw-version"; export const DEFAULT_APP_VERSION = BASE_APP_VERSION; // Keep in sync with public/sw.js const ENV_APP_VERSION = From 6af2e07f1bffe5e00b95c26c8ae1eb67ac276568 Mon Sep 17 00:00:00 2001 From: Kojib <123880127+kojibai@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:40:52 -0500 Subject: [PATCH 02/77] Improve exhale note QR sizing --- src/components/exhale-note/banknoteSvg.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/exhale-note/banknoteSvg.ts b/src/components/exhale-note/banknoteSvg.ts index ce11a494..d9681865 100644 --- a/src/components/exhale-note/banknoteSvg.ts +++ b/src/components/exhale-note/banknoteSvg.ts @@ -103,13 +103,14 @@ export function buildBanknoteSVG(opts: BuildBanknoteSvgOpts): string { const sigilEmbed = embedSigilIntoSlotClickable(sigilInner, slot.x, slot.y, slot.w, slot.h, verifyUrl); // QR block (avoid 8-digit hex colors; use stroke-opacity instead) - // QR block — perfectly centered inside the 110×110 slot (and thus the framed box) - const QR_PX = 110; // intended QR slot size + // QR block — perfectly centered inside the 140×140 slot (and thus the framed box) + const QR_PX = 140; // intended QR slot size const QR_PAD = 8; // frame padding used by the rect (x/y = -8) - const QR_BOX_W = QR_PX + QR_PAD * 2; // 126 - const QR_BOX_H = 142; // keep your existing tuned height (label fits) + const QR_BOX_W = QR_PX + QR_PAD * 2; + const QR_BOX_H = QR_PX + 32; // add room for the label below the QR + const QR_LABEL_Y = QR_PX + 22; - const qrSvg = makeQrSvgTagSafe(qrPayload || verifyUrl, QR_PX, 2); + const qrSvg = makeQrSvgTagSafe(qrPayload || verifyUrl, QR_PX, 3); // makeQrSvgTagSafe() may output an SVG smaller than QR_PX due to integer cell sizing. // We measure that and center it inside the QR_PX slot. @@ -128,7 +129,7 @@ export function buildBanknoteSVG(opts: BuildBanknoteSvgOpts): string { ${qrSvg} - SCAN • VERIFY `; From f646dbab19d6d81b2ae57a375dfe29bbba36fc79 Mon Sep 17 00:00:00 2001 From: Kojib <123880127+kojibai@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:58:38 -0500 Subject: [PATCH 03/77] Adjust exhale note QR position --- src/components/exhale-note/banknoteSvg.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/exhale-note/banknoteSvg.ts b/src/components/exhale-note/banknoteSvg.ts index d9681865..681b20f2 100644 --- a/src/components/exhale-note/banknoteSvg.ts +++ b/src/components/exhale-note/banknoteSvg.ts @@ -125,7 +125,7 @@ export function buildBanknoteSVG(opts: BuildBanknoteSvgOpts): string { const qrOff = Math.max(0, Math.round((QR_PX - qrSvgPx) / 2)); const qrBlock = ` - + ${qrSvg} From be58c26d454e59b1d46c991ed1b812535b6308b9 Mon Sep 17 00:00:00 2001 From: Kojib Date: Sat, 31 Jan 2026 00:15:48 -0500 Subject: [PATCH 04/77] v42.7.2 --- public/sw.js | 2 +- src/version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/sw.js b/public/sw.js index a419dfaf..ebacc051 100644 --- a/public/sw.js +++ b/public/sw.js @@ -14,7 +14,7 @@ // Update this version string manually to keep the app + cache versions in sync. // The value is forwarded to the UI via the service worker "SW_ACTIVATED" message. -const APP_VERSION = "42.7.1"; // update on release +const APP_VERSION = "42.7.2"; // update on release const VERSION = new URL(self.location.href).searchParams.get("v") || APP_VERSION; // derived from build const PREFIX = "PHINETWORK"; diff --git a/src/version.ts b/src/version.ts index bc4edd1d..4004aa44 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,7 +1,7 @@ // src/version.ts // Shared PWA version constants so the app shell, SW registration, and UI stay in sync. -export const BASE_APP_VERSION = "42.7.1"; // Canonical offline/PWA version +export const BASE_APP_VERSION = "42.7.2"; // Canonical offline/PWA version export const SW_VERSION_EVENT = "kairos:sw-version"; export const DEFAULT_APP_VERSION = BASE_APP_VERSION; // Keep in sync with public/sw.js const ENV_APP_VERSION = From 7d79c8ee11116fe41d38ad4bbc5d5c932300af5f Mon Sep 17 00:00:00 2001 From: Kojib Date: Sat, 31 Jan 2026 00:20:18 -0500 Subject: [PATCH 05/77] v42.7.3 --- public/sw.js | 2 +- src/components/exhale-note/banknoteSvg.ts | 15 +++++++-------- src/version.ts | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/public/sw.js b/public/sw.js index ebacc051..80e61d88 100644 --- a/public/sw.js +++ b/public/sw.js @@ -14,7 +14,7 @@ // Update this version string manually to keep the app + cache versions in sync. // The value is forwarded to the UI via the service worker "SW_ACTIVATED" message. -const APP_VERSION = "42.7.2"; // update on release +const APP_VERSION = "42.7.3"; // update on release const VERSION = new URL(self.location.href).searchParams.get("v") || APP_VERSION; // derived from build const PREFIX = "PHINETWORK"; diff --git a/src/components/exhale-note/banknoteSvg.ts b/src/components/exhale-note/banknoteSvg.ts index 681b20f2..ce11a494 100644 --- a/src/components/exhale-note/banknoteSvg.ts +++ b/src/components/exhale-note/banknoteSvg.ts @@ -103,14 +103,13 @@ export function buildBanknoteSVG(opts: BuildBanknoteSvgOpts): string { const sigilEmbed = embedSigilIntoSlotClickable(sigilInner, slot.x, slot.y, slot.w, slot.h, verifyUrl); // QR block (avoid 8-digit hex colors; use stroke-opacity instead) - // QR block — perfectly centered inside the 140×140 slot (and thus the framed box) - const QR_PX = 140; // intended QR slot size + // QR block — perfectly centered inside the 110×110 slot (and thus the framed box) + const QR_PX = 110; // intended QR slot size const QR_PAD = 8; // frame padding used by the rect (x/y = -8) - const QR_BOX_W = QR_PX + QR_PAD * 2; - const QR_BOX_H = QR_PX + 32; // add room for the label below the QR - const QR_LABEL_Y = QR_PX + 22; + const QR_BOX_W = QR_PX + QR_PAD * 2; // 126 + const QR_BOX_H = 142; // keep your existing tuned height (label fits) - const qrSvg = makeQrSvgTagSafe(qrPayload || verifyUrl, QR_PX, 3); + const qrSvg = makeQrSvgTagSafe(qrPayload || verifyUrl, QR_PX, 2); // makeQrSvgTagSafe() may output an SVG smaller than QR_PX due to integer cell sizing. // We measure that and center it inside the QR_PX slot. @@ -125,11 +124,11 @@ export function buildBanknoteSVG(opts: BuildBanknoteSvgOpts): string { const qrOff = Math.max(0, Math.round((QR_PX - qrSvgPx) / 2)); const qrBlock = ` - + ${qrSvg} - SCAN • VERIFY `; diff --git a/src/version.ts b/src/version.ts index 4004aa44..fd7bfbee 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,7 +1,7 @@ // src/version.ts // Shared PWA version constants so the app shell, SW registration, and UI stay in sync. -export const BASE_APP_VERSION = "42.7.2"; // Canonical offline/PWA version +export const BASE_APP_VERSION = "42.7.3"; // Canonical offline/PWA version export const SW_VERSION_EVENT = "kairos:sw-version"; export const DEFAULT_APP_VERSION = BASE_APP_VERSION; // Keep in sync with public/sw.js const ENV_APP_VERSION = From 55eef562a41ba2a761f9a831375fc846ea4e0898 Mon Sep 17 00:00:00 2001 From: Kojib <123880127+kojibai@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:25:11 -0500 Subject: [PATCH 06/77] Add exhale note preview to verify page --- src/pages/VerifyPage.css | 41 +++++++++++++ src/pages/VerifyPage.tsx | 129 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/src/pages/VerifyPage.css b/src/pages/VerifyPage.css index 00909698..f9d00437 100644 --- a/src/pages/VerifyPage.css +++ b/src/pages/VerifyPage.css @@ -326,6 +326,47 @@ html.verify-shell, body.verify-shell{ .vreceipt-row{ display:flex; align-items:center; justify-content:space-between; gap: 8px; margin-top: 6px; flex-wrap:wrap; } .vreceipt-label{ font-size: 0.60rem; letter-spacing: 0.28em; text-transform: uppercase; color: rgba(190,220,255,0.7); } .vreceipt-actions{ display:flex; gap: 6px; flex-wrap:wrap; } +.vnote-card{ + margin-top: 10px; + display: grid; + gap: 6px; + align-items: stretch; + text-align: left; + width: 100%; + border-radius: 18px; + padding: 10px 12px; + border: 1px solid rgba(120,180,255,0.22); + background: rgba(7,14,24,0.5); + color: inherit; + cursor: pointer; + transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease; +} +.vnote-card:hover{ + border-color: rgba(120,200,255,0.45); + box-shadow: 0 14px 28px rgba(0,0,0,0.28), 0 0 0 1px rgba(120,200,255,0.16) inset; + transform: translateY(-1px); +} +.vnote-card:focus-visible{ + outline: 2px solid rgba(120,200,255,0.7); + outline-offset: 2px; +} +.vnote-label{ + font-size: 0.60rem; + letter-spacing: 0.28em; + text-transform: uppercase; + color: rgba(190,220,255,0.7); +} +.vnote-preview{ + border-radius: 12px; + overflow: hidden; + background: rgba(6,10,18,0.75); + border: 1px solid rgba(120,180,255,0.16); +} +.vnote-preview svg{ + display: block; + width: 100%; + height: auto; +} .vnote-claim{ font-size: 0.65rem; letter-spacing: 0.2em; diff --git a/src/pages/VerifyPage.tsx b/src/pages/VerifyPage.tsx index fb5a3d27..6f145f31 100644 --- a/src/pages/VerifyPage.tsx +++ b/src/pages/VerifyPage.tsx @@ -69,6 +69,12 @@ import type { VerifiedCardData } from "../og/types"; import { jcsCanonicalize } from "../utils/jcs"; import { svgCanonicalForHash } from "../utils/svgProof"; import { svgStringToPngBlob, triggerDownload } from "../components/exhale-note/svgToPng"; +import NotePrinter from "../components/ExhaleNote"; +import { buildNotePayload } from "../components/verifier/utils/notePayload"; +import { buildBanknoteSVG } from "../components/exhale-note/banknoteSvg"; +import type { BanknoteInputs as NoteBanknoteInputs } from "../components/exhale-note/types"; +import { safeShowDialog } from "../components/verifier/utils/modal"; +import type { SigilMetadata } from "../components/verifier/types/local"; import useRollingChartSeries from "../components/VerifierStamper/hooks/useRollingChartSeries"; import { BREATH_MS } from "../components/valuation/constants"; import { @@ -1078,6 +1084,8 @@ export default function VerifyPage(): ReactElement { const [openZkProof, setOpenZkProof] = useState(false); const [openZkInputs, setOpenZkInputs] = useState(false); const [openZkHints, setOpenZkHints] = useState(false); + const noteDlgRef = useRef(null); + const [noteOpen, setNoteOpen] = useState(false); // Live chart popover const [chartOpen, setChartOpen] = useState(false); @@ -2678,6 +2686,85 @@ body: [ }; }, [hasKASAuthSig, result.status, sealKAS, sealPopover, sealStateLabel, sealZK]); + const noteMeta = useMemo(() => { + if (result.status !== "ok") return null; + const raw = result.embedded.raw; + return isRecord(raw) ? (raw as SigilMetadata) : null; + }, [result]); + + const notePulseNow = useMemo(() => currentPulse ?? getKaiPulseEternalInt(new Date()), [currentPulse]); + + const noteInitial = useMemo(() => { + const base = buildNotePayload({ + meta: noteMeta, + sigilSvgRaw: svgText.trim() ? svgText.trim() : null, + verifyUrl: currentVerifyUrl, + pulseNow: notePulseNow, + }); + const rawBundle = embeddedProof?.raw; + const rawRecord = isRecord(rawBundle) ? rawBundle : null; + const proofBundleJson = rawRecord ? JSON.stringify(rawRecord) : ""; + const bundleHashValue = + embeddedProof?.bundleHash ?? + sharedReceipt?.bundleHash ?? + (rawRecord && typeof rawRecord.bundleHash === "string" ? rawRecord.bundleHash : ""); + const receiptHashValue = + embeddedProof?.receiptHash ?? + sharedReceipt?.receiptHash ?? + (rawRecord && typeof rawRecord.receiptHash === "string" ? rawRecord.receiptHash : ""); + const verifiedAtPulseValue = + typeof embeddedProof?.verifiedAtPulse === "number" + ? embeddedProof.verifiedAtPulse + : typeof sharedReceipt?.verifiedAtPulse === "number" + ? sharedReceipt.verifiedAtPulse + : rawRecord && typeof rawRecord.verifiedAtPulse === "number" + ? rawRecord.verifiedAtPulse + : undefined; + const capsuleHashValue = + embeddedProof?.capsuleHash ?? + sharedReceipt?.capsuleHash ?? + (rawRecord && typeof rawRecord.capsuleHash === "string" ? rawRecord.capsuleHash : ""); + const svgHashValue = + embeddedProof?.svgHash ?? sharedReceipt?.svgHash ?? (rawRecord && typeof rawRecord.svgHash === "string" ? rawRecord.svgHash : ""); + + return { + ...base, + proofBundleJson, + bundleHash: bundleHashValue, + receiptHash: receiptHashValue, + verifiedAtPulse: verifiedAtPulseValue, + capsuleHash: capsuleHashValue, + svgHash: svgHashValue, + }; + }, [currentVerifyUrl, embeddedProof, noteMeta, notePulseNow, sharedReceipt, svgText]); + + const canShowNotePreview = result.status === "ok" && Boolean(svgText.trim()); + const notePreviewSvg = useMemo(() => { + if (!canShowNotePreview) return ""; + const valuePhi = displayPhi != null ? displayPhi.toFixed(4) : noteInitial.valuePhi ?? ""; + const valueUsd = displayUsd != null ? fmtUsd(displayUsd) : ""; + return buildBanknoteSVG({ + ...noteInitial, + valuePhi, + valueUsd, + sigilSvg: svgText.trim(), + verifyUrl: currentVerifyUrl, + }); + }, [canShowNotePreview, currentVerifyUrl, displayPhi, displayUsd, noteInitial, svgText]); + + const openNote = useCallback(() => { + if (!noteDlgRef.current) return; + safeShowDialog(noteDlgRef.current); + setNoteOpen(true); + }, []); + + const closeNote = useCallback(() => { + if (!noteDlgRef.current) return; + noteDlgRef.current.close(); + noteDlgRef.current.setAttribute("data-open", "false"); + setNoteOpen(false); + }, []); + const hasSvgBytes = Boolean(svgText.trim()); const expectedSvgHash = sharedReceipt?.svgHash ?? embeddedProof?.svgHash ?? ""; const identityStatusLabel = hasKASOwnerSig ? ownerAuthStatus || "Not present" : ""; @@ -3313,6 +3400,12 @@ React.useEffect(() => { ) : null} + {proofCapsule && canShowNotePreview ? ( + + ) : null} @@ -3383,6 +3476,42 @@ React.useEffect(() => { ) : null} + +
+
+
Kairos — Exhale Note
+ +
+ +
+ {valuationPayload && svgText.trim() ? ( + + ) : ( +
Load and verify a sigil to render an exhale note.
+ )} +
+
+
+ {/* Body */}
From 0a72840abde6bca018ab684e404b5d25171025fa Mon Sep 17 00:00:00 2001 From: Kojib <123880127+kojibai@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:34:18 -0500 Subject: [PATCH 07/77] Refine verify note preview layout --- src/pages/VerifyPage.css | 43 +++++++++++++++++++++------------------- src/pages/VerifyPage.tsx | 14 ++++++++++--- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/pages/VerifyPage.css b/src/pages/VerifyPage.css index f9d00437..c7b4aefc 100644 --- a/src/pages/VerifyPage.css +++ b/src/pages/VerifyPage.css @@ -326,41 +326,44 @@ html.verify-shell, body.verify-shell{ .vreceipt-row{ display:flex; align-items:center; justify-content:space-between; gap: 8px; margin-top: 6px; flex-wrap:wrap; } .vreceipt-label{ font-size: 0.60rem; letter-spacing: 0.28em; text-transform: uppercase; color: rgba(190,220,255,0.7); } .vreceipt-actions{ display:flex; gap: 6px; flex-wrap:wrap; } -.vnote-card{ - margin-top: 10px; - display: grid; - gap: 6px; - align-items: stretch; - text-align: left; - width: 100%; - border-radius: 18px; - padding: 10px 12px; +.vnote-row{ + display:flex; + align-items:center; + justify-content:space-between; + gap: 10px; + margin-top: 8px; +} +.vnote-label{ + font-size: 0.60rem; + letter-spacing: 0.28em; + text-transform: uppercase; + color: rgba(190,220,255,0.7); +} +.vnote-preview-btn{ + appearance: none; + padding: 0; border: 1px solid rgba(120,180,255,0.22); - background: rgba(7,14,24,0.5); - color: inherit; + background: rgba(10,16,26,0.7); + border-radius: 12px; cursor: pointer; transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease; } -.vnote-card:hover{ +.vnote-preview-btn:hover{ border-color: rgba(120,200,255,0.45); - box-shadow: 0 14px 28px rgba(0,0,0,0.28), 0 0 0 1px rgba(120,200,255,0.16) inset; + box-shadow: 0 12px 24px rgba(0,0,0,0.28), 0 0 0 1px rgba(120,200,255,0.16) inset; transform: translateY(-1px); } -.vnote-card:focus-visible{ +.vnote-preview-btn:focus-visible{ outline: 2px solid rgba(120,200,255,0.7); outline-offset: 2px; } -.vnote-label{ - font-size: 0.60rem; - letter-spacing: 0.28em; - text-transform: uppercase; - color: rgba(190,220,255,0.7); -} .vnote-preview{ border-radius: 12px; overflow: hidden; background: rgba(6,10,18,0.75); border: 1px solid rgba(120,180,255,0.16); + width: 140px; + max-width: 100%; } .vnote-preview svg{ display: block; diff --git a/src/pages/VerifyPage.tsx b/src/pages/VerifyPage.tsx index 6f145f31..44200356 100644 --- a/src/pages/VerifyPage.tsx +++ b/src/pages/VerifyPage.tsx @@ -3401,10 +3401,18 @@ React.useEffect(() => { ) : null} {proofCapsule && canShowNotePreview ? ( - + +
) : null} From 0090702b51d6d671bcd7a7d74fc958acab62aaec Mon Sep 17 00:00:00 2001 From: Kojib <123880127+kojibai@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:40:22 -0500 Subject: [PATCH 08/77] Make verify note trigger a compact button --- src/pages/VerifyPage.css | 49 +++++++++------------------------------- src/pages/VerifyPage.tsx | 25 +++++++++----------- 2 files changed, 22 insertions(+), 52 deletions(-) diff --git a/src/pages/VerifyPage.css b/src/pages/VerifyPage.css index c7b4aefc..cea97b45 100644 --- a/src/pages/VerifyPage.css +++ b/src/pages/VerifyPage.css @@ -326,49 +326,22 @@ html.verify-shell, body.verify-shell{ .vreceipt-row{ display:flex; align-items:center; justify-content:space-between; gap: 8px; margin-top: 6px; flex-wrap:wrap; } .vreceipt-label{ font-size: 0.60rem; letter-spacing: 0.28em; text-transform: uppercase; color: rgba(190,220,255,0.7); } .vreceipt-actions{ display:flex; gap: 6px; flex-wrap:wrap; } -.vnote-row{ - display:flex; - align-items:center; - justify-content:space-between; - gap: 10px; - margin-top: 8px; -} -.vnote-label{ - font-size: 0.60rem; - letter-spacing: 0.28em; - text-transform: uppercase; - color: rgba(190,220,255,0.7); -} -.vnote-preview-btn{ - appearance: none; - padding: 0; - border: 1px solid rgba(120,180,255,0.22); - background: rgba(10,16,26,0.7); - border-radius: 12px; - cursor: pointer; - transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease; +.vbtn--note{ + padding: 6px; } -.vnote-preview-btn:hover{ - border-color: rgba(120,200,255,0.45); - box-shadow: 0 12px 24px rgba(0,0,0,0.28), 0 0 0 1px rgba(120,200,255,0.16) inset; - transform: translateY(-1px); -} -.vnote-preview-btn:focus-visible{ - outline: 2px solid rgba(120,200,255,0.7); - outline-offset: 2px; -} -.vnote-preview{ - border-radius: 12px; +.vbtn-note-preview{ + display: block; + width: 24px; + height: 24px; + border-radius: 6px; overflow: hidden; - background: rgba(6,10,18,0.75); - border: 1px solid rgba(120,180,255,0.16); - width: 140px; - max-width: 100%; + border: 1px solid rgba(120,180,255,0.2); + background: rgba(6,10,18,0.8); } -.vnote-preview svg{ +.vbtn-note-preview svg{ display: block; width: 100%; - height: auto; + height: 100%; } .vnote-claim{ font-size: 0.65rem; diff --git a/src/pages/VerifyPage.tsx b/src/pages/VerifyPage.tsx index 44200356..ec0a488c 100644 --- a/src/pages/VerifyPage.tsx +++ b/src/pages/VerifyPage.tsx @@ -3368,6 +3368,17 @@ React.useEffect(() => { + {canShowNotePreview ? ( + + ) : null} {isExhaleNoteUpload ? null : ( - - ) : null} From d0821db60d122ec6cfe144624197af8cc38203fa Mon Sep 17 00:00:00 2001 From: Kojib <123880127+kojibai@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:50:20 -0500 Subject: [PATCH 09/77] Adjust verify note layout and dialog --- src/pages/VerifyPage.css | 81 +++++++++++++++++++++++++- src/pages/VerifyPage.tsx | 121 +++++++++++++++++++++------------------ 2 files changed, 146 insertions(+), 56 deletions(-) diff --git a/src/pages/VerifyPage.css b/src/pages/VerifyPage.css index cea97b45..c7880f1e 100644 --- a/src/pages/VerifyPage.css +++ b/src/pages/VerifyPage.css @@ -323,9 +323,13 @@ html.verify-shell, body.verify-shell{ /* KPIs */ .vkpis{ display:grid; grid-template-columns: 1fr 1fr; gap: 8px; } -.vreceipt-row{ display:flex; align-items:center; justify-content:space-between; gap: 8px; margin-top: 6px; flex-wrap:wrap; } +.vreceipt-block{ display:flex; flex-direction:column; gap: 8px; margin-top: 6px; } +.vreceipt-row{ display:flex; align-items:center; justify-content:space-between; gap: 8px; flex-wrap:wrap; } .vreceipt-label{ font-size: 0.60rem; letter-spacing: 0.28em; text-transform: uppercase; color: rgba(190,220,255,0.7); } .vreceipt-actions{ display:flex; gap: 6px; flex-wrap:wrap; } +.vreceipt-note{ display:flex; align-items:center; justify-content:space-between; gap: 8px; flex-wrap:wrap; } +.vreceipt-note-left{ display:flex; align-items:center; gap: 8px; flex-wrap:wrap; } +.vreceipt-note-actions{ display:flex; gap: 6px; flex-wrap:wrap; } .vbtn--note{ padding: 6px; } @@ -1030,6 +1034,81 @@ html.verify-shell, body.verify-shell{ color: rgba(185,220,255,0.7); } +/* Glass dialog (note preview) */ +dialog.glass-modal{ + box-sizing: border-box; + inline-size: min(1000px, calc(100vw - (var(--pad) * 2))); + max-inline-size: calc(100vw - (var(--pad) * 2)); + max-height: calc(100vh - 2 * clamp(8px, 6vh, 24px)); + margin-block: clamp(8px, 6vh, 24px); + margin-inline: auto; + border: 0; + padding: 0; + border-radius: var(--br2); + background: linear-gradient(180deg, rgba(10,12,20,0.92), rgba(8,12,18,0.88)); + box-shadow: 0 24px 64px rgba(0, 0, 0, 0.55), inset 0 0 0 1px rgba(255,255,255,0.08); + color: var(--ink); + overflow: hidden; +} + +dialog.glass-modal::backdrop{ + background: radial-gradient(900px 600px at 80% -10%, rgba(120,200,255,0.20), transparent 40%), + radial-gradient(900px 600px at 10% 110%, rgba(90,255,220,0.16), transparent 40%), + rgba(0, 0, 10, 0.55); + -webkit-backdrop-filter: blur(4px); + backdrop-filter: blur(4px); +} + +dialog.glass-modal .modal-topbar{ + position: sticky; + top: 0; + z-index: 2; + min-height: 48px; + display: grid; + grid-template-columns: auto 1fr; + align-items: center; + gap: 8px; + background: linear-gradient(180deg, rgba(10, 12, 20, 0.9), rgba(10, 12, 20, 0.7)); + border-bottom: 1px solid rgba(255,255,255,0.08); +} + +dialog.glass-modal .modal-topbar .close-btn{ + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + border-radius: 999px; + border: 1px solid rgba(255,255,255,0.18); + font-size: 18px; + font-weight: 700; + line-height: 1; + color: rgba(240,245,255,0.98); + background: radial-gradient(circle at 30% 0%, rgba(255, 255, 255, 0.22), transparent 55%), rgba(15, 23, 42, 0.98); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.65); + cursor: pointer; + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + transition: transform 0.12s ease, box-shadow 0.18s ease, background 0.18s ease, color 0.18s ease; +} + +dialog.glass-modal .modal-topbar .close-btn:hover{ + background: radial-gradient(circle at 30% 0%, rgba(255, 255, 255, 0.28), transparent 55%), rgba(15, 23, 42, 1); + box-shadow: 0 7px 20px rgba(0, 0, 0, 0.72); + transform: translateY(-1px); +} + +dialog.glass-modal .modal-topbar .close-btn:active{ + transform: translateY(1px); + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.65); +} + +dialog.glass-modal .modal-topbar .close-btn:focus-visible{ + outline: 2px solid rgba(120,200,255,0.65); + outline-offset: 2px; +} + /* Modal (unchanged) */ .vmodal-backdrop{ position: fixed; diff --git a/src/pages/VerifyPage.tsx b/src/pages/VerifyPage.tsx index ec0a488c..bd09e880 100644 --- a/src/pages/VerifyPage.tsx +++ b/src/pages/VerifyPage.tsx @@ -3351,65 +3351,76 @@ React.useEffect(() => { {proofCapsule ? ( -
-
Proof
- {noteClaimStatus ? ( -
- {noteClaimStatus} -
- ) : null} -
- - - {canShowNotePreview ? ( - - ) : null} - {isExhaleNoteUpload ? null : ( - - )} - {noteSvgFromPng && result.status === "ok" && !noteClaimed ? ( - - ) : null} - {isExhaleNoteUpload ? null : ( - - )} + {isExhaleNoteUpload ? null : ( + + )} + {isExhaleNoteUpload ? null : ( + + )} +
+ {canShowNotePreview || noteClaimStatus || (noteSvgFromPng && result.status === "ok" && !noteClaimed) ? ( +
+
+
Note
+ {noteClaimStatus ? ( +
+ {noteClaimStatus} +
+ ) : null} +
+
+ {canShowNotePreview ? ( + + ) : null} + {noteSvgFromPng && result.status === "ok" && !noteClaimed ? ( + + ) : null} +
+
+ ) : null} - ) : null} From 2308ce7a85f59ac4da48f58b9941ae7f59a717a5 Mon Sep 17 00:00:00 2001 From: Kojib Date: Sat, 31 Jan 2026 12:36:47 -0500 Subject: [PATCH 10/77] v42.7.4 --- public/sw.js | 2 +- src/components/ExhaleNote.css | 382 ++++++++++++++++++++++++++++++++++ src/components/ExhaleNote.tsx | 253 +++++++++++----------- src/pages/VerifyPage.css | 49 +++++ src/pages/VerifyPage.tsx | 8 +- src/version.ts | 2 +- 6 files changed, 572 insertions(+), 124 deletions(-) diff --git a/public/sw.js b/public/sw.js index 80e61d88..b5072be9 100644 --- a/public/sw.js +++ b/public/sw.js @@ -14,7 +14,7 @@ // Update this version string manually to keep the app + cache versions in sync. // The value is forwarded to the UI via the service worker "SW_ACTIVATED" message. -const APP_VERSION = "42.7.3"; // update on release +const APP_VERSION = "42.7.4"; // update on release const VERSION = new URL(self.location.href).searchParams.get("v") || APP_VERSION; // derived from build const PREFIX = "PHINETWORK"; diff --git a/src/components/ExhaleNote.css b/src/components/ExhaleNote.css index 6d190fee..2412c85c 100644 --- a/src/components/ExhaleNote.css +++ b/src/components/ExhaleNote.css @@ -1087,3 +1087,385 @@ #print-root { display: block !important; } body { background: #fff !important; } } +/* ───────────────────────────────────────────────────────────── + v26.3.x COMPACT HERO OVERRIDES + - Shrinks Render/Lock + Export controls + - Reduces VALUE block height + - Keeps everything visible while reclaiming vertical space + ───────────────────────────────────────────────────────────── */ + +.kk-hero2{ + margin-top: 10px; + padding: 10px; +} + +.kk-hero2__row{ + gap: 10px; + align-items: start; + grid-template-columns: 1fr minmax(240px, 34%); +} + +@media (max-width: 980px){ + .kk-hero2__row{ grid-template-columns: 1fr; } +} + +/* Status chips are duplicated by the headbar — hide them on small to save height */ +@media (max-width: 720px){ + .kk-hero2__status{ display: none; } +} + +/* Tighten chips when visible */ +.kk-hero2__status{ + margin-bottom: 6px; + gap: 6px; +} + +.kk-chip2{ + font-size: 11px; + padding: 5px 8px; + gap: 5px; +} + +/* VALUE block: smaller but still “premium” */ +.kk-hero2__big{ + padding: 10px 12px; +} + +.kk-big__label{ + font-size: 11px; + letter-spacing: 2.2px; + margin-bottom: 4px; +} + +.kk-big__num{ + gap: 8px; +} + +.kk-big__phi{ + font-size: clamp(20px, 4.2vw, 32px); +} + +.kk-big__int{ + font-size: clamp(30px, 7.2vw, 52px); +} + +.kk-big__frac{ + font-size: clamp(13px, 2.8vw, 20px); + padding-bottom: 2px; +} + +.kk-big__usd{ + margin-top: 4px; + font-size: 12px; +} + +/* ───────────────────────────────────────────────────────────── + Compact action row + ───────────────────────────────────────────────────────────── */ + +.kk-hero2__actions{ + align-content: start; +} + +/* One refined strip for Render/Lock + tools */ +.kk-actionsRow{ + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + flex-wrap: wrap; +} + +/* Tools cluster stays tight */ +.kk-actionsRow__tools{ + display: inline-flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +/* Render button: compact primary */ +.kk-btn-compact{ + padding: 10px 12px; + font-size: 14px; + border-radius: 12px; + min-height: 40px; +} + +/* Export buttons: mini pills (still finger-safe) */ +.kk-btn-mini{ + padding: 8px 10px; + font-size: 13px; + border-radius: 11px; + min-height: 36px; +} + +/* Locked card: slimmer */ +.kk-lockcard--compact{ + padding: 8px 10px; + border-radius: 14px; +} + +.kk-lockcard--compact .kk-lockcard__t{ + font-size: 12px; +} + +.kk-lockcard--compact .kk-lockcard__s{ + font-size: 11px; + margin-top: 3px; +} + +.kk-lockcard__dot{ + margin: 0 6px; + opacity: 0.7; +} + +.kk-lockcard__k{ + opacity: 0.85; +} + +/* Mobile: keep it compact and readable */ +@media (max-width: 560px){ + .kk-actionsRow{ + justify-content: stretch; + } + + /* Render takes full width, tools below in one tight row */ + .kk-actionsRow > .kk-btn-compact, + .kk-actionsRow > .kk-lockcard--compact{ + flex: 1 1 100%; + } + + .kk-actionsRow__tools{ + width: 100%; + justify-content: space-between; + gap: 8px; + } + + .kk-actionsRow__tools .kk-btn-mini{ + flex: 1 1 0; + justify-content: center; + } +} +/* ───────────────────────────────────────────────────────────── + VALUE BOX: inline Lock control (compact) + ───────────────────────────────────────────────────────────── */ + +.kk-big__top{ + display:flex; + align-items:center; + justify-content:space-between; + gap:10px; + margin-bottom: 4px; +} + +.kk-big__label{ + margin:0; +} + +.kk-big__lockSlot{ + display:flex; + align-items:center; + justify-content:flex-end; + min-width: 0; +} + +/* Small Lock button that lives on the VALUE box */ +.kk-lockBtn{ + appearance:none; + border: 1px solid rgba(86,255,227,0.34); + background: linear-gradient(180deg, rgba(86,255,227,0.18), rgba(86,255,227,0.08)); + color: rgba(234,241,255,0.96); + + padding: 6px 10px; + border-radius: 999px; + font-weight: 950; + font-size: 12px; + letter-spacing: 0.14px; + + min-height: 30px; + line-height: 1; + cursor:pointer; + + box-shadow: 0 10px 22px rgba(86,255,227,0.10), inset 0 1px 0 rgba(255,255,255,0.08); + transition: transform var(--kk-fast) var(--kk-ease), + border-color var(--kk-fast) var(--kk-ease), + background var(--kk-fast) var(--kk-ease), + box-shadow var(--kk-fast) var(--kk-ease), + filter var(--kk-fast) var(--kk-ease); +} + +.kk-lockBtn:hover{ + transform: translateY(-1px); + border-color: rgba(86,255,227,0.48); + filter: brightness(1.03); + box-shadow: 0 14px 28px rgba(86,255,227,0.14), inset 0 1px 0 rgba(255,255,255,0.10); +} + +.kk-lockBtn:active{ + transform: translateY(0); +} + +.kk-lockBtn:focus-visible{ + outline:none; + box-shadow: var(--kk-focus); +} + +.kk-lockBtn[disabled]{ + opacity: 0.62; + cursor: not-allowed; + transform: none; + filter: none; + box-shadow: 0 10px 22px rgba(0,0,0,0.14), inset 0 1px 0 rgba(255,255,255,0.06); +} + +/* Locked pill (tiny, informational, non-bulky) */ +.kk-lockPill{ + display:inline-flex; + align-items:center; + gap: 6px; + + border: 1px solid rgba(245,217,141,0.34); + background: linear-gradient(180deg, rgba(245,217,141,0.14), rgba(245,217,141,0.06)); + color: rgba(234,241,255,0.92); + + padding: 6px 10px; + border-radius: 999px; + + font-size: 11px; + font-weight: 900; + min-height: 30px; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + box-shadow: 0 10px 22px rgba(245,217,141,0.08), inset 0 1px 0 rgba(255,255,255,0.08); +} + +.kk-lockPill__k{ opacity: 0.92; } +.kk-lockPill__dot{ opacity: 0.70; } +.kk-lockPill__mono{ font-variant-numeric: tabular-nums; } + +/* Right column tools stay compact and aligned */ +.kk-hero2__actions{ + display:flex; + align-items:flex-start; + justify-content:flex-end; +} + +.kk-actionsRow__tools{ + display:inline-flex; + align-items:center; + gap: 8px; + flex-wrap: wrap; + justify-content: flex-end; +} + +/* Mobile: exports become full-width row buttons */ +@media (max-width: 560px){ + .kk-actionsRow__tools{ + width: 100%; + justify-content: space-between; + } + .kk-actionsRow__tools .kk-btn-mini{ + flex: 1 1 0; + justify-content: center; + } +} +/* ───────────────────────────────────────────────────────────── + Send Amount — COMPACT + SLEEK (paste at end of ExhaleNote.css) + ───────────────────────────────────────────────────────────── */ + +.kk-sendbar--compact{ + padding: 10px 12px 9px; +} + +/* tighter left text */ +.kk-sendbar--compact .kk-sendbar__label{ + font-size: 13px; + font-weight: 950; +} +.kk-sendbar--compact .kk-sendbar__sub{ + margin-top: 2px; + font-size: 11.5px; + line-height: 1.15; +} + +/* tighter grid inside right side */ +.kk-sendbar--compact .kk-sendbar__right{ + row-gap: 6px; + column-gap: 10px; +} + +/* unit toggle: smaller pill */ +.kk-sendbar--compact .kk-sendbar__unit{ + padding: 5px; + gap: 6px; +} + +/* perfect center for Φ / $ */ +.kk-sendbar--compact .kk-sendbar__unitBtn{ + width: 36px; + height: 28px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; + text-align: center; +} + +/* optical centering (Φ usually sits low) */ +.kk-sendbar--compact .kk-unitGlyph{ + display: block; + line-height: 1; + transform: translateY(-0.06em); + font-weight: 950; +} + +/* compact input wrap */ +.kk-sendbar--compact .kk-sendbar__inputWrap{ + min-height: 38px; + padding: 7px 10px; + width: min(320px, 100%); +} + +/* slightly smaller input text */ +.kk-sendbar--compact .kk-sendbar__input{ + font-size: 13.5px; +} + +/* meta: tighter, no wasted height */ +.kk-sendbar--compact .kk-sendbar__meta{ + gap: 2px; +} + +.kk-sendbar--compact .kk-sendbar__usd{ + font-size: 12.5px; + line-height: 1.1; +} + +.kk-sendbar--compact .kk-sendbar__hint{ + font-size: 11.5px; + line-height: 1.1; +} + +.kk-sendbar__warn{ + color: rgba(255, 165, 185, 0.92); + font-weight: 850; +} + +/* Mobile: keep it stacked but tight */ +@media (max-width: 720px){ + .kk-sendbar--compact{ + padding: 10px 12px; + } + .kk-sendbar--compact .kk-sendbar__right{ + justify-items: center; + row-gap: 8px; + } + .kk-sendbar--compact .kk-sendbar__inputWrap{ + width: 100%; + } +} diff --git a/src/components/ExhaleNote.tsx b/src/components/ExhaleNote.tsx index 14267db4..49c21038 100644 --- a/src/components/ExhaleNote.tsx +++ b/src/components/ExhaleNote.tsx @@ -1705,50 +1705,63 @@ const ExhaleNote: React.FC = ({ {fTiny(displayPhiPerUsd)} +
+
+
VALUE
+ +
+ {!isLocked ? ( + + ) : ( +
+ Minted + · + + ☤KAI {locked ? fPulse(locked.lockedPulse) : fPulse(displayPulse)} + +
+ )} +
+
+ +
+ Φ + {phiParts.int} + {phiParts.frac} +
+ +
≈ {fUsd(displayUsd)}
+
+ +
+
+ + + +
+
+ + -
-
VALUE
-
- Φ - {phiParts.int} - {phiParts.frac} -
-
≈ {fUsd(displayUsd)}
-
- - -
- {!isLocked ? ( - - ) : ( -
-
Locked
-
- ☤KAI {locked ? fPulse(locked.lockedPulse) : fPulse(displayPulse)} · stamp{" "} - {form.valuationStamp || locked?.seal.stamp || "—"} -
-
- )} - -
- - - -
-
{/* TOP ANSWER BOX + SEND AMOUNT (side-by-side) */} @@ -1758,14 +1771,14 @@ const ExhaleNote: React.FC = ({
- {isLocked ? "LOCKED" : `${guideIdx + 1}/${guideSteps.length}`} + {isLocked ? "MINTED" : `${guideIdx + 1}/${guideSteps.length}`}
{currentGuide.label}
-
{isLocked ? "Locked — only Send Amount can change." : currentGuide.prompt}
+
{isLocked ? "Minted — only Send Amount can change." : currentGuide.prompt}
= ({ className="kk-qaInput" value={isLocked ? "" : draft} onChange={(e) => setDraft(e.target.value)} - placeholder={isLocked ? "Locked" : currentGuide.placeholder} + placeholder={isLocked ? "Minted" : currentGuide.placeholder} disabled={isLocked} onKeyDown={(e) => { if (e.key === "Enter") { @@ -1832,82 +1845,86 @@ const ExhaleNote: React.FC = ({ ) : null}
) : null} +{/* Send Amount — compact + centered unit toggle */} +
+
+
Exhale Amount
+
Minted on Print/SVG/PNG.
+
+ +
+ {/* Unit toggle */} +
+ + + +
- {/* Send Amount */} -
-
-
Send Amount
-
Committed when printing/saving exports.
-
- -
- {/* Unit toggle */} -
- - -
+ {/* Amount input */} +
+ + { + if (sendUnit === "phi") setSendPhiInput(e.target.value); + else setSendUsdInput(e.target.value); + }} + placeholder={ + !isLocked + ? "Mint to set amount" + : sendUnit === "phi" + ? fTiny(defaultSendPhi) + : formatUsdInput(defaultSendUsd) + } + disabled={!isLocked} + className={`kk-sendbar__input ${sendPhiOverBalance ? "is-error" : ""}`} + inputMode="decimal" + aria-invalid={sendPhiOverBalance || undefined} + /> +
-
- {sendUnit === "phi" ? "Φ" : "$"} - { - if (sendUnit === "phi") setSendPhiInput(e.target.value); - else setSendUsdInput(e.target.value); - }} - placeholder={ - !isLocked - ? "Render to set amount" - : sendUnit === "phi" - ? fTiny(defaultSendPhi) - : formatUsdInput(defaultSendUsd) - } - disabled={!isLocked} - className={`kk-sendbar__input ${sendPhiOverBalance ? "is-error" : ""}`} - inputMode="decimal" - aria-invalid={sendPhiOverBalance || undefined} - /> -
+ {/* Meta (single tight line + optional hint) */} +
+
+ {sendUnit === "phi" ? ( + <>≈ {fUsd(effectiveValueUsd)} + ) : ( + <> + ≈ Φ {fTiny(effectiveSendPhi)} + + )} +
-
-
- {sendUnit === "phi" ? ( - <>≈ {fUsd(effectiveValueUsd)} - ) : ( - <> - ≈ Φ {fTiny(effectiveSendPhi)} - - )} -
+ {isLocked && typeof availablePhi === "number" && Number.isFinite(availablePhi) ? ( +
+ Avail {fTiny(availablePhi)} + {sendPhiOverBalance ? · exceeds : null} +
+ ) : ( +
Mint locks valuation.
+ )} +
+
+
- {isLocked && typeof availablePhi === "number" && Number.isFinite(availablePhi) ? ( -
- Available: {fTiny(availablePhi)} {sendPhiOverBalance ? "· exceeds" : ""} -
- ) : ( -
Render locks valuation.
- )} -
-
-
diff --git a/src/pages/VerifyPage.css b/src/pages/VerifyPage.css index c7880f1e..33c6b3bb 100644 --- a/src/pages/VerifyPage.css +++ b/src/pages/VerifyPage.css @@ -1731,3 +1731,52 @@ dialog.glass-modal .modal-topbar .close-btn:focus-visible{ margin: 0 !important; transform: translateY(0) !important; } +/* ──────────────────────────────────────────────────────────────── + FIX: Note modal preview button — perfect centering + (prevents .vbtn-ic 16x16 overrides from squashing the thumbnail) +──────────────────────────────────────────────────────────────── */ + +.vbtn--note{ + /* make centering deterministic */ + display: grid !important; + place-items: center !important; + padding: 0 !important; /* kill any asymmetric padding math */ +} + +/* If your note button still uses the standard icon wrapper, + override it to match the thumbnail size */ +.vbtn--note .vbtn-ic{ + width: 24px !important; + height: 24px !important; + display: grid !important; + place-items: center !important; + margin: 0 !important; + line-height: 1 !important; +} + +/* The thumbnail itself must be a centered 1:1 viewport */ +.vbtn--note .vbtn-note-preview{ + width: 24px !important; + height: 24px !important; + display: grid !important; + place-items: center !important; + margin: 0 !important; + border-radius: 6px; + overflow: hidden; +} + +/* Center any kind of payload (inline svg, img, nested wrappers) */ +.vbtn--note .vbtn-note-preview > svg, +.vbtn--note .vbtn-note-preview > img, +.vbtn--note .vbtn-note-preview > *{ + display: block !important; + width: 100% !important; + height: 100% !important; + margin: 0 auto !important; +} + +/* If the preview is an , enforce true center-crop/center-fit */ +.vbtn--note .vbtn-note-preview > img{ + object-fit: cover; + object-position: center; +} diff --git a/src/pages/VerifyPage.tsx b/src/pages/VerifyPage.tsx index bd09e880..9adb59a5 100644 --- a/src/pages/VerifyPage.tsx +++ b/src/pages/VerifyPage.tsx @@ -3383,7 +3383,7 @@ React.useEffect(() => { {canShowNotePreview || noteClaimStatus || (noteSvgFromPng && result.status === "ok" && !noteClaimed) ? (
-
Note
+
☤Kai-Note (Legal Tender)
{noteClaimStatus ? (
{ className="vbtn vbtn--ghost vbtn--note" onPointerDown={openNote} onClick={openNote} - aria-label="Open Exhale note" - title="Open Exhale note" + aria-label="Open note Exhaler" + title="Open note Exhaler" >