diff --git a/hubzilla/addon/g1wallet/Widget/G1wallet.php b/hubzilla/addon/g1wallet/Widget/G1wallet.php
new file mode 100644
index 0000000..1c2c809
--- /dev/null
+++ b/hubzilla/addon/g1wallet/Widget/G1wallet.php
@@ -0,0 +1,80 @@
+';
+ $out .= '
';
+
+ // Static locked state — shown on page load.
+ // JS will update this div via 'g1wallet:unlocked' / 'g1wallet:locked' events.
+ $out .= '';
+ $out .= $this->render_locked_status($wallet_url, $cached_pubkey, $cached_balance);
+ $out .= '
';
+
+ // Data attributes for JS to pick up the widget instance.
+ $out .= '';
+
+ $out .= '';
+ return $out;
+ }
+
+ private function render_locked_status($wallet_url, $cached_pubkey, $cached_balance) {
+ $out = '';
+ $out .= '
🔒 ';
+ $out .= '
Locked';
+
+ // If we have a cached pubkey from a previous session, show it as context.
+ if ($cached_pubkey) {
+ $short = substr($cached_pubkey, 0, 12) . '…';
+ $out .= '
';
+ $out .= $this->h($short);
+ if ($cached_balance) {
+ $out .= ' · ' . $this->h($cached_balance) . ' Ğ1';
+ }
+ $out .= '
';
+ }
+
+ $out .= '
';
+ $out .= '
Unlock';
+ $out .= '
';
+ $out .= '
';
+ return $out;
+ }
+
+ private function h($value) {
+ // HTML-escapes a value for safe output.
+ return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
+ }
+}