aboutsummaryrefslogtreecommitdiffstats
path: root/src/style.css
blob: 1e5a1c79e2d0a779c58c5a89a53d6f41170af177 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888 } /* Comment */
.highlight .err { color: #A61717; background-color: #E3D2D2 } /* Error */
.highlight .k { color: #080; font-weight: bold } /* Keyword */
.highlight .ch { color: #888 } /* Comment.Hashbang */
.highlight .cm { color: #888 } /* Comment.Multiline */
.highlight .cp { color: #C00; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888 } /* Comment.Single */
.highlight .cs { color: #C00; font-weight: bold; background-color: #FFF0F0 } /* Comment.Special */
.highlight .gd { color: #000; background-color: #FDD } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #A00 } /* Generic.Error */
.highlight .gh { color: #333 } /* Generic.Heading */
.highlight .gi { color: #000; background-color: #DFD } /* Generic.Inserted */
.highlight .go { color: #888 } /* Generic.Output */
.highlight .gp { color: #555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666 } /* Generic.Subheading */
.highlight .gt { color: #A00 } /* Generic.Traceback */
.highlight .kc { color: #080; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #080; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #080; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #080 } /* Keyword.Pseudo */
.highlight .kr { color: #080; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #00D; font-weight: bold } /* Literal.Number */
.highlight .s { color: #D20; background-color: #FFF0F0 } /* Literal.String */
.highlight .na { color: #369 } /* Name.Attribute */
.highlight .nb { color: #038 } /* Name.Builtin */
.highlight .nc { color: #B06; font-weight: bold } /* Name.Class */
.highlight .no { color: #036; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555 } /* Name.Decorator */
.highlight .ne { color: #B06; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #06B; font-weight: bold } /* Name.Function */
.highlight .nl { color: #369; font-style: italic } /* Name.Label */
.highlight .nn { color: #B06; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #369; font-weight: bold } /* Name.Property */
.highlight .nt { color: #B06; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #369 } /* Name.Variable */
.highlight .ow { color: #080 } /* Operator.Word */
.highlight .w { color: #BBB } /* Text.Whitespace */
.highlight .mb { color: #00D; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #00D; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #00D; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #00D; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #00D; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #D20; background-color: #FFF0F0 } /* Literal.String.Affix */
.highlight .sb { color: #D20; background-color: #FFF0F0 } /* Literal.String.Backtick */
.highlight .sc { color: #D20; background-color: #FFF0F0 } /* Literal.String.Char */
.highlight .dl { color: #D20; background-color: #FFF0F0 } /* Literal.String.Delimiter */
.highlight .sd { color: #D20; background-color: #FFF0F0 } /* Literal.String.Doc */
.highlight .s2 { color: #D20; background-color: #FFF0F0 } /* Literal.String.Double */
.highlight .se { color: #04D; background-color: #FFF0F0 } /* Literal.String.Escape */
.highlight .sh { color: #D20; background-color: #FFF0F0 } /* Literal.String.Heredoc */
.highlight .si { color: #33B; background-color: #FFF0F0 } /* Literal.String.Interpol */
.highlight .sx { color: #2B2; background-color: #F0FFF0 } /* Literal.String.Other */
.highlight .sr { color: #080; background-color: #FFF0FF } /* Literal.String.Regex */
.highlight .s1 { color: #D20; background-color: #FFF0F0 } /* Literal.String.Single */
.highlight .ss { color: #A60; background-color: #FFF0F0 } /* Literal.String.Symbol */
.highlight .bp { color: #038 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #06B; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #369 } /* Name.Variable.Class */
.highlight .vg { color: #D70 } /* Name.Variable.Global */
.highlight .vi { color: #33B } /* Name.Variable.Instance */
.highlight .vm { color: #369 } /* Name.Variable.Magic */
.highlight .il { color: #00D; font-weight: bold } /* Literal.Number.Integer.Long */
{"version":3,"file":"whitelist.js","sourceRoot":"","sources":["whitelist.ts"],"names":[],"mappings":";;AAAA,kBAAe;IACb,SAAS;IACT,OAAO;IACP,MAAM;IACN,cAAc;IACd,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,YAAY;IACZ,UAAU;IACV,aAAa;IACb,SAAS;IACT,OAAO;IACP,MAAM;IACN,WAAW;IACX,SAAS;IACT,QAAQ;IACR,YAAY;IACZ,SAAS;IACT,IAAI;IACJ,OAAO;IACP,MAAM;IACN,UAAU;IACV,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,MAAM;IACN,QAAQ;IACR,MAAM;IACN,KAAK;IACL,UAAU;IACV,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,MAAM;IACN,MAAM;IACN,cAAc;IACd,cAAc;IACd,OAAO;IACP,WAAW;IACX,OAAO;IACP,UAAU;IACV,UAAU;CACX,CAAC"}
> 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208
/* Public Sans, the coinos.io face (OFL, one variable-weight file each way).
   font-display swap: the system stack paints first and the face lands when
   the file has; offline before the first fetch, the fallback stays. */
@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/PublicSans.woff2") format("woff2");
}
@font-face {
  font-family: "Public Sans";
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
  src: url("/PublicSans-Italic.woff2") format("woff2");
}
/* Bitcoin Wallet — hand-rolled CSS. Clean light "paper wallet" look. */

:root {
  --bg: #eef0f3;
  --paper: #ffffff;
  --ink: #15171a;
  --muted: #6b7280;
  --faint: #9aa1ac;
  --line: #e6e8ec;
  --line-strong: #d4d8de;
  --accent: #15171a; /* coinos black */
  --accent-hover: #2c3037; /* accent, slightly lifted */
  --accent-fg: #ffffff; /* text on accent */
  --accent-ink: #000000;
  --accent-soft: #ececef;
  --green: #14935a;
  --green-soft: #e7f6ee;
  --green-line: #c4e7d4;
  --red: #c43b3b;
  --red-soft: #fbeaea;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 10px 30px rgba(16, 24, 40, 0.07);
  --surface2: #f7f8fa; /* subtle raised surface (hover, mnemonic cells) */
  --toast-bg: #15171a; /* dark toast on light page */
  --toast-fg: #ffffff;
  --tab-bg: #ffffff; /* tab strip: paper, like the flat cards beside it */
  --tab-border: var(--line);
  --tab-active: #ececef; /* the chosen tab: a quiet grey pill, no shadow */
  --info: #3949ab;
  --info-soft: #eef2ff;
  --info-line: #d4ddfb;
  --radius: 16px;
  --wash: rgba(247, 147, 26, 0.05); /* warm glow behind the page top */
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  --sans: "Public Sans", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Dark theme — applied via data-theme="dark" on <html>. */
:root[data-theme="dark"] {
  --bg: #0f1216;
  --paper: #181b21;
  --ink: #e7e9ee;
  --muted: #9aa1ac;
  --faint: #6f7782;
  --line: #2a2f37;
  --line-strong: #3a414b;
  --accent: #ffffff; /* coinos white on dark */
  --accent-hover: #d9dce1; /* accent, slightly dimmed */
  --accent-fg: #0f1216; /* text on accent */
  --accent-ink: #e7e9ee;
  --accent-soft: #262b33;
  --green: #2dbd80;
  --green-soft: #14271d;
  --green-line: #235340;
  --red: #e66a6a;
  --red-soft: #2c1a1a;
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 30px rgba(0, 0, 0, 0.5);
  --surface2: #20242b;
  --toast-bg: #2c323b; /* raised toast that reads against the dark page */
  --toast-fg: #f3f5f8;
  --tab-bg: #20242b;
  --tab-border: transparent;
  --tab-active: var(--paper);
  --wash: rgba(247, 147, 26, 0.07);
  --info: #9db4ff;
  --info-soft: #1a2030;
  --info-line: #2c3a5e;
}

* { box-sizing: border-box; }

html {
  /* Reserve the scrollbar's gutter always, so it doesn't appear the instant
     the history list fills the page and shrink the viewport — that width
     change re-centered the balance carousel's snapped card, the "slight move
     one last time" after a switch. Stable gutter = no reflow when content
     grows. */
  scrollbar-gutter: stable;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  font-size: 16px;
  line-height: 1.45;
}

#app {
  max-width: 560px;
  margin: 0 auto;
  padding: 24px 16px 24px;
  /* A column the height of the window, so the footer sits at the bottom of
     short screens instead of floating under the content. svh, not dvh: dvh
     can resolve to the tall viewport (URL bar hidden) at first paint, which
     puts the footer just below the fold until you scroll — the phone browser
     showing its address bar is the case that has to fit. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

/* ---- typography ---- */
h1, h2, h3 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
.muted { color: var(--muted); }
.faint { color: var(--faint); }
.small { font-size: 13px; }
/* The reading size for a post or a message — the copy you actually read,
   as opposed to a caption or a row subtitle, which is what .small is for.
   Post bodies wore .small (13px) and a feed of them read like footnotes on
   a phone. 16px is Amethyst's own default for a note body, and at the
   inherited 1.45 line-height the line pitch lands within a pixel of what
   Amethyst renders. Measured on a Pixel 9, side by side. */
.note-text { font-size: 16px; }
.mono { font-family: var(--mono); }
.center { text-align: center; }
.break { word-break: break-all; }
a { color: var(--accent-ink); text-decoration: none; }
a:hover { text-decoration: underline; }
summary { cursor: pointer; }

/* breathing room when an input is scrolled to the top for the keyboard */
input { scroll-margin-top: 12px; }
/* room below the recipient candidates so the field can scroll to the top
   even when the page would otherwise be too short (the margin sits under
   the keyboard, never seen) */
@media (pointer: coarse) { .send-suggest { margin-bottom: 45vh; } }

/* ---- layout helpers ---- */
.row { display: flex; align-items: center; gap: 10px; }
.between { justify-content: space-between; }
.wrap { flex-wrap: wrap; }
.col { display: flex; flex-direction: column; gap: 10px; }
.grow { flex: 1; }
.gap6 { gap: 6px; }
.mt8 { margin-top: 8px; } .mt16 { margin-top: 16px; } .mt24 { margin-top: 24px; }
.hidden { display: none !important; }

/* ---- brand / header ---- */
.brand { display: flex; align-items: center; gap: 10px; }
.logo-full { height: 40px; flex: none; color: var(--ink); display: grid; place-items: center; }
.logo-full svg { height: 40px; width: auto; display: block; }
.brand h1 { font-size: 19px; }

.badge {
  font-size: 12px; font-weight: 600; padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--line-strong); color: var(--muted); white-space: nowrap;
}
.badge.dot::before {
  content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--green); margin-right: 6px; vertical-align: middle;
}
.badge.off.dot::before { background: var(--faint); }
.badge.live { color: var(--green); border-color: var(--green-line); background: var(--green-soft); }
.badge.live.dot::before { animation: pulse 1.5s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
.badge.testnet { color: var(--accent-ink); border-color: var(--accent); background: var(--accent-soft); }

/* ---- card ---- */
.card {
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 20px;
}
.card + .card { margin-top: 16px; }

/* ---- balance card ---- */
/* Account identity at a glance: a symbol beside the label and a colored
   hairline across the card's crown — spending wears lightning + the ark
   mark (violet), savings the bitcoin disc (orange). Inset shadow stacks
   with the card's own and clips to the radius; no layout shift. */
.face-ico { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; line-height: 1; flex-shrink: 0; }
.face-ico.save { color: #f7931a; }
.face-ico.spend { color: #7c3aed; }
/* No drop shadow on the balance cards: it smeared as they slid and sat oddly
   against the Receive/Send bar beneath. The colour stripe stays. */
.balance, .history-card { box-shadow: none; }
.balance.face-spending { box-shadow: inset 0 3px 0 rgba(124, 58, 237, .55); }
.balance.face-savings { box-shadow: inset 0 3px 0 rgba(247, 147, 26, .6); }
.balance .amt { font-size: 34px; font-weight: 700; letter-spacing: -0.02em; font-family: var(--mono); }
.balance .amt .unit { font-size: 18px; color: var(--muted); margin-left: 6px; font-weight: 600; }
.balance .usd { color: var(--muted); margin-top: 2px; }
.balance .split { margin-top: 14px; display: flex; gap: 22px; }
.balance .split .k { font-size: 12px; color: var(--faint); text-transform: uppercase; letter-spacing: .04em; }
.balance .split .v { font-family: var(--mono); font-size: 14px; }
.balance .split .v.pending { color: var(--accent-ink); }

/* ---- tabs ---- */
.page-pick { width: auto; padding: 6px 28px 6px 10px; font-size: 13px; border-radius: 9px; background-color: var(--paper); }
.tabs { display: flex; gap: 4px; background: var(--tab-bg); border: 1px solid var(--tab-border); border-radius: 12px; padding: 4px; margin: 16px 0; }
.tabs button {
  flex: 1; border: 0; background: transparent; padding: 9px 4px; border-radius: 9px;
  font: inherit; font-weight: 600; font-size: 14px; color: var(--muted); cursor: pointer;
}
.tabs button.active { background: var(--tab-active); color: var(--ink); }

/* ---- buttons ---- */
button, .btn {
  font: inherit; font-weight: 600; cursor: pointer; border-radius: 11px;
  border: 1px solid var(--line-strong); background: var(--paper); color: var(--ink);
  padding: 11px 16px; transition: filter .12s, background .12s, border-color .12s;
  /* no double-tap-zoom arbitration: taps commit immediately on mobile */
  touch-action: manipulation;
}
button:hover { background: var(--surface2); }
button:active { transform: translateY(1px); }
button:disabled { opacity: .5; cursor: not-allowed; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-ghost { background: transparent; }
.btn-sm { padding: 7px 11px; font-size: 13px; border-radius: 9px; }
.btn-block { width: 100%; display: block; }
.btn-danger { color: var(--red); border-color: var(--red); background: var(--red-soft); }
.iconbtn { padding: 9px 11px; line-height: 1; }

/* ---- inputs ---- */
label.field { display: block; }
.field .lab { font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 6px; display: block; }
input[type=text], input[type=password], input[type=number], textarea, select {
  width: 100%; font: inherit; color: var(--ink); background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: 11px; padding: 11px 13px;
}
input:focus, textarea:focus, select:focus { outline: 2px solid var(--accent); outline-offset: -1px; border-color: var(--accent); }
/* Custom caret so it isn't crammed against the right border (native is fixed). */
select {
  -webkit-appearance: none; appearance: none; padding-right: 38px;
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%236b7280" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6 6-6"/></svg>');
  background-repeat: no-repeat; background-position: right 14px center;
}
textarea { resize: vertical; min-height: 84px; font-family: var(--mono); font-size: 15px; }
.mono-input { font-family: var(--mono); }
.input-group { display: flex; gap: 8px; }
.input-group input { flex: 1; }

/* unit toggle */
.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 10px; overflow: hidden; }
.seg button { border: 0; border-radius: 0; padding: 9px 14px; background: var(--paper); }
.seg button.active { background: var(--accent-soft); color: var(--accent-ink); }

/* ---- address / qr ---- */
.qr { width: 220px; height: 220px; display: block; border-radius: 12px; border: 1px solid var(--line); }
.addr-box {
  font-family: var(--mono); font-size: 15px; color: var(--ink); background: var(--surface2); border: 1px dashed var(--line-strong);
  border-radius: 11px; padding: 12px 14px; word-break: break-all; text-align: center;
}
/* quiet edit/copy affordances living inside the address box */
.addr-editable { position: relative; padding-right: 62px; }
.addr-actions {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center;
}
.addr-actions button {
  background: none; border: none; box-shadow: none; padding: 5px; line-height: 0;
  color: var(--muted); cursor: pointer;
}
.addr-actions button:hover { color: var(--ink); background: none; }
.addr-actions button:active { transform: none; }
.path { font-family: var(--mono); font-size: 12px; color: var(--faint); }
.addr-check { font-family: var(--mono); font-size: 12px; line-height: 1.5; color: var(--muted); word-break: break-all; padding: 0 2px; }
.addr-check .hl { color: var(--ink); font-weight: 700; }

/* ---- mnemonic grid ---- */
/* Three columns read ACROSS — 1 2 3 on the first line, 4 5 6 on the next —
   the way a printed sheet is read. Two columns (a phone) run DOWN instead,
   the way a hand copies a list: the first column is 1–6, not 1,3,5,…; that
   row count comes from the word count (--rows-2, set inline). */
.words { display: grid; grid-auto-flow: row; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.words .w {
  display: flex; align-items: baseline; gap: 7px; background: var(--surface2);
  border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; font-family: var(--mono);
}
.words .w .n { color: var(--faint); font-size: 12px; width: 18px; text-align: right; }
.words .w .t { font-weight: 600; }
.warn-box {
  background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent-ink);
  border-radius: 11px; padding: 12px 14px; font-size: 14px;
}

/* ---- lists (tx / utxo) ---- */
.list { display: flex; flex-direction: column; }
.item { display: flex; align-items: center; gap: 12px; padding: 13px 2px; border-top: 1px solid var(--line); }
.item:first-child { border-top: 0; }
.ico { width: 34px; height: 34px; border-radius: 9px; flex: none; display: grid; place-items: center; font-size: 16px; }
.ico.in { background: var(--green-soft); color: var(--green); }
.ico.out { background: var(--red-soft); color: var(--red); }
.amount-pos { color: var(--green); font-family: var(--mono); font-weight: 600; }
.amount-neg { color: var(--red); font-family: var(--mono); font-weight: 600; }
.amount { font-family: var(--mono); font-weight: 600; }
.tag { font-size: 11px; font-weight: 700; padding: 2px 7px; border-radius: 6px; }
.tag.pending { background: var(--accent-soft); color: var(--accent-ink); }
.tag.conf { background: var(--green-soft); color: var(--green); }

/* coin control rows */
.coin { display: flex; align-items: center; gap: 11px; padding: 11px 2px; border-top: 1px solid var(--line); }
.coin:first-child { border-top: 0; }
.coin input[type=checkbox] { width: 18px; height: 18px; accent-color: var(--accent); }
/* the Spending coins table: labels once in the header, numbers right-
   aligned in tabular figures; on a narrow screen the WRAPPER scrolls
   sideways so the page itself never does */
.coin-scroll { overflow-x: auto; margin: 4px -2px 0; -webkit-overflow-scrolling: touch; }
.coin-table { border-collapse: collapse; width: 100%; font-size: 13.5px; white-space: nowrap; }
.coin-table th {
  text-align: left; font-weight: 600; font-size: 12px; color: var(--muted);
  padding: 4px 5px 8px; border-bottom: 1px solid var(--line-strong);
}
.coin-table td { padding: 10px 5px; border-top: 1px solid var(--line); vertical-align: middle; }
.coin-table tbody tr:first-child td { border-top: 0; }
.coin-table th:first-child, .coin-table td:first-child { width: 22px; padding-left: 2px; padding-right: 2px; }
.coin-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.coin-table td.warn { color: var(--accent-ink); font-weight: 600; }
.coin-table tr.clickable { cursor: pointer; }
.coin-table input[type=checkbox] { width: 18px; height: 18px; accent-color: var(--accent); margin: 0; display: block; }

/* ---- review / summary ---- */
.summary { background: var(--surface2); border: 1px solid var(--line); border-radius: 12px; padding: 14px; }
.summary .line { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; font-size: 14px; align-items: baseline; }
.summary .line .k { color: var(--muted); flex: none; }
.summary .line .v { font-family: var(--mono); text-align: right; min-width: 0; word-break: break-word; }

/* ---- misc ---- */
.notice { border-radius: 11px; padding: 11px 13px; font-size: 14px; }
.notice.err { background: var(--red-soft); color: var(--red); border: 1px solid #f1c9c9; }
.notice.ok { background: var(--green-soft); color: var(--green); border: 1px solid var(--green-line); }
.notice.info { background: var(--info-soft); color: var(--info); border: 1px solid var(--info-line); }
.spinner {
  width: 18px; height: 18px; border: 2px solid var(--line-strong); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .7s linear infinite; display: inline-block; vertical-align: middle;
}
.spinner.sm { width: 13px; height: 13px; border-width: 2px; }

/* ---- qr scanner overlay ---- */
.scan-overlay { position: fixed; inset: 0; z-index: 60; background: #000; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.scan-overlay video { width: 100%; height: 100%; object-fit: cover; }
.scan-frame { position: absolute; width: min(70vw, 260px); height: min(70vw, 260px); border: 3px solid #fff; border-radius: 18px; box-shadow: 0 0 0 9999px rgba(0,0,0,0.45); }
.scan-hint { position: absolute; bottom: 70px; left: 0; right: 0; text-align: center; color: #fff; font-size: 14px; padding: 0 24px; text-shadow: 0 1px 3px rgba(0,0,0,0.7); }
.scan-close {
  position: absolute; top: 16px; right: 16px; width: 42px; height: 42px; border-radius: 50%;
  background: rgba(0,0,0,0.5); color: #fff; border: 1px solid rgba(255,255,255,0.4);
  font-size: 18px; line-height: 1; cursor: pointer;
  /* the base button rule's 11px/16px padding shoved the glyph off-center
     inside this fixed circle — center it properly and drop the padding */
  display: flex; align-items: center; justify-content: center; padding: 0;
}
@keyframes spin { to { transform: rotate(360deg); } }
.toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  background: var(--toast-bg); color: var(--toast-fg); padding: 10px 16px; border-radius: 10px;
  font-size: 14px; box-shadow: var(--shadow); z-index: 50; opacity: 0; pointer-events: none;
  transition: opacity .2s, transform .2s;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.words .w.masked .t { color: var(--faint); letter-spacing: 2px; }

.check-badge {
  width: 64px; height: 64px; border-radius: 50%; flex: none;
  background: var(--green-soft); color: var(--green);
  display: grid; place-items: center; font-size: 34px; font-weight: 700;
}
.footer { margin-top: auto; padding-top: 28px; padding-bottom: 8px; }
.footer a { color: var(--accent-ink); font-weight: 600; }

/* Clickable unit labels (BTC/sats) — look like text, toggle the global unit. */
.unit-tag {
  background: none; border: 0; padding: 0; margin: 0; font: inherit; cursor: pointer;
  color: var(--muted); font-weight: 600;
  border-radius: 0;
  border-bottom: 1px dotted var(--line-strong);
}
.unit-tag:hover { background: none; color: var(--accent-ink); border-bottom-color: var(--accent); }
.unit-tag:active { transform: none; }
.divider { height: 1px; background: var(--line); margin: 16px 0; border: 0; }
hr.divider { margin: 16px 0; }
.linklike { background: none; border: 0; padding: 0; color: var(--accent-ink); font-weight: 600; cursor: pointer; }
.linklike:hover { text-decoration: underline; background: none; }

/* ---- how it works ---- */

@media (max-width: 420px) {
  .words { grid-auto-flow: column; grid-template-columns: none; grid-auto-columns: 1fr; grid-template-rows: repeat(var(--rows-2, 6), auto); }
  .balance .amt { font-size: 28px; }
}

/* Icon dropdown (receive mode) — a <select> can't render SVG in options. */
.icon-select-btn {
  width: 100%; display: flex; align-items: center; gap: 9px;
  font: inherit; color: var(--ink); background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: 11px; padding: 11px 13px; cursor: pointer;
}
.icon-select-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }
.icon-select-menu {
  background: var(--paper); border: 1px solid var(--line-strong); border-radius: 11px;
  padding: 5px; box-shadow: 0 10px 28px rgba(0,0,0,.18); display: flex; flex-direction: column; gap: 2px;
}
.icon-select-item {
  display: flex; align-items: center; gap: 9px; width: 100%;
  font: inherit; color: var(--ink); background: transparent;
  border: 0; border-radius: 8px; padding: 10px 10px; cursor: pointer; text-align: left;
}
.icon-select-item:hover { background: var(--hover, rgba(128,128,128,.12)); }
.icon-select-item.active { background: var(--accent-soft, rgba(247,147,26,.14)); }
.sel-ico { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; flex-shrink: 0; font-size: 16px; line-height: 1; }
.sel-ico svg { display: block; }

/* Messages — Concord community chat. */
/* An open conversation fills the window. Its header and composer stay put;
   only the message log scrolls. Let flex layout account for the brand header
   and padding instead of subtracting a fixed estimate from the viewport. */
html:has(#app .chat-card), body:has(#app .chat-card) { overflow: hidden; }
#app:has(.chat-card) {
  height: 100vh;
  height: var(--chat-viewport-height, 100dvh);
  min-height: 0;
}
#app:has(.chat-card) > .footer { display: none; }
#app > .col:has(> .chat-card) { flex: 1; min-height: 0; }
#app > .col:has(> .chat-card) > :not(.chat-card) { flex-shrink: 0; }
.chat-card {
  padding: 0; overflow: hidden; display: flex; flex-direction: column;
  flex: 1; min-height: 0;
}
.chat-card > :not(.chat-log) { flex-shrink: 0; }
.chat-card > :is(.chat-invite, .chat-members) { flex-shrink: 1; min-height: 0; }
.chat-head { padding: 14px 16px 10px; border-bottom: 1px solid var(--line); flex: 0 0 auto; }
.chat-title { font-weight: 650; }
.chat-log {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior-y: contain; padding: 12px 14px;
  display: flex; flex-direction: column; gap: 10px; background: var(--surface2);
}
.chat-row { display: flex; gap: 9px; align-items: flex-start; }
.chat-row.grouped { margin-top: -6px; }
.chat-avatar {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0; object-fit: cover;
  background: var(--accent-soft);
}
.ava-img { background-size: cover; background-position: center; }
.chat-avatar.fallback {
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 650; color: var(--muted); text-transform: uppercase;
}
.chat-avatar.spacer { background: transparent; height: 0; }
.chat-body { min-width: 0; flex: 1; }
.chat-meta { display: flex; align-items: baseline; gap: 7px; margin-bottom: 2px; }
.chat-name { font-size: 15px; font-weight: 650; }
.chat-badge {
  font-size: 10px; font-weight: 650; color: var(--accent-fg); background: var(--accent);
  border-radius: 5px; padding: 1px 5px; margin-left: 5px; vertical-align: 1px;
}
.chat-time { font-size: 12px; color: var(--faint); }
.chat-bubble {
  position: relative; display: inline-block; max-width: 100%;
  background: var(--paper); border: 1px solid var(--line); border-radius: 12px;
  padding: 7px 11px; font-size: 16px; line-height: 1.45;
  overflow-wrap: break-word; white-space: pre-wrap;
}
/* the messages surfaces run edge to edge on phones, like the notes feed —
   the card walls step outside #app's padding and the borders/radius go */
@media (max-width: 520px) {
  .chat-page {
    margin-left: -16px; margin-right: -16px;
    border-left: none; border-right: none; border-radius: 0;
  }
}
.chat-edited { font-size: 11px; color: var(--faint); }
.chat-del {
  position: absolute; top: -7px; right: -7px; width: 18px; height: 18px;
  display: none; align-items: center; justify-content: center;
  border: 1px solid var(--line-strong); border-radius: 50%;
  background: var(--paper); color: var(--muted); font-size: 12px; line-height: 1; cursor: pointer;
  /* the global button padding (11px 16px) floors the border-box past 18px
     wide and turns the circle into an oval — a fixed-size round button
     must zero it */
  padding: 0;
}
.chat-row:hover .chat-del { display: flex; }
/* Telegram-style message action sheet: quick reactions + actions, opened by
   a tap on any bubble (rides the confirm-pop backdrop). */
.msg-sheet { width: 100%; max-width: 300px; gap: 2px; }
.msg-sheet-emojis { display: flex; gap: 4px; margin-bottom: 8px; }
.msg-sheet-emojis button {
  flex: 1; font-size: 19px; line-height: 1; padding: 8px 0;
  border: 1px solid var(--line); border-radius: 10px; background: var(--paper);
}
.msg-sheet-emojis button.on { border-color: var(--accent); }
.msg-sheet-item {
  display: flex; gap: 12px; align-items: center; text-align: left;
  border: none; background: none; padding: 11px 8px; font-weight: 550; border-radius: 10px;
}
.msg-sheet-item:hover { background: var(--surface2); }
.msg-sheet-ico { width: 20px; text-align: center; color: var(--muted); flex-shrink: 0; }
/* the quoted message a reply carries, and the composer's replying-to bar */
.chat-quote {
  display: flex; flex-direction: column; gap: 0; margin-bottom: 4px;
  border-left: 3px solid var(--accent); background: var(--surface2);
  border-radius: 6px; padding: 3px 8px; font-size: 12px; max-width: 100%;
}
.chat-quote-name { font-weight: 650; }
.chat-quote-text {
  color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.reply-bar {
  display: flex; align-items: center; gap: 8px; padding: 7px 12px;
  border-top: 1px solid var(--line); background: var(--surface2); flex: 0 0 auto;
}
/* Reactions sit INSIDE the bubble, tucked under the text (Telegram-style).
   The pill tint/border derive from the bubble's own text colour, so they read
   correctly on both the plain bubble and the accent "me" bubble. */
.chat-reacts { display: flex; gap: 5px; margin-top: 5px; flex-wrap: wrap; }
.chat-react {
  font-size: 12px; border-radius: 10px; padding: 1px 7px;
  background: color-mix(in srgb, currentColor 13%, transparent);
  border: 1px solid transparent;
}
.chat-react.clickable { cursor: pointer; }
.chat-react.on { border-color: color-mix(in srgb, currentColor 55%, transparent); font-weight: 600; }
/* zap tally: a little bolt + sats, amber on both themes; .on = we zapped it */
.zap-tally { display: inline-flex; align-items: center; gap: 2px; font-size: 11px; font-weight: 650; color: #e6a800; white-space: nowrap; flex-shrink: 0; }
.zap-tally.chat-react { font-size: 12px; padding: 1px 7px 1px 5px; }
.zap-tally.clickable { cursor: pointer; }
/* A tap-local lightning strike, followed by a floating amount and embers. */
.zap-fx { position: fixed; inset: 0; z-index: 10000; pointer-events: none; overflow: hidden; contain: strict; }
.zap-fx-impact { position: absolute; left: var(--zap-x); top: var(--zap-y); width: 0; height: 0; color: #ffc83d; }
.zap-fx-bolt { position: absolute; width: 82px; height: 180px; left: -41px; bottom: 0; fill: #fff5c2; filter: drop-shadow(0 0 5px #fff3a1) drop-shadow(0 0 13px #ff9d00); transform-origin: 50% 100%; animation: zap-strike 430ms ease-out both; }
.zap-fx-flash, .zap-fx-ring { position: absolute; left: -28px; top: -28px; width: 56px; height: 56px; border-radius: 50%; animation: zap-impact 550ms 60ms ease-out both; }
.zap-fx-flash { background: radial-gradient(circle, #fffce0, #ffca45 25%, #ff980066 50%, transparent 70%); }
.zap-fx-ring { border: 2px solid #ffd76a; box-shadow: 0 0 12px #ffad32, inset 0 0 10px #ffad3266; animation-name: zap-ring; }
.zap-fx-amount { position: absolute; left: var(--zap-label-x); top: -60px; display: flex; align-items: baseline; gap: 6px; white-space: nowrap; color: #fff2ad; text-shadow: 0 2px 2px #713500, 0 0 9px #ff9c00, 0 0 22px #ff9500aa; animation: zap-amount 1450ms cubic-bezier(.16, 1, .3, 1) both; }
.zap-fx-amount strong { font-size: clamp(34px, 9vw, 48px); font-weight: 900; line-height: 1; letter-spacing: -.045em; }
.zap-fx-amount span { font-size: 13px; font-weight: 800; letter-spacing: .04em; }
.zap-fx-spark { position: absolute; width: 10px; height: 3px; left: -5px; top: -2px; border-radius: 3px; background: #ffe997; box-shadow: 0 0 6px #ffaf20; animation: zap-spark 720ms var(--delay) ease-out both; }
.zap-fx-spark.ember { left: calc(var(--zap-label-x) - 2px); top: -80px; width: 3px; height: 3px; background: #fff4c2; animation-duration: 750ms; }
@keyframes zap-strike {
  0% { opacity: 0; transform: scaleY(.15); }
  16%, 38% { opacity: 1; transform: scaleY(1); }
  65% { opacity: .8; }
  100% { opacity: 0; transform: scaleY(1.04); }
}
@keyframes zap-impact { 0% { opacity: 1; transform: scale(.2); } 100% { opacity: 0; transform: scale(2.4); } }
@keyframes zap-ring { 0% { opacity: .9; transform: scale(.25); } 100% { opacity: 0; transform: scale(2); } }
@keyframes zap-amount {
  0% { opacity: 0; transform: translate(-50%, 35px) scale(.35) rotate(-9deg); }
  16% { opacity: 1; transform: translate(-50%, -8px) scale(1.2) rotate(-5deg); }
  30% { opacity: 1; transform: translate(-50%, -4px) scale(1) rotate(-3deg); }
  58% { opacity: 1; filter: blur(0); }
  100% { opacity: 0; transform: translate(-50%, -65px) scale(1.08) rotate(2deg); filter: blur(3px); }
}
@keyframes zap-spark {
  0% { opacity: 0; transform: translate(0, 0) rotate(var(--angle)) scale(.4); }
  12% { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--angle)) scale(.1); }
}
@media (prefers-reduced-motion: reduce) {
  .zap-fx-bolt, .zap-fx-flash, .zap-fx-ring, .zap-fx-spark { display: none; }
  .zap-fx-amount { animation: zap-amount-reduced 1200ms ease-out both; }
  @keyframes zap-amount-reduced { 0%, 65% { opacity: 1; transform: translateX(-50%); } 100% { opacity: 0; transform: translateX(-50%); } }
}
/* A zap in flight: the chip lands the moment you tap and breathes until the
   payment settles — the bolt carries the pulse, the number stays readable. */
.zap-tally.flying { animation: zap-breathe 1.15s ease-in-out infinite; }
.zap-tally.flying svg { animation: zap-flicker 1.15s ease-in-out infinite; transform-origin: center; }
@keyframes zap-breathe { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
@keyframes zap-flicker { 0%, 100% { transform: scale(.82); } 50% { transform: scale(1.12); } }
@media (prefers-reduced-motion: reduce) {
  .zap-tally.flying, .zap-tally.flying svg { animation: none; opacity: .7; }
}
.chat-compose {
  display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--line);
  align-items: center; flex: 0 0 auto;
}
.chat-compose input, .chat-compose textarea {
  flex: 1; min-width: 0; font: inherit; color: var(--ink); background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: 11px; padding: 10px 13px;
}
.chat-compose textarea { resize: none; max-height: 120px; overflow-y: auto; line-height: 1.4; }
.chat-compose input:focus-visible, .chat-compose textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: -1px; }

/* Messages v2 — home lists, DM threads, invite panel. */
.chat-back { font-size: 22px; line-height: 1; padding: 2px 10px 4px; flex-shrink: 0; }
.chat-thread-row { display: flex; gap: 10px; align-items: center; cursor: pointer; }
/* list rows read at message size: names 16, previews/member counts 15 —
   the generic .small (13px) made the inbox feel like fine print */
.chat-thread-row .chat-name { font-size: 16px; }
.chat-thread-row .small { font-size: 15px; }
.chat-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thread-when { display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; }
.thread-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--red); flex-shrink: 0; }
.chat-thread-row > .thread-dot { margin-right: 8px; }
.chat-thread-row.unread .chat-name { font-weight: 700; }
.chat-thread-row.unread .chat-preview { color: var(--ink); }
.chat-invite { padding: 10px 12px; border-bottom: 1px solid var(--line); background: var(--surface2); flex: 0 0 auto; max-height: 40vh; overflow-y: auto; }
.chat-invite input { font: inherit; color: var(--ink); background: var(--paper); border: 1px solid var(--line-strong); border-radius: 9px; padding: 7px 10px; min-width: 0; }
.chat-row.dm { justify-content: flex-start; }
.chat-row.dm.mine { justify-content: flex-end; }
.chat-row.dm .chat-body { max-width: 82%; }
.chat-row.dm.mine .chat-body { text-align: right; }
.chat-bubble.me { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); text-align: left; }
/* a link inside the accent-colored own bubble: the page's link color is
   calibrated against the PAGE background, not the bubble's (in dark mode a
   near-white link sat on the white bubble) — take the bubble's own
   foreground and let the underline say "link" */
.chat-bubble.me a { color: inherit; text-decoration: underline; }
.chat-row.dm .chat-time { font-size: 10px; color: var(--faint); margin-top: 2px; }

/* Profile note feed: inline images stay contained, links wrap. */
.note-img { display: block; max-width: 100%; max-height: 320px; border-radius: 10px; margin-top: 6px; object-fit: contain; }
.note-img.clickable { cursor: zoom-in; }
/* The row under a post: reply, boost, quote, react, zap — spread across the
   post's full width so each is a comfortable target, quiet until one carries
   a count or you've used it. */
.note-acts { margin-top: 8px; gap: 4px; width: min(100%, 340px); }
.note-acts > .note-act { flex: 1 1 0; min-width: 0; justify-content: center; padding-inline: 2px; }
/* server comparison table (Settings → Advanced → Ark server) */
.cmp-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.cmp-table th { text-align: left; font-weight: 600; padding: 4px 6px; border-bottom: 1px solid var(--line); }
.cmp-table td { padding: 5px 6px; border-bottom: 1px solid var(--line); vertical-align: top; }
.cmp-table td.k { color: var(--muted); width: 38%; }
.cmp-table tr:last-child td { border-bottom: 0; }
/* point of sale */
.input-group input.pos-amount, input.pos-amount { font-size: 40px; font-weight: 700; text-align: right; letter-spacing: -.5px; padding: 8px 12px; height: auto; line-height: 1.2; }
.pos-big { font-size: 34px; font-weight: 700; line-height: 1.1; letter-spacing: -.5px; }
.pos-tips { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; width: 100%; }
.pos-tip { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 12px 6px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface2); color: var(--ink); cursor: pointer; }
.pos-tip.on { border-color: var(--accent); background: var(--accent); color: var(--accent-fg); }
.pos-tip.on .muted { color: inherit; opacity: .8; }
.pos-tip-label { font-size: 20px; font-weight: 600; }
.pos-total { font-size: 22px; font-weight: 600; margin-top: 4px; }
.pos-qr svg { width: min(260px, 70vw); height: auto; display: block; }
.pos-check { width: 64px; height: 64px; border-radius: 50%; background: var(--green, #2e7d32); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 34px; }
.pos-sale.faint { opacity: .55; }
.pos-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; width: 100%; }
.pos-key { padding: 14px 0; font-size: 22px; font-weight: 600; border: 1px solid var(--line); border-radius: 12px; background: var(--surface2); color: var(--ink); cursor: pointer; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.pos-key:active { background: var(--accent); color: var(--accent-fg); }
.pos-key.del { font-weight: 400; }
/* the feeds row: pills that scroll sideways, the one on screen filled */
.feed-chips { gap: 6px; flex-wrap: nowrap; overflow-x: auto; margin: 0 -14px; padding: 2px 14px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.feed-chips::-webkit-scrollbar { display: none; }
.feed-chip { flex: 0 0 auto; border: 1px solid var(--line); background: var(--surface2); color: var(--muted); border-radius: 999px; padding: 5px 12px; font-size: 14px; line-height: 1.2; cursor: pointer; white-space: nowrap; max-width: 180px; overflow: hidden; text-overflow: ellipsis; }
.feed-chip.on { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.feed-chip.add { font-weight: 600; padding: 5px 11px; }
.note-act {
  background: none; border: 0; padding: 4px 6px; margin: 0; line-height: 1;
  color: var(--faint); display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  border-radius: 8px;
}
.note-act:hover { background: var(--surface2); color: var(--ink); }
.note-act.on { color: #e0245e; }
.note-act.on[aria-label="Boost"] { color: var(--green); }
/* the zap button carries the sats tally: amber once anyone has zapped, the
   number in amber too; breathing while our own zap is in the air */
.note-act.note-zap.zapped, .note-act.note-zap.zapped .note-act-n { color: #e6a800; }
/* holdable: no text selection or callout while the finger rests on it */
.note-act.note-zap, .zap-tally.clickable { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.note-act.note-zap[aria-disabled] { cursor: default; }
.note-act.note-zap[aria-disabled]:hover { background: none; color: var(--faint); }
.note-act.note-zap.zapped[aria-disabled]:hover { color: #e6a800; }
.note-act.note-zap.flying { animation: zap-breathe 1.15s ease-in-out infinite; }
.note-act.note-zap.flying svg { animation: zap-flicker 1.15s ease-in-out infinite; transform-origin: center; }
@media (prefers-reduced-motion: reduce) {
  .note-act.note-zap.flying, .note-act.note-zap.flying svg { animation: none; opacity: .7; }
}
.note-act-emoji { font-size: 17px; line-height: 1; }
.note-act-n { font-size: 12px; font-weight: 600; color: var(--muted); }
.note-who-toggle.on { color: var(--muted); }
.note-who-toggle[aria-disabled] { opacity: .3; cursor: default; }
.note-who-toggle[aria-disabled]:hover { background: none; color: var(--faint); }
.note-who-chev { display: flex; transition: transform .2s; }
.note-who-chev.open { transform: rotate(180deg); }
/* who reacted, boosted and zapped: opened by the chevron on the bar */
.note-who { margin-top: 6px; padding: 8px 10px; gap: 8px; background: var(--surface2); border: 1px solid var(--line); border-radius: 10px; font-size: 13px; }
.note-who-line { gap: 10px; align-items: flex-start; min-width: 0; }
.note-who-lead { flex: 0 0 22px; display: flex; justify-content: center; color: var(--muted); padding-top: 3px; }
.note-who-people { gap: 6px 10px; align-items: center; min-width: 0; }
.note-who-person { display: inline-flex; align-items: center; gap: 5px; padding: 2px 6px 2px 2px; border: 0; background: transparent; color: var(--ink); font: inherit; border-radius: 999px; cursor: pointer; min-width: 0; }
.note-who-person:hover { background: var(--surface); }
.note-who-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 140px; }
.note-who-sats { color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.alert-row.fresh { background: var(--accent-soft, rgba(128,128,128,.08)); border-radius: 8px; padding-left: 8px !important; padding-right: 8px !important; margin: 0 -8px; }
.alert-excerpt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The attach button sits between Post and Cancel: a square the size of the
   buttons beside it, not the sliver a .btn-sm made of a 20px icon. */
.attach-btn {
  flex: 0 0 auto; width: 48px; align-self: stretch; min-height: 44px;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
/* beside a growing reply box the icons keep their size, sat on the baseline */
.thread-reply .reply-tools .attach-btn { height: 40px; }
/* the profile's action row: a label never breaks inside its button — when
   four don't fit across, the row wraps a whole button to the next line */
.prof-actions > button { white-space: nowrap; }
/* the field inside the panel: a quiet hairline, the panel is the frame */
.thread-reply .thread-reply-input { border: 1px solid var(--line); background: var(--surface, #fff); border-radius: 10px; }
.thread-reply .thread-reply-input:focus { border-color: var(--ink); outline: none; }
/* Cancel and Reply: one size, the icon buttons' height */
.thread-reply .reply-tools .thread-reply-cancel, .thread-reply .reply-tools .thread-reply-send {
  height: 40px; padding: 0 18px; border-radius: 10px; font-size: 15px; line-height: 38px; box-sizing: border-box;
}
/* A picture, full screen: the image centred on a dark ground, everything
   else out of the way. Fixed to the viewport (svh, so a phone's address bar
   can't push it), and above every other layer including the toast. */
.lightbox {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(8, 10, 14, .94); padding: 16px;
  animation: lightbox-in .12s ease-out;
}
/* Nothing scrolls behind a picture that fills the screen — and the reserved
   scrollbar gutter (see html, top of file) stands down with it, or it shows
   as a pale stripe beside an otherwise full-screen image. Fixed positioning
   is sized to the viewport WITHOUT that gutter, so this is the only way to
   reach the edge. No layout shift worth the name: it happens under the
   backdrop. */
html.no-scroll { overflow: hidden; scrollbar-gutter: auto; }
.lightbox img {
  max-width: 100%; max-height: 100svh; max-height: 100vh;
  object-fit: contain; border-radius: 6px; cursor: zoom-out;
}
.lightbox-x {
  position: absolute; top: max(10px, env(safe-area-inset-top)); right: 10px;
  width: 40px; height: 40px; padding: 0; font-size: 26px; line-height: 1;
  border-radius: 999px; border: 0; color: #fff; background: rgba(255, 255, 255, .14);
}
.lightbox-x:hover { background: rgba(255, 255, 255, .24); }
@keyframes lightbox-in { from { opacity: 0; } to { opacity: 1; } }
.note-video { display: block; max-width: 100%; max-height: 360px; border-radius: 10px; margin-top: 6px; background: #000; }
/* a video plays by itself, muted, while it is on screen; the corner button
   turns the sound on (see autoplay in messages.js) */
.note-video-box { position: relative; display: block; max-width: 100%; margin-top: 6px; }
.note-video-box .note-video { margin-top: 0; }
.vid-sound {
  position: absolute; top: 8px; left: 8px; z-index: 2; margin: 0; padding: 4px 10px; border: 0;
  border-radius: 999px; background: rgba(18, 18, 18, .7); color: #fff; font-size: 13px; line-height: 1.3;
  cursor: pointer; display: inline-flex; align-items: center; gap: 5px;
}
.vid-sound:hover { background: rgba(18, 18, 18, .9); }
/* A quoted note, inside the post that quotes it: its own little card, so the
   two voices don't run together. Context, not another post to act on — the
   action row stops at the outer post. */
.quote-card {
  margin-top: 8px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface2); display: flex; flex-direction: column; gap: 4px;
}
.quote-card.clickable { cursor: pointer; }
.quote-card.clickable:hover { border-color: var(--line-strong); }
.quote-loading { flex-direction: row; align-items: center; gap: 8px; }
/* Posts that landed while you were reading further down. Floats over the
   feed near the top, out of the way of the thumb, and takes you up to them
   when tapped. Fixed, not sticky: the feed is the page scroller, and a
   sticky element inside the card would scroll away with the card. */
.feed-new-pill {
  /* The header scrolls away with the page, so this rides at the very top of
     the window — floating over a post for the moment it is there beats
     sitting in the middle of one. */
  position: fixed; top: calc(env(safe-area-inset-top, 0px) + 10px);
  left: 50%; transform: translateX(-50%);
  z-index: 30; width: auto; padding: 7px 16px; border-radius: 999px;
  background: var(--accent); color: var(--accent-fg); border: none;
  font-size: 13px; font-weight: 650; cursor: pointer;
  box-shadow: 0 3px 12px rgba(0, 0, 0, .28);
  animation: pill-in .18s ease-out;
}
/* the count bumps each time it changes: a fresh node per value replays it */
.feed-new-pill .n { display: inline-block; min-width: 1.1em; text-align: center; animation: pill-bump .32s cubic-bezier(.2,.9,.3,1.4); }
@keyframes pill-bump {
  from { transform: scale(1.7); opacity: .4; }
  to { transform: scale(1); opacity: 1; }
}
@keyframes pill-in {
  from { opacity: 0; transform: translate(-50%, -8px); }
  to { opacity: 1; transform: translate(-50%, 0); }
}

/* What a draft will look like once it's out there. Framed and labelled, so
   it can hold real post content — pictures, players, quoted notes — without
   ever being mistaken for the post itself. */
.draft-preview {
  border: 1px dashed var(--line-strong); border-radius: 10px;
  padding: 10px 12px; background: var(--surface2);
  display: flex; flex-direction: column; gap: 6px;
}
.draft-preview-tag {
  font-size: 11px; font-weight: 650; letter-spacing: .04em;
  text-transform: uppercase; color: var(--faint);
}
.attach-btn.on { color: var(--accent-ink); border-color: var(--accent); }
.quote-card .note-text { font-size: 14px; }
.quote-name { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.quote-card .note-img { max-height: 220px; }
.quote-card .yt-embed { margin-top: 4px; }

/* A YouTube link, shown as the video it is: the still, a play button, and a
   corner link out. The player replaces all of it on the first tap — see
   youtubeEmbed for why it isn't there from the start. */
.yt-embed {
  position: relative; margin-top: 6px; border-radius: 10px; overflow: hidden;
  background: #000; aspect-ratio: 16 / 9; max-width: 100%;
}
.yt-embed img.yt-poster, .yt-embed iframe {
  display: block; width: 100%; height: 100%; border: 0;
  object-fit: cover; /* hqdefault is 4:3 with bars; cover trims them */
}
.yt-play {
  position: absolute; inset: 0; margin: 0; padding: 0; border: 0; border-radius: 0;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  background: rgba(0, 0, 0, .18); color: #fff;
}
.yt-play > span {
  width: 62px; height: 44px; border-radius: 11px; background: rgba(18, 18, 18, .78);
  display: flex; align-items: center; justify-content: center; padding-left: 3px;
  transition: background .12s;
}
.yt-play:hover { background: rgba(0, 0, 0, .28); }
.yt-play:hover > span { background: #e00; }
.yt-open {
  position: absolute; top: 6px; right: 8px; color: #fff; font-size: 15px; line-height: 1;
  padding: 4px 7px; border-radius: 8px; background: rgba(18, 18, 18, .6); text-decoration: none;
}
.yt-open:hover { background: rgba(18, 18, 18, .85); text-decoration: none; }
/* the profile notes feed: page-scrolled (no inner scrollbox); on phones it
   runs edge to edge — the card walls step outside #app's padding */
.notes-feed { padding: 2px 14px; }
/* rows scrolled well out of view are not in the DOM: a spacer stands in
   at their measured height, so the page keeps its length (see listWindow) */
.feed-spacer { display: block; flex: 0 0 auto; }
@media (max-width: 520px) {
  .notes-feed {
    margin-left: -16px; margin-right: -16px;
    border-left: none; border-right: none; border-radius: 0;
  }
}
/* Inside the chat page the posts ARE the page, not a card sitting in one:
   they step out through its padding to the container's own edge and drop the
   inner walls, leaving one continuous list with a hairline top and bottom. */
.chat-page .notes-feed {
  margin-left: -20px; margin-right: -20px;
  border-left: none; border-right: none; border-radius: 0;
  box-shadow: none;
}


/* Inline move-money panel on the balance card. */
.balance-move { gap: 10px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); text-align: left; }
/* balance carousel: the other face peeks at the card's edge and drags into
   place — native scroll-snap does the physics. Full-bleed inside the card
   so the peeking neighbor touches the card wall. */
/* A page-level strip of whole balance CARDS: full-bleed past the app column
   so the neighbor card peeks in from the screen edge. The negative margins +
   matching padding enlarge the clip box so card shadows aren't cropped. */
.bal-carousel {
  position: relative; /* offsetParent for the slides: offsetLeft math stays container-relative */
  display: flex; gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory;
  margin: -16px; padding: 16px; scrollbar-width: none; -ms-overflow-style: none;
  touch-action: pan-x pan-y;
}
.bal-carousel::-webkit-scrollbar { display: none; }
/* Isolate each card's layout + paint from the rest of the page: the history
   list building in below (and the balance number ticking) can't invalidate
   the carousel's layer, and the scroll stays on the compositor even while the
   main thread is busy with boot work. */
.bal-slide {
  flex: 0 0 calc(100% - 40px); scroll-snap-align: center; min-width: 0;
  /* layout containment (not paint — that would clip the card's own shadow):
     the history list building in below and the balance ticking can't force
     the slide to reflow, so the scroll stays on the compositor during boot */
  contain: layout;
}
.bal-carousel .card + .card { margin-top: 0; }
.bal-carousel { cursor: grab; } /* touch never shows a cursor — no media-query hedging */
.bal-carousel.grabbing, .bal-carousel.grabbing * { cursor: grabbing; user-select: none; }
.balance-move .seg { display: flex; width: 100%; }
.balance-move .seg button { flex: 1; }
.send-suggest { max-height: 260px; overflow-y: auto; }
.clickable { cursor: pointer; }
.profile-avatar { width: 64px; height: 64px; font-size: 18px; }
/* profile cover: full-bleed across the top of the profile card; the avatar
   row overlaps it so the face sits half on the cover, half on paper */
.profile-banner {
  height: 118px; margin: -20px -20px 0; background-size: cover; background-position: center;
  border-radius: var(--radius) var(--radius) 0 0;
}
/* The overlap must move the row's DIRECT child: when a hat is worn that's
   the .hat-wrap — pulling the avatar up inside the wrap left the hat
   behind, planted on the face. */
.profile-banner + .row > .profile-avatar,
.profile-banner + .row > .hat-wrap { margin-top: -26px; }
.profile-banner + .row .profile-avatar {
  border: 3px solid var(--paper); box-sizing: content-box;
}
.zap-chip { display: inline-flex; gap: 5px; align-items: center; cursor: pointer; background: var(--surface2); border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px 1px 2px; }
.chat-avatar.mini { width: 18px; height: 18px; font-size: 8px; flex-shrink: 0; }
/* A feed row is the one place a face is the point, not a label on a line —
   Amethyst gives it 55dp; at our 30px it read as a bullet beside the post.
   The thumbnail we cache is sized to cover this (see THUMB_PX), so a bigger
   circle costs no extra network. */
.chat-avatar.note-avatar { width: 44px; height: 44px; font-size: 15px; }
.chat-avatar.fallback { position: relative; overflow: hidden; }
.chat-avatar.fallback .punk { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.header-avatar { padding: 0; border: none; background: none; cursor: pointer; display: flex; }
.chat-typing { display: flex; align-items: center; gap: 7px; padding: 2px 4px 6px; min-height: 20px; }
.typing-dots { display: inline-flex; gap: 3px; align-items: center; }
.typing-dots i { width: 4px; height: 4px; border-radius: 50%; background: var(--muted); animation: typing-bounce 1.2s infinite ease-in-out; }
.typing-dots i:nth-child(2) { animation-delay: .15s; }
.typing-dots i:nth-child(3) { animation-delay: .3s; }
@keyframes typing-bounce { 0%, 60%, 100% { opacity: .3; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-3px); } }
.chat-members { flex: 0 0 auto; max-height: min(260px, 40vh); overflow-y: auto; border-bottom: 1px solid var(--line); }
.ava-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.ava-wrap.online::after {
  content: ''; position: absolute; right: -1px; bottom: -1px; width: 10px; height: 10px;
  border-radius: 50%; background: #3fbf5f; box-shadow: 0 0 0 2px var(--paper);
}
.small.is-online { color: #3fbf5f; }
.online-count { color: #3fbf5f; }
.iconbtn.bell.on { color: var(--ink); }
.header-msgs { position: relative; padding: 4px; border: none; background: none; cursor: pointer; display: flex; color: var(--muted); }
.header-msgs:hover { color: var(--ink); }
.header-msgs .hm-ico { display: flex; transition: transform .15s ease; }
.header-msgs:hover .hm-ico { transform: scale(1.08); }
.header-msgs.unread { color: var(--ink); }
.header-msgs.unread::after {
  content: ''; position: absolute; top: 2px; right: 2px; width: 9px; height: 9px;
  border-radius: 50%; background: var(--red);
  /* No entrance animation: render() rebuilds the whole DOM, so a pop here
     replayed on every repaint and read as the dot flashing. It either says
     someone is waiting or it doesn't. */
  box-shadow: 0 0 0 2px var(--bg);
}
.chat-avatar.header-ava { width: 30px; height: 30px; font-size: 11px; }
.npub-box { display: flex; align-items: center; gap: 8px; }
.copy-ico { flex-shrink: 0; background: none; border: none; padding: 2px; color: var(--muted); cursor: pointer; display: flex; }
.copy-ico:hover { color: var(--ink); }
.settings-nav { text-align: left; font-weight: 600; padding: 14px 16px; position: relative; }
.settings-nav::after { content: '›'; position: absolute; right: 16px; color: var(--faint); font-weight: 400; }
.avatar-menu-wrap { position: relative; display: inline-flex; }
.menu-backdrop { position: fixed; inset: 0; z-index: 40; }
.avatar-menu { position: absolute; right: 0; top: calc(100% + 8px); z-index: 41; min-width: 170px; }
.chat-avatar.fallback.loading { background: var(--surface2); }

/* hats: a supporter hat perched above any avatar. The wrapper takes the
   avatar's place in the layout; the hat overflows upward, clicks fall
   through it. */
.hat-wrap { position: relative; display: inline-flex; flex-shrink: 0; }
.ava-hat { position: absolute; left: 50%; z-index: 1; pointer-events: none; filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .28)); }
.ava-hat svg { display: block; width: 100%; height: auto; }
.hat-row { display: flex; gap: 12px; align-items: center; padding: 9px 2px; }
.hat-row + .hat-row { border-top: 1px solid var(--line); }
.hat-demo { position: relative; display: inline-flex; width: 34px; height: 34px; flex-shrink: 0; margin-top: 12px; }
.hat-demo-head { width: 100%; height: 100%; border-radius: 50%; background: var(--accent-soft); }
.hat-btn { white-space: nowrap; flex-shrink: 0; }
.hat-btn.wearing { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.hat-btn.confirm { border-color: var(--green); color: var(--green); }

/* a small centered confirm popup over a dimmed page */
.confirm-pop-backdrop {
  position: fixed; inset: 0; z-index: 50; background: rgba(0, 0, 0, .45);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.confirm-pop { width: 100%; max-width: 340px; }

/* staging builds: an unmissable stamp across the header logo */
.brand.staging, .brand.beta { position: relative; }
.staging-badge, .beta-badge {
  position: absolute; left: 2px; bottom: -3px; z-index: 2; pointer-events: none;
  background: #f7931a; color: #fff; font-size: 9px; font-weight: 750;
  letter-spacing: .06em; text-transform: uppercase; line-height: 1;
  padding: 2px 6px; border-radius: 5px; transform: rotate(-6deg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
}
.staging-badge { background: #6d28d9; }
.wallet-row-btn { font-size: 19px; line-height: 1; padding: 8px 12px; }
.chat-avatar.header-ava.fallback { align-items: center; justify-content: center; display: flex; color: var(--muted); }

/* ============================ motion & polish ============================ */
/* Navigation animates (the renderer adds .anim-page only when the visible
   page changed); background repaints re-create the DOM silently. */
@keyframes rise-in { from { opacity: 0; transform: translateY(10px); } }
.anim-page { animation: rise-in .32s cubic-bezier(.16, .84, .3, 1) both; }

@keyframes menu-pop { from { opacity: 0; transform: translateY(-6px) scale(.95); } }
.anim-menu { animation: menu-pop .2s cubic-bezier(.2, .9, .3, 1.35) both; transform-origin: top right; }

@keyframes unfold-in { from { opacity: 0; transform: translateY(-10px); } }
.anim-unfold { animation: unfold-in .28s cubic-bezier(.16, .84, .3, 1) both; }

@keyframes flash-green { 0% { color: var(--green); } 100% { color: inherit; } }
.amt-flash { animation: flash-green 1.2s ease-out; }

/* the success check pops and rings, once, when its page arrives */
@keyframes check-pop { 0% { transform: scale(.3); opacity: 0; } 60% { transform: scale(1.12); } 100% { transform: scale(1); opacity: 1; } }
@keyframes check-ring { from { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 45%, transparent); } to { box-shadow: 0 0 0 30px transparent; } }
.anim-page .check-badge { animation: check-pop .55s cubic-bezier(.34, 1.56, .64, 1) both, check-ring 1.1s ease-out .25s both; }

/* Auto-pay card: the badge is a spinner ring while paying, then flips to the
   green success check with a pop — one node, so the transition is smooth and
   nothing around it shifts. */
.autopay-badge { position: relative; transition: background .3s ease, color .3s ease; }
.autopay-card:not(.paid) .autopay-badge { background: var(--accent-soft); color: transparent; animation: none; }
.autopay-card:not(.paid) .autopay-badge::after {
  content: ''; position: absolute; inset: -7px; border-radius: 50%;
  border: 2px solid var(--line-strong); border-top-color: var(--accent);
  animation: spin .7s linear infinite;
}
.autopay-card.paid .autopay-badge { animation: check-pop .5s cubic-bezier(.34, 1.56, .64, 1) both, check-ring 1.1s ease-out .2s both; }
.autopay-foot { min-height: 1.15em; }
@media (prefers-reduced-motion: reduce) {
  .autopay-card:not(.paid) .autopay-badge::after { animation: none; }
  .autopay-card.paid .autopay-badge { animation: none; }
}

/* micro-interactions */
button, .btn { transition: filter .12s, background .15s, border-color .15s, box-shadow .15s, transform .1s; }
.btn-primary:hover { filter: brightness(1.15); }
.tabs button, .seg button { transition: background .18s, color .18s, box-shadow .18s; }
.item { transition: background .15s; border-radius: 10px; }
.item:hover { background: var(--surface2); }
.clickable { transition: transform .15s ease; }
.chat-avatar.clickable:hover { transform: scale(1.07); }
.header-avatar .chat-avatar { transition: transform .15s ease; }
.header-avatar:hover .chat-avatar { transform: scale(1.08); }
input[type=text], input[type=password], input[type=number], textarea, select { transition: border-color .15s, box-shadow .15s; }
.settings-nav::after { transition: transform .18s ease, color .18s; }
.settings-nav:hover::after { transform: translateX(3px); color: var(--ink); }

/* atmosphere: a faint warm wash pooling at the top, plus film grain */
body {
  background-image: radial-gradient(900px 420px at 50% -160px, var(--wash), transparent 70%);
  background-attachment: fixed;
  /* our swipe-nav owns horizontal gestures; keep the browser's edge-back out */
  overscroll-behavior-x: none;
}
body::after {
  content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 2147483647;
  opacity: .028;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

@media (prefers-reduced-motion: reduce) {
  .anim-page, .anim-menu, .anim-unfold, .anim-tab-left, .anim-tab-right, .amt-flash, .check-badge { animation: none !important; }
  *, *::before, *::after { transition-duration: .01ms !important; }
}
/* tab carousel: only the content pane moves, with a soft cross-fade */
@keyframes tab-in-left { from { opacity: .2; transform: translateX(26px); } }
@keyframes tab-in-right { from { opacity: .2; transform: translateX(-26px); } }
/* will-change promotes the sliding pane to its own compositor layer BEFORE
   the animation's first frame — without it, a slow re-render mid-slide let
   Chrome expose unpainted tiles (gray strips) at the viewport edges */
.anim-tab-left { animation: tab-in-left .26s cubic-bezier(.25, .8, .35, 1) both; will-change: transform, opacity; }
.anim-tab-right { animation: tab-in-right .26s cubic-bezier(.25, .8, .35, 1) both; will-change: transform, opacity; }

/* ---- onboarding wizard + tour ---- */
/* width:100%, not just max-width: with auto margins the flex item doesn't
   stretch, and the punks strip's intrinsic width (12 avatars, ~780px — a
   scroll container still reports its full content width) would size the
   column to the 420px cap even on narrower phones, overflowing the page. */
.onb { width: 100%; max-width: 420px; margin: 8vh auto 0; padding: 0 8px; }
.onb-title { margin: 0; font-size: 26px; letter-spacing: -0.02em; }
/* The line between making your own keys and signing in with someone else's
   account. Without it the four buttons read as four equal choices. */
.onb-or {
  display: flex; align-items: center; gap: 12px; margin: 4px 0 -4px;
  color: var(--faint); font-size: 13px;
}
.onb-or::before, .onb-or::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}
.onb-hero { margin-bottom: 8px; }
.onb-punks { display: flex; gap: 10px; overflow-x: auto; padding: 6px 2px; }
.onb-punk { width: 56px; height: 56px; border-radius: 50%; flex-shrink: 0; cursor: pointer; border: 3px solid transparent; transition: transform .15s, border-color .15s; }
.onb-punk:hover { transform: scale(1.08); }
.onb-punk.sel { border-color: var(--accent); transform: scale(1.08); }
.onb-avatar { width: 160px; height: 160px; border-radius: 50%; object-fit: cover; align-self: center; box-shadow: var(--shadow); transition: opacity .15s; }
.onb-confetti { position: fixed; inset: 0; pointer-events: none; overflow: hidden; }
.onb-confetti i { position: absolute; top: -12px; width: 9px; height: 14px; opacity: .9; animation: confetti-fall 2.8s ease-in forwards; }
.onb-confetti i:nth-child(4n) { background: #f7931a; }
.onb-confetti i:nth-child(4n+1) { background: #ffd166; }
.onb-confetti i:nth-child(4n+2) { background: var(--green); }
.onb-confetti i:nth-child(4n+3) { background: #b84fe8; }
.onb-confetti i:nth-child(1) { left: 6%; animation-delay: 0s; } .onb-confetti i:nth-child(2) { left: 12%; animation-delay: .3s; }
.onb-confetti i:nth-child(3) { left: 19%; animation-delay: .1s; } .onb-confetti i:nth-child(4) { left: 26%; animation-delay: .5s; }
.onb-confetti i:nth-child(5) { left: 33%; animation-delay: .2s; } .onb-confetti i:nth-child(6) { left: 40%; animation-delay: .65s; }
.onb-confetti i:nth-child(7) { left: 47%; animation-delay: .05s; } .onb-confetti i:nth-child(8) { left: 54%; animation-delay: .4s; }
.onb-confetti i:nth-child(9) { left: 61%; animation-delay: .15s; } .onb-confetti i:nth-child(10) { left: 68%; animation-delay: .55s; }
.onb-confetti i:nth-child(11) { left: 75%; animation-delay: .25s; } .onb-confetti i:nth-child(12) { left: 82%; animation-delay: .7s; }
.onb-confetti i:nth-child(13) { left: 89%; animation-delay: .35s; } .onb-confetti i:nth-child(14) { left: 95%; animation-delay: .6s; }
.onb-confetti i:nth-child(15) { left: 15%; animation-delay: .8s; } .onb-confetti i:nth-child(16) { left: 45%; animation-delay: .9s; }
.onb-confetti i:nth-child(17) { left: 70%; animation-delay: 1s; } .onb-confetti i:nth-child(18) { left: 30%; animation-delay: 1.1s; }
@keyframes confetti-fall { 0% { transform: translateY(-10px) rotate(0); } 100% { transform: translateY(105vh) rotate(560deg); } }
@media (prefers-reduced-motion: reduce) { .onb-confetti i { animation: none; display: none; } }
.onb-check { width: 96px; height: 96px; font-size: 52px; align-self: center; margin: 6px 0; }
/* the nostr mark heading the sign-in steps: big, centered, gently afloat */
.onb-mark { align-self: center; margin-bottom: -4px; animation: mark-float 4.5s ease-in-out infinite; }
.onb-mark svg { width: 72px; height: 72px; filter: drop-shadow(0 8px 22px rgba(184, 79, 232, .35)); }
@keyframes mark-float { 50% { transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) { .onb-mark { animation: none; } }
.onb-addr { color: var(--muted); font-family: var(--mono); font-size: 13px; }
/* balance card: one account at a time, dots to flip */
.balance-face { min-height: 78px; }
/* amount input with an inline Max affordance */
.amt-field { position: relative; display: flex; flex: 1; min-width: 0; }
.amt-field input { width: 100%; padding-right: 52px; }
.amt-max {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  padding: 4px 8px; font-size: 12px; font-weight: 600; line-height: 1;
  border: 0; border-radius: 7px; background: var(--accent-soft); color: var(--muted); cursor: pointer;
}
.amt-max:hover { background: var(--surface2); color: var(--ink); }
/* recipient chip: large variant for pay/zap screens */
.zap-chip.lg { gap: 8px; padding: 4px 12px 4px 4px; font-weight: 600; }
.chat-avatar.chip-lg { width: 30px; height: 30px; font-size: 11px; flex-shrink: 0; }
/* the clickable way to change account (swiping only helps on touch) */
.balance-switch {
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  padding: 6px 12px; font-size: 13px; font-weight: 600;
  color: var(--muted); background: var(--surface2);
  border: 1px solid var(--line); border-radius: 999px; cursor: pointer;
}
.balance-switch:hover { color: var(--ink); border-color: var(--line-strong); }
.balance-switch svg { opacity: .75; }
/* Move-money row: the action labels are long ("Move to Spending"), so let the
   row wrap to two full-width lines rather than crushing the amount field. */
.balance-move .input-group { flex-wrap: wrap; }
.balance-move .amt-field { flex: 1 1 150px; }
.balance-move .input-group > button { flex: 1 1 150px; }
.chat-signer-off { padding: 6px 8px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface2); }
.chan-pick { width: auto; min-width: 0; padding: 6px 32px 6px 10px; font-size: 13px; font-weight: 600; border-radius: 9px; background-position: right 10px center; }
.or-split { display: flex; align-items: center; gap: 10px; color: var(--faint); font-size: 13px; }
.or-split::before, .or-split::after { content: ''; flex: 1; height: 1px; background: var(--line); }
.paste-wrap { position: relative; display: block; }
.paste-wrap textarea { display: block; }
.paste-in {
  position: absolute; right: 10px; bottom: 12px; display: flex; align-items: center; gap: 6px;
  padding: 5px 9px; font-size: 12px; font-weight: 600; border-radius: 8px;
  color: var(--muted); background: var(--paper); border: 1px solid var(--line-strong);
}
.paste-in:hover { color: var(--ink); }
.paste-wrap.has-text .paste-in { display: none; }

/* Short screens (a small phone with its address bar showing) — buy back the
   vertical room the footer needs by tightening the frame, not the content. */
@media (max-height: 620px) {
  #app { padding-top: 12px; padding-bottom: 12px; }
  .card:not(.chat-card) { padding: 16px; }
  .profile-banner { margin: -16px -16px 0; }
  .footer { padding-top: 14px; padding-bottom: 0; }
  .tabs { margin: 10px 0; }
}

/* an encrypted picture on its way: a box the picture's shape, then the picture */
.chat-attach-ph { display: block; width: 240px; max-width: 100%; max-height: 320px; margin-top: 6px; border-radius: 10px; background: var(--line); }
.chat-attach { display: inline-block; margin-top: 6px; }
.chat-clip { flex: none; align-self: flex-end; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; }
.msg-sheet-emojis button.more { flex: 0 0 40px; font-size: 20px; color: var(--muted); }
.emoji-grid { display: flex; flex-wrap: wrap; gap: 2px; max-height: 260px; overflow-y: auto; }
.emoji-grid button { width: 40px; height: 40px; font-size: 22px; line-height: 1; border: 1px solid transparent; border-radius: 8px; background: none; padding: 0; }
.emoji-grid button.on { border-color: var(--accent); }
.hub-list { max-height: 360px; overflow-y: auto; }
.hub-icon { object-fit: cover; }

/* custom emoji (NIP-30): an inline picture the height of a capital, sat on
   the baseline like the character it stands for */
.cemoji { display: inline-block; height: 1.4em; width: auto; max-width: 3em; vertical-align: -0.35em; object-fit: contain; }
/* a bubble that is nothing but one to three of them shows them big */
.chat-bubble.jumbo .cemoji { height: 2.8em; max-width: 5em; vertical-align: middle; }
.chat-react .cemoji { height: 1.25em; vertical-align: -0.3em; }
.emoji-grid .cemoji { height: 28px; max-width: 36px; vertical-align: middle; }
.msg-sheet-emojis .cemoji { height: 22px; vertical-align: middle; }
/* the :shortcode: strip above the composer */
.emoji-ac { display: flex; flex-wrap: wrap; gap: 4px; }
.emoji-ac:empty { display: none; }
.emoji-ac button {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px; font-size: 18px; line-height: 1;
  border: 1px solid var(--line); border-radius: 10px; background: var(--paper);
}
.emoji-ac button .small { font-size: 12px; color: var(--muted); }
.emoji-ac .cemoji { height: 22px; vertical-align: middle; }
/* the packs behind the picker, and the add box */
.emoji-packs { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.emoji-pack { display: inline-flex; align-items: center; gap: 2px; }
.emoji-pack .linklike { padding: 0 2px; font-size: 14px; color: var(--muted); }
/* a pack share link (Vector's or ours) as a chip: one tap adds the pack */
.pack-link {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; font-size: 14px; line-height: 1.3;
  border: 1px solid var(--line-strong); border-radius: 10px; background: var(--paper); color: inherit; cursor: pointer;
}
.pack-link:disabled { opacity: 0.7; cursor: default; }
.chat-bubble.me .pack-link { background: transparent; border-color: color-mix(in srgb, currentColor 45%, transparent); }
/* a custom emoji whose picture won't load falls back to its :code: alt text;
   in the 40px grid cell that text must not spill into the next cell */
.emoji-grid button { overflow: hidden; }
.emoji-grid .cemoji { font-size: 8px; }
/* quoted replies and the composer's reply bar run small; their emoji follow */
.chat-quote-text .cemoji { height: 1.25em; vertical-align: -0.3em; }