/* Master stylesheet — imports every layer in order.
   Order matters: tokens first, base next, then layout, then components. */

/* Design tokens — spacing, typography, radii, shadows, colors.
   Color structure:
     1. :root, [data-theme="light"]  — light (default + explicit)
     2. [data-theme="dark"]          — dark explicit
     3. @media prefers-color-scheme  — OS-preference catch-all (no data-theme) */


/* ─── Static tokens (theme-independent) ──────────────────────────────────── */

:root {
  /* Spacing */
  --sp-4xs: 2px;
  --sp-3xs: 4px;
  --sp-xxs: 6px;
  --sp-xs: 8px;
  --sp-sm: 12px;
  --sp-md: 16px;
  --sp-lg: 20px;
  --sp-xl: 24px;
  --sp-xxl: 32px;
  --sp-3xl: 40px;
  --sp-4xl: 48px;

  /* Sizing */
  --size-4xs: 2px;
  --size-3xs: 4px;
  --size-xxs: 6px;
  --size-xs: 8px;
  --size-sm: 12px;
  --size-md: 16px;
  --size-lg: 20px;
  --size-xl: 24px;
  --size-xxl: 32px;
  --size-3xl: 40px;
  --size-4xl: 48px;
  --size-5xl: 56px;

  /* Typography */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --font-serif: "New York", ui-serif, Georgia, serif;
  --text-xs: 11px;
  --text-sm: 13px;
  --text-base: 14px;
  --text-md: 15px;
  --text-lg: 16px;
  --text-xl: 18px;
  --text-2xl: 22px;
  --text-3xl: 28px;
  --text-4xl: 32px;

  /* Radius */
  --radius-sm: 4px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-xl: 24px;
  --radius-full: 9999px;

  /* brand gradient */
  --fill-gradient:linear-gradient(180deg, #62D475 0%, #58BE68 100%);

  /* Brand pastel — the Please-agent tint (iOS `ColorSystem.Gradients.
     brandPastelColors`, the single source: #F0B6F6 @0, #89C3FD @0.49,
     #56E06D @1). Vertical for glyphs/rings, horizontal for the glow
     plane, and the bare stop list for the comet's conic wheel. */
  --pastel-1: #F0B6F6;
  --pastel-2: #89C3FD;
  --pastel-3: #56E06D;
  --gradient-pastel: linear-gradient(180deg, var(--pastel-1) 0%, var(--pastel-2) 49%, var(--pastel-3) 100%);
  --gradient-pastel-horizontal: linear-gradient(90deg, var(--pastel-1) 0%, var(--pastel-2) 49%, var(--pastel-3) 100%);

  /* Code spans — iOS pins ColorSystem.Code (PlsCodeText on PlsCodeBackground)
     in BOTH appearances so code reads like a terminal either way. */
  --code-text: #30D158;
  --code-bg:   #2B2D2E;

  /* Accent color base rgb values to inject into RGB functions.
     iOS ColorSystem parity (Aug 2026): *-1 is the light-mode value,
     *-2 the dark-mode value. */
  --blue-1:       0, 102, 235;
  --blue-2:       20, 122, 255;
  --red-1:        255, 56, 60;
  --red-2:        255, 66, 69;
  --green-1:      52, 199, 89;
  --green-2:      48, 209, 88;
  --yellow-1:     255, 214, 0;
  --yellow-2:     255, 204, 0;
  --orange-1:     255, 146, 48;
  --orange-2:     255, 141, 40;
  --purple-1:     175, 82, 222;
  --purple-2:     191, 90, 242;

  --neutral-white:    255, 255, 255;
  --neutral-01:       242, 242, 247;
  --neutral-02:       58, 58, 60;
  --neutral-03:       44, 44, 46;
  --neutral-04:       28, 28, 30;
  --neutral-05:       13, 13, 13;
  --neutral-black:    0, 0, 0;


--neutral-01: 242, 242, 247; /* 1 */
--fill-tertiary: 237, 237, 237; /* 2 */
--fill-secondary: 224, 224, 224; /* 3 */
--fill-primary: 204, 204, 204; /* 4 */
--neutral-02: 58, 58, 60; /* 5 */
--fill-primary-inverse: 51, 51, 51; /* 6 */
--neutral-03: 44, 44, 46; /* 7 */
--fill-secondary-inverse: 31, 31, 31; /* 8 */
--neutral-04: 28, 28, 30; /* 9 */
--fill-tertiary-inverse: 18, 18, 18; /* 10 */
--neutral-05: 13, 13, 13; /* 11 */


--neutral-01: 242, 242, 247;
--neutral-02: 237, 237, 237;
--neutral-03: 224, 224, 224;
--neutral-04: 204, 204, 204;
--neutral-05: 58, 58, 60;
--neutral-06: 51, 51, 51;
--neutral-07: 44, 44, 46;
--neutral-08: 31, 31, 31;
--neutral-09: 28, 28, 30;
--neutral-10: 18, 18, 18;
--neutral-11: 13, 13, 13;
}


/* ─── Light theme — default + explicit ───────────────────────────────────── */

:root,
[data-theme="light"] {

  --blend-mode:      darken;

  --bg-base:               var(--neutral-white);
  --bg-base-inverse:       var(--neutral-black);

  --color-gray-1:          var(--neutral-01);
  --color-gray-2:          var(--neutral-05);
  --color-gray-3:          var(--neutral-07);
  --color-gray-4:          var(--neutral-09);
  --color-gray-5:          var(--neutral-11);

  --color-red:             var(--red-1);
  --color-orange:          var(--orange-1);
  --color-yellow:          var(--yellow-1);
  --color-green:           var(--green-1);
  --color-blue:            var(--blue-1);
  --color-purple:          var(--purple-1);

  --color-red-inverse:     var(--red-2);
  --color-orange-inverse:  var(--orange-2);
  --color-yellow-inverse:  var(--yellow-2);
  --color-green-inverse:   var(--green-2);
  --color-blue-inverse:    var(--blue-2);
  --color-purple-inverse:  var(--purple-2);

  /* Background */
  --bg-body:                       rgba(var(--bg-base), 1);
  --bg-primary:                    rgba(var(--bg-base), 1);
  --bg-secondary:                  rgba(var(--color-gray-1), 1);
  --bg-tertiary:                   rgba(var(--bg-base), .04);
  --bg-quarternary:                rgba(var(--bg-base), .02);

  --bg-elevated-primary:           rgba(var(--bg-base), 1);
  --bg-elevated-secondary:         rgba(var(--color-gray-1), 1);
  --bg-elevated-tertiary:          rgba(var(--bg-base), 1);

  --bg-body-inverse:               rgba(var(--color-gray-5), 1);
  --bg-primary-inverse:            rgba(var(--bg-base-inverse), 1);
  --bg-elevated-primary-inverse:   rgba(var(--color-gray-4), 1);
  --bg-elevated-secondary-inverse: rgba(var(--color-gray-3), 1);
  --bg-elevated-tertiary-inverse:  rgba(var(--color-gray-2), 1);    
  --bg-secondary-inverse:          rgba(var(--color-gray-4), 1);
  --bg-tertiary-inverse:           rgba(var(--color-gray-3), 1);
  --bg-quarternary-inverse:        rgba(var(--bg-base-inverse), .02);

  --bg-disabled:                   var(--bg-quarternary);
  --bg-disabled-inverse:           var(--bg-quarternary-inverse);
  --bg-0:                          rgba(var(--bg-base),0);
  --bg-70:                         rgba(var(--bg-base),.7);
  --bg-80:                         rgba(var(--bg-base),.8);
  --bg-70-inverse:                 rgba(var(--bg-base-inverse),.7);
  --bg-80-inverse:                 rgba(var(--bg-base-inverse),.8);

  /* Fill — base (solid) + elevated (translucent overlay) */
  --fill-primary:                     rgba(var(--neutral-04));
  --fill-secondary:                   rgba(var(--neutral-03));
  --fill-tertiary:                    rgba(var(--neutral-02));
  --fill-elevated-primary:            rgba(120, 120, 120, 0.20);
  --fill-elevated-secondary:          rgba(120, 120, 128, 0.16);
  --fill-elevated-tertiary:           rgba(118, 118, 128, 0.12);
  --fill-elevated-quaternary:         rgba(116, 116, 128, 0.08);
  
  --fill-primary-inverse:             rgba(var(--neutral-06));
  --fill-secondary-inverse:           rgba(var(--neutral-08));
  --fill-tertiary-inverse:            rgba(var(--neutral-10));
  --fill-elevated-primary-inverse:    rgba(120, 120, 128, 0.36);
  --fill-elevated-secondary-inverse:  rgba(120, 120, 128, 0.32);
  --fill-elevated-tertiary-inverse:   rgba(118, 118, 128, 0.24);
  --fill-elevated-quaternary-inverse: rgba(118, 118, 128, 0.18);

  /* Brand gradients */
  --fill-gradient-green: linear-gradient(180deg, #62D475 0%, #58BE68 100%);
  
  /* Text */
  --text-primary:               rgba(var(--neutral-black));
  /* iOS parity: solid #8E8E8E in light (was rgba(60,60,67,.6)). */
  --text-secondary:           #8E8E8E;
  --text-tertiary:            rgba(60,60,67,0.3);
  --text-quarternary:         rgba(60,60,67,0.18);
  --text-disabled:              var(--text-quarternary);
  /* iOS PlsLabelSpecial — the reminder BODY reading colour, between
     primary (too heavy for a wall of copy) and secondary (too faint). */
  --label-special:              #454545;

  --text-primary-inverse:       rgba(var(--neutral-white));
  --text-secondary-inverse:   rgba(235,235,245,0.7);
  --text-tertiary-inverse:    rgba(235,235,245,0.3);
  --text-quarternary-inverse: rgba(235,235,245,0.16);
  --text-disabled-inverse:      var(--text-quarternary-inverse);
  
  --text-primary-vibrant:             #1A1A1A;
  --text-primary-vibrant-inverse:     #F5F5F5;
  --text-secondary-vibrant:           #727272;
  --text-secondary-vibrant-inverse:   #8A8A8A;
  --text-tertiary-vibrant:            #BFBFBF;
  --text-tertiary-vibrant-inverse:    #404040;
  --text-quarternary-vibrant:         #D9D9D9;
  --text-quarternary-vibrant-inverse: #262626;
  --text-disabled-vibrant:            var(--text-quarternary-vibrant);
  --text-disabled-vibrant-inverse:    var(--text-quarternary-vibrant-inverse);

  /* Border */
  --border:           #e5e5e5;
  --border-strong:    #d4d4d4;
  --border-primary:   #C6C6C8;
  --border-secondary: rgba(0, 0, 0, 0.12);
  --border-tertiary:  rgba(0, 0, 0, 0.05);
  --border-vibrant:   #E6E6E6;

  /* Accent */
  --accent-blue:          rgba(var(--color-blue), 1);
  --accent-blue-inverse:  rgba(var(--color-blue-inverse), 1);
  --accent-red:           rgba(var(--color-red), 1);
  --accent-red-inverse:   rgba(var(--color-red-inverse), 1);
  --accent-green:         rgba(var(--color-green), 1);
  --accent-green-inverse: rgba(var(--color-green-inverse), 1);
  --accent-yellow:        rgba(var(--color-yellow), 1);
  --accent-yellow-inverse:rgba(var(--color-yellow-inverse), 1);
  --accent-orange:        rgba(var(--color-orange), 1);
  --accent-orange-inverse:rgba(var(--color-orange-inverse), 1);
  --accent-purple:        rgba(var(--color-purple), 1);
  --accent-purple-inverse:rgba(var(--color-purple-inverse), 1);

  /* iOS `*Elevated` tonal fill: the accent hue at low alpha, composited
     live (never a frozen hex). Alpha deliberately diverges by mode
     (10% light / 16% dark), matching ColorSystem.accentBlueElevated. */
  --accent-blue-elevated: rgba(var(--color-blue), 0.10);
  --accent-red-elevated: rgba(var(--color-red), 0.10);

  /* Chat bubbles (iOS ChatBubble parity) */
  --bubble-sent:     #FFFFFF;
  --bubble-received: #E5E5EA;

  /* Timeline list (iOS parity) */
  --canvas-bg:         #F2F2F7;
  --tl-text-primary:   #000000;
  --tl-text-secondary: #8E8E8E;
  --ui-borders:        #C6C6C8;
  --brand-blue:        rgba(var(--color-blue), 1);
  --brand-blue-12:     rgba(var(--color-blue), 0.12);

  /* Shadow */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
  --shadow:     0 2px 8px rgba(0, 0, 0, 0.06);
  /* Floating capsule over content (the desktop detail's Messages pill,
     mock 2026-09-24): hairline edge + soft drop. */
  --shadow-float: 0 4px 12px rgba(0, 0, 0, 0.14);
  --border-float: rgba(0, 0, 0, 0.08);

  /* Deprecated aliases — remove once consumers are migrated */
  --bg:         #ffffff;
  --bg-subtle:  #f5f5f4;
  --bg-elevated: #ffffff;
  --fg:         #0a0a0a;
  --accent:     #0a0a0a;
  --accent-fg:  #ffffff;

  color-scheme: light;
}


/* ─── Dark theme — explicit ───────────────────────────────────────────────── */

[data-theme="dark"] {

  --blend-mode:      lighten;

  --bg-base:                var(--neutral-black);
  --bg-base-inverse:        var(--neutral-white);

  --color-gray-1:           var(--neutral-01);
  --color-gray-2:           var(--neutral-05);
  --color-gray-3:           var(--neutral-07);
  --color-gray-4:           var(--neutral-09);
  --color-gray-5:           var(--neutral-11);

  --color-red:              var(--red-2);
  --color-orange:           var(--orange-2);
  --color-yellow:           var(--yellow-2);
  --color-green:            var(--green-2);
  --color-blue:             var(--blue-2);
  --color-purple:           var(--purple-2);

  --color-red-inverse:      var(--red-1);
  --color-orange-inverse:   var(--orange-1);
  --color-yellow-inverse:   var(--yellow-1);
  --color-green-inverse:    var(--green-1);
  --color-blue-inverse:     var(--blue-1);
  --color-purple-inverse:   var(--purple-1);

  /* Background */
  --bg-body:               rgba(var(--color-gray-5), 1);
  --bg-primary:            rgba(var(--bg-base), 1); 
  --bg-secondary:          rgba(var(--color-gray-4), 1);
  --bg-tertiary:           rgba(var(--color-gray-3), 1);
  --bg-quarternary:        rgba(var(--bg-base), .02);

  --bg-elevated-primary:   rgba(var(--color-gray-4), 1);
  --bg-elevated-secondary: rgba(var(--color-gray-3), 1);
  --bg-elevated-tertiary:  rgba(var(--color-gray-2), 1);   

  --bg-body-inverse:                       rgba(var(--bg-base-inverse), 1);
  --bg-primary-inverse:                    rgba(var(--bg-base-inverse), 1);
  --bg-elevated-primary-inverse:           rgba(var(--bg-base-inverse), 1);
  --bg-elevated-secondary-inverse:         rgba(var(--color-gray-1), 1);
  --bg-elevated-tertiary-inverse:          rgba(var(--bg-base-inverse), 1);
  --bg-secondary-inverse:                  rgba(var(--color-gray-1), 1);
  --bg-tertiary-inverse:                   rgba(var(--bg-base-inverse), .04);
  --bg-quarternary-inverse:                rgba(var(--bg-base-inverse), .02);

  --bg-disabled:            var(--bg-quarternary);
  --bg-disabled-inverse:    var(--bg-quarternary-inverse);
  --bg-0:                   rgba(var(--bg-base),0);
  --bg-70:                  rgba(var(--bg-base),.7);
  --bg-80:                  rgba(var(--bg-base),.8);
  --bg-70-inverse:          rgba(var(--bg-base-inverse),.7);
  --bg-80-inverse:          rgba(var(--bg-base-inverse),.8);
  
  /* Fill — base (solid) + elevated (translucent overlay) */
  --fill-primary:                      rgba(var(--neutral-06));
  --fill-secondary:                    rgba(var(--neutral-08));
  --fill-tertiary:                     rgba(var(--neutral-10));
  --fill-elevated-primary:            rgba(120, 120, 128, 0.36);
  --fill-elevated-secondary:          rgba(120, 120, 128, 0.32);
  --fill-elevated-tertiary:           rgba(118, 118, 128, 0.24);
  --fill-elevated-quaternary:         rgba(118, 118, 128, 0.18);
  
  --fill-primary-inverse:               rgba(var(--neutral-04));
  --fill-secondary-inverse:             rgba(var(--neutral-03));
  --fill-tertiary-inverse:              rgba(var(--neutral-02));
  --fill-elevated-primary-inverse:    rgba(120, 120, 120, 0.20);
  --fill-elevated-secondary-inverse:  rgba(120, 120, 128, 0.16);
  --fill-elevated-tertiary-inverse:   rgba(118, 118, 128, 0.12);
  --fill-elevated-quaternary-inverse: rgba(116, 116, 128, 0.08);


  /* Text */
  
  --text-primary:             #FEFFFF; /* iOS PlsTextPrimary dark */
  --text-secondary:           rgba(235,235,245,0.7);
  --text-tertiary:            rgba(235,235,245,0.3);
  --text-quarternary:         rgba(235,235,245,0.16);
  --text-disabled:              var(--text-quarternary);
  --label-special:              #EBEBF5;

  --text-primary-inverse:     rgba(var(--neutral-black));
  --text-secondary-inverse:   #8E8E8E;
  --text-tertiary-inverse:    rgba(60,60,67,0.3);
  --text-quarternary-inverse: rgba(60,60,67,0.18);
  --text-disabled-inverse:      var(--text-quarternary-inverse);


  --text-primary-vibrant:            #F5F5F5;
  --text-primary-vibrant-inverse:    #1A1A1A;
  --text-secondary-vibrant:          #8A8A8A;
  --text-secondary-vibrant-inverse:  #727272;
  --text-tertiary-vibrant:           #404040;
  --text-tertiary-vibrant-inverse:   #BFBFBF;
  --text-quarternary-vibrant:        #262626;
  --text-quarternary-vibrant-inverse:#D9D9D9;
  --text-disabled-vibrant:             var(--text-quarternary-vibrant);
  --text-disabled-vibrant-inverse:     var(--text-quarternary-vibrant-inverse);


  /* Border */
  --border:           rgba(255, 255, 255, 0.3);
  --border-strong:    rgba(255, 255, 255, 0.5);
  --border-primary:   #38383A;
  --border-secondary: rgba(255, 255, 255, 0.17);
  --border-tertiary:  #1A1A1A;

  /* Accent */
  --accent-blue:                  rgba(var(--color-blue), 1);
  --accent-red:                   rgba(var(--color-red), 1);
  --accent-green:                 rgba(var(--color-green), 1);
  --accent-yellow:                rgba(var(--color-yellow), 1);
  --accent-orange:                rgba(var(--color-orange), 1);
  --accent-purple:                rgba(var(--color-purple), 1);
  
  --accent-blue-inverse:          rgba(var(--color-blue-inverse), 1);
  --accent-red-inverse:           rgba(var(--color-red-inverse), 1);
  --accent-green-inverse:         rgba(var(--color-green-inverse), 1);
  --accent-yellow-inverse:        rgba(var(--color-yellow-inverse), 1);
  --accent-orange-inverse:        rgba(var(--color-orange-inverse), 1);
  --accent-purple-inverse:        rgba(var(--color-purple-inverse), 1);

  
  /* iOS `*Elevated` tonal fill — dark mode runs blue at a higher alpha
     (16%); red stays 10% in both (PlsAccentRedElevated). */
  --accent-blue-elevated: rgba(var(--color-blue), 0.16);
  --accent-red-elevated: rgba(var(--color-red), 0.10);

  
  /* Chat bubbles (iOS ChatBubble parity) */
  --bubble-sent:     #000000;
  --bubble-received: #2C2C2E;

  /* Timeline list (iOS parity) */
  --canvas-bg:         #0C0C0C; /* iOS backgroundCanvas dark (2026-09-23): darker than the
                                   black cards so they read as lifted, as on iOS. Was #1C1C1E. */
  --tl-text-primary:   #FEFFFF;
  --tl-text-secondary: rgba(235, 235, 245, 0.7);
  --ui-borders:        #38383A;
  --brand-blue:        rgba(var(--color-blue), 1);
  --brand-blue-12:     rgba(var(--color-blue), 0.12);

  /* Shadow */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow:     0 2px 8px rgba(0, 0, 0, 0.4);
  --shadow-float: 0 4px 12px rgba(0, 0, 0, 0.5);
  --border-float: rgba(255, 255, 255, 0.12);

  /* Deprecated aliases — remove once consumers are migrated */
  --bg:         #0a0a0a;
  --bg-subtle:  #171717;
  --bg-elevated: #1c1c1c;
  --fg:         #fafafa;
  --accent:     #fafafa;
  --accent-fg:  #0a0a0a;

  color-scheme: dark;
}


/* ─── OS dark preference — catch-all when no data-theme is set ────────────── */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {

  --blend-mode:      lighten;

  --bg-base:                var(--neutral-black);
  --bg-base-inverse:        var(--neutral-white);

  --color-gray-1:           var(--neutral-01);
  --color-gray-2:           var(--neutral-05);
  --color-gray-3:           var(--neutral-07);
  --color-gray-4:           var(--neutral-09);
  --color-gray-5:           var(--neutral-11);

  --color-red:              var(--red-2);
  --color-orange:           var(--orange-2);
  --color-yellow:           var(--yellow-2);
  --color-green:            var(--green-2);
  --color-blue:             var(--blue-2);
  --color-purple:           var(--purple-2);

  --color-red-inverse:      var(--red-1);
  --color-orange-inverse:   var(--orange-1);
  --color-yellow-inverse:   var(--yellow-1);
  --color-green-inverse:    var(--green-1);
  --color-blue-inverse:     var(--blue-1);
  --color-purple-inverse:   var(--purple-1);

  /* Background */
  --bg-body:               rgba(var(--color-gray-5), 1);
  --bg-primary:            rgba(var(--bg-base), 1); 
  --bg-secondary:          rgba(var(--color-gray-4), 1);
  --bg-tertiary:           rgba(var(--color-gray-3), 1);
  --bg-quarternary:        rgba(var(--bg-base), .02);

  --bg-elevated-primary:   rgba(var(--color-gray-4), 1);
  --bg-elevated-secondary: rgba(var(--color-gray-3), 1);
  --bg-elevated-tertiary:  rgba(var(--color-gray-2), 1);   

  --bg-body-inverse:                       rgba(var(--bg-base-inverse), 1);
  --bg-primary-inverse:                    rgba(var(--bg-base-inverse), 1);
  --bg-elevated-primary-inverse:           rgba(var(--bg-base-inverse), 1);
  --bg-elevated-secondary-inverse:         rgba(var(--color-gray-1), 1);
  --bg-elevated-tertiary-inverse:          rgba(var(--bg-base-inverse), 1);
  --bg-secondary-inverse:                  rgba(var(--color-gray-1), 1);
  --bg-tertiary-inverse:                   rgba(var(--bg-base-inverse), .04);
  --bg-quarternary-inverse:                rgba(var(--bg-base-inverse), .02);

  --bg-disabled:            var(--bg-quarternary);
  --bg-disabled-inverse:    var(--bg-quarternary-inverse);
  --bg-0:                   rgba(var(--bg-base),0);
  --bg-70:                  rgba(var(--bg-base),.7);
  --bg-80:                  rgba(var(--bg-base),.8);
  --bg-70-inverse:          rgba(var(--bg-base-inverse),.7);
  --bg-80-inverse:          rgba(var(--bg-base-inverse),.8);
  
  /* Fill — base (solid) + elevated (translucent overlay) */
  --fill-primary:                      rgba(var(--neutral-06));
  --fill-secondary:                    rgba(var(--neutral-08));
  --fill-tertiary:                     rgba(var(--neutral-10));
  --fill-elevated-primary:            rgba(120, 120, 128, 0.36);
  --fill-elevated-secondary:          rgba(120, 120, 128, 0.32);
  --fill-elevated-tertiary:           rgba(118, 118, 128, 0.24);
  --fill-elevated-quaternary:         rgba(118, 118, 128, 0.18);
  
  --fill-primary-inverse:               rgba(var(--neutral-04));
  --fill-secondary-inverse:             rgba(var(--neutral-03));
  --fill-tertiary-inverse:              rgba(var(--neutral-02));
  --fill-elevated-primary-inverse:    rgba(120, 120, 120, 0.20);
  --fill-elevated-secondary-inverse:  rgba(120, 120, 128, 0.16);
  --fill-elevated-tertiary-inverse:   rgba(118, 118, 128, 0.12);
  --fill-elevated-quaternary-inverse: rgba(116, 116, 128, 0.08);


  /* Text */
  
  --text-primary:             #FEFFFF; /* iOS PlsTextPrimary dark */
  --text-secondary:           rgba(235,235,245,0.7);
  --text-tertiary:            rgba(235,235,245,0.3);
  --text-quarternary:         rgba(235,235,245,0.16);
  --text-disabled:              var(--text-quarternary);
  --label-special:              #EBEBF5;

  --text-primary-inverse:     rgba(var(--neutral-black));
  --text-secondary-inverse:   #8E8E8E;
  --text-tertiary-inverse:    rgba(60,60,67,0.3);
  --text-quarternary-inverse: rgba(60,60,67,0.18);
  --text-disabled-inverse:      var(--text-quarternary-inverse);


  --text-primary-vibrant:            #F5F5F5;
  --text-primary-vibrant-inverse:    #1A1A1A;
  --text-secondary-vibrant:          #8A8A8A;
  --text-secondary-vibrant-inverse:  #727272;
  --text-tertiary-vibrant:           #404040;
  --text-tertiary-vibrant-inverse:   #BFBFBF;
  --text-quarternary-vibrant:        #262626;
  --text-quarternary-vibrant-inverse:#D9D9D9;
  --text-disabled-vibrant:             var(--text-quarternary-vibrant);
  --text-disabled-vibrant-inverse:     var(--text-quarternary-vibrant-inverse);


  /* Border */
  --border:           rgba(255, 255, 255, 0.3);
  --border-strong:    rgba(255, 255, 255, 0.5);
  --border-primary:   #38383A;
  --border-secondary: rgba(255, 255, 255, 0.17);
  --border-tertiary:  #1A1A1A;

  /* Accent */
  --accent-blue:                  rgba(var(--color-blue), 1);
  --accent-red:                   rgba(var(--color-red), 1);
  --accent-green:                 rgba(var(--color-green), 1);
  --accent-yellow:                rgba(var(--color-yellow), 1);
  --accent-orange:                rgba(var(--color-orange), 1);
  --accent-purple:                rgba(var(--color-purple), 1);
  
  --accent-blue-inverse:          rgba(var(--color-blue-inverse), 1);
  --accent-red-inverse:           rgba(var(--color-red-inverse), 1);
  --accent-green-inverse:         rgba(var(--color-green-inverse), 1);
  --accent-yellow-inverse:        rgba(var(--color-yellow-inverse), 1);
  --accent-orange-inverse:        rgba(var(--color-orange-inverse), 1);
  --accent-purple-inverse:        rgba(var(--color-purple-inverse), 1);

  
  /* iOS `*Elevated` tonal fill — dark mode runs blue at a higher alpha
     (16%); red stays 10% in both (PlsAccentRedElevated). */
  --accent-blue-elevated: rgba(var(--color-blue), 0.16);
  --accent-red-elevated: rgba(var(--color-red), 0.10);

  
  /* Chat bubbles (iOS ChatBubble parity) */
  --bubble-sent:     #000000;
  --bubble-received: #2C2C2E;

  /* Timeline list (iOS parity) */
  --canvas-bg:         #0C0C0C; /* iOS backgroundCanvas dark (2026-09-23): darker than the
                                   black cards so they read as lifted, as on iOS. Was #1C1C1E. */
  --tl-text-primary:   #FEFFFF;
  --tl-text-secondary: rgba(235, 235, 245, 0.7);
  --ui-borders:        #38383A;
  --brand-blue:        rgba(var(--color-blue), 1);
  --brand-blue-12:     rgba(var(--color-blue), 0.12);

  /* Shadow */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow:     0 2px 8px rgba(0, 0, 0, 0.4);
  --shadow-float: 0 4px 12px rgba(0, 0, 0, 0.5);
  --border-float: rgba(255, 255, 255, 0.12);

  /* Deprecated aliases — remove once consumers are migrated */
  --bg:         #0a0a0a;
  --bg-subtle:  #171717;
  --bg-elevated: #1c1c1c;
  --fg:         #fafafa;
  --accent:     #fafafa;
  --accent-fg:  #0a0a0a;

    color-scheme: dark;
  }
}

/* The user's accent colour over the tokens — see accent.css. */
/* The user's accent colour (Settings → Accent Color), after iOS's
   AccentPreference. `data-accent` on <html> (app/settings
   apply_accent_effect, restored pre-paint by index.html) re-points the
   accent tokens the app already paints with: --accent-blue and its 10% /
   16% elevated wash. No attribute = the stock accent from tokens.css.

   Values are iOS's PleaseUI Colors.xcassets bases. Three blocks per colour,
   matching tokens.css's theme resolution: light (default), explicit dark,
   and OS-dark when no theme is forced. The .settings-accent--* classes are
   the swatches, which always show their own colour whatever is chosen. */

:root[data-accent="orange"],
.settings-accent--orange {
  --accent-blue: rgb(120, 61, 2);
  --accent-blue-elevated: rgba(120, 61, 2, 0.10);
  --swatch: rgb(120, 61, 2);
}

:root[data-theme="dark"][data-accent="orange"],
:root[data-theme="dark"] .settings-accent--orange {
  --accent-blue: rgb(255, 146, 48);
  --accent-blue-elevated: rgba(255, 146, 48, 0.10);
  --swatch: rgb(255, 146, 48);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme])[data-accent="orange"],
  :root:not([data-theme]) .settings-accent--orange {
    --accent-blue: rgb(255, 146, 48);
    --accent-blue-elevated: rgba(255, 146, 48, 0.10);
    --swatch: rgb(255, 146, 48);
  }
}

:root[data-accent="yellow"],
.settings-accent--yellow {
  --accent-blue: rgb(94, 75, 7);
  --accent-blue-elevated: rgba(94, 75, 7, 0.10);
  --swatch: rgb(94, 75, 7);
}

:root[data-theme="dark"][data-accent="yellow"],
:root[data-theme="dark"] .settings-accent--yellow {
  --accent-blue: rgb(255, 214, 0);
  --accent-blue-elevated: rgba(255, 214, 0, 0.10);
  --swatch: rgb(255, 214, 0);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme])[data-accent="yellow"],
  :root:not([data-theme]) .settings-accent--yellow {
    --accent-blue: rgb(255, 214, 0);
    --accent-blue-elevated: rgba(255, 214, 0, 0.10);
    --swatch: rgb(255, 214, 0);
  }
}

:root[data-accent="green"],
.settings-accent--green {
  --accent-blue: rgb(18, 89, 36);
  --accent-blue-elevated: rgba(18, 89, 36, 0.10);
  --swatch: rgb(18, 89, 36);
}

:root[data-theme="dark"][data-accent="green"],
:root[data-theme="dark"] .settings-accent--green {
  --accent-blue: rgb(48, 209, 88);
  --accent-blue-elevated: rgba(48, 209, 88, 0.10);
  --swatch: rgb(48, 209, 88);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme])[data-accent="green"],
  :root:not([data-theme]) .settings-accent--green {
    --accent-blue: rgb(48, 209, 88);
    --accent-blue-elevated: rgba(48, 209, 88, 0.10);
    --swatch: rgb(48, 209, 88);
  }
}

:root[data-accent="blue"],
.settings-accent--blue {
  --accent-blue: rgb(0, 102, 235);
  --accent-blue-elevated: rgba(0, 102, 235, 0.10);
  --swatch: rgb(0, 102, 235);
}

:root[data-theme="dark"][data-accent="blue"],
:root[data-theme="dark"] .settings-accent--blue {
  --accent-blue: rgb(20, 122, 255);
  --accent-blue-elevated: rgba(20, 122, 255, 0.16);
  --swatch: rgb(20, 122, 255);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme])[data-accent="blue"],
  :root:not([data-theme]) .settings-accent--blue {
    --accent-blue: rgb(20, 122, 255);
    --accent-blue-elevated: rgba(20, 122, 255, 0.16);
    --swatch: rgb(20, 122, 255);
  }
}

:root[data-accent="indigo"],
.settings-accent--indigo {
  --accent-blue: rgb(72, 26, 218);
  --accent-blue-elevated: rgba(72, 26, 218, 0.10);
  --swatch: rgb(72, 26, 218);
}

:root[data-theme="dark"][data-accent="indigo"],
:root[data-theme="dark"] .settings-accent--indigo {
  --accent-blue: rgb(181, 167, 255);
  --accent-blue-elevated: rgba(181, 167, 255, 0.10);
  --swatch: rgb(181, 167, 255);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme])[data-accent="indigo"],
  :root:not([data-theme]) .settings-accent--indigo {
    --accent-blue: rgb(181, 167, 255);
    --accent-blue-elevated: rgba(181, 167, 255, 0.10);
    --swatch: rgb(181, 167, 255);
  }
}

:root[data-accent="purple"],
.settings-accent--purple {
  --accent-blue: rgb(132, 1, 148);
  --accent-blue-elevated: rgba(132, 1, 148, 0.10);
  --swatch: rgb(132, 1, 148);
}

:root[data-theme="dark"][data-accent="purple"],
:root[data-theme="dark"] .settings-accent--purple {
  --accent-blue: rgb(242, 137, 255);
  --accent-blue-elevated: rgba(242, 137, 255, 0.10);
  --swatch: rgb(242, 137, 255);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme])[data-accent="purple"],
  :root:not([data-theme]) .settings-accent--purple {
    --accent-blue: rgb(242, 137, 255);
    --accent-blue-elevated: rgba(242, 137, 255, 0.10);
    --swatch: rgb(242, 137, 255);
  }
}

/* Reset + base element styles. Consumes tokens.css. */

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* Active panel drag-resize (dom.mjs startPanelResize sets/clears this):
   keep the col-resize cursor everywhere the pointer wanders, stop text
   selection, and pin the resizing panels' width transitions off so they
   track the pointer 1:1. */
body.panel-resizing {
  user-select: none;
  -webkit-user-select: none;
}
body.panel-resizing * {
  cursor: col-resize !important;
}
body.panel-resizing .sidebar,
body.panel-resizing .detail-ai-panel {
  transition: none !important;
}

/* No rubber-band/overscroll bounce anywhere. html/body alone (below) only
   covers the root scroller — every inner overflow container (detail scroll,
   chat lists, sidebars, sheets) still bounces on macOS/iOS without this.
   `none` also stops scroll chaining out of a scroller that hit its edge,
   which is what makes the app feel like a fixed shell rather than a page. */
* {
  overscroll-behavior: none;
}

html, body {
  /* No background here, on purpose (2026-09-23). The app canvas lives on
     .app-shell (shell.css), not on body: html's colour is written at runtime
     by syncChromeColor (ffi/dom.mjs) to mirror the visible pane, and it is
     what shows in the iOS safe-area strip under the shell — a body fill
     would sit between the two and paint that strip the canvas colour under a
     white detail page. Pages outside the shell (boot, auth, share, join)
     keep painting their own surfaces. */
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
  overscroll-behavior: none;
  /* Ensure the body never exceeds the true visual viewport on Android */
  max-height: 100dvh;
}

a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; }

ul, ol { list-style: none; }

button {
  font: inherit;
  color: inherit;
  background: transparent;
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  padding: var(--sp-xs) var(--sp-sm);
  cursor: pointer;
}
button:hover {
  border-color: var(--border-primary);
}

:focus-visible {
  outline: 2px solid var(--bg-secondary);
  outline-offset: 2px;
}

/* Prevent iOS Safari autozoom on field focus. The browser zooms whenever a
   focused field's font-size is < 16px, so floor every text-entry surface at
   16px on mobile — native controls (input / textarea / select) AND the
   contenteditable editors (Milkdown / ProseMirror), which the bare element
   selectors miss. `!important` is required because component classes set
   sub-16px sizes that win on specificity; `max(16px, 1em)` only ever raises,
   never shrinks. The `:not()`s preserve the few intentionally-large fields
   (phone entry, reminder title) and the off-screen file-input sizing hack.
   This intentionally does not disable pinch-zoom. */
@media (max-width: 834px) {
  input:not(.auth-phone-input):not(.capture-file-input-hidden),
  textarea:not(.nr-title),
  select,
  [contenteditable="true"],
  .ProseMirror {
    font-size: max(16px, 1em) !important;
  }

  /* Placeholder text. Real ::placeholder inherits the (now ≥16px) field size;
     the editors render their placeholder as a ::before pseudo-element that
     carries its own font-size, so floor those too. */
  .chat-composer-milkdown::before,
  .capture-text-editor .ProseMirror p.is-editor-empty::before {
    font-size: max(16px, 1em) !important;
  }
}

/* Lucide-style icons rendered via CSS mask so they inherit currentColor.
   Each icon span carries a `--icon-mask` custom property pointing to an
   inline SVG data URI, and explicit width/height. See `ui/icons.gleam`. */
.icon {
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-image: var(--icon-mask);
          mask-image: var(--icon-mask);
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
  flex-shrink: 0;
  vertical-align: middle;
  line-height: 0;
  /* WebKit hit-tests against the mask alpha channel, so only painted pixels
     would register clicks. Opt out entirely — let the parent button/anchor
     be the sole click target. */
  pointer-events: none;
}

/* Brand-gradient tint through the icon mask (the mask only reads alpha,
   so a background gradient shows through the glyph). Generic sibling of
   sidebar.css's .sidebar-row-icon-gradient-green — wrap any icon:
     html.span([class("icon-gradient-green")], [icons.x(size)]) */
.icon-gradient-green .icon {
  background: var(--fill-gradient-green);
}

/* ── <waveform-tile> — 1:1 audio waveform preview ──────────────────────────
   Canvas bars draw in `currentColor`; size by setting a width on the
   element (or letting a square slot like the Activity media box or
   .tl-row-thumb size it). */
.wf-tile {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  background: var(--fill-elevated-quaternary);
  border-radius: var(--radius-md);
  color: var(--text-tertiary);
  overflow: hidden;
  pointer-events: none; /* parent anchor owns the click, like .icon */
}

/* Animated inline-SVG icons (e.g. check_hollow_animated).
   Cannot use the mask approach — browsers don't animate SVG data URIs.
   Instead the SVG is injected as real DOM via unsafe_raw_html and wrapped
   in this span so it flows like a regular .icon. */
.icon-animated-check {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  vertical-align: middle;
  line-height: 0;
  pointer-events: none;
}

.icon-animated-check svg {
  display: block;
}

.pill {
    border: none;
    font: inherit;
    cursor: pointer;
}

/* iOS DueBadgePill: bell 16 + 13/500/18 label, 4px icon gap, 8px/4px
   padding, accentBlue on accentBlueElevated capsule. */
.due-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    border-radius: var(--radius-full);
    background: var(--accent-blue-elevated);
    color: var(--accent-blue);
}

.due-pill > .due-pill-label {
    font-size: var(--text-sm);
    font-weight: 500;
    line-height: 18px;
    letter-spacing: 0;
}

/* Empty state ("Set alert"): same geometry, neutral/grey capsule. Carried
   over from feed.css's old .due-pill--empty. */
.due-pill--empty {
    background: var(--fill-elevated-quaternary);
    color: var(--text-primary);
}

/* Notifications off (due_pill.view_silenced, 2026-09-24): the toDo card
   mock's grey pill — background secondary, label-secondary bell and text.
   Built ahead of its data; nothing renders it until the server says an
   alert is muted (see the view's doc). */
.due-pill--silenced {
    background: var(--bg-secondary);
    color: var(--text-secondary);
}

/* Spoken-only text inside a pill (view_silenced's ", notifications off"). */
.due-pill-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
}
.button {
    width: 100%;
    padding: var(--sp-xs) var(--sp-md);
    background: transparent;
    /* Neutralize the UA <button> border; variants (.danger) re-add their
       own. */
    border: none;
    border-radius: var(--radius-full);
    font-family: inherit;
    font-size: var(--text-base);
    font-weight: 500;
    cursor: pointer;
}

.button.destructive {
    color: var(--accent-red);
    background: rgba(var(--color-red), .1);
    background-blend-mode: var(--blend-mode);
}

.button.danger {
    color: var(--accent-red);
    background: transparent;
    border: 1px solid var(--accent-red);
}
/* Phone + OTP authentication screen — centered card on a muted background. */

.auth-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-subtle);
  padding: var(--sp-xl);
}

.auth-card {
  width: 100%;
  max-width: 440px;
  padding: var(--sp-xxl);
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.auth-logo {
  font-family: var(--font-serif);
  font-size: var(--text-3xl);
  font-style: italic;
  font-weight: 400;
  text-align: center;
  margin: 0 0 var(--sp-xs);
  color: var(--text-primary);
}

.auth-title {
  font-size: var(--text-lg);
  font-weight: 500;
  text-align: center;
  color: var(--text-primary);
  margin: 0 0 var(--sp-xl);
}

.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
}

.auth-label {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.auth-field input,
.auth-field-input {
  padding: var(--sp-sm);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text-primary);
  font: inherit;
  outline: none;
  transition: border-color 0.15s ease;
  width: 100%;
  box-sizing: border-box;
}

.auth-field input:focus,
.auth-field-input:focus {
  border-color: var(--bg-secondary);
}

.auth-error {
  font-size: var(--text-sm);
  color: var(--accent-red);
  text-align: center;
  margin: 0;
}

.auth-submit {
  padding: var(--sp-sm);
  border: none;
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  margin-top: var(--sp-xs);
  transition: background 0.15s ease, color 0.15s ease;
}

.auth-submit:not(:disabled) {
  background: var(--fg);
  color: var(--bg);
}

.auth-submit:disabled {
  cursor: not-allowed;
}

/* Phone input */
.auth-phone-input {
  font-size: var(--text-2xl);
  text-align: center;
  letter-spacing: 0.1em;
  padding: var(--sp-md);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text-primary);
  font-family: var(--font-sans);
  width: 100%;
  box-sizing: border-box;
}
.auth-phone-input:focus { border-color: var(--bg-secondary); outline: none; }

.auth-phone-row {
  display: flex;
  gap: 8px;
  width: 100%;
}

.auth-country-button {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 var(--sp-md);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  cursor: pointer;
  font: inherit;
  white-space: nowrap;
}

.auth-country-button:hover {
  background: var(--bg-subtle);
}

.auth-country-flag {
  font-size: 18px;
  line-height: 1;
}

.auth-country-code {
  font-weight: 500;
}

.auth-country-caret {
  font-size: 10px;
  opacity: 0.6;
}

.auth-phone-row .auth-phone-input {
  flex: 1;
  min-width: 0;
}

.auth-country-picker {
  position: relative;
  margin-top: 8px;
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  max-height: 300px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  z-index: 10;
}

.auth-country-search {
  padding: 10px 12px;
  border: none;
  border-bottom: 1px solid var(--border-primary);
  font: inherit;
  outline: none;
  background: transparent;
}

.auth-country-list {
  overflow-y: auto;
  flex: 1;
}

.auth-country-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 12px;
  border: none;
  background: transparent;
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.auth-country-row:hover {
  background: var(--bg-subtle);
}

.auth-country-row-flag {
  font-size: 18px;
  line-height: 1;
}

.auth-country-row-name {
  flex: 1;
}

.auth-country-row-code {
  opacity: 0.6;
}

/* OTP display */
.auth-otp-wrapper { position: relative; }

.auth-otp-hidden-input {
  position: absolute;
  inset: 0;
  opacity: 0;
  width: 100%;
  height: 100%;
  font-size: 24px;
}

.auth-otp-display {
  display: flex;
  gap: var(--sp-sm);
  justify-content: center;
}

.auth-otp-box {
  width: 48px;
  height: 56px;
  border: 2px solid var(--border-primary);
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-2xl);
  font-weight: 500;
  color: var(--text-primary);
  background: var(--bg);
}
.auth-otp-box-active { border-color: var(--bg-secondary); }

/* OTP sub-text */
.auth-phone-hint {
  font-size: var(--text-sm);
  color: var(--text-primary);
  text-align: center;
  margin-top: var(--sp-xs);
}

.auth-resend {
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  text-align: center;
  margin-top: var(--sp-sm);
}
.auth-resend-btn {
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  font: inherit;
  padding: 0;
}
.auth-resend-btn:hover { text-decoration: underline; }

/* Interest picker */
.auth-interest-form {
  gap: var(--sp-md);
}

.auth-subtitle {
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  text-align: center;
  margin: 0 0 var(--sp-xs);
}

.auth-interest-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-xs);
  justify-content: center;
  min-height: 80px;
}

.auth-interest-loading {
  text-align: center;
  color: var(--text-tertiary);
  font-size: var(--text-sm);
  padding: var(--sp-xl) 0;
}

.auth-interest-chip {
  padding: 6px 14px;
  border-radius: 20px;
  border: 1px solid var(--border-primary);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.auth-interest-chip:hover {
  background: var(--bg-subtle);
}

.auth-interest-chip.selected {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}

.auth-interest-hint {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  text-align: center;
  margin: 0;
}

.auth-interest-buttons {
  display: flex;
  gap: var(--sp-sm);
}

.auth-interest-buttons .auth-submit {
  flex: 1;
  margin-top: 0;
}

.auth-submit-secondary {
  background: transparent;
  color: var(--text-primary);
  border: 1px solid var(--border-primary);
}

.auth-submit-secondary:hover:not(:disabled) {
  background: var(--bg-subtle);
}

/* Back link */
.auth-back {
  display: flex;
  align-items: center;
  gap: var(--sp-3xs);
  font-size: var(--text-sm);
  color: var(--text-primary);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  margin-bottom: var(--sp-md);
}
.auth-back:hover { color: var(--text-primary); }

/* The App Store button an iPhone visitor sees above the wordmark — the way to
   the app from /open when Safari's Smart App Banner isn't there (in-app
   browsers, Chrome, a dismissed banner). The pill is .share-duplicate-btn,
   the same one the join page's "Get the app to join" uses; this only gives
   an <a> the box a <button> gets for free and stretches it to the card. */
.auth-install-btn {
  display: block;
  text-align: center;
  text-decoration: none;
  margin: 0 0 var(--sp-xl);
}

/* Shell layout styles — app-shell root, shared page-header primitives,
 * main content pane, and the mobile bottom action bar.
 * Sidebar navigation lives in sidebar.css (imported right after this file).
 */

.app-shell {
  display: flex;
  height: 100vh;                  /* fallback: no dvh support */
  height: 100dvh;                 /* fallback: dvh but no visualViewport JS */
  height: var(--vvh, 100dvh);    /* primary: set in real-time by dom.mjs from
                                     visualViewport.height — reliably shrinks when
                                     the iOS Safari keyboard appears */
  overflow: hidden;
  /* Respect the gesture-nav bar on Android and iOS notch devices */
  padding-bottom: env(safe-area-inset-bottom);
  /* The app canvas (2026-09-23): the rail and sidebar paint nothing and show
     this through; .main-pane paints bg-primary on top. --canvas-bg, not
     --bg-secondary (2026-09-25): the two are the same #F2F2F7 in light, but
     in dark bg-secondary is #1C1C1E while the canvas is iOS's #0C0C0C —
     which .settings-canvas paints, so desktop Settings (rail → main, no
     list pane) showed a seam at the rail's edge. Anything that paints
     itself to blend with the shell uses --canvas-bg too. content-box, not
     the default border-box, so the safe-area padding above stays
     transparent and the runtime-mirrored html colour shows there, as it
     did before the canvas existed. */
  background: var(--canvas-bg);
  background-clip: content-box;
}

/* Keyboard up: the on-screen keyboard covers the gesture-nav zone, but
   env() keeps reporting the inset — collapse it so bottom-anchored UI
   hugs the keyboard instead of floating above it (.kb-open is set by
   the --vvh visualViewport updater in dom.mjs). */
.kb-open .app-shell {
  padding-bottom: 0;
}

/* ── Shared header icon button ──────────────────────────────────────────
   Ghost, fully-round icon button used for every header action across
   Activity, Messages, Please AI chat, Reminders/Timeline, Profile, and
   Settings — sidebar toggle, back/close, search, compose, menu, settings.
   Desktop: 32x32 hit box / 20px icon. Mobile: 48x48 hit box / 32px icon.
   Icon calls should pass size 0 so this class controls the icon size. */

/* ── Standardized page header (ui/page_header) ─────────────────────────
   Extracted from .chat-header (chat.css) — values mirrored exactly.
   [leading action] [centered flex:1 title] [trailing actions]; the
   leading slot swaps back-link (desktop) ↔ sidebar-toggle (≤768px). */
.page-header {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  /* Top: sp-sm + sp-xs = 20px — the same two-token stack as the sidebar
     (.sidebar 12px + .sidebar-header 8px), so the page header and the
     sidebar header top-align exactly. Page roots must not add their own
     top padding above this. */
  padding: calc(var(--sp-sm) + var(--sp-xs)) var(--sp-md) 0;
  flex-shrink: 0;
  min-height: 68px;
}

/* Action slots: equal flex share on both sides keeps the title truly
   centered regardless of how many actions each side holds; an empty slot
   still occupies its share. min-width reserves at least one icon-button
   footprint. */
.page-header-slot {
  flex: 1 1 0;
  min-width: 32px;
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
}
.page-header-slot--left {
  justify-content: flex-start;
}
.page-header-slot--right {
  justify-content: flex-end;
}

.page-header-title {
  flex: 0 1 auto;
  min-width: 0;
  text-align: center;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.page-header-title-link {
  text-decoration: none;
  cursor: pointer;
}
.page-header-title-link:hover {
  text-decoration: none;
  color: var(--accent-blue);
}

/* Viewport pinning: the sidebar toggle is mobile drawer chrome — hidden on
   desktop, shown ≤768px. Scoped under .page-header — an unscoped single
   class ties with .header-icon-btn's `display: inline-flex` on specificity
   and loses on source order, which made "hidden" actions render anyway. */
.page-header .page-header-sidebar-toggle {
  display: none;
}

@media (max-width: 834px) {
  .page-header .page-header-sidebar-toggle {
    display: inline-flex;
  }

  .page-header-title {
    font-size: var(--text-2xl);
  }
}

.header-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text-primary);
  text-decoration: none;
  cursor: pointer;
  /* Fade the ghost background in on hover/focus and back out on leave,
     instead of snapping instantly. */
  transition: background-color 150ms ease, color 150ms ease;
}

.header-icon-btn:hover,
.header-icon-btn:focus-visible {
  background: var(--bg-subtle);
  color: var(--text-primary);
  text-decoration: none;
}

/* Quick fade-down on press for tactile feedback. */
.header-icon-btn:active {
  background: var(--bg-subtle);
  transition-duration: 60ms;
}

.header-icon-btn .icon {
  width: 20px;
  height: 20px;
}

@media (max-width: 834px) {
  .header-icon-btn {
    width: 48px;
    height: 48px;
  }

  .header-icon-btn .icon {
    width: 24px;
    height: 24px;
  }
}

/* Main content pane beside the sidebar */
.main-pane {
  flex: 1;
  min-width: 0;
  min-height: 0;
  height: 100%;
  overflow: hidden;
  /* The one surface that is bg-primary (2026-09-23): the shell is the
     canvas, the rail and sidebar are transparent on it, and the content
     pane is the white sheet lifted off it — the mock's only white. */
  background: var(--bg-primary);
}

/* ── Breakpoint hosts ─────────────────────────────────────────────────────
   The home route renders two main-pane bodies and lets CSS pick one: the
   timeline page on mobile (where the sidebar drawer holds nav only) and
   the new-chat landing on desktop (where the timeline lives in the
   sidebar). Both hosts fill the pane so the child owns its own scroll. */
.main-mobile-only,
.main-desktop-only {
  height: 100%;
  min-height: 0;
}

@media (min-width: 835px) {
  .main-mobile-only {
    display: none;
  }
}

@media (max-width: 834px) {
  .main-desktop-only {
    display: none;
  }
}

/* ── Mobile action bar (search + create) ─────────────────────────────────
   Search-pill + create-button styling for the persistent bottom bar shown
   on the Timeline view only (mobile). Desktop keeps Search/New Reminder in
   the sidebar, so this stays hidden above the mobile breakpoint.

   Fixed positioning over the scrolling item list lives in feed.css
   (`.feed .mobile-action-bar`), not here. */

.mobile-action-bar {
  display: none;
}

@media (max-width: 834px) {
  .mobile-action-bar {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-sm);
    padding: var(--sp-lg) var(--sp-md) calc(var(--sp-xl) + env(safe-area-inset-bottom));
  }

  .mobile-action-bar-search {
    flex: 1;
    min-width: 0;
    max-width: 350px;
    display: flex;
    align-items: center;
    gap: var(--sp-xs);
    height: 48px;
    padding: 0 var(--sp-md);
    border-radius: var(--radius-full);
    border: 1px solid var(--border-secondary);
    background: var(--bg-70);
    color: var(--text-primary);
    backdrop-filter: blur(4px);
    text-decoration: none;
  }
  @media (hover: hover) {
    .mobile-action-bar-search:hover {
      text-decoration: none;
    }
  }

  .mobile-action-bar-search-label {
    font-size: var(--text-lg);
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .mobile-action-bar-create {
    flex-shrink: 0;
    width: 48px;
    height: 48px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-full);
    border: none;
    background: var(--fill-gradient-green);
    color: #ffffff;
    cursor: pointer;
    padding: 0;
  }
  @media (hover: hover) {
    .mobile-action-bar-create:hover {
      filter: brightness(1.05);
    }
  }
}

@media (max-width: 834px) {
  .mobile-action-bar-search {
    height: var(--size-5xl);
  }
  .mobile-action-bar-create {
    height: var(--size-5xl);
    width: var(--size-5xl);
  }
}  

/* Sidebar navigation — desktop persistent rail / mobile slide-in drawer.
 * Extracted from shell.css for granularity (shell.css now holds only the
 * app-shell root, the shared page-header/header-icon-btn primitives, the
 * main content pane, and the mobile bottom action bar).
 *
 * Responsive breakpoints:
 *   Mobile  : max-width 834px  — sidebar hidden, slide-in drawer
 *   Desktop : min-width 835px  — beside the rail, 406–480px wide; collapsed =
 *             display:none (the rail's header then offers the expand button)
 */

/* ── Mobile nav backdrop ──────────────────────────────────────────────── */

.mobile-nav-backdrop {
  display: none;
}

.sidebar {
  /* --sidebar-width is the drag-resize override (absolute px, persisted;
     set inline by client.view_sidebar and live-updated by the resize FFI
     during a drag). The mobile drawer's own width rules below ignore it. */
  /* 406 default and minimum (2026-09-24): room for the toDo card's 370px
     min-width inside the list's 16px inset each side (the mock's, 2026-09-25;
     it was the pane's 10px padding plus the list's 8px), which leaves the
     card 374 at the default. Was 320 default / 395 max, which left cards
     284px wide. */
  width: var(--sidebar-width, 406px);
  max-width: 480px;
  /* No background: like the rail, the sidebar is canvas and shows the
     shell's --canvas-bg through. The one exception is the mobile drawer below,
     which slides OVER the white main pane and has to paint itself. */
  flex-shrink: 0;
  border-right: 1px solid var(--border-tertiary);
  display: flex;
  flex-direction: column;
  /* 10px horizontal padding: the mobile drawer's. Desktop drops it — the
     lists carry the pane's 16px inset themselves (see the min-width: 835px
     block below). */
  padding: var(--sp-sm) 10px;
  gap: var(--sp-md);
  box-sizing: border-box;
  position: sticky;
  top: 0;
  height: 100vh;   /* fallback */
  height: 100dvh;  /* dynamic viewport — keeps sidebar flush with the shell */
  overflow-x: hidden;
  overflow-y: hidden; /* .sidebar-scroll owns scrolling; header/footer stay pinned */
  transition: width 0.3s ease-in-out, padding 0.3s ease-in-out;
}

.sidebar-section {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.sidebar-section-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: var(--sp-sm) var(--sp-xs) var(--sp-xs) var(--sp-xs);
  overflow: hidden;
  white-space: nowrap;
  opacity: 1;
  transition: opacity 0.3s ease-in-out;
  /* Pins to the top of .sidebar-scroll once scrolled up to it, so chat rows
     scroll underneath instead of the header scrolling out of view. Opaque
     the canvas colour, so rows scrolling under it are hidden — the sidebar
     itself paints nothing and shows the shell's --canvas-bg through (not
     bg-secondary, 2026-09-25: #1C1C1E against the #0C0C0C canvas in dark;
     the mobile drawer, which paints bg-secondary, overrides it below).
     Unstuck, it's visually identical to the old static header. */
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--canvas-bg);
}

.sidebar-section-header__label {
  font-size: var(--text-lg); /* 16px */
  color: var(--text-primary);
}

/* Collapsed-rail label swap: "Messages" ↔ "Msg". The short label hides by
   default; the desktop collapsed state flips both (and drops the + action,
   which has nowhere sane to live in the 72px rail). The mobile drawer
   never collapses visually, so the mobile block below restores the full
   set like the other collapsed resets. */
.sidebar-section-header__label--collapsed {
  display: none;
}

/* "+" action in a section header (e.g. Messages → new chat). */
.sidebar-section-header-action {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
}

@media (hover: hover) {
  .sidebar-section-header-action:hover {
    background: var(--fill-elevated-secondary);
  }
}

.sidebar-row {
  /* Button reset — ensures <button> rows are identical to <a> rows */
  font: inherit;
  border: none;
  background: none;
  box-sizing: border-box;
  /* Row layout */
  display: flex;
  align-items: center;
  gap: var(--sp-xxs);
  padding: 6px var(--sp-sm);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  color: var(--text-primary);
  min-height: 44px;
  text-decoration: none !important;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  /* Smooth the padding change between expanded (6px 12px) and collapsed (12px) */
  transition: padding 0.3s ease-in-out;
  position: relative;
  flex-shrink: 0;
}

@media (hover: hover) {
  .sidebar-row:hover {
  background: var(--fill-elevated-secondary);
  text-decoration: none;
  transition: .3s ease-in-out;
}
}
.sidebar-row-active {
  background: var(--fill-elevated-secondary);
  font-weight: 500;
  box-shadow: none;
}
@media (hover: hover) {
  .sidebar-row-active:hover {
  background: var(--fill-elevated-secondary);
  font-weight: 500;
  box-shadow: none;
}
}

.sidebar-row-icon {
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-primary);
  font-size: var(--text-base);
}

/* Brand-gradient icon tint (e.g. New Reminder). The base .icon paints
   currentColor through an alpha mask, so a background-image gradient
   shows through the same mask. */
.sidebar-row-icon-gradient-green .icon {
  background: var(--fill-gradient-green);
}

.sidebar-row-active .sidebar-row-icon {
  color: var(--text-primary);
}

.sidebar-label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-secondary);
  font-size: 16px;
  line-height: 21px;
  letter-spacing: -0.31px;
  max-width: 200px;
  opacity: 1;
  transition: max-width 0.3s ease-in-out, opacity 0.3s ease-in-out;
}

.sidebar-row-active .sidebar-label,
.sidebar-row-button .sidebar-label {
  color: var(--text-primary);
}

.sidebar-label--with-you {
  display: flex;
  align-items: center;
  gap: var(--sp-4xs);
}

.sidebar-label-name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  min-width: 0;
}

.sidebar-label-you {
  color: var(--text-secondary);
  font-weight: 400;
  flex-shrink: 0;
}

.sidebar-row-unread .sidebar-label {
  font-weight: 600;
  color: var(--text-primary);
}

/* Typing indicator: animated gradient sweep on the label */
.sidebar-row-typing .sidebar-label {
  font-weight: 600;
  background: radial-gradient(
    circle at 100%,
    #fda8a8,
    #f97878 50%,
    #fec7c7 75%,
    #fd8d8d 75%
  );
  background-size: 200% auto;
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: sidebar-typing-gradient 1.5s linear infinite;
}

.sidebar-unread-badge {
  display: none;
}

/* Scroll region for the reminder/chat LIST only. Nav rows, the daily
   brief, and the Reminders/Chats mode toggle are pinned above it as
   direct children of .sidebar (header/footer were always pinned) — the
   list alone fills the remaining space and scrolls. The Messages section
   header (.sidebar-section-header) sticks to the top of this container
   once scrolled up to it. */
.sidebar-scroll {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.sidebar-scroll::-webkit-scrollbar {
  display: none;
}

/* The pinned sections above the scroll region must never get crushed by
   a long list — the scroll region is the only child that flexes. */
.sidebar > .sidebar-section,
.sidebar > .sidebar-mode-header {
  flex-shrink: 0;
}

/* The nav section holds only the mobile drawer's labeled rows — on desktop
   it renders empty, so drop it entirely rather than leave its gap. */
.sidebar-nav {
  display: none;
}

@media (max-width: 834px) {
  .sidebar-nav {
    display: flex;
  }
}

/* ── Drag-resize handle (desktop) ─────────────────────────────────────
   Invisible 5px strip along the sidebar's right edge; shows a hairline
   on hover and while a drag is running. Dragging below the collapse
   threshold collapses the sidebar (see shell.ResizeEnded). Collapsed, the
   pane and this handle are display:none, so expanding is the rail header's
   button or ⌘\ — there is no edge left to drag back out. */
.sidebar-resize-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 5px;
  cursor: col-resize;
  z-index: 10;
}

@media (hover: hover) {
  .sidebar-resize-handle:hover {
    background: var(--border-primary);
  }
}

body.panel-resizing .sidebar-resize-handle {
  background: var(--border-primary);
}

/* Spacer no longer needed — .sidebar-scroll grows to fill the gap */
.sidebar-spacer {
  display: none;
}

.sidebar-footer {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-sm);
  font-size: var(--text-sm);
  color: var(--text-primary);
  /* No fill. This used to be bg-primary, which was invisible while the whole
     sidebar was bg-primary; once the drawer went to the secondary canvas
     (2026-09-23) it rendered as a 259x52 solid white (black in dark) band at
     the drawer's foot — pixel-proved at 375x812 and 834x700 nav-open. */
  flex-shrink: 0;
  transition: padding 0.3s ease-in-out, gap 0.3s ease-in-out;
}

.sidebar-ai-avatar {
  width: 22px;
  height: 22px;
  border-radius: var(--radius-full);
  background: var(--fg);
  color: var(--bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.sidebar-footer-menu-anchor {
  position: relative;
  flex: 1;
  min-width: 0;
}

.sidebar-footer-user {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  color: inherit;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
  padding: 0;
  width: 100%;
  overflow: hidden;
  transition: padding 0.3s ease-in-out;
}
@media (hover: hover) {
  .sidebar-footer-user:hover {
  text-decoration: none;
}
}

.user-menu-backdrop {
  position: fixed;
  inset: 0;
  z-index: 99;
  cursor: default;
}

.user-menu {
  position: absolute;
  bottom: calc(100% + var(--sp-xs));
  left: 0;
  min-width: 180px;
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  padding: var(--sp-3xs);
  z-index: 100;
}

.user-menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  width: 100%;
  white-space: nowrap;
  padding: var(--sp-xs) var(--sp-sm);
  border: none;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
  text-decoration: none;
  cursor: pointer;
}
@media (hover: hover) {
  .user-menu-item:hover {
  background: var(--bg-subtle);
  text-decoration: none;
}
}

/* (Feed column toggle removed — internal test UI; the feed still reads
   the persisted column count via feed_cols_ffi.) */

.sidebar-theme-toggle {
  width: 28px;
  height: 28px;
  border: none;
  background: transparent;
  color: var(--text-tertiary);
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  overflow: hidden;
  max-width: 28px;
  opacity: 1;
  transition: max-width 0.3s ease-in-out, opacity 0.3s ease-in-out;
}
@media (hover: hover) {
  .sidebar-theme-toggle:hover {
  color: var(--text-primary);
  /* Same hover fill as every other sidebar row; bg-primary was another
     white-sidebar leftover and would read as a white flash on the canvas. */
  background: var(--fill-elevated-secondary);
}
}

/* ── Mobile responsive (≤ 768px) ─────────────────────────────────────── */

/* ─── Keyframe animations ────────────────────────────────────────────── */

@keyframes sidebar-typing-gradient {
  to {
    background-position: -200% center;
  }
}

/* ─── Media queries ──────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .sidebar-row-typing .sidebar-label {
    animation: none;
  }
}

@media (max-width: 834px) {
  /* No drag-resize on the mobile drawer. */
  .sidebar-resize-handle {
    display: none;
  }

  /* Sidebar becomes an off-screen left drawer */
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 200;
    width: 280px;
    /* Always show expanded labels on mobile, ignore collapsed state */
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    box-shadow: 4px 0 32px rgba(0, 0, 0, 0.18);
    /* The drawer covers the main pane, which is bg-primary; without its own
       fill the pane's content would show straight through it. */
    background: var(--bg-secondary);
  }

  /* The sticky section header is opaque in whatever the list sits on: the
     drawer's own fill here, the canvas on desktop. */
  .sidebar-section-header {
    background: var(--bg-secondary);
  }

  .sidebar-row {
    padding: var(--sp-sm) var(--sp-md);
  }

  .sidebar-label {
    font-size: var(--text-lg);
  }

  /* Reveal the drawer */
  .app-shell.nav-open .sidebar {
    transform: translateX(0);
  }

  /* Backdrop dims the content behind the open drawer */
  .mobile-nav-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.7);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: 199;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
  }

  .app-shell.nav-open .mobile-nav-backdrop {
    opacity: 1;
    pointer-events: auto;
  }
}

/* ── Mobile-only nav rows ─────────────────────────────────────────────────
   Reminders / Messages render as labeled rows in the mobile drawer only.
   On desktop the Reminders timeline is the sidebar body itself and the
   chat list lives behind the mode toggle. The action stack (New Reminder /
   Search / New AI Chat) renders on every breakpoint. */
.sidebar-row--mobile-only {
  display: none;
}

@media (max-width: 834px) {
  .sidebar-row--mobile-only {
    display: flex;
  }
}

/* ── Sidebar mode toggle: full-width Reminders | Chats segmented pill ──────
   Replaces the static "Reminders" heading. A capsule track with two equal
   segments; the active one is a raised pill. */
.sidebar-mode-header {
  padding: var(--sp-xs) var(--sp-xs) 0;
}

.sidebar-mode-toggle {
  display: flex;
  width: 100%;
  padding: 3px;
  box-sizing: border-box;
  gap: 2px;
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
}

.sidebar-mode-segment {
  flex: 1;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xs);
  border: none;
  background: none;
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-full);
  font: inherit;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
}

@media (hover: hover) {
  .sidebar-mode-segment:hover {
    color: var(--text-primary);
  }
}

.sidebar-mode-segment--active,
.sidebar-mode-segment--active:hover {
  background: var(--bg-primary);
  color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}

/* Chats-with-unread count on the toggle — the blue read/unread language
   of the chat list, not the old red page badge. border-box keeps a
   single digit a true 16px circle (content-box let the side padding
   widen it into a subtle pill, reading as an off-center digit). */
.sidebar-mode-badge {
  box-sizing: border-box;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--accent-blue);
  color: #ffffff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
}

/* ── Sidebar chat list (Chats mode) ─────────────────────────────────────── */
.sidebar-chats {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  padding: var(--sp-sm) var(--sp-xs);
}

@media (max-width: 834px) {
  /* The drawer keeps its labeled nav rows as the primary controls; the
     heading toggle is desktop chrome. The chat list itself does render in
     the drawer — the Messages row flips the mode to reveal it. */
  .sidebar-mode-header {
    display: none;
  }

  .sidebar--chats-mode .sidebar-chats {
    display: flex;
  }

  .sidebar:not(.sidebar--chats-mode) .sidebar-chats {
    display: none;
  }
}

/* ── Sidebar bucket list (Search mode) ──────────────────────────────────
   The search surface's bucket catalog as rows: All (unscoped search),
   derived + accepted buckets, then the Suggested section. Rows follow
   .sidebar-row's visual language; the tint dot reads --bucket-tint from
   the .bucket-card-tint-N classes (styles/search.css), so a bucket wears
   the same colour here and on the mobile entry grid. */
.sidebar-buckets {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-sm) var(--sp-xs);
}

.sidebar-bucket-row {
  /* Button reset — matches .sidebar-row */
  font: inherit;
  border: none;
  background: none;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  width: 100%;
  min-height: 40px;
  padding: 6px var(--sp-sm);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  cursor: pointer;
  text-align: left;
}

@media (hover: hover) {
  .sidebar-bucket-row:hover {
    background: var(--fill-elevated-secondary);
  }
}

.sidebar-bucket-row--active {
  background: var(--fill-elevated-secondary);
}

/* All row's icon and the tint dot share a 16px slot so titles align. */
.sidebar-bucket-icon {
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-secondary);
}

.sidebar-bucket-dot {
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.sidebar-bucket-dot::before {
  content: '';
  width: 10px;
  height: 10px;
  border-radius: var(--radius-full);
  background: rgba(var(--bucket-tint), 1);
}

.sidebar-bucket-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-secondary);
  font-size: 16px;
  line-height: 21px;
  letter-spacing: -0.31px;
}

.sidebar-bucket-row--active .sidebar-bucket-title {
  color: var(--text-primary);
  font-weight: 500;
}

.sidebar-bucket-count {
  flex-shrink: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.sidebar-bucket-section-title {
  margin: var(--sp-md) 0 var(--sp-3xs);
  padding: 0 var(--sp-sm);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
}

.sidebar-bucket-suggested {
  display: flex;
  flex-direction: column;
}

/* Keep / No thanks (pill styles from search.css), indented to the title
   column: row padding + 16px slot + gap. */
.sidebar-bucket-suggested-actions {
  display: flex;
  gap: var(--sp-xs);
  padding: var(--sp-3xs) var(--sp-sm) var(--sp-xs)
    calc(var(--sp-sm) + 16px + var(--sp-xs));
}

/* ── Sidebar timeline (desktop, expanded only) ──────────────────────────── */
.sidebar-timeline {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  padding: var(--sp-md) var(--sp-xs) var(--sp-sm);
}

.sidebar-tl-section {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
  margin: 0;
}

.sidebar-tl-section-title {
  margin: var(--sp-sm) 0 0;
  /* Flush with the cards: the mock's "Today" starts on the card edge. */
  padding: 0;
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--text-primary);
}

.sidebar-timeline-empty {
  /* Vertical only: .sidebar-timeline carries the column's inset. */
  padding: var(--sp-sm) 0;
  font-size: var(--text-base);
  color: var(--text-secondary);
}

.sidebar-timeline-skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  padding: var(--sp-sm) 0;
}

@media (max-width: 834px) {
  .sidebar-timeline {
    display: none;
  }
}


/* ── Mobile drawer header: wordmark, ⌘K, close ─────────────────────────── */
.sidebar-drawer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-xs);
  flex-shrink: 0;
}

.sidebar-logo {
  color: var(--text-primary);
  line-height: 1;
  text-decoration: none;
}

@media (hover: hover) {
  .sidebar-logo:hover {
    text-decoration: none;
  }
}

.sidebar-drawer-header-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-3xs);
}

.sidebar-drawer-header .header-icon-btn {
  position: relative;
  color: var(--text-secondary);
}

/* Chrome falls through `background: transparent` buttons in hollow-icon
   areas because there's no painted surface to hit-test against. A
   near-zero-alpha overlay IS painted, so every click in bounds lands. */
.sidebar-drawer-header .header-icon-btn::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: rgba(0, 0, 0, 0.001);
}

/* ── Desktop: collapsed = hidden ──────────────────────────────────────────
   The app rail (rail.css) is the always-present column; the sidebar is
   the panel beside it — collapsed by its own header's button, expanded by
   the rail header's (2026-09-24). Nothing "folds" any more — no icon rail,
   no label fades — so a hidden sidebar costs zero chrome. The drawer
   header and footer are mobile chrome (the rail carries their jobs). */
@media (min-width: 835px) {
  .sidebar-collapsed {
    display: none;
  }

  .sidebar-drawer-header,
  .sidebar-footer {
    display: none;
  }
}

/* ── Desktop list header: the collapse button (2026-09-24, the mock) ──────
   One 56px row at the very top of the list pane — whatever it lists
   (timeline, messages, search) — holding the collapse button at its right,
   right-aligned with the cards below. Collapsed, the pane is display:none
   and the rail's header shows the expand button instead. The pane's 12px
   top padding goes on desktop so this row sits flush with the window top,
   level with the rail's own 56px header. */
.sidebar-list-header {
  display: none;
}

@media (min-width: 835px) {
  .sidebar {
    padding-top: 0;
    /* No side padding on desktop (2026-09-25): the mock insets the cards
       16px from the pane's edges, and that inset is the lists' own padding
       (.sidebar-timeline, .sidebar-chats, .sidebar-buckets below) so it sits
       INSIDE .sidebar-scroll's clip — the selected card's ring draws 4px
       outside the card and would be cut off at a padding-box edge. */
    padding-left: 0;
    padding-right: 0;
    /* Never narrower than a 370px toDo card needs (see .sidebar). */
    min-width: 406px;
    /* No divider on desktop (2026-09-24): the mock has none between the
       list pane and the main pane — canvas meets the white sheet — and
       Julian confirmed leaving it out. The mobile drawer keeps its edge:
       it slides OVER the page and needs one. */
    border-right: none;
  }

  .sidebar-list-header {
    display: flex;
    flex: 0 0 56px;
    align-items: center;
    justify-content: space-between;
    /* The headline lines up with the cards' left edge (the lists' 16px
       inset), as the button does with their right. */
    padding-left: var(--sp-md);
    /* The lists' 16px inset (the pane has no side padding on desktop), so
       the button's right edge lines up with the cards' (2026-09-25: cards
       80→454, button 414→454 at the default 406 width; was 8 on top of the
       pane's old 10px padding). */
    padding-right: var(--sp-md);
  }

  /* The list column's 16px inset from the pane's edges (the mock,
     2026-09-25), whichever list the pane shows: the timeline's cards, the
     chat list, the search buckets. 406 - 2×16 = a 374px card. */
  .sidebar-timeline,
  .sidebar-chats,
  .sidebar-buckets {
    padding-left: var(--sp-md);
    padding-right: var(--sp-md);
  }
}

/* The rail's button, in the list pane: 40px hollow, 24px glyph, label
   primary, the elevated-secondary wash on hover. */
/* "Home" / "Messages" / "Search" — which list the pane holds. */
.sidebar-list-title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 700;
  line-height: 1.2;
  color: var(--text-primary);
}

.sidebar-list-header-button {
  font: inherit;
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-md);
  color: var(--text-primary);
  transition: background-color 150ms ease;
}

.sidebar-list-header-button .icon {
  width: 24px;
  height: 24px;
}

@media (hover: hover) {
  .sidebar-list-header-button:hover {
    background: var(--fill-elevated-secondary);
  }
}

.sidebar-list-header-button:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: 1px;
}

/* Settings is rail → main on desktop (2026-09-24): no list pane beside it
   (client.view_session tags the shell). Phones keep the aside — it is their
   navigation drawer. */
@media (min-width: 835px) {
  .app-shell--no-list > .sidebar {
    display: none;
  }
}

/* The app rail left of the sidebar — see app/shell.view_rail. */
/* App rail — the narrow always-present column left of the list pane (see
   app/shell.view_rail). Desktop only: the mobile drawer carries labeled
   rows for the same actions.

   Three stacked parts (2026-09-24 spec): a 56px header, the nav column
   filling the height between, and a 56px footer. Header and footer are
   fixed rows, full rail width, so the nav's centre never moves with what
   they hold — the header's expand button comes and goes with the list
   pane, and the row stays. Replaced a 1fr/auto/1fr grid (2026-09-23) whose
   bottom group sat in the last row with 12px padding above and below. */

.app-rail {
  width: 64px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  /* No background of its own: the rail is canvas, and the canvas is the
     shell's --canvas-bg showing through. No divider either (2026-09-24):
     the mock runs one flat canvas from the rail into the list pane, and
     Julian confirmed leaving the line out. It was border-right, then an
     inset shadow — a border takes its pixel out of the content box and
     put every 40px button at x = 11.5, half-pixel soft. With no line at
     all, the full 64px is content and the buttons sit on x = 12. */
  z-index: 20;
}

.app-rail-header,
.app-rail-footer {
  flex: 0 0 56px;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The nav spans everything between header and footer and centres its
   items both ways. 36px between the 40px buttons is the spec's gap — a
   76px pitch, centre to centre. */
.app-rail-nav {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 36px;
}

/* 40px round buttons, 24px glyphs, label primary for every item. The
   current page is the filled drawing (Search keeps its hollow one) on a
   raised disc — revised 2026-09-24: the filled glyph alone didn't read as
   a state. Fully round (100px radius) like the New Reminder circle; the
   relative position anchors the alert badge. */
.app-rail-button {
  /* Button + anchor reset */
  font: inherit;
  border: none;
  background: transparent;
  text-decoration: none;
  cursor: pointer;
  padding: 0;
  position: relative;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 100px;
  color: var(--text-primary);
  transition: background-color 150ms ease;
}

.app-rail-button .icon {
  width: 24px;
  height: 24px;
}

@media (hover: hover) {
  .app-rail-button:hover {
    background: var(--fill-elevated-secondary);
    text-decoration: none;
  }
}

/* After the hover rule so the current page keeps its disc under the
   pointer too. Background elevated primary, not background primary
   (2026-09-25): the two are the same white in light, but in dark
   bg-primary is #000, which all but vanished (about 1.07:1) once the shell
   became the #0C0C0C canvas; it had read at about 1.23:1 on the old
   #1C1C1E shell. Elevated primary is iOS's raised #1C1C1E, about 1.15:1
   on the canvas. */
.app-rail-button[aria-current="page"] {
  background: var(--bg-elevated-primary);
}

/* Alert badge (2026-09-24): 12px, accent red, the count in label primary
   inverse — Messages' unread chats (DMs, replies, mentions). A 12px circle
   for one digit, a capsule past that ("99+" caps it). Its corner sits on
   the 24px glyph's top-right (the glyph spans 8–32 of the 40px button). */
.app-rail-badge {
  position: absolute;
  top: 4px;
  left: 24px;
  box-sizing: border-box;
  min-width: 12px;
  height: 12px;
  padding: 0 2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 100px;
  background: var(--accent-red);
  color: var(--text-primary-inverse);
  font-size: 9px;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.app-rail-button:focus-visible,
.app-rail-new:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: 1px;
}

/* New Reminder: a 32px accent circle inside a 40px slot, so the one
   creative action keeps the column's 40px rhythm. The thick plus (lg art)
   at 20px in label-primary-inverse. The accent is web's default blue —
   there is no persisted user accent on web yet. */
.app-rail-slot {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.app-rail-new {
  font: inherit;
  border: none;
  padding: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent-blue);
  color: var(--text-primary-inverse);
  cursor: pointer;
}

.app-rail-new .icon {
  width: 20px;
  height: 20px;
}

@media (max-width: 834px) {
  .app-rail {
    display: none;
  }
}

/* Feed view styles — tabs, item cards, grid, loading/empty/error states. */

/* ---------- Pull-to-refresh indicator ---------- */

/*
 * #feed-ptr-indicator sits as the first child of .feed-items-wrap.
 * Its height is driven by JS during the gesture (inline style). When the
 * gesture completes, JS removes inline styles and Gleam adds --active.
 * The indicator collapses back to height:0 when --active is absent.
 */
.feed-ptr-indicator {
  width: 100%;
  height: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: height 0.25s ease, opacity 0.2s ease, margin-bottom 0.25s ease;
  opacity: 0;
  /* Idle at height:0 the indicator still occupies a flex slot, so the
     wrap's gap rendered a phantom --sp-xl between the header and the
     first card. Cancel that gap while idle; --active restores it so the
     spinner keeps its spacing during a refresh. */
  margin-bottom: calc(-1 * var(--sp-xl));
}

/* JS sets --pulling during the drag; Lustre sets --active while the
   manifest refresh is in flight. Both restore the gap slot the idle
   negative margin cancels. */
.feed-ptr-indicator--pulling {
  margin-bottom: 0;
}

.feed-ptr-indicator--active {
  height: 52px;
  opacity: 1;
  margin-bottom: 0;
}

/* Spinner ring — pure CSS, no external assets. */
.feed-ptr-spinner {
  width: 22px;
  height: 22px;
  border: 2.5px solid var(--border-primary);
  border-top-color: var(--text-secondary);
  border-radius: 50%;
  animation: feed-ptr-spin 0.7s linear infinite;
}

@keyframes feed-ptr-spin {
  to { transform: rotate(360deg); }
}

/*
 * During the drag phase JS mutates `style.height` and `style.opacity`
 * directly on .feed-ptr-indicator, bypassing the CSS transition so the
 * motion tracks the finger in real time.
 *
 * On release with a confirmed refresh:
 *   1. onRefresh() fires → Lustre queues a re-render with --active
 *   2. One rAF later, JS clears the inline styles
 *   3. Lustre's render has already applied --active (height: 52px), so no
 *      visual jump occurs — the indicator stays pinned at 52px
 *   4. When ManifestRefreshed clears pull_refreshing, Lustre removes --active
 *      and the CSS transition collapses height back to 0
 *
 * `feed-ptr-indicator--ready` is toggled by JS when drag >= threshold.
 * It brightens the spinner to signal "release to refresh".
 */
.feed-ptr-indicator.feed-ptr-indicator--ready .feed-ptr-spinner {
  border-top-color: var(--text-primary);
}

/* Full-page wrapper: hosts the standardized page header (full width,
   own inset) above the column-constrained .feed body. */
.feed-page {
  height: 100%;
  background: var(--bg-secondary);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.feed {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  flex-direction: column;
  padding: var(--sp-xs) var(--sp-xl) 0 var(--sp-xl);
  width: 54vw;
  max-width: 900px;
  margin: 0 auto;
  overflow: hidden;
}

/* Search entry below the header — shares .search-input's chrome (border,
   radius, bg, padding); these overrides make the <a> behave as a static
   pill rather than a live input. */
.feed-search-pill {
  flex: 0 0 auto !important;
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  color: var(--text-tertiary);
  text-decoration: none;
  cursor: pointer;
  margin-bottom: var(--sp-md);
  height: 48px;
}
@media (hover: hover) {
  .feed-search-pill:hover {
  text-decoration: none;
  border-color: var(--border-primary);
}
}

@media (min-width: 835px) {
    .feed-search-pill {
        width: 100%;
        max-width: 800px;
    }
}

/* Mobile: search lives in the bottom action bar — no pill under the header. */
@media (max-width: 834px) {
  .feed-search-pill {
    display: none;
  }
}

/* ---------- Grid ---------- */

.feed-items-wrap {
  flex: 1;
  min-height: 0;
  width: 100%;
  overflow-y: auto;
  /* Explicit x-axis lock: when overflow-y is non-visible, CSS computes
     overflow-x as `auto` unless we say otherwise — on iOS that means the
     container can scroll horizontally if any child is 1px too wide.
     `touch-action: pan-y` tells iOS to only recognise vertical panning here
     so the PTR gesture doesn't open a sideways scroll context. */
  overflow-x: hidden;
  touch-action: pan-y;
  display: flex;
  flex-direction: column;
  gap: var(--sp-xl);
  padding-bottom: var(--sp-xl);
  scrollbar-width: none;
  -ms-overflow-style: none;
}
/* Timeline sections: header + its own masonry grid, stacked in the wrap. */
.feed-section {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

/* Desktop: constrain sections (centered by .feed-items-wrap--timeline). */
@media (min-width: 835px) {
  .feed-section {
    width: 100%;
    max-width: 800px;
  }
}

/* iOS `sectionHeading`: 24/700/33.6, text-primary, zero tracking. */
.feed-section-title {
  margin: 0;
  font-size: 24px;
  font-weight: 700;
  line-height: 140%;
  letter-spacing: 0;
  color: var(--text-primary);
}

.feed-items-wrap::-webkit-scrollbar {
  display: none;
}

.feed-grid {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: var(--sp-xl);
}

.feed-col {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  gap: var(--sp-xl);
}

/* 4-col needs more room */
.feed--cols-4 {
  width: 80vw;
  max-width: 80vw;
}

/* ---------- Card ---------- */

/* Wrapper: inner grid drives the collapse animation (grid-template-rows: 1fr → 0fr).
   No margin-bottom needed — gap on .feed-grid handles row spacing. */
.feed-card-wrapper {
  display: grid;
  grid-template-rows: 1fr;
}

/* Collapse siblings upward once the card has flown out.
   The 1-minute delay lets the user see the completed state (and undo if
   needed) before the card disappears. Collapse starts just as the flyout
   is finishing: 60s card delay + 230ms into the 260ms flyout. */
.feed-card-wrapper:has(.feed-card--completing) {
  animation: feed-card-collapse 300ms calc(60s + 230ms) ease-in-out forwards;
}

@keyframes feed-card-collapse {
  to {
    grid-template-rows: 0fr;
    margin-bottom: 0;
  }
}

@keyframes feed-card-flyout-up {
  to { transform: translateY(-115%); opacity: 0; }
}

/* All completing cards fly upward — column position isn't knowable via CSS
   selectors in a multi-column layout, so we use a single consistent direction. */
.feed-card--completing {
  animation: feed-card-flyout-up 260ms 60s ease-in forwards;
}

.feed-card {
  width: 100%;
  display: inline-flex;
  flex-direction: column;
  background: var(--bg-primary);
  border: none;
  border-radius: 16px;
  overflow: hidden; /* clips media image to card's rounded corners */
  transition: transform 0.15s ease;
  break-inside: avoid;
  min-height: 0; /* required for grid-template-rows collapse */
}

@media (hover: hover) {
  .feed-card:hover {
  transform: translateY(-1px);
}
}

/* Text cards use the same chrome-free style as photo/bookmark cards.
   When a thumbnail is present it shows above the title just like a photo. */
.feed-card-text {
  color: var(--text-primary);
}

/* The anchor wraps only the media area. The card body (title, author, likes)
   sits as a sibling outside the anchor so the like button doesn't trigger
   modem's anchor-intercepted navigation. */
.feed-card-link {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  position: relative;
}
@media (hover: hover) {
  .feed-card-link:hover {
  text-decoration: none;
}
}

/* ---------- Media area ---------- */

.feed-card-media {
  position: relative;
  display: flex;
  align-items: flex-end;
  width: 100%;
  padding: var(--sp-sm);
  color: #ffffff;
  font-size: var(--text-xs);
  overflow: hidden;
  /* No border-radius here — the card's overflow:hidden + border-radius:16px
     clips the media to rounded corners at the top automatically. */
}

.feed-card-media-photo {
  padding: 0;
}
.feed-card-media-voice {
  aspect-ratio: 4 / 3;
}
.feed-card-media-bookmark {
  aspect-ratio: 16 / 9;
  min-width: 100%;
  min-height: 120px;
}

/* Tiles with no media (calendar tiles; text tiles without a thumbnail)
   reserve a slim band when a manifest badge is present, so the
   absolute-positioned `.feed-badge-pill` has room to sit without overlapping
   the card body. The pill is ~32px tall plus its top inset, so 48px clears it. */
.feed-card-badge-spacer {
  height: 48px;
  width: 100%;
}

.feed-card-photo-placeholder {
  width: 100%;
  height: 100%;
}

/* Deterministic gradients for placeholders */
.feed-card-gradient-1 { background: linear-gradient(135deg, #667eea, #764ba2); }
.feed-card-gradient-2 { background: linear-gradient(135deg, #f093fb, #f5576c); }
.feed-card-gradient-3 { background: linear-gradient(135deg, #4facfe, #00f2fe); }
.feed-card-gradient-4 { background: linear-gradient(135deg, #43e97b, #38f9d7); }
.feed-card-gradient-5 { background: linear-gradient(135deg, #fa709a, #fee140); }
.feed-card-gradient-6 { background: linear-gradient(135deg, #30cfd0, #330867); }

.feed-card-voice-duration,
.feed-card-bookmark-site {
  background: rgba(0, 0, 0, 0.5);
  padding: var(--sp-3xs) var(--sp-xs);
  border-radius: var(--radius-md);
  backdrop-filter: blur(4px);
  font-weight: 500;
}

.feed-card-voice-icon {
  position: absolute;
  top: var(--sp-sm);
  left: var(--sp-sm);
  background: rgba(0, 0, 0, 0.5);
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(4px);
  color: #fff;
}

/* ---------- Header (avatar + name) ---------- */

.feed-card-header {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-xs);
  padding: var(--sp-sm);
  text-decoration: none;
  color: var(--text-tertiary);
  min-width: 0;
  overflow: hidden;
}
@media (hover: hover) {
  .feed-card-header:hover {
  text-decoration: none;
  color: var(--text-primary);
}
}

.feed-card-header-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.feed-card-header-name {
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: inherit;
}

.feed-card-header-timestamp {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  line-height: 1.3;
}

/* ---------- Body (title row + footer) ---------- */

.feed-card-body {
  padding: var(--sp-sm) var(--sp-xs);
  width: 100%;
}

/* Title row: completion checkbox + title link side by side */
.feed-card-title-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-xs);
}

/* Title link fills remaining width */
.feed-card-title-link {
  flex: 1;
  min-width: 0;
  text-decoration: none;
  color: inherit;
}
@media (hover: hover) {
  .feed-card-title-link:hover {
  text-decoration: none;
}
}

.feed-card-title {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.3;
  color: var(--text-primary);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Completed state: struck-through, lighter weight and color */
.feed-card-title--done {
  text-decoration: line-through;
  font-weight: 400;
  color: var(--text-tertiary);
}

/* Event time line shown above the title on calendar tiles. Sits inside
   the title link so it inherits the same hit area as the title. */
.feed-card-event-time {
  display: block;
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-3xs);
}

/* Calendar metadata row: source-calendar name badge under the title.
   Lives inside `.feed-card-title-link` so it inherits the title's
   left indent automatically. */
.feed-card-meta-row {
  display: flex;
  margin-top: var(--sp-3xs);
}
.feed-card-meta-badge {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  background: var(--bg-secondary);
  padding: var(--sp-3xs) var(--sp-xs);
  border-radius: var(--radius-md);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Completion checkbox button */
.feed-card-complete-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-top: 1px; /* optical alignment with first line of title */
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--text-tertiary);
  font-family: inherit;
}
@media (hover: hover) {
  .feed-card-complete-btn:hover {
  color: var(--text-primary);
}
}
.feed-card-complete-btn--done {
  color: var(--color-primary, var(--accent));
}
@media (hover: hover) {
  .feed-card-complete-btn--done:hover {
  color: var(--color-primary, var(--accent));
}
}

/* Footer: timestamp left, engagement actions right */
/* Please-agent comment preview on the tile. */
.feed-card-agent {
  margin: var(--sp-xs) 0 0;
  padding: var(--sp-xs) var(--sp-sm);
  background: var(--surface-secondary, rgba(127, 127, 127, 0.06));
  border-radius: var(--radius-sm, 8px);
}
.feed-card-agent-label {
  display: block;
  font-size: var(--text-2xs, 0.625rem);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent-blue);
  margin-bottom: 2px;
}
.feed-card-agent-text {
  margin: 0;
  font-size: var(--text-xs);
  line-height: 1.4;
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.feed-card-agent-pending {
  font-size: var(--text-xs);
  font-style: italic;
  color: var(--text-tertiary);
}

.feed-card-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-top: var(--sp-sm);
}

.feed-card-footer-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
}

/* Comments count */
.feed-card-comments {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3xs);
  color: var(--text-secondary);
  flex-shrink: 0;
}

.feed-card-comments-icon {
  display: inline-flex;
  align-items: center;
}

.feed-card-comments .icon {
  width: 13px;
  height: 13px;
}

.feed-card-comments-count {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

/* Bookmark button */
.feed-card-bookmark-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3xs);
  color: var(--text-secondary);
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  flex-shrink: 0;
}
@media (hover: hover) {
  .feed-card-bookmark-btn:hover {
  color: var(--text-primary);
}
}
.feed-card-bookmark-btn--active {
  color: var(--color-primary, var(--accent));
}
@media (hover: hover) {
  .feed-card-bookmark-btn--active:hover {
  color: var(--color-primary, var(--accent));
}
}
.feed-card-bookmark-btn .icon {
  width: 14px;
  height: 14px;
}

/* Likes */
.feed-card-likes {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3xs);
  color: var(--text-secondary);
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  flex-shrink: 0;
}
@media (hover: hover) {
  .feed-card-likes:hover {
  color: var(--text-primary);
}
}
.feed-card-likes-active {
  color: var(--accent-red);
}
@media (hover: hover) {
  .feed-card-likes-active:hover {
  color: var(--accent-red);
}
}

.feed-card-likes-icon {
  display: inline-flex;
  align-items: center;
  color: var(--text-secondary);
}

.feed-card-likes-count {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.feed-card-likes .icon {
  width: 13px;
  height: 13px;
}

/* ---------- States ---------- */

.feed-loading,
.feed-empty,
.feed-error {
  padding: var(--sp-xxl) 0;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  text-align: center;
}
.feed-error {
  color: var(--accent-red);
}

/* Real images in cards — absolutely positioned so they fill the media
   container without competing with overlay siblings (e.g. bookmark site
   badge) in the flex layout. The parent's overflow:hidden + border-radius
   handles corner clipping. */
.feed-card-img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.feed-card-media-text {
  aspect-ratio: 1;
  overflow: hidden;
  width: 100%;
  padding: 0;
}

/* ---------- Badge pill ---------- */

.feed-badge-pill {
  position: absolute;
  top: var(--sp-sm);
  left: var(--sp-sm);
  z-index: 2;
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.4;
  white-space: nowrap;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
  background: var(--bg-70);
  color: var(--text-primary);
  /* border: 1px solid var(--border-primary); */
  width: fit-content;
  backdrop-filter: blur(12px);
}

.feed-card-calendar .feed-badge-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3xs);
}

.feed-card-calendar .feed-badge-pill::before {
  content: '';
  display: inline-block;
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 xmlns=%22http://www.w3.org/2000/svg%22%3E %3Cg clip-path=%22url(%23clip0_17956_8822)%22%3E %3Cpath d=%22M8.94719 14.0525C8.94751 10.1868 12.081 7.05296 15.9467 7.05276C19.8124 7.05282 22.9459 10.1866 22.9464 14.0522C22.9461 17.918 19.8119 21.0525 15.9461 21.0525C12.0806 21.052 8.94744 17.918 8.94719 14.0525ZM13.267 9.41761C11.9629 10.303 11.1607 11.6717 10.9324 13.1377L10.7741 12.8935C10.5787 12.5917 10.1817 12.5092 9.88702 12.7092C9.59259 12.9094 9.51248 13.3158 9.7076 13.6177L10.9007 15.4642C11.0961 15.7657 11.4934 15.847 11.788 15.6471L13.5891 14.4244C13.8837 14.2242 13.9646 13.8173 13.7693 13.5154C13.5739 13.2139 13.1768 13.1313 12.8822 13.3311L12.1495 13.8272C12.2035 12.532 12.8405 11.2816 13.9744 10.5117C15.8561 9.2345 18.3932 9.76175 19.64 11.6895C20.8862 13.6174 20.3705 16.2158 18.4891 17.4931C17.1863 18.3773 15.569 18.3973 14.282 17.6836C13.9712 17.5114 13.583 17.6298 13.4145 17.9479C13.2463 18.2664 13.3615 18.6648 13.6723 18.8373C15.3588 19.7727 17.4837 19.7496 19.1964 18.5871C21.6675 16.9096 22.3443 13.4966 20.7073 10.9648C19.0698 8.43296 15.7382 7.74014 13.267 9.41761ZM4.04413 3.97441C3.97353 3.56673 4.24666 3.1788 4.65408 3.1075C5.0622 3.03673 5.45075 3.31015 5.52152 3.71827L5.68865 4.67945L11.0449 3.74977L10.8786 2.78805C10.808 2.38009 11.0814 1.99142 11.4894 1.92061C11.897 1.85032 12.2857 2.1239 12.3568 2.53138L12.5228 3.49445L13.4054 3.34309C14.7569 3.10909 15.9553 4.10536 16.1831 5.41884C16.2537 5.82678 15.9789 6.21513 15.571 6.28601C15.1633 6.35622 14.7752 6.08241 14.7044 5.6747C14.5978 5.06188 14.0845 4.74664 13.6602 4.82021L3.61189 6.5628C3.18756 6.63644 2.81138 7.10668 2.9177 7.71982L3.16715 9.16415L8.42199 8.2759C8.82999 8.20714 9.21678 8.48097 9.28618 8.88883C9.35514 9.29698 9.08047 9.6842 8.67244 9.75355L3.41029 10.6431L4.42524 16.4958C4.50337 16.9448 4.94649 17.2691 5.43981 17.1837L7.85129 16.7655C8.25931 16.6948 8.64792 16.9682 8.71873 17.3762C8.78909 17.7839 8.51635 18.1721 8.10878 18.2431L5.69595 18.6611C4.41368 18.8831 3.172 18.0443 2.94784 16.752L1.71726 9.65628C1.70941 9.61091 1.70563 9.56528 1.706 9.52082L1.43841 7.97486C1.21116 6.6617 2.00349 5.31987 3.3544 5.08512L4.21044 4.93612L4.04413 3.97441Z%22 fill=%22black%22/%3E %3C/g%3E %3Cdefs%3E %3CclipPath id=%22clip0_17956_8822%22%3E %3Crect width=%2224%22 height=%2224%22 fill=%22white%22/%3E %3C/clipPath%3E %3C/defs%3E %3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 xmlns=%22http://www.w3.org/2000/svg%22%3E %3Cg clip-path=%22url(%23clip0_17956_8822)%22%3E %3Cpath d=%22M8.94719 14.0525C8.94751 10.1868 12.081 7.05296 15.9467 7.05276C19.8124 7.05282 22.9459 10.1866 22.9464 14.0522C22.9461 17.918 19.8119 21.0525 15.9461 21.0525C12.0806 21.052 8.94744 17.918 8.94719 14.0525ZM13.267 9.41761C11.9629 10.303 11.1607 11.6717 10.9324 13.1377L10.7741 12.8935C10.5787 12.5917 10.1817 12.5092 9.88702 12.7092C9.59259 12.9094 9.51248 13.3158 9.7076 13.6177L10.9007 15.4642C11.0961 15.7657 11.4934 15.847 11.788 15.6471L13.5891 14.4244C13.8837 14.2242 13.9646 13.8173 13.7693 13.5154C13.5739 13.2139 13.1768 13.1313 12.8822 13.3311L12.1495 13.8272C12.2035 12.532 12.8405 11.2816 13.9744 10.5117C15.8561 9.2345 18.3932 9.76175 19.64 11.6895C20.8862 13.6174 20.3705 16.2158 18.4891 17.4931C17.1863 18.3773 15.569 18.3973 14.282 17.6836C13.9712 17.5114 13.583 17.6298 13.4145 17.9479C13.2463 18.2664 13.3615 18.6648 13.6723 18.8373C15.3588 19.7727 17.4837 19.7496 19.1964 18.5871C21.6675 16.9096 22.3443 13.4966 20.7073 10.9648C19.0698 8.43296 15.7382 7.74014 13.267 9.41761ZM4.04413 3.97441C3.97353 3.56673 4.24666 3.1788 4.65408 3.1075C5.0622 3.03673 5.45075 3.31015 5.52152 3.71827L5.68865 4.67945L11.0449 3.74977L10.8786 2.78805C10.808 2.38009 11.0814 1.99142 11.4894 1.92061C11.897 1.85032 12.2857 2.1239 12.3568 2.53138L12.5228 3.49445L13.4054 3.34309C14.7569 3.10909 15.9553 4.10536 16.1831 5.41884C16.2537 5.82678 15.9789 6.21513 15.571 6.28601C15.1633 6.35622 14.7752 6.08241 14.7044 5.6747C14.5978 5.06188 14.0845 4.74664 13.6602 4.82021L3.61189 6.5628C3.18756 6.63644 2.81138 7.10668 2.9177 7.71982L3.16715 9.16415L8.42199 8.2759C8.82999 8.20714 9.21678 8.48097 9.28618 8.88883C9.35514 9.29698 9.08047 9.6842 8.67244 9.75355L3.41029 10.6431L4.42524 16.4958C4.50337 16.9448 4.94649 17.2691 5.43981 17.1837L7.85129 16.7655C8.25931 16.6948 8.64792 16.9682 8.71873 17.3762C8.78909 17.7839 8.51635 18.1721 8.10878 18.2431L5.69595 18.6611C4.41368 18.8831 3.172 18.0443 2.94784 16.752L1.71726 9.65628C1.70941 9.61091 1.70563 9.56528 1.706 9.52082L1.43841 7.97486C1.21116 6.6617 2.00349 5.31987 3.3544 5.08512L4.21044 4.93612L4.04413 3.97441Z%22 fill=%22black%22/%3E %3C/g%3E %3Cdefs%3E %3CclipPath id=%22clip0_17956_8822%22%3E %3Crect width=%2224%22 height=%2224%22 fill=%22white%22/%3E %3C/clipPath%3E %3C/defs%3E %3C/svg%3E");
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-position: center;
          mask-position: center;
}

/* ---------- Mobile header (hidden on desktop) ---------- */

/* ---------- Mobile FAB (hidden on desktop) ---------- */

.feed-fab {
  display: none;
}

/* ── Mid-desktop (769px – 1440px) ────────────────────────────────────── */

@media (min-width: 835px) and (max-width: 1440px) {
  .feed {
    width: 68vw;
    max-width: 68vw;
    padding-left: var(--sp-xs);
    padding-right: var(--sp-xs);
  }
}

/* ── Mobile responsive (≤ 768px) ─────────────────────────────────────── */

/* ─── Media queries ──────────────────────────────────────────────────── */

@media (max-width: 834px) {
  .feed {
    width: 100%;
    max-width: 100vw;
    padding: var(--sp-xl) var(--sp-md)  var(--sp-sm);
    overflow: hidden;
  }

  .feed-items-wrap {
    gap: var(--sp-xxl);
  }

  /* Tighter gap on mobile */
  .feed-grid {
    gap: var(--sp-md);
  }

  .feed-col {
    gap: var(--sp-md);
  }

  /* Reset 4-col container override on mobile */
  .feed--cols-4 {
    width: 100%;
    max-width: 100vw;
  }

  .feed-card-title {
    font-size: 13px;
  }

      /* Notifications entry point — replaces the old top search icon now that
     search lives in the bottom mobile-action-bar (mobile_action_bar.gleam). */
  /* .feed-header-notifications sizing/color/radius/hover come from
     .header-icon-btn — only the badge-dot anchor is kept here. */
  .feed-header-notifications {
    position: relative;
  }

  .feed-header-notifications-dot {
    position: absolute;
    top: 4px;
    right: 4px;
    width: 8px;
    height: 8px;
    border-radius: var(--radius-full);
    background: var(--accent-red);
    border: 2px solid var(--bg-body);
    box-sizing: content-box;
    pointer-events: none;
  }

  /* Extra bottom padding so content isn't hidden behind the mobile
     action bar (search + create), fixed at the bottom of the viewport. */
  .feed-items-wrap {
    padding-bottom: 88px;
  }

  /* On Timeline the bar floats over the scrolling item list — fixed to
     the viewport bottom, unlike the Please AI chat's in-flow placement
     (see `.chat .mobile-action-bar` in chat.css). */
  .feed .mobile-action-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
  }
}


/* Shared by the reminders list and search — see ui/timeline_list. */
/* Timeline reminder rows (iOS TimelineListRow parity).

   Lifted out of feed.css: the reminders list and search now render the same
   rows through ui/timeline_list, so this is a shared component's stylesheet,
   not a feed-specific one. Same move .due-pill made when it stopped being
   feed-only. */


/* The Timeline tab swaps the masonry grid for a vertical list of reminder
   rows on the iOS canvas color. Only the Timeline tab gets this class —
   Saved/ForYou/Following/search/profile keep the grid. */
.feed-items-wrap--timeline {
  align-items: center;
}

.tl-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm); /* 12px between rows — distinct from the 16px
                         (--sp-md) title→content gap on .feed-section */
}

/* The toDo card (designer spec + mock, 2026-09-24): pinned 91px, 16px
   padding, [header row / meta row] with the media 12px to their right.
   91 = 16 + 25.2 (title) + 8 + 26 (meta row) + 16 — exactly iOS
   TimelineListRow's title + pill row, pinned: a title-only card keeps the
   height, so every title in a list sits on the same line. The 370px
   min-width holds in the list pane, which is sized to fit it (see the
   .sidebar rule below); elsewhere — a phone, the search pane at a narrow
   window — the card takes its column's width rather than scroll sideways. */
.tl-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  box-sizing: border-box;
  height: 91px;
  padding: 16px;
  /* iOS TimelineListRow: 24px continuous-corner card. */
  border-radius: 24px;
  background: var(--bg-primary);
  border: none;
  box-shadow: none;
  transition: background-color 150ms ease;
}

.sidebar .tl-row {
  min-width: 370px;
}

/* Whole-card affordance: animated background tint on hover. (Solid color
   → solid color interpolates fine through the tokens — unlike gradients,
   which is why the Done button needed the opacity-overlay trick.) */
@media (hover: hover) {
  .tl-row:hover {
    background: var(--fill-elevated-secondary);
  }
}

/* Never underline the card's nested anchors on hover (overrides the
   global `a:hover` underline from base.css). */
.tl-row a {
  text-decoration: none;
}
@media (hover: hover) {
  .tl-row a:hover {
  text-decoration: none;
}
}

/* Stretched link: the content anchor's ::after covers the whole card, so
   any tap that isn't on a nested action navigates to the reminder's
   detail view. Nested actions (completion circle) sit above it. */
.tl-row-content::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 24px;
}


/* ── Processing border: the comet (iOS `AnimatedLoadingComet`) ─────────
   A rotating CONIC gradient stroking the card's outline: two soft pastel
   lobes (90° each, centred at 90° and 270°) that fade in through
   transparent shoulders, sweep the perimeter and compress through the
   corners — constant angular velocity, the projection supplies the
   easing. One revolution = 3s. Two rings painted by the same wheel at the
   same angle: a 1.4px core and, behind it, a wider stroke blurred 15px at
   0.4 — the halo. Both are overlay pseudo-elements masked to a ring, so
   the card fill stays var(--bg-primary). Reduce-motion (and browsers
   without `@property`) get the static 1.5px pastel ring instead. */
@property --tl-comet-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.tl-row--processing::before,
.tl-row--processing::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 24px;
  pointer-events: none;
  background: var(--gradient-pastel);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask-composite: exclude;
}

/* Static fallback: the core ring alone, 1.5px, no halo. */
.tl-row--processing::before {
  padding: 1.5px;
}
.tl-row--processing::after {
  display: none;
}

@supports (background: conic-gradient(from var(--tl-comet-angle), #000, #fff)) {
  @media (prefers-reduced-motion: no-preference) {
    .tl-row--processing::before,
    .tl-row--processing::after {
      /* Per lobe: transparent shoulder → 45% ramp-in → full core → 45%
         ramp-out → transparent shoulder, at the iOS stop fractions
         (centre ± half, ± half·0.55). Lobe 1 centred at 90°, lobe 2 at
         270°; the gaps between them are the reveal. */
      background: conic-gradient(
        from var(--tl-comet-angle),
        transparent 45deg,
        color-mix(in srgb, var(--pastel-1) 45%, transparent) 65.25deg,
        var(--pastel-2) 90deg,
        color-mix(in srgb, var(--pastel-3) 45%, transparent) 114.75deg,
        transparent 135deg,
        transparent 225deg,
        color-mix(in srgb, var(--pastel-1) 45%, transparent) 245.25deg,
        var(--pastel-2) 270deg,
        color-mix(in srgb, var(--pastel-3) 45%, transparent) 294.75deg,
        transparent 315deg
      );
      animation: tl-comet-spin 3s linear infinite;
    }
    .tl-row--processing::before {
      padding: 1.4px;
      filter: blur(0.5px);
    }
    .tl-row--processing::after {
      display: block;
      padding: 2.4px;
      filter: blur(15px);
      opacity: 0.4;
    }
  }
}

@keyframes tl-comet-spin {
  to {
    --tl-comet-angle: 360deg;
  }
}

/* ── Unread-insight halo debut (iOS `BreathingGradientGlow` + debut) ────
   The brand pastel plane spread 4px past the card and blurred 20px,
   BEHIND the opaque card fill (a sibling under it), so only the bleed
   past the corners shows. It debuts at the crest and breathes toward the
   trough over 3.6s while its hue cycles (14s, never restarted), then
   fades out over 0.4s ease-out; the wrapper is unmounted by the tracker
   once the window elapses. */
.tl-row-wrap {
  position: relative;
}
.tl-row-glow {
  position: absolute;
  inset: -4px;
  border-radius: 28px;
  background: var(--gradient-pastel-horizontal);
  filter: blur(20px);
  pointer-events: none;
  opacity: 0.62;
  animation:
    tl-glow-breathe 3.6s ease-in-out 1 forwards,
    tl-glow-hue 14s linear infinite,
    tl-glow-fade 0.4s ease-out 3.6s 1 forwards;
}
.tl-row-wrap > .tl-row {
  position: relative;
}
@keyframes tl-glow-breathe {
  from {
    opacity: 0.62;
  }
  to {
    opacity: 0.38;
  }
}
@keyframes tl-glow-hue {
  to {
    filter: blur(20px) hue-rotate(360deg);
  }
}
@keyframes tl-glow-fade {
  to {
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .tl-row-glow {
    animation: tl-glow-fade 0.4s ease-out 3.6s 1 forwards;
  }
}

/* Completion circle: hollow ring (ui-borders) unchecked, filled check
   (text-secondary) when done. Rendered as a <button> when tappable
   (reminder present) or a <span> otherwise. */
/* Layout-only overrides for the shared completion circle
   (ui/completion_circle → .nr-completion-circle states) inside a
   timeline row: keep the unchecked ring in the row's border color. The
   header row centres it on the title line. */
.tl-row-check.nr-completion-circle {
  flex-shrink: 0;
  /* Reset .nr-completion-circle's 6px heading margin (new_reminder.css):
     left in, the header grew to 30px and the circle sat 3px below the
     title's centre — measured, 2026-09-24 review. */
  margin-top: 0;
  border-color: var(--ui-borders);
  /* Above the stretched card link so the toggle stays tappable. */
  position: relative;
  z-index: 1;
}
.tl-row-check.nr-completion-circle--done {
  border-color: transparent;
}
/* On the grey hover fill the ring's --ui-borders grey all but matched the
   card — 1.02:1 in dark mode, the ring vanished — so the ring steps up to
   label secondary there, the same step the circle takes when hovered
   directly (Julian, 2026-09-24). A selected card stays white under the
   pointer (2026-09-25), so it keeps the resting ring. */
@media (hover: hover) {
  .tl-row:not(.tl-row--selected):hover .tl-row-check.nr-completion-circle:not(.nr-completion-circle--done) {
    border-color: var(--text-secondary);
  }
}

/* Quick actions without opening (iOS TimelineListRow's swipe strips):
   pin (swipe-right tack) then delete (swipe-left), 28px discs in the
   card's top-right corner, over the media. The cluster sits above the
   title link's stretched ::after. Revealed on hover / keyboard focus
   where there is a pointer; always there on touch, which has no hover. */
.tl-row-actions {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  display: flex;
  gap: 6px;
}
.tl-row-pin,
.tl-row-delete,
.tl-row-share {
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--bg-primary);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
  cursor: pointer;
}
.tl-row-delete {
  color: var(--accent-red);
}
.tl-row-pin {
  color: var(--accent-purple);
}
.tl-row-share {
  color: var(--accent-blue);
}
@media (hover: hover) {
  .tl-row-actions {
    opacity: 0;
    transition: opacity 120ms ease;
  }
  .tl-row:hover .tl-row-actions,
  .tl-row:focus-within .tl-row-actions {
    opacity: 1;
  }
}

/* The column beside the media: header row over meta row, 8px apart. */
.tl-row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Header row: completion circle + title, 12px apart, centred on the line. */
.tl-row-header {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

/* The title's link — its ::after stretches over the whole card. */
.tl-row-content {
  flex: 1;
  min-width: 0;
  display: block;
  text-decoration: none;
  color: inherit;
}

/* iOS `bodySemibold`: 18/600/25.2, single line, tail truncation, zero
   tracking (all tokens flattened 2026-07-23). */
.tl-row-title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 140%;
  letter-spacing: 0;
  color: var(--tl-text-primary);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tl-row-title--done {
  text-decoration: line-through;
}

/* Titleless capture (iOS `NewCapturePhase`): the dated media label wears
   the agent shimmer while a run is expected or live, in the title's own
   type; plain secondary text once the server has given up. */
.tl-row .tl-row-title--placeholder .agent-status-line-text {
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
  letter-spacing: inherit;
}
.tl-row-title--untitled {
  color: var(--text-secondary);
}

/* ── Meta row (2026-09-24) ──────────────────────────────────────────────
   Pinned 26px, children 4px apart: the notification pill (styles/pill.css,
   .due-pill — already 26px), the member pile (26px faces), the agent
   slot. Pills never shrink; the agent preview is the one child that
   truncates. */
.tl-row-meta {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 26px;
  min-width: 0;
  overflow: hidden;
  /* Everything in the row is inert, and the card's link is the stretched
     ::after underneath. Without this, the pile's masked faces (each its own
     stacking context) sat above that ::after and swallowed clicks. */
  pointer-events: none;
}
/* Tight row (a phone with a long day label, a pile and the circle): the
   pile and the circle keep their size and the due pill's label truncates —
   the circle used to be the child that shrank, down to a sliver. */
.tl-row-meta > .shared-pile,
.tl-row-meta > .tl-row-agent--circle {
  flex-shrink: 0;
}
.tl-row-meta > .due-pill {
  min-width: 0;
}
.tl-row-meta > .due-pill > .due-pill-label {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The agent slot, a 26px capsule on background secondary: the 16px agent
   glyph 4px from one line of text — label secondary for the preview. */
.tl-row-agent {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  box-sizing: border-box;
  height: 26px;
  min-width: 0;
  padding: 0 8px;
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  color: var(--text-secondary);
}
.tl-row-agent-glyph {
  display: inline-flex;
  flex-shrink: 0;
}
.tl-row-agent-text {
  min-width: 0;
  font-size: var(--text-sm); /* 13px, the due pill's size */
  font-weight: 400;
  line-height: 18px;
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* With siblings: the spec's COUNT pill, standing in as a 26px circle with
   the gradient glyph until the server carries a per-item comment count. */
.tl-row-agent--circle {
  width: 26px;
  padding: 0;
  justify-content: center;
}

/* The gradient glyph: the circle, and a run in flight. */
.tl-row-agent--circle .tl-row-agent-glyph .icon,
.tl-row-agent--pending .tl-row-agent-glyph .icon {
  background: var(--gradient-pastel);
}

/* A run in flight: the working line takes the preview's place, one line,
   at the preview's 4px / 8px (agent_status.css pads the line 4px 8px for
   its own surfaces; stacked in this pill it measured 12px / 16px). */
.tl-row-agent--pending .agent-status-line {
  min-width: 0;
  padding: 0;
}
.tl-shimmer-bar--pill {
  width: 96px;
}

/* A comment that landed live this session crossfades in (iOS insight
   slot `.transition(.opacity)`, 0.25s ease-in-out). The working → comment
   edge is instant: the class only exists on rows the tracker marked. */
.tl-row-agent--landed {
  animation: tl-insight-in 0.25s ease-in-out;
}
@keyframes tl-insight-in {
  from {
    opacity: 0;
  }
}

/* iOS `ShimmerBar`: a faint track with a brighter band sweeping across
   one way (1.2s linear, no autoreverse). */
.tl-shimmer-bar {
  position: relative;
  overflow: hidden;
  height: 10px;
  border-radius: 5px;
  background: var(--fill-elevated-tertiary);
}
.tl-shimmer-bar::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--text-primary) 14%, transparent) 50%,
    transparent 100%
  );
  transform: translateX(-100%);
  animation: tl-shimmer-sweep 1.2s linear infinite;
}
@keyframes tl-shimmer-sweep {
  to {
    transform: translateX(100%);
  }
}
@media (prefers-reduced-motion: reduce) {
  .tl-shimmer-bar::after {
    animation: none;
    transform: none;
    opacity: 0.5;
  }
}

/* Thumbnail slot: 42×42, cover, front card tilted 8° with a soft shadow
   (iOS TimelineThumbnailDeck — the multi-card fan is not ported yet; the
   tilt on the single visible card is). Radius 8, iOS's 8/42 and the
   mock's (was 12 until 2026-09-24). 12px from the column beside it (the
   row gap). */
.tl-row-thumb {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  border-radius: 8px;
  overflow: hidden;
  display: block;
  transform: rotate(8deg);
  box-shadow: 0 1.5px 3px rgba(0, 0, 0, 0.18);
  /* The 3px on the left keeps the spec's 12px to the TILTED tile: an 8°
     turn swings a 42px square's corners 2.7px past its box (measured 9.3
     without it) — iOS pads its media box for the same overhang. The 4px on
     the right keeps the corner inside the card's padding. */
  margin: 2px 4px 0 3px;
}
.tl-thumb-img,
.tl-thumb-media {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Calendar date tile (calendar items without artwork) — iOS
   CalendarDateBadge and the 2026-09-24 mock: a white tile with a 1px
   border, the short weekday in bold red (side × 0.22 ≈ 9px) over the day
   number (side × 0.52 ≈ 22px), 2px tucked. Was a grey month/day badge. */
.tl-thumb-calendar {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  border: 1px solid var(--ui-borders);
  border-radius: inherit;
  background: var(--bg-primary);
}
.tl-thumb-cal-weekday {
  font-size: 9px;
  font-weight: 700;
  line-height: 11px;
  color: var(--accent-red);
}
.tl-thumb-cal-day {
  margin-top: -2px;
  font-size: 22px;
  font-weight: 400;
  line-height: 26px;
  color: var(--text-primary);
}

@media (max-width: 834px) {
  .feed-section {
    width: 100%;
  }
  .tl-list {
    width: 100%;
  }
}

/* ── The list pane's list ───────────────────────────────────────────────
   Same card as everywhere (2026-09-24 — the denser 10px / 15px-title
   sidebar variant is gone); only the gap between cards differs. */
.tl-list--compact {
  gap: var(--sp-xxs, 4px);
}

/* Selected: the open detail route's card (the mock, 2026-09-25). The card
   keeps its white fill and wears a 1px accent ring 3px outside its edge,
   following its corners — under the pointer too, where an unselected card
   takes the grey hover fill. Measured in mock3/mock4's selected "Today"
   card: 1.1px of ink, clear of the card by 3.2 top, 2.6 bottom, 3.8 left,
   5.1 right — the mock's ring box is wider than the card (about 4.5 each
   side against 2.9 top and bottom), not just shifted. An outline takes one
   offset on every side, so the vertical 3px sets it: the side gaps run
   about 1.5px tighter than the mock's. The grey fill was the 2026-09-24
   "for now" choice.

   An outline, so nothing moves and it draws outside the card: its corners
   follow the card's 24px radius out by the offset, concentric (27.5 at the
   stroke's centre; the mock's corner is tighter, ~24), and it paints
   over the insight glow (a sibling behind the card) and around the
   processing comet (pseudo-elements inside the card's edge) without
   touching either. The list pane's 16px inset leaves it room inside
   .sidebar-scroll's clip. Cards sit 6px apart (.tl-list--compact's
   --sp-xxs), so the ring's outer edge stays 2px clear of a neighbour. */
.tl-row--selected,
.tl-row--selected:hover {
  background: var(--bg-primary);
  outline: 1px solid var(--accent-blue);
  outline-offset: 3px;
}

/* An always-shown day with nothing in it: "Nothing due today" (iOS
   TimelineListPresentation's empty Today/Tomorrow rows). */
.tl-empty-day {
  margin: 0;
  padding: var(--sp-sm) var(--sp-md);
  color: var(--text-tertiary);
  font-size: var(--text-base);
}

/* A row's share menu (iOS ShareFromSwipeMenu): the row's title over the
   share pair. */
.tl-share-menu__title {
  margin: 0;
  padding: var(--sp-lg) var(--sp-lg) var(--sp-xs);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The member pile on shared reminders — see ui/shared_badge. */
/* The member pile on a shared reminder — see ui/shared_badge (iOS
   SharedReminderBadgeView parity). Avatars stack at the MIDPOINT (each
   face's edge lands on the centre of the one under it), trailing on top,
   each wearing a host-coloured halo OUTSIDE its edge so the pile punches
   out of whatever it sits on. The halo colour is inherited from the host
   via --pile-ring (defaults to the surface background). */

.shared-pile {
  display: inline-flex;
  align-items: center;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  color: inherit;
  --pile-ring: var(--bg-primary);
}

.shared-pile--button {
  cursor: pointer;
  border-radius: var(--radius-full);
}
.shared-pile--button:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: 2px;
}

.shared-pile-avatar {
  display: inline-flex;
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--pile-ring);
  flex-shrink: 0;
}
.shared-pile-avatar + .shared-pile-avatar {
  /* Midpoint stack: overlap by half the diameter. The inline width is
     set by the view; a negative margin of half of it is the overlap. */
  margin-left: calc(var(--pile-overlap, 13px) * -1);
}

/* Invited: dimmed, still in colour. Departed: desaturated and dimmed. */
.shared-pile-avatar--invited {
  opacity: 0.7;
}
.shared-pile-avatar--departed {
  opacity: 0.7;
  filter: saturate(0);
}

/* On a toDo card (2026-09-24 spec): the stack "cuts through" to the card.
   Not a ring in the card's colour — a hovered or selected card is the
   translucent elevated fill, and a ring in that colour stacks a second
   tint over the face beneath. Instead each face under another is masked
   away where the next one lands (its centre sits at this face's right
   edge — the midpoint stack), 1px wider than the face, so whatever the
   card paints shows through in every state and theme. */
.tl-row .shared-pile {
  --pile-ring: transparent;
}
.tl-row .shared-pile-avatar:not(:last-child) {
  -webkit-mask: radial-gradient(circle at 100% 50%, transparent 14px, #000 14.5px);
          mask: radial-gradient(circle at 100% 50%, transparent 14px, #000 14.5px);
}

/* The detail meta bar: the pile sits between the author pill and the nav. */
.detail-meta-pile {
  display: inline-flex;
  align-items: center;
  margin-left: var(--sp-sm);
  --pile-overlap: 16px;
}

/* The Collaborators sheet — see app/detail/view_collab. */
/* The Collaborators sheet — see app/detail/view_collab (iOS
   CollaboratorsSheet parity): one list, three states; the avatar's
   dressing (ui/shared_badge presence) and the row's own subtitle carry
   the state, not headers. Sheet chrome comes from sheet.css. */

.slide-up-sheet.collab-sheet {
  padding-bottom: env(safe-area-inset-bottom);
}

/* House sheet header: semibold body, centred, 24 below the grabber. */
.collab-sheet__title {
  margin: 24px 0 var(--sp-lg);
  text-align: center;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
}

.collab-sheet__list {
  display: flex;
  flex-direction: column;
  max-height: min(60vh, 520px);
  overflow-y: auto;
}

.collab-row {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  padding: var(--sp-sm) var(--sp-md);
  width: 100%;
  border: none;
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  text-align: left;
}
.collab-row--busy {
  opacity: 0.5;
  pointer-events: none;
}
.collab-row--action {
  cursor: pointer;
  color: var(--accent-blue);
}
.collab-row--action:hover {
  background: var(--bg-secondary);
}
/* The link row sits below the people, deliberately: the one grant with no
   face on it. A hairline above marks it as a different kind of thing. */
.collab-row--link {
  border-top: 1px solid var(--bg-tertiary);
  margin-top: var(--sp-xs);
  padding-top: var(--sp-md);
  flex-wrap: wrap;
}

.collab-row__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}
.collab-row__name {
  font-size: var(--text-md);
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Footnote 13/18 at label-secondary (2026-09-08 spec). */
.collab-row__sub {
  font-size: 13px;
  line-height: 18px;
  color: var(--text-secondary);
}

/* The worded pill, matching the chat menu's member row: height IS the
   contract (40) with 16 on the sides, body regular on the accent pair. */
.collab-pill {
  height: 40px;
  padding: 0 var(--sp-md);
  border-radius: var(--radius-full);
  border: none;
  background: var(--bg-tertiary);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-md);
  cursor: pointer;
  flex-shrink: 0;
}
.collab-pill--danger {
  background: rgba(var(--color-red), 0.12);
  color: var(--accent-red);
}
.collab-pill:disabled {
  opacity: 0.5;
  cursor: default;
}

.collab-sheet__note {
  margin: var(--sp-xs) var(--sp-md) 0;
  font-size: 13px;
  line-height: 18px;
  color: var(--text-secondary);
}

/* The invitation banner at the top of an invited viewer's detail page. */
.collab-invite-banner {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  padding: var(--sp-md);
  margin-bottom: var(--sp-md);
  border-radius: var(--radius-lg);
  background: var(--accent-blue-elevated);
  color: var(--accent-blue);
  flex-wrap: wrap;
}
.collab-invite-banner .collab-row__name {
  color: var(--text-primary);
}
.collab-pill--primary {
  background: var(--accent-blue);
  color: var(--bg-primary);
}
.collab-pill--muted {
  background: transparent;
  color: var(--text-secondary);
}

/* The collab-invite card in chat: the content card plus an action strip.
   The whole card is one tap surface (tapping joins / opens); Leave is the
   one separate pill. */
.chat-att-collab {
  display: flex;
  flex-direction: column;
  cursor: pointer;
}
.chat-att-collab--inert {
  cursor: default;
}
.chat-att-collab-row {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
}
.chat-att-collab-strip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-sm);
  margin-top: var(--sp-xs);
  padding-top: var(--sp-xs);
  border-top: 1px solid var(--bg-tertiary);
}
.chat-att-collab-label {
  font-size: 13px;
  line-height: 18px;
  color: var(--accent-blue);
}
.chat-att-collab--inert .chat-att-collab-label {
  color: var(--text-secondary);
}
.chat-att-collab-strip .collab-pill {
  height: 32px;
}

/* Agent working-status line — iOS AgentStatusShimmerText parity.

   A single line of muted text with a brighter band sweeping through it
   once per cycle (one-way marquee, never a bounce — a bounce reads as a
   broken loading bar; see the iOS component's grammar notes). Web port:
   the gradient is painted through background-clip:text and slid with
   background-position, which approximates the mask-band original within
   one declaration.

   Sizes: `.agent-status-line` is the chat variant (agentStatus token:
   18/400/25.2, −0.43px tracking — the only token that kept tracking);
   `--tile` is the timeline-row variant (metadata: 14/400/20, 1 line). */

.agent-status-line {
  display: flex;
  align-items: center;
  min-width: 0;
  padding: var(--sp-3xs) var(--sp-xs);
}

.agent-status-line-text {
  font-size: 18px;
  font-weight: 400;
  line-height: 140%;
  letter-spacing: -0.43px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: transparent;
  background-image: linear-gradient(
    90deg,
    var(--text-secondary) 0%,
    var(--text-secondary) 35%,
    var(--text-primary) 50%,
    var(--text-secondary) 65%,
    var(--text-secondary) 100%
  );
  background-size: 250% 100%;
  background-repeat: no-repeat;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  /* Band travel: fully off one end to fully off the other (no reverse),
     1.5s linear with the iOS 0.35s lead-in; keyed re-mounts on text swap
     restart the fade below. */
  animation:
    agent-status-sweep 1.5s linear 0.35s infinite,
    agent-status-fade-in 0.25s ease-in-out;
}

/* iOS `AgentStatusShimmerText(size: .tile)`: footnote 13/18, one line. */
.agent-status-line--tile .agent-status-line-text {
  font-size: 13px;
  line-height: 18px;
  letter-spacing: 0;
}

@keyframes agent-status-sweep {
  from {
    background-position: 100% 0;
  }
  to {
    background-position: 0% 0;
  }
}

@keyframes agent-status-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* Reduce-motion: park at full opacity, no sweep (iOS parity). */
@media (prefers-reduced-motion: reduce) {
  .agent-status-line-text {
    animation: none;
    color: var(--text-secondary);
    background: none;
  }
}

/* Agent suggestion pills — iOS ChatSuggestionPills parity.

   Not chips in a row: a vertical stack of full-width capsule buttons,
   16px gaps, accent-blue label on the accent-blue-elevated tonal fill
   (the tonal pair reads as an offered action rather than as another
   surface). A leading model-emitted emoji is split out with a controlled
   12px gap so stray whitespace can't leak into layout; the emoji keeps
   its own colors. Capsule (not fixed radius) so a two-line suggestion
   stays fully rounded. Accept-only — there is no dismiss. */

.suggestion-pills {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
  box-sizing: border-box;
  padding-top: var(--sp-sm);
}

/* AI threads: the message text is indented past the row's avatar column
   (28px .chat-message-avatar-slot + --sp-xs row gap); the pills render
   outside any row, so give them the same left inset — their outer edge
   lines up with the text's left edge. Right edges already coincide
   (ai-bare rows stretch to the container edge). */
.chat--ai .suggestion-pills {
  padding-left: calc(28px + var(--sp-xs));
}

.suggestion-pill {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 16px;
  border: none;
  border-radius: var(--radius-full);
  background: var(--accent-blue-elevated);
  color: var(--accent-blue);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: transform 120ms ease;
}

@media (hover: hover) {
  .suggestion-pill:hover:not(:disabled) {
    transform: scale(1.02);
  }
}

.suggestion-pill:active:not(:disabled) {
  transform: scale(1.04);
}

.suggestion-pill:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Sized to the AI-chat message text (--text-md) so accepted suggestions
   don't read louder than the conversation they join. */
.suggestion-pill-emoji {
  flex-shrink: 0;
  font-size: var(--text-md);
  line-height: 140%;
}

.suggestion-pill-label {
  min-width: 0;
  font-size: var(--text-md);
  font-weight: 600;
  line-height: 140%;
}

/* The daily-brief card at the top of the Timeline — see ui/daily_brief_card. */
/* Daily-brief card (ui/daily_brief_card — iOS DailyBriefCardRow parity).

   Rendered at the very top of the Timeline. Shares the timeline rows'
   card chrome (radius, --bg-primary fill, hover tint) but none of their
   affordances: the whole card is a tap target that opens the brief's
   pre-seeded AI chat, plus one dismiss button.

   The glow is the brand pastel spread and blurred BEHIND the card — a
   separate absolutely-positioned sibling under the opaque card fill, so
   only the bleed past the corners shows (a shadow, not the border-ring
   treatment processing rows use). Breathing at full emphasis while the
   brief is unread; halved and still once readAt lands. */

.brief-card-wrap {
  position: relative;
  width: 100%;
  flex-shrink: 0;
}

/* Match .feed-section's desktop constraint so the brief sits flush with
   the sections below it (.feed-items-wrap--timeline centers children). */
@media (min-width: 835px) {
  .brief-card-wrap {
    max-width: 800px;
  }
}

.brief-card-glow {
  position: absolute;
  inset: -4px;
  border-radius: 20px;
  background: var(--gradient-pastel);
  filter: blur(20px);
  pointer-events: none;
}

/* Unread: full emphasis — the halo breathes around the static midpoint. */
.brief-card-glow--unread {
  opacity: 0.5;
  animation: brief-glow-breathe 3.2s ease-in-out infinite;
}

/* Read: halved, still. Seen state never changes the visuals. */
.brief-card-glow--read {
  opacity: 0.25;
}

@keyframes brief-glow-breathe {
  0%,
  100% {
    opacity: 0.38;
  }
  50% {
    opacity: 0.62;
  }
}

/* The card itself paints over the glow (positioned sibling above). */
.brief-card {
  position: relative;
  display: flex;
  align-items: flex-start;
  border-radius: 16px;
  background: var(--bg-primary);
  transition: background-color 150ms ease;
}

@media (hover: hover) {
  .brief-card:hover {
    background: var(--fill-elevated-secondary);
  }
}

/* The tap target: everything except the dismiss button. */
.brief-card-main {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 16px 12px;
  background: none;
  border: none;
  border-radius: 16px;
  cursor: pointer;
  text-align: left;
  color: inherit;
  font: inherit;
}

/* The rows' completion-circle slot: 24px box, so the brief's text column
   starts on the same x as every reminder title beneath it. */
.brief-card-glyph {
  flex-shrink: 0;
  width: 24px;
  display: flex;
  justify-content: center;
  font-size: 18px;
  line-height: 25px;
}

.brief-card-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.brief-card-title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 600;
  line-height: 140%;
  letter-spacing: -0.43px;
  color: var(--tl-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Primary, unlike the rows' secondary preview: for a brief this line IS
   the message, not a peek at something you open to read. */
.brief-card-subtitle {
  margin: 0;
  font-size: 15px;
  line-height: 140%;
  color: var(--tl-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.brief-card-more {
  padding-top: 4px;
  font-size: 13px;
  color: var(--text-secondary);
}

.brief-card-dismiss {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: 12px 12px 0 0;
  padding: 0;
  background: none;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  color: var(--text-secondary);
  transition: background-color 150ms ease, color 150ms ease;
}

@media (hover: hover) {
  .brief-card-dismiss:hover {
    background: var(--fill-elevated-secondary);
    color: var(--accent-red);
  }
}

/* Detail screen — single item view with header, body, and right-side rail. */

.detail {
  display: flex;
  flex-direction: column;
  height: 100vh;   /* fallback */
  height: 100dvh;  /* dynamic viewport */
  overflow: hidden;
  position: relative; /* stacking context for the nav toast */
}

.detail-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 0 var(--sp-xl);
}

.detail-content {
  display: flex;
  flex-direction: column;
  /* 100%, not the old 64vw: a VIEWPORT fraction inside a pane that is the
     viewport minus rail and sidebar defeated the auto margins at every width
     below ~1172px — the column pinned left, overflowed .detail-scroll and
     clipped the prev/next chevrons (12px gone at 1100, both gone at 900).
     100% + max-width is the only pair that is centred at every width. */
  width: 100%;
  /* 740, centred: the editable column is capped at what an eye can scan in
     one pass (2026-09-23). Was 720 — nearly home-body's cap already, and
     the two should read as one measure. */
  max-width: 740px;
  margin: 0 auto;
}

/* Header: full width with border below, outside the grid. */
.detail-header {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-md) var(--sp-xl);
  flex-shrink: 0;
}

.detail-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-md);
}

.detail-meta-nav {
  display: flex;
  gap: var(--sp-3xs);
}

/* Sidebar toggle: hidden on desktop, replaces back button on mobile */
.detail-header-sidebar-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  color: var(--text-primary);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 0;
}
.detail-header-sidebar-toggle:hover {
  background: var(--bg-subtle);
  color: var(--text-primary);
}

.detail-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-md);
  color: var(--text-primary);
  text-decoration: none;
  font-size: var(--text-lg);
}
.detail-back:hover {
  background: var(--bg-subtle);
  color: var(--text-primary);
  text-decoration: none;
}

.detail-author-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
}

.detail-author-avatar-link {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  text-decoration: none;
  color: inherit;
}

.detail-author-info {
  display: flex;
  flex-direction: column;
}

.detail-author-name {
  font-size: var(--text-lg);
  font-weight: 400;
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--text-primary);
}

.detail-author-timestamp {
  font-size: var(--text-base);
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--text-secondary);
}

/* keep .detail-author-type selectable for any other consumers */
.detail-author-type {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: 500;
}

.detail-archived-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--text-secondary);
  background: var(--bg-tertiary-inverse);
  font-size: var(--text-xs);
  padding: 2px var(--sp-xs);
  border-radius: var(--radius-full);
}

.detail-nav-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  background: transparent;
  color: var(--text-primary);
  font-size: var(--text-xl);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.detail-nav-btn:hover {
  background: var(--bg-secondary);
  color: var(--text-primary);
}
.detail-nav-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.detail-nav-btn:disabled:hover {
  background: transparent;
  color: var(--text-quarternary);
}

.detail-nav-menu {
  margin-left: var(--sp-xs);
  font-size: var(--text-2xl);
}

/* Wrapper for the ellipsis menu button + dropdown */
.detail-menu-wrapper {
  position: relative;
}

/* Ellipsis dropdown menu */
.detail-menu-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 50;
  margin-top: var(--sp-3xs);
  padding: var(--sp-3xs);
  background: var(--bg-primary);
  border-radius: var(--radius-lg);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  min-width: 240px;
  display: flex;
  flex-direction: column;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.detail-menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  width: 100%;
  padding: var(--sp-xs) var(--sp-sm);
  border: none;
  background: transparent;
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-family: inherit;
  text-align: left;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.detail-menu-item:hover {
  background: var(--bg-subtle);
}

.detail-menu-item-danger {
  color: var(--accent-red);
}
.detail-menu-item-danger:hover {
  background: var(--danger-subtle, var(--bg-subtle));
}

/* Delete confirmation dialog */
.detail-confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
}
/* Tap-out-to-cancel layer (the timeline row's delete confirm passes one). */
.detail-confirm-backdrop {
  position: absolute;
  inset: 0;
}
.detail-confirm-dialog {
  position: relative;
  background: var(--bg);
  border-radius: var(--radius-lg);
  padding: var(--sp-xl);
  max-width: 360px;
  width: 90%;
  box-shadow: var(--shadow-lg, 0 8px 32px rgba(0,0,0,.2));
}
.detail-confirm-title {
  font-weight: 600;
  font-size: 1.1rem;
  margin: 0 0 var(--sp-xs);
}
.detail-confirm-body {
  color: var(--text-tertiary);
  margin: 0 0 var(--sp-lg);
  font-size: 0.9rem;
}
.detail-confirm-actions {
  display: flex;
  gap: var(--sp-sm);
  justify-content: flex-end;
}
.detail-confirm-btn {
  padding: var(--sp-xs) var(--sp-md);
  border-radius: var(--radius-md);
  border: none;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
}
.detail-confirm-cancel {
  background: var(--bg-subtle);
  color: var(--text-primary);
}
.detail-confirm-cancel:hover {
  background: var(--bg-hover, var(--bg-subtle));
}
.detail-confirm-delete {
  background: var(--accent-red);
  color: white;
}
.detail-confirm-delete:hover {
  opacity: 0.9;
}

/* Body column */
.detail-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-lg);
  min-width: 0;
}

.detail-hero {
  width: 100%;
  overflow: hidden;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}

.detail-hero-photo,
.detail-hero-voice,
.detail-hero-bookmark {
  width: -webkit-fill-available;
  height: auto;
  aspect-ratio: 4 / 3;
  
}

.detail-hero-text {
  aspect-ratio: 1;
  max-width: 400px;
}

.detail-hero-img {
  width: -webkit-fill-available;
  display: block;
  height: auto;
  object-fit: cover;
}

.detail-hero-zoomable {
  cursor: zoom-in;
  transition: opacity 120ms ease;
}

.detail-hero-zoomable:hover {
  opacity: 0.9;
}

.detail-hero-text,
.detail-hero-photo,
.detail-hero-voice,
.detail-hero-bookmark,
.detail-hero-img {
  border-radius: var(--radius-lg);
}

.detail-hero-voice .detail-hero-play-icon,
.detail-hero-voice .detail-hero-duration,
.detail-hero-bookmark .detail-hero-site,
.detail-hero-bookmark .detail-hero-external {
  color: #fff;
}

.detail-hero-play-icon {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 28px;
  backdrop-filter: blur(8px);
}

/* Click-to-preview thumbnail for a single video capture (view_hero.gleam's
   `video_hero`) — same visual footprint as the plain photo hero, plus a
   centered play badge (reusing `.detail-hero-play-icon`'s look) signalling
   the tap opens the fullscreen video viewer instead of playing inline. */
.detail-hero-video-btn {
  display: block;
  position: relative;
  width: 100%;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
}

.detail-hero-video-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 64px;
  height: 64px;
  border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(8px);
}

.detail-hero-duration {
  position: absolute;
  bottom: var(--sp-sm);
  right: var(--sp-sm);
  background: rgba(0, 0, 0, 0.5);
  padding: var(--sp-3xs) var(--sp-xs);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  backdrop-filter: blur(4px);
}

.detail-hero-site {
  position: absolute;
  top: var(--sp-sm);
  left: var(--sp-sm);
  background: rgba(0, 0, 0, 0.45);
  padding: var(--sp-3xs) var(--sp-sm);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  backdrop-filter: blur(4px);
}

.detail-hero-external {
  position: absolute;
  top: var(--sp-sm);
  right: var(--sp-sm);
  font-size: var(--text-xl);
  background: rgba(0, 0, 0, 0.45);
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
}

.detail-text-content {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
}

.detail-title {
  font-size: var(--text-3xl);
  font-weight: 600;
  color: var(--text-primary);
  line-height: 1.2;
  margin: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  overflow: hidden;
}

.detail-title-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-xs);
  flex-wrap: wrap;
}

.detail-title-editable {
  background: transparent;
  border: none;
  resize: none;
  width: 100%;
  padding: 0;
  font-family: var(--font-sans);
  color: inherit;
  field-sizing: content;
  max-height: calc(3 * 1.25em);
  overflow: hidden;
  outline: none;
}

.detail-save-indicator {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  white-space: nowrap;
}

.detail-save-indicator-saved {
  color: var(--accent-green);
}

.detail-save-indicator-failed {
  color: var(--accent-red);
}

.detail-divider {
  border: none;
  border-top: 1px solid var(--border-primary);
  margin: var(--sp-md) 0;
}

.detail-description {
  font-size: var(--text-base);
  color: var(--label-special);
  line-height: 1.6;
  margin: 0;
}

/* Inline images inside a note body (markdown or the Milkdown editor) — keep them
   modestly sized instead of rendering at full natural resolution. */
.detail-description img,
.detail-body-editor img {
  max-width: 100%;
  max-height: 240px;
  width: auto;
  height: auto;
  border-radius: var(--radius-md, 12px);
  object-fit: contain;
}

.detail-description p {
  margin: 0 0 var(--sp-xs) 0;
}
.detail-description p:last-child {
  margin-bottom: 0;
}
/* A blank row the author made (markdown.mjs renderBodyMarkdown): one
   line's height, no extra gap, as the editor shows it. */
.detail-description p.md-blank-row {
  margin: 0;
}

.detail-description img {
  max-width: 100%;
  height: auto;
}

/* iOS has two heading sizes, both under the 32px title: `#` at .title2
   (22) and everything deeper at .title3 (20), bold (MarkdownStyleAttributes;
   `###`+ parse as H2). The browser defaults (2em = 36px) out-shouted the
   title. */
.detail-description h1,
.detail-description h2,
.detail-description h3,
.detail-description h4,
.detail-body-editor .ProseMirror h1,
.detail-body-editor .ProseMirror h2,
.detail-body-editor .ProseMirror h3,
.detail-body-editor .ProseMirror h4,
.detail-body-editor .ProseMirror h5,
.detail-body-editor .ProseMirror h6 {
  font-weight: 700;
  font-size: 20px;
  line-height: 1.3;
  letter-spacing: 0;
  color: var(--label-special);
  margin: var(--sp-sm) 0 var(--sp-3xs);
}
.detail-description h1,
.detail-body-editor .ProseMirror h1 {
  font-size: 22px;
}

.detail-description ul,
.detail-description ol {
  margin: var(--sp-xs) 0;
  padding-left: var(--sp-lg);
}
.detail-description ul { list-style: disc; }
.detail-description ol { list-style: decimal; }
.detail-description li { margin-bottom: var(--sp-3xs); }

/* ── Task list shared icon variables ────────────────────────────────────── */
/* SVG data URIs for the three checklist icons (checkbox/empty/hollow,
   checkbox/checked/filled, checklist/drag). Used as CSS mask-image so the
   icons inherit currentColor via background-color. */

:root {
  --icon-task-unchecked: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M21 18.6504C21 18.9179 21.0011 19.1639 20.9844 19.3682C20.967 19.5812 20.9268 19.8147 20.8096 20.0449C20.6418 20.374 20.374 20.6418 20.0449 20.8096C19.8147 20.9268 19.5812 20.967 19.3682 20.9844C19.1639 21.0011 18.9179 21 18.6504 21H5.34961C5.08211 21 4.83612 21.0011 4.63184 20.9844C4.41878 20.967 4.18529 20.9268 3.95508 20.8096C3.62597 20.6418 3.35817 20.374 3.19043 20.0449C3.07313 19.8147 3.03402 19.5812 3.0166 19.3682C2.99991 19.1639 3 18.9179 3 18.6504V5.34961C3 5.08211 2.99991 4.83612 3.0166 4.63184C3.03403 4.4188 3.07314 4.18526 3.19043 3.95508C3.35818 3.62597 3.62597 3.35818 3.95508 3.19043C4.18526 3.07314 4.4188 3.03403 4.63184 3.0166C4.83612 2.99991 5.08211 3 5.34961 3H18.6504C18.9179 3 19.1639 2.99991 19.3682 3.0166C19.5812 3.03402 19.8147 3.07313 20.0449 3.19043C20.374 3.35817 20.6418 3.62597 20.8096 3.95508C20.9268 4.18529 20.967 4.41878 20.9844 4.63184C21.0011 4.83612 21 5.08211 21 5.34961V18.6504ZM19.5 5.34961C19.5 5.05745 19.4998 4.88297 19.4893 4.75391C19.4794 4.63384 19.4643 4.62034 19.4727 4.63672C19.4487 4.58968 19.4103 4.55131 19.3633 4.52734C19.3797 4.53569 19.3662 4.52058 19.2461 4.51074C19.1171 4.5002 18.9426 4.5 18.6504 4.5H5.34961C5.05745 4.5 4.88297 4.5002 4.75391 4.51074C4.63384 4.52058 4.62034 4.53569 4.63672 4.52734C4.58968 4.55131 4.55131 4.58968 4.52734 4.63672C4.53569 4.62034 4.52058 4.63384 4.51074 4.75391C4.5002 4.88297 4.5 5.05745 4.5 5.34961V18.6504C4.5 18.9426 4.5002 19.1171 4.51074 19.2461C4.52058 19.3662 4.53569 19.3797 4.52734 19.3633C4.55131 19.4103 4.58968 19.4487 4.63672 19.4727C4.62034 19.4643 4.63384 19.4794 4.75391 19.4893C4.88297 19.4998 5.05745 19.5 5.34961 19.5H18.6504C18.9426 19.5 19.1171 19.4998 19.2461 19.4893C19.3662 19.4794 19.3797 19.4643 19.3633 19.4727C19.4103 19.4487 19.4487 19.4103 19.4727 19.3633C19.4643 19.3797 19.4794 19.3662 19.4893 19.2461C19.4998 19.1171 19.5 18.9426 19.5 18.6504V5.34961Z' fill='black'/%3E %3C/svg%3E");
  --icon-task-checked: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M18.6768 3C18.934 2.99998 19.1706 3.00046 19.3682 3.0166C19.5812 3.03402 19.8147 3.07313 20.0449 3.19043C20.374 3.35818 20.6418 3.62598 20.8096 3.95508C20.9268 4.1853 20.967 4.41878 20.9844 4.63184C21.0005 4.82952 21 5.06674 21 5.32422V18.6758C21 18.9333 21.0005 19.1705 20.9844 19.3682C20.967 19.5812 20.9268 19.8147 20.8096 20.0449C20.6418 20.374 20.374 20.6418 20.0449 20.8096C19.8147 20.9268 19.5812 20.967 19.3682 20.9844C19.1705 21.0005 18.9333 21 18.6758 21H5.32324C5.06597 21 4.82939 21.0005 4.63184 20.9844C4.41878 20.967 4.1853 20.9268 3.95508 20.8096C3.62598 20.6418 3.35818 20.374 3.19043 20.0449C3.07314 19.8147 3.03402 19.5812 3.0166 19.3682C3.00045 19.1705 2.99998 18.9333 3 18.6758V5.32422C2.99998 5.06676 3.00045 4.82951 3.0166 4.63184C3.03403 4.4188 3.07314 4.18527 3.19043 3.95508C3.35818 3.62598 3.62598 3.35818 3.95508 3.19043C4.18527 3.07314 4.4188 3.03403 4.63184 3.0166C4.82951 3.00045 5.06675 2.99998 5.32422 3H18.6768ZM16.0273 9.38379C15.7345 9.09097 15.2597 9.09113 14.9668 9.38379L10.9971 13.3535L9.52734 11.8838C9.23452 11.591 8.75971 11.5911 8.4668 11.8838C8.1739 12.1767 8.1739 12.6514 8.4668 12.9443L10.4668 14.9443C10.7597 15.2371 11.2345 15.2372 11.5273 14.9443L16.0273 10.4443C16.32 10.1514 16.3201 9.67662 16.0273 9.38379Z' fill='black'/%3E %3C/svg%3E");
  --icon-task-drag: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath d='M9.25 16.5C10.2165 16.5 11 17.2835 11 18.25C11 19.2165 10.2165 20 9.25 20C8.2835 20 7.5 19.2165 7.5 18.25C7.5 17.2835 8.2835 16.5 9.25 16.5Z' fill='black'/%3E %3Cpath d='M14.75 16.5C15.7165 16.5 16.5 17.2835 16.5 18.25C16.5 19.2165 15.7165 20 14.75 20C13.7835 20 13 19.2165 13 18.25C13 17.2835 13.7835 16.5 14.75 16.5Z' fill='black'/%3E %3Cpath d='M9.25 10.1504C10.2165 10.1504 11 10.9339 11 11.9004V12C11 12.9665 10.2165 13.75 9.25 13.75C8.2835 13.75 7.5 12.9665 7.5 12V11.9004C7.5 10.9339 8.2835 10.1504 9.25 10.1504Z' fill='black'/%3E %3Cpath d='M14.75 10.1504C15.7165 10.1504 16.5 10.9339 16.5 11.9004V12C16.5 12.9665 15.7165 13.75 14.75 13.75C13.7835 13.75 13 12.9665 13 12V11.9004C13 10.9339 13.7835 10.1504 14.75 10.1504Z' fill='black'/%3E %3Cpath d='M9.25 4C10.2165 4 11 4.7835 11 5.75C11 6.7165 10.2165 7.5 9.25 7.5C8.2835 7.5 7.5 6.7165 7.5 5.75C7.5 4.7835 8.2835 4 9.25 4Z' fill='black'/%3E %3Cpath d='M14.75 4C15.7165 4 16.5 4.7835 16.5 5.75C16.5 6.7165 15.7165 7.5 14.75 7.5C13.7835 7.5 13 6.7165 13 5.75C13 4.7835 13.7835 4 14.75 4Z' fill='black'/%3E %3C/svg%3E");
}

/* Shared mask helper — applies to both .md-task-drag/.md-task-checkbox spans
   and the Milkdown editor ::before pseudo-element. */
.md-task-drag, .md-task-checkbox,
.detail-body-editor .ProseMirror li[data-item-type="task"]::before {
  display: inline-block;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  background-color: var(--label-special);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
}

/* The checkbox is iOS's 20pt list marker (MarkdownStyleAttributes
   listMarkerSide); the drag handle keeps 16. */
.md-task-checkbox {
  width: 20px;
  height: 20px;
}

/* ── Task list (checklist) — read path (marked.js + transformTaskItems) ── */

.detail-description ul:has(.md-task-item),
.detail-description .md-list--tasks {
  list-style: none;
  padding-left: 0;
}

/* A plain bullet that shares its list with checklist items ("- [ ] milk"
   then "- eggs" — iOS writes exactly this, and typing "- " right under a
   checklist joins it into that list) keeps its bullet: the rule above
   strips every marker in the list, which left it bare (QA 2026-09-29). */
.detail-description ul:has(.md-task-item) > li:not(.md-task-item),
.detail-body-editor .ProseMirror ul:has(li[data-item-type="task"])
  > li:not([data-item-type="task"]) {
  list-style: disc;
  margin-left: var(--sp-lg);
}

.detail-description .md-task-item {
  display: flex;
  align-items: flex-start;
  gap: 4px;
  padding: var(--sp-3xs) 0;
  margin-bottom: 0;
  line-height: 1.5;
}

/* The share link and public item pages are read-only: nothing drags, so
   no handle (prod stress test 2026-09-29). */
.share-page .md-task-drag,
.public-item-page .md-task-drag {
  display: none;
}

.detail-description .md-task-drag {
  -webkit-mask-image: var(--icon-task-drag);
          mask-image: var(--icon-task-drag);
  margin-top: 2px;
  opacity: 0.4;
  cursor: grab;
}

.detail-description .md-task-checkbox {
  -webkit-mask-image: var(--icon-task-unchecked);
          mask-image: var(--icon-task-unchecked);
  margin-top: 2px;
}

.detail-description .md-task-item--checked .md-task-checkbox {
  -webkit-mask-image: var(--icon-task-checked);
          mask-image: var(--icon-task-checked);
}

.detail-description .md-task-item--checked {
  color: color-mix(in srgb, var(--label-special) 70%, transparent);
  text-decoration: line-through;
}

.detail-description .md-task-item--checked .md-task-drag,
.detail-description .md-task-item--checked .md-task-checkbox {
  text-decoration: none;
  background-color: var(--label-special);
}

/* ── Milkdown editor task list items (owner edit path) ─────────────────── */
/* Milkdown GFM: <li data-item-type="task" data-checked="true|false">.
   Icons are real DOM elements (.pm-task-drag, .pm-task-check) injected by
   a Decoration.widget plugin — ::before inside contenteditable doesn't
   receive pointer events reliably in WebKit. */

.detail-body-editor .ProseMirror ul:has(li[data-item-type="task"]) {
  list-style: none;
  padding-left: 0;
}

/* Layout: iOS Reminders anatomy — checkbox gutter on the LEFT, drag handle
   pinned to the row's RIGHT edge. Both buttons are absolutely positioned,
   NOT flow siblings of the text: inline contenteditable=false neighbors
   reproduce a known Chromium/WebKit bug where the native caret fails to
   render/blink next to them. Out-of-flow placement keeps the text run's
   inline layout free of non-editable neighbors on BOTH ends.
   `top: 0.75em - 8px` centers each 16px icon on the first text line
   (line-height 1.5), which reads as vertical centering on single-line
   rows and stays pinned to the first line when the text wraps. */
.detail-body-editor .ProseMirror li[data-item-type="task"] {
  position: relative;
  /* 20px checkbox + 12px gap (iOS's 34pt marker column, less its 2pt
     lead-in). */
  padding: var(--sp-3xs) 28px var(--sp-3xs) 32px;
  margin-bottom: 0;
  line-height: 1.5;
}

.detail-body-editor .ProseMirror .pm-task-check {
  position: absolute;
  left: 0;
  top: calc(var(--sp-3xs) + 0.75em - 10px);
  user-select: none;
}

.detail-body-editor .ProseMirror .pm-task-drag {
  position: absolute;
  right: 0;
  top: calc(var(--sp-3xs) + 0.75em - 8px);
  user-select: none;
}

/* Button reset — strip browser defaults so we get a clean 16×16 icon button */
.detail-body-editor .ProseMirror .pm-task-drag,
.detail-body-editor .ProseMirror .pm-task-check {
  appearance: none;
  -webkit-appearance: none;
  display: inline-block;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  padding: 0;
  margin: 0;
  border: none;
  background-color: var(--text-primary);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  cursor: pointer;
  outline: none;
}

.detail-body-editor .ProseMirror .pm-task-check {
  width: 20px;
  height: 20px;
}

.detail-body-editor .ProseMirror .pm-task-drag {
  -webkit-mask-image: var(--icon-task-drag);
          mask-image: var(--icon-task-drag);
  cursor: grab;
  opacity: 0.5;
}

.detail-body-editor .ProseMirror .pm-task-check {
  -webkit-mask-image: var(--icon-task-unchecked);
          mask-image: var(--icon-task-unchecked);
}

.detail-body-editor .ProseMirror .pm-task-check--checked {
  -webkit-mask-image: var(--icon-task-checked);
          mask-image: var(--icon-task-checked);
}

/* iOS MarkdownStyleAttributes: strikethrough at 70% — not italic. */
.detail-body-editor .ProseMirror li[data-item-type="task"][data-checked="true"] {
  color: color-mix(in srgb, var(--label-special) 70%, transparent);
  text-decoration: line-through;
}

/* Visual feedback during pointer drag-to-reorder: the row itself rides
   under the pointer (inline translateY set by the NodeView) as a lifted
   card. pointer-events:none is load-bearing — trackDrop's elementFromPoint
   must see the rows BENEATH the lifted one. */
.detail-body-editor .ProseMirror li.task-dragging {
  backdrop-filter: blur(2px);
  pointer-events: none;
  position: relative;
  z-index: 5;
  background: rgba(var(--color-blue), .4);
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  opacity: 0.95;
  will-change: transform;
}
.detail-body-editor .ProseMirror li.task-drop-above {
  border-top: 2px solid var(--accent-blue);
}
.detail-body-editor .ProseMirror li.task-drop-below {
  border-bottom: 2px solid var(--accent-blue);
}

.detail-description code {
  font-family: "SF Mono", "Fira Code", monospace;
  font-size: 0.9em;
  background: rgba(0, 0, 0, 0.06);
  padding: 1px 4px;
  border-radius: var(--radius-sm);
}
[data-theme="dark"] .detail-description code { background: rgba(255, 255, 255, 0.1); }
[data-theme="light"] .detail-description code { background: rgba(0, 0, 0, 0.06); }

.detail-description pre {
  background: rgba(0, 0, 0, 0.06);
  padding: var(--sp-sm);
  border-radius: var(--radius-md);
  overflow-x: auto;
  margin: var(--sp-xs) 0;
}
[data-theme="dark"] .detail-description pre { background: rgba(255, 255, 255, 0.08); }
[data-theme="light"] .detail-description pre { background: rgba(0, 0, 0, 0.06); }
.detail-description pre code { background: none; padding: 0; }

.detail-description blockquote {
  border-left: 3px solid var(--border-primary);
  padding-left: var(--sp-sm);
  color: var(--text-secondary);
  margin: var(--sp-xs) 0;
}

.detail-description strong { font-weight: 600; color: var(--label-special); }
.detail-description em { font-style: italic; }

.detail-description a {
  color: var(--accent-blue);
  text-decoration: underline;
}

/* Owner-only mount point for the Milkdown body editor. Milkdown injects
 * .milkdown > .ProseMirror once initialised. Mirrors the chat composer's
 * styling pattern (chat.css §"Milkdown rich-text composer") but scaled
 * to detail-page typography so the editable surface visually matches
 * `.detail-description` (the non-owner read path). */
.detail-body-editor {
  min-height: 1.6em;
  cursor: text;
  outline: none;
}

.detail-body-editor .milkdown {
  all: unset;
  display: block;
  width: 100%;
}

.detail-body-editor .ProseMirror {
  outline: none;
  font-family: inherit;
  font-size: var(--text-base);
  color: var(--label-special);
  line-height: 1.6;
  margin: 0;
  padding: 0;
  white-space: pre-wrap;
  word-break: break-word;
}

.detail-body-editor .ProseMirror p.is-editor-empty:first-child::before,
.detail-body-editor .ProseMirror:empty::before {
  content: "Add a description…";
  float: left;
  height: 0;
  color: var(--text-tertiary);
  pointer-events: none;
}

.detail-body-editor .ProseMirror p {
  margin: 0 0 var(--sp-xs) 0;
}
.detail-body-editor .ProseMirror p:last-child {
  margin-bottom: 0;
}

.detail-body-editor .ProseMirror strong { font-weight: 700; }
.detail-body-editor .ProseMirror em     { font-style: italic; }
.detail-body-editor .ProseMirror code {
  font-family: monospace;
  font-size: 0.9em;
  background: var(--bg-secondary, rgba(0,0,0,0.08));
  border-radius: 3px;
  padding: 0.1em 0.3em;
}
.detail-body-editor .ProseMirror a {
  color: var(--accent-blue);
  text-decoration: underline;
}

.detail-body-editor .ProseMirror ul,
.detail-body-editor .ProseMirror ol {
  margin: var(--sp-xs) 0;
  padding-left: 1.4em;
}
.detail-body-editor .ProseMirror ul { list-style-type: disc; }
.detail-body-editor .ProseMirror ol { list-style-type: decimal; }
.detail-body-editor .ProseMirror li { margin: 0; }

.detail-body-editor .ProseMirror pre {
  background: var(--bg-secondary, rgba(0,0,0,0.08));
  border-radius: var(--radius-sm, 4px);
  padding: var(--sp-xs) var(--sp-sm);
  overflow-x: auto;
  font-family: monospace;
  font-size: 0.9em;
  margin: var(--sp-xs) 0;
}
.detail-body-editor .ProseMirror pre code {
  background: none;
  padding: 0;
}

.detail-body-editor .ProseMirror blockquote {
  border-left: 3px solid var(--border-primary, #ccc);
  margin: var(--sp-xs) 0;
  padding-left: var(--sp-sm);
  color: var(--text-secondary);
}

.detail-body-editor .ProseMirror h1,
.detail-body-editor .ProseMirror h2,
.detail-body-editor .ProseMirror h3 {
  font-weight: 700;
  margin: var(--sp-sm) 0 var(--sp-xs) 0;
  line-height: 1.3;
  color: var(--label-special);
}

.detail-body-editor .ProseMirror ::selection {
  background: var(--accent-blue-elevated);
}

.detail-description hr {
  border: none;
  border-top: 1px solid var(--border-primary);
  margin: var(--sp-md) 0;
}

.detail-text-body {
  font-size: var(--text-base);
  color: var(--text-primary);
  line-height: 1.7;
  margin-top: var(--sp-xs);
}

.detail-text-body p {
  margin: 0 0 var(--sp-sm) 0;
}
.detail-text-body p:last-child {
  margin-bottom: 0;
}

.detail-text-body h1,
.detail-text-body h2,
.detail-text-body h3,
.detail-text-body h4 {
  font-weight: 600;
  margin: var(--sp-md) 0 var(--sp-xs);
}
.detail-text-body h1 { font-size: var(--text-2xl); }
.detail-text-body h2 { font-size: var(--text-xl); }
.detail-text-body h3 { font-size: var(--text-lg); }
.detail-text-body h4 { font-size: var(--text-base); }

.detail-text-body ul,
.detail-text-body ol {
  margin: var(--sp-xs) 0;
  padding-left: var(--sp-lg);
}
.detail-text-body ul { list-style: disc; }
.detail-text-body ol { list-style: decimal; }
.detail-text-body li { margin-bottom: var(--sp-3xs); }

.detail-text-body code {
  font-family: "SF Mono", "Fira Code", monospace;
  font-size: 0.9em;
  background: rgba(0, 0, 0, 0.06);
  padding: 1px 4px;
  border-radius: var(--radius-sm);
}
[data-theme="dark"] .detail-text-body code { background: rgba(255, 255, 255, 0.1); }
[data-theme="light"] .detail-text-body code { background: rgba(0, 0, 0, 0.06); }

.detail-text-body pre {
  background: rgba(0, 0, 0, 0.06);
  padding: var(--sp-sm);
  border-radius: var(--radius-md);
  overflow-x: auto;
  margin: var(--sp-xs) 0;
}
[data-theme="dark"] .detail-text-body pre { background: rgba(255, 255, 255, 0.08); }
[data-theme="light"] .detail-text-body pre { background: rgba(0, 0, 0, 0.06); }
.detail-text-body pre code { background: none; padding: 0; }

.detail-text-body blockquote {
  border-left: 3px solid var(--border-primary);
  padding-left: var(--sp-sm);
  color: var(--text-secondary);
  margin: var(--sp-xs) 0;
}

.detail-text-body strong { font-weight: 600; }
.detail-text-body em { font-style: italic; }

.detail-text-body a {
  color: var(--accent-blue);
  text-decoration: underline;
}

.detail-text-body hr {
  border: none;
  border-top: 1px solid var(--border-primary);
  margin: var(--sp-md) 0;
}

/* ── Extra text section (transcript / extracted page text) ──── */

.detail-extra-section {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
}

.detail-extra-label {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.detail-extra-content {
  position: relative;
  max-height: 220px;
  overflow: hidden;
}

.detail-extra-content--expanded {
  max-height: none;
  overflow: visible;
}

.detail-extra-fade {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 80px;
  background: linear-gradient(to bottom, transparent, var(--bg-body));
  pointer-events: none;
}

.detail-extra-toggle {
  align-self: flex-start;
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
}
.detail-extra-toggle:hover {
  color: var(--text-primary);
}

.detail-bookmark-link {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  margin-top: var(--sp-xs);
  color: var(--accent-blue);
  font-size: var(--text-sm);
  text-decoration: none;
}
.detail-bookmark-link:hover {
  text-decoration: underline;
}

.detail-actions {
  display: flex;
  gap: var(--sp-xs);
  flex-wrap: wrap;
  margin-top: var(--sp-xs);
}

.detail-action-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-md);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  background: var(--bg-primary);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-family: inherit;
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}
.detail-action-btn:hover {
  border-color: var(--border-primary);
  background: var(--bg-subtle);
}

.detail-action-icon {
  display: inline-flex;
  align-items: center;
  color: var(--text-tertiary);
}

.detail-action-text {
  display: flex;
  flex-direction: column;
  line-height: 1.2;
}

.detail-action-label {
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: 500;
}

/* Right-side vertical action rail */
.detail-rail {
  position: absolute;
  bottom: 44px;
  right: 44px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

.detail-rail-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var( --size-3xl);
  height: var( --size-3xl);
  padding: var(--sp-3xs);
  border: none;
  background: var(--bg-primary);
  border-radius: var(--radius-full);
  color: var(--text-primary);
  cursor: pointer;
  font-family: inherit;
}
.detail-rail-btn:hover {
  color: var(--text-primary);
  opacity: 0.7;
}

@media (min-width: 835px) {
  .detail-rail-btn:has(.detail-rail-count:not(:empty)) {
    padding-inline: var(--sp-xs);
  }
}

.detail-rail-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.detail-rail-count {
  font-size: 9px;
  line-height: 1;
  color: var(--text-secondary);
}

.detail-rail-icon .icon {
  width: 18px;
  height: 18px;
}

/* The like rail entry is a real <button>; reset the default chrome so it
   matches the surrounding rail_btn divs, and show the filled/colored
   state when the viewer has already liked the item. */
.detail-rail-btn-like {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  width: fit-content;
  border: none;
  font-family: inherit;
  gap: 4px;
}


.detail-rail-btn-like > .detail-rail-count {
  font-size: var(--size-sm);
}

.detail-rail-btn-like-active,
.detail-rail-btn-like-active:hover {
  color: var(--text-primary);
}

.detail-rail-btn-like-active {
  padding-left: 8px;
  padding-right: 8px;
  transition: .3s ease all;
}

/* Bookmark rail button — same reset as the like button. */
.detail-rail-btn-bookmark {
  border: none;
  font-family: inherit;
}
.detail-rail-btn-bookmark-active,
.detail-rail-btn-bookmark-active:hover {
  color: var(--text-primary);
}
/* Wrapper that stacks the toggle + groups-pill vertically in the rail */
.detail-visibility-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

/* "N People" pill shown when visibility is GROUPS */
.detail-groups-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border-primary);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s;
}
.detail-groups-pill:hover {
  background: var(--bg-tertiary);
}

/* ─── Privacy options sheet ──────────────────────────────────────────────── */

.vis-sheet {
  height: auto;
  min-height: unset;
  max-height: 80vh;
}

.vis-sheet-content {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-bottom: var(--sp-md);
}

.vis-sheet-option {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  width: 100%;
  padding: var(--sp-sm) var(--sp-xs);
  border-radius: var(--radius-md);
  border: none;
  background: transparent;
  cursor: pointer;
  text-align: left;
  color: var(--text-primary);
  transition: background 0.15s;
}
.vis-sheet-option:hover {
  background: var(--bg-secondary);
}
.vis-sheet-option--active {
  background: var(--bg-secondary);
}

.vis-sheet-option-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-full);
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  flex-shrink: 0;
}

.vis-sheet-option-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

.vis-sheet-option-label {
  font-size: var(--text-lg);
  font-weight: 500;
  color: var(--text-primary);
}

.vis-sheet-option-sublabel {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  line-height: 1.3;
}

.vis-sheet-option-check {
  display: flex;
  align-items: center;
  color: var(--text-primary);
  flex-shrink: 0;
}

/* ─── Groups picker sheet ────────────────────────────────────────────────── */

.groups-picker-sheet,
.groups-manage-sheet {
  height: 80vh;
  display: flex;
  flex-direction: column;
}

.groups-picker-header,
.groups-manage-header {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-sm);
  padding: var(--sp-sm) 0 var(--sp-md);
  flex-shrink: 0;
}

.groups-picker-header-icon,
.groups-manage-header-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  flex-shrink: 0;
  margin-top: 2px;
}

.groups-picker-header-text,
.groups-manage-header-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
}

.groups-picker-title,
.groups-manage-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
}

.groups-picker-subtitle,
.groups-manage-subtitle {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  line-height: 1.35;
}

.groups-picker-close-btn {
  border: none;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  padding: 4px;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}

.groups-manage-add-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: none;
  background: transparent;
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  padding: 4px 0;
  flex-shrink: 0;
  white-space: nowrap;
}

.groups-picker-search-wrap {
  padding-bottom: var(--sp-xs);
}

.groups-picker-search {
  width: 100%;
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-primary);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-size: var(--text-sm);
  outline: none;
}
.groups-picker-search:focus {
  border-color: var(--border-primary-inverse);
}

.groups-picker-count {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  padding: var(--sp-xs) 0 4px;
}

.groups-manage-label {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  padding-bottom: 4px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.groups-picker-list,
.groups-manage-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.groups-picker-empty {
  font-size: var(--text-sm);
  color: var(--text-tertiary);
  text-align: center;
  padding: var(--sp-lg) 0;
}

/* Shared row layout used in both picker and manage views */
.groups-picker-row {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-xs) 0;
}

.groups-picker-row-info {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
}

.groups-picker-row-name {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.groups-picker-row-username {
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.groups-picker-add-btn {
  border: 1px solid var(--border-primary);
  background: var(--bg-primary);
  color: var(--text-primary);
  font-size: var(--text-xs);
  font-weight: 500;
  padding: 4px 12px;
  border-radius: var(--radius-full);
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s;
}
.groups-picker-add-btn:hover {
  background: var(--bg-secondary);
}

.groups-picker-remove-wrap {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

.groups-picker-remove-btn {
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--text-xs);
  font-weight: 500;
  cursor: pointer;
  padding: 4px 2px;
}

.groups-picker-remove-x {
  border: none;
  background: transparent;
  color: var(--text-tertiary);
  cursor: pointer;
  padding: 2px;
  display: flex;
  align-items: center;
}

.groups-picker-confirm-btn {
  position: sticky;
  bottom: 0;
  width: calc(100% + var(--sp-md) * 2);
  margin: 0 calc(-1 * var(--sp-md));
  padding: var(--sp-sm) var(--sp-md);
  background: var(--bg-primary);
  border: none;
  border-top: 1px solid var(--border-primary);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  transition: background 0.15s;
}
.groups-picker-confirm-btn:hover {
  background: var(--bg-secondary);
}

/* Chat-with-AI rail button — accent-colored to signal the primary action,
   dimmed while the Stream create-channel request is in flight. */
.detail-rail-btn-ai {
  color: var(--text-primary);
  background: var(--bg-primary);
}
.detail-rail-btn-ai:hover {
  /* color: var(--bg-secondary); */
  opacity: 0.8;
}
.detail-rail-btn-ai-loading {
  opacity: 0.5;
  cursor: progress;
}

/* Unread dot on the Chat-with-AI rail button. Item AI chats are hidden
   from the sidebar, so this dot is the only place users see new agent
   replies. Positioned over the top-right corner of the round button. */
.detail-rail-btn-ai {
  position: relative;
}
.detail-rail-btn-ai-unread-dot {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--accent-red);
  border: 2px solid var(--bg-primary);
  box-sizing: content-box;
  pointer-events: none;
}

/* Voice hero + player wrapper — player renders below the gradient hero. */
.detail-voice-wrapper {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

/* ── Audio player for voice detail ────────────────────────────── */

.detail-player {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  padding: var(--sp-md) var(--sp-lg);
  background: var(--bg-subtle);
  border-radius: var(--radius-lg);
}

.detail-player-btn {
  width: 48px;
  height: 48px;
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
  flex-shrink: 0;
}

.detail-player-progress { flex: 1; }

.detail-player-bar {
  height: 4px;
  background: var(--border-primary);
  border-radius: var(--radius-full);
  overflow: hidden;
  cursor: pointer;
}
.detail-player-bar-fill {
  height: 100%;
  background: var(--bg-secondary);
  transition: width 0.1s linear;
}

.detail-player-times {
  display: flex;
  justify-content: space-between;
  margin-top: var(--sp-3xs);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.detail-player-transcript {
  margin: var(--sp-sm) 0 0 0;
  font-size: var(--text-sm);
  color: color-mix(in srgb, var(--text-primary) 85%, transparent);
  line-height: 1.5;
}

.detail-loading,
.detail-error {
  padding: var(--sp-xxl);
  text-align: center;
  color: var(--text-secondary);
}

/* ── Container attachment grid ──────────────────────────────────── */

.detail-container-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--sp-sm);
}

.detail-container-thumb {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--radius-md, 12px);
  background: var(--surface-secondary, rgba(127, 127, 127, 0.08));
  display: block;
}

.detail-container-attach {
  margin-top: var(--sp-sm);
}

.detail-container-attach-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-md);
  border-radius: var(--radius-md, 12px);
  border: 1px dashed var(--border-subtle, rgba(127, 127, 127, 0.35));
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  cursor: pointer;
}

.detail-container-attach-btn:hover {
  background: var(--surface-secondary, rgba(127, 127, 127, 0.08));
  color: var(--text-primary);
}

.detail-container-file {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--sp-sm);
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-md, 12px);
  background: var(--surface-secondary, rgba(127, 127, 127, 0.08));
  font-size: var(--text-xs);
  color: var(--text-secondary);
  overflow: hidden;
  overflow-wrap: anywhere;
}

/* ── Inline Please AI chat (replaces comments section) ──────────── */

.detail-ai-chat {
  margin-top: var(--sp-lg);
  padding-bottom: var(--sp-4xl);
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

/* Owner reminder surface (.nr-root): inset the AI-chat section like the
   rest of the page's content (it previously ran flush on desktop), but
   leave it unboxed — it should blend into the plain page background
   rather than read as its own colored panel like the reminder card. */
.nr-root .detail-ai-chat {
  padding: var(--sp-lg) var(--sp-xl);
  background: var(--bg-secondary);
}

.detail-ai-chat-title {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}

.detail-ai-chat-status {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  padding: var(--sp-md) 0;
}

/* Offline: Please AI is entirely server-side, so it can't be used at all
   without a connection. Framed as a state to wait out rather than as an
   error — the old copy ("Couldn't open the chat.") read like the feature
   was broken and gave no hint that reconnecting would fix it. */
.detail-ai-chat-offline {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-sm) var(--sp-md);
  margin-bottom: var(--sp-sm);

  border-radius: var(--radius-md, 10px);
  background: var(--fill-elevated-secondary);
  color: var(--text-secondary);
}

.detail-ai-chat-offline-text {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 18px;
}

/* ── Camera capture modal (owner-container hardware camera) ───────── */

.camera-modal-body {
  display: flex;
  flex-direction: column;
  background: #000;
  border-radius: inherit;
  overflow: hidden;
}

.camera-viewfinder {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  background: #111;
}

.camera-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-lg) var(--sp-md);
  background: #000;
}

.camera-shutter-btn {
  width: 64px;
  height: 64px;
  border-radius: var(--radius-full);
  border: 4px solid #fff;
  background: rgba(255, 255, 255, 0.85);
  cursor: pointer;
  transition: transform 80ms ease, background 80ms ease;
  flex-shrink: 0;
}

.camera-shutter-btn:hover {
  background: #fff;
  transform: scale(1.06);
}

.camera-shutter-btn:active {
  transform: scale(0.94);
  background: rgba(255, 255, 255, 0.65);
}

.detail-ai-chat-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

/* No independent scrollbox here — messages flow in the same single
   page-level scroll as the reminder card above (.nr-body owns the only
   overflow-y: auto on this page). */
.detail-ai-chat-messages {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

.detail-ai-chat-empty {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  padding: var(--sp-lg) 0;
  text-align: center;
}

.detail-ai-chat-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

/* Plain doc-like thread, not two-sided chat bubbles: the agent's messages
   (.detail-ai-msg-them) render as bare full-width text with a name+time
   header; only the viewer's own messages (.detail-ai-msg-me) get a bubble,
   right-aligned and width-capped like a normal chat reply. */
.detail-ai-msg {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
}

.detail-ai-msg-them {
  align-self: stretch;
  max-width: 100%;
}

.detail-ai-msg-me {
  align-self: flex-end;
  align-items: flex-end;
  max-width: 85%;
}

.detail-ai-msg-header {
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.detail-ai-msg-header-name {
  font-weight: 600;
  color: var(--text-primary);
}

/* Rich agent cards above the text — full width across the thread for
   every sender (iOS InlineChatMessageRows.richCards: a card is an item,
   not a bubble). */
.detail-ai-msg-cards {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
  width: 100%;
  align-self: stretch;
}

.detail-ai-msg-bubble {
  padding: var(--sp-sm) var(--sp-md);
  border-radius: var(--radius-lg, 16px);
  background: var(--surface-secondary, rgba(127, 127, 127, 0.08));
}

.detail-ai-msg-me .detail-ai-msg-bubble {
  border-bottom-right-radius: var(--radius-sm);
  background: var(--fill-elevated-primary);
  color: var(--text-primary);
  backdrop-filter: contrast(2);
}

.detail-ai-msg-body {
  /* AI prose reads a step larger than human-chat bubbles. */
  font-size: var(--text-md);
  line-height: 1.5;
  overflow-wrap: break-word;
}

.detail-ai-msg-body p {
  margin: 0;
}

.detail-ai-msg-body p + p {
  margin-top: var(--sp-sm);
}

/* Floats directly above the composer's input pill (see ai_chat.gleam's
   view_composer — it's the first child inside .chat-composer, right
   before the form), the same spot chat.gleam's own typing indicator
   occupies. .chat-composer has no padding of its own in this detail
   context (.detail-ai-chat .chat-composer resets it to 0), so this
   supplies its own horizontal inset + the gap above the pill. */
.detail-ai-chat-typing {
  display: flex;
  align-items: center;
  gap: var(--sp-3xs);
  padding: 0 var(--sp-md);
  margin-bottom: var(--sp-sm);
}

.detail-ai-chat-typing-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-tertiary);
  animation: detail-ai-typing 1.2s infinite ease-in-out;
}

.detail-ai-chat-typing-dot:nth-child(2) { animation-delay: 0.15s; }
.detail-ai-chat-typing-dot:nth-child(3) { animation-delay: 0.3s; }

@keyframes detail-ai-typing {
  0%, 60%, 100% { opacity: 0.3; }
  30% { opacity: 1; }
}

.detail-ai-chat-composer {
  display: flex;
  gap: var(--sp-sm);
  align-items: flex-end;
}

.detail-ai-chat-input {
  flex: 1;
  resize: none;
  border: 1px solid var(--border-subtle, rgba(127, 127, 127, 0.2));
  border-radius: var(--radius-md, 12px);
  padding: var(--sp-sm) var(--sp-md);
  font: inherit;
  font-size: var(--text-sm);
  background: var(--bg-input, transparent);
  color: var(--text-primary);
  min-height: 40px;
  max-height: 160px;
}

.detail-ai-chat-input::placeholder {
  color: var(--text-tertiary);
}

.detail-ai-chat-attach {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: var(--radius-md, 12px);
  border: 1px solid var(--border-subtle, rgba(127, 127, 127, 0.2));
  background: var(--bg-input, transparent);
  color: var(--text-secondary);
  font-size: var(--text-lg);
  cursor: pointer;
}

@media (hover: hover) {
  .detail-ai-chat-attach:hover {
    background: var(--surface-secondary, rgba(127, 127, 127, 0.08));
  }
}

.detail-ai-attach-input {
  display: none;
}

.detail-ai-chat-send {
  border: none;
  border-radius: var(--radius-md, 12px);
  padding: 0 var(--sp-lg);
  height: 40px;
  background: var(--accent-blue);
  color: #fff;
  font-weight: 600;
  cursor: pointer;
}

.detail-ai-chat-send:disabled {
  background: var(--bg-disabled);
  color: var(--text-disabled);
  cursor: not-allowed;
}

/* ── Transparent backdrop for closing dropdowns on outside click ── */

.detail-backdrop {
  position: fixed;
  inset: 0;
  z-index: 9;
}

/* ── Reminder picker dropdown ────────────────────────────────── */

.detail-action-remind-wrapper {
  position: relative;
}

.detail-reminder-picker {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 10;
  margin-top: var(--sp-xs);
  padding: var(--sp-xs);
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
  min-width: 220px;
}

/* When the reminder picker is rendered inside the owner-container slide-up
   sheet (Ping Me), drop the absolute dropdown positioning and let it sit
   in-flow, full-width, without the border/shadow the sheet already provides. */
.nr-reminder-sheet .detail-reminder-picker {
  position: static;
  margin-top: 0;
  padding: 0;
  border: none;
  box-shadow: none;
  min-width: 0;
  width: 100%;
}

.detail-reminder-option {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  width: 100%;
  padding: var(--sp-xs) var(--sp-sm);
  border: none;
  background: transparent;
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-family: inherit;
  text-align: left;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.detail-reminder-option:hover {
  background: var(--bg-subtle);
}
.detail-reminder-option-time {
  margin-left: auto;
  color: var(--text-secondary);
  font-size: var(--text-xs);
}

/* The opened alert's own actions (Snooze, Dismiss), above the quick picks. */
.detail-reminder-actions {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
  padding-bottom: var(--sp-3xs);
  margin-bottom: var(--sp-3xs);
  border-bottom: 1px solid var(--border-primary);
}

/* "Repeats [Never ▾]" — sits between the quick picks and the custom row. */
.detail-reminder-repeat {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-sm);
  border-top: 1px solid var(--border-primary);
  margin-top: var(--sp-3xs);
  color: var(--text-primary);
  font-size: var(--text-sm);
}
.detail-reminder-repeat-label {
  flex: 1;
}
.detail-reminder-repeat-select {
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  padding: var(--sp-3xs) var(--sp-xs);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text-primary);
  background: var(--bg);
  cursor: pointer;
}

.detail-reminder-label-input {
  flex: 0 1 12rem;
  min-width: 0;
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  padding: var(--sp-3xs) var(--sp-xs);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text-primary);
  background: var(--bg);
}

.detail-reminder-custom {
  display: flex;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-sm);
  border-top: 1px solid var(--border-primary);
  margin-top: var(--sp-3xs);
  padding-top: var(--sp-sm);
}

.detail-reminder-custom-input {
  flex: 1;
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  padding: var(--sp-3xs) var(--sp-xs);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text-primary);
  background: var(--bg);
}

.detail-reminder-custom-btn {
  padding: var(--sp-3xs) var(--sp-sm);
  border: none;
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-family: inherit;
  cursor: pointer;
}
.detail-reminder-custom-btn:hover {
  opacity: 0.9;
}

/* ── Existing reminders (chips below action row) ─────────────── */

.detail-reminders-list {
  display: flex;
  flex-direction: row;
  gap: 16px;
  margin-top: var(--sp-sm);
  justify-content: flex-start;
  align-items: center;
}

.detail-reminder-chip-wrap {
  position: relative;
  display: inline-flex;
}

.detail-reminder-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  height: var(--size-3xl);
  padding: var(--sp-3xs) var(--sp-sm);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  background: var(--bg-subtle);
  font-size: var(--text-sm);
  font-family: inherit;
  color: var(--text-primary);
  cursor: pointer;
}
.detail-reminder-chip:hover {
  background: var(--bg-hover, var(--bg-subtle));
  border-color: var(--border-primary);
}

.detail-reminder-chip-icon {
  display: inline-flex;
  align-items: center;
  color: var(--text-tertiary);
  line-height: 1;
}

.detail-reminder-chip-date {
  color: var(--text-primary);
}

.detail-reminder-chip-overdue {
  border-color: var(--accent-red);
  color: var(--accent-red);
}
.detail-reminder-chip-overdue .detail-reminder-chip-icon,
.detail-reminder-chip-overdue .detail-reminder-chip-date {
  color: var(--accent-red);
}

/* In-flight CompleteReminder request — chip taps are inert (see
   completing_reminder in app/detail/reminders.gleam). */
.detail-reminder-chip--completing {
  opacity: 0.6;
  cursor: default;
  pointer-events: none;
}

/* Chip action dropdown */
.detail-reminder-chip-menu {
  position: absolute;
  top: calc(100% + var(--sp-3xs));
  left: 0;
  z-index: 20;
  min-width: 140px;
  padding: var(--sp-3xs);
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.detail-reminder-chip-menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  width: 100%;
  padding: var(--sp-xs) var(--sp-sm);
  border: none;
  background: transparent;
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-family: inherit;
  text-align: left;
  border-radius: var(--radius-md);
  cursor: pointer;
  white-space: nowrap;
}
.detail-reminder-chip-menu-item:hover {
  background: var(--bg-subtle);
}
.detail-reminder-chip-menu-item-danger {
  color: var(--accent-red);
}
.detail-reminder-chip-menu-item-danger:hover {
  background: var(--danger-subtle, var(--bg-subtle));
}

/* ── Keyboard-nav end-of-list toast ──────────────────────────────────── */

.detail-nav-toast {
  position: absolute;
  bottom: var(--sp-xl);
  left: 50%;
  transform: translateX(-50%);
  padding: var(--sp-xs) var(--sp-md);
  background: var(--bg-elevated, var(--bg-primary));
  color: var(--text-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  white-space: nowrap;
  pointer-events: none;
  z-index: 10;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  animation: detail-toast-in 0.18s ease-out, detail-toast-out 0.3s ease-in 2.2s forwards;
}

/* ─── Keyframe animations ────────────────────────────────────────────── */

@keyframes detail-toast-in {
  from { opacity: 0; transform: translateX(-50%) translateY(6px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

@keyframes detail-toast-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* ─── Media queries ──────────────────────────────────────────────────── */

@media (max-width: 834px) {
  .detail-content {
    width: 100%;
    max-width: 100%;
  }

  .detail-back { display: none; }
  .detail-header-sidebar-toggle { display: inline-flex; }

  /* 16px stops iOS Safari from zooming the composer on focus. */
  .detail-ai-chat-input {
    font-size: 16px;
  }

  /* Remove scroll-container horizontal padding so media bleeds edge-to-edge,
     then re-apply the same value directly to content sections. */
  .detail-scroll {
    padding-left: 0;
    padding-right: 0;
  }

  .detail-meta,
  .detail-text-content,
  .detail-ai-chat {
    padding-left: var(--sp-xl);
    padding-right: var(--sp-xl);
  }

  /* Full-width media on mobile — let natural aspect ratio dictate height. */
  .detail-hero {
    border-radius: 0;
  }

  .detail-hero-photo,
  .detail-hero-voice,
  .detail-hero-bookmark,
  .detail-hero-text {
    width: 100%;
    height: auto;
    aspect-ratio: unset;
    max-width: 100%;
    border-radius: 0;
  }

  .detail-hero-img {
    width: 100%;
    height: auto;
    object-fit: contain;
    border-radius: 0;
  }
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .detail-description code { background: rgba(255, 255, 255, 0.1); }
  :root:not([data-theme]) .detail-description pre { background: rgba(255, 255, 255, 0.08); }
  :root:not([data-theme]) .detail-text-body code { background: rgba(255, 255, 255, 0.1); }
  :root:not([data-theme]) .detail-text-body pre { background: rgba(255, 255, 255, 0.08); }
}

.detail-no-access {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-sm);
  padding: var(--sp-xl);
  color: var(--text-secondary);
  font-size: var(--text-lg);
  text-align: center;
}

.detail-no-access p { margin: 0; }

.detail-no-access-title {
  color: var(--text-primary);
  font-weight: 600;
}

.detail-no-access-detail {
  max-width: 360px;
  font-size: var(--text-sm);
}

/* ── Title-row attachment thumbnail deck ─────────────────────────────────
   Port of iOS CaptureThumbnailStack: up to three tilted 80×80 cards inside
   a 96×88 footprint, trailing the container title. One click target. */

.detail-title-row--with-thumb {
  flex-wrap: nowrap;
  align-items: flex-start;
}

.detail-title-row--with-thumb .detail-title {
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
}

.thumb-stack {
  position: relative;
  flex: 0 0 auto;
  align-self: flex-start;
  /* Tight 80px box — the fan/rotation is transform-only and overhangs
     the box visibly without costing any layout height. */
  width: 80px;
  height: 80px;
  margin-left: 6px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.thumb-stack-card {
  position: absolute;
  top: 0;
  left: 0;
  width: 80px;
  height: 80px;
  box-sizing: border-box;
  border: 2px solid var(--bg-body); /* base background — flips with theme */
  border-radius: 16px;
  background: var(--bg-body);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
}

.thumb-stack-front {
  transform: rotate(-7deg);
  z-index: 3;
}

.thumb-stack-back1 {
  transform: translate(6px, 2px) rotate(6deg);
  opacity: 0.9;
  z-index: 2;
}

.thumb-stack-back2 {
  transform: translate(-5px, 2px) rotate(-3deg);
  opacity: 0.9;
  z-index: 1;
}

/* Inner clip so media cover-crops to the rounded card without clipping the
   +N badge (mirrors iOS, where the overlay draws after clipShape). */
.thumb-stack-clip {
  position: absolute;
  inset: 0;
  border-radius: 14px; /* card radius minus border width */
  overflow: hidden;
}

.thumb-stack-media {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.thumb-stack-placeholder {
  background: var(--bg-secondary);
}

/* Calendar date badge card — scaled-up sibling of the 56px timeline badge
   (.tl-thumb-calendar): red uppercase month over the day numeral. */
.thumb-stack-calendar {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  background: var(--fill-elevated-quaternary);
}

.thumb-stack-cal-month {
  font-size: 14px;
  font-weight: 600;
  line-height: 17px;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent-red);
}

.thumb-stack-cal-day {
  font-size: 32px;
  font-weight: 500;
  line-height: 36px;
  color: var(--tl-text-primary);
}

.thumb-stack-voice {
  position: relative; /* anchors the play chip */
  display: flex;
  align-items: center;
  justify-content: center;
}

.thumb-stack-voice .wf-tile {
  height: 100%;
  border-radius: 0; /* the card's clip owns the rounding */
}

/* Translucent play chip centered on the voice card — signals that tapping
   opens the fullscreen voice viewer. */
.thumb-stack-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.thumb-stack-badge {
  position: absolute;
  right: 4px;
  bottom: 4px;
  z-index: 1;
  padding: 2px 6px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
}

/* ============================================================
   Inline "Audio transcript:" block — standalone voice notes on the
   unified owner view (between the heading container and the body editor).
   ============================================================ */

.detail-audio-transcript {
  padding: var(--sp-sm) var(--sp-xl) 0;
}

.detail-audio-transcript-label {
  margin: 0 0 var(--sp-3xs);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
}

.detail-audio-transcript-text {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--text-secondary);
  white-space: pre-wrap;
}

/* ============================================================
   Fullscreen media viewer (voice notes + videos) — web port of iOS's
   VoiceNoteViewer / MediaViewer. Forced-dark surface like the image
   lightbox (.mo-lightbox); sits above the modal system (900), below
   the chat lightbox (1000).
   ============================================================ */

.media-viewer {
  position: fixed;
  inset: 0;
  z-index: 950;
  display: flex;
  flex-direction: column;
  background: #0b0b0d;
  color: #fff;
  animation: mo-lb-in 0.3s ease-in-out;
}

/* ── top bar ── */

.media-viewer-top {
  display: flex;
  align-items: center;
  padding: 12px 8px 8px;
  flex-shrink: 0;
}

.media-viewer-top-spacer { flex: 1; }

/* Uploaded/captured file name + extension, centered between the flex
   spacers (the ghost below balances a hidden ⋯ side). */
.media-viewer-title {
  max-width: 60%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-sm);
  font-weight: 500;
  color: rgba(255, 255, 255, 0.85);
}

/* Same footprint as an icon button — keeps the title centered when the
   right side has no ⋯ menu (local previews). */
.media-viewer-top-ghost {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
}

.media-viewer-icon-btn {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-radius: var(--radius-full);
  color: #fff;
  cursor: pointer;
  transition: background 120ms ease;
}
.media-viewer-icon-btn:hover { background: rgba(255, 255, 255, 0.08); }

/* ── pager (dots / chevrons / image page) ── */

.media-viewer-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* Animated page container: the keyed child remounts on page change, so
   the slide-in replays; direction class picks the entry side. */
.media-viewer-pagewrap {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.media-viewer-page {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.media-viewer-pagewrap--fwd > .media-viewer-page {
  animation: mv-page-fwd 240ms cubic-bezier(0.25, 0.1, 0.25, 1);
}
.media-viewer-pagewrap--back > .media-viewer-page {
  animation: mv-page-back 240ms cubic-bezier(0.25, 0.1, 0.25, 1);
}
@keyframes mv-page-fwd {
  from {
    transform: translateX(48px);
    opacity: 0.3;
  }
  to {
    transform: none;
    opacity: 1;
  }
}
@keyframes mv-page-back {
  from {
    transform: translateX(-48px);
    opacity: 0.3;
  }
  to {
    transform: none;
    opacity: 1;
  }
}

/* Bottom pagination dots (iOS MediaViewer parity). */
.media-viewer-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-sm);
  padding: var(--sp-md) 0;
  flex-shrink: 0;
}
.media-viewer-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  border: none;
  padding: 0;
  background: rgba(255, 255, 255, 0.35);
  cursor: pointer;
}
.media-viewer-dot--current {
  background: #ffffff;
}

/* Desktop-only hover chevrons at the viewer edges. */
.media-viewer-chevrons {
  display: none;
}
@media (min-width: 835px) {
  .media-viewer-chevrons {
    display: block;
  }
  .media-viewer-chevron {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: var(--radius-full);
    background: rgba(118, 118, 128, 0.28);
    color: #ffffff;
    cursor: pointer;
    opacity: 0;
    transition: opacity 120ms ease;
    z-index: 2;
  }
  .media-viewer:hover .media-viewer-chevron {
    opacity: 1;
  }
  .media-viewer-chevron--prev { left: var(--sp-md); }
  .media-viewer-chevron--next { right: var(--sp-md); }
}

/* Destructive ⋯ row (Delete attachment). */
.media-viewer-menu-row--danger {
  color: var(--accent-red);
}

/* Delete-attachment confirm layer above the viewer (shared
   ui/confirm_modal markup under the media-viewer-confirm prefix). */
.media-viewer-confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-viewer-confirm-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
}
.media-viewer-confirm-dialog {
  position: relative;
  width: min(360px, calc(100% - 2 * var(--sp-xl)));
  padding: var(--sp-lg);
  border-radius: var(--radius-lg);
  background: #2c2c2e;
  color: #ffffff;
}
.media-viewer-confirm-body {
  margin: 0 0 var(--sp-md);
  font-size: var(--text-base);
}
.media-viewer-confirm-actions {
  display: flex;
  gap: var(--sp-sm);
  justify-content: flex-end;
}
.media-viewer-confirm-btn {
  padding: var(--sp-xs) var(--sp-lg);
  border: none;
  border-radius: var(--radius-full);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  background: rgba(118, 118, 128, 0.24);
  color: #ffffff;
}
.media-viewer-confirm-delete {
  background: var(--accent-red);
}

/* ── centered media ── */

.media-viewer-center {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: 0 16px;
}

.media-viewer-canvas {
  width: min(320px, 78vw);
  height: 64px;
  cursor: pointer;
  touch-action: none; /* pointer-capture drag scrubbing */
}

.media-viewer-video {
  max-width: 100%;
  max-height: 100%;
  border-radius: var(--radius-sm);
}

/* ── playback bar + transcript ── */

.media-viewer-lower {
  flex-shrink: 0;
  width: min(560px, 100%);
  margin: 0 auto;
  padding: 0 24px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.media-viewer-playbar {
  display: flex;
  align-items: center;
  gap: 12px;
}

.media-viewer-play {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: #fff;
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
}

.media-viewer-progress {
  flex: 1;
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.25);
  overflow: hidden;
}

.media-viewer-progress-fill {
  height: 100%;
  border-radius: 999px;
  background: #fff;
  transition: width 0.1s linear;
}

.media-viewer-time {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: rgba(255, 255, 255, 0.6);
  flex-shrink: 0;
}

.media-viewer-transcript {
  max-height: 32vh;
  overflow-y: auto;
}

.media-viewer-transcript-label {
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.5);
}

.media-viewer-transcript-text {
  margin: 0;
  font-size: 17px;
  line-height: 1.45;
}

.media-viewer-seg {
  color: rgba(255, 255, 255, 0.45);
  transition: color 120ms ease;
}

.media-viewer-seg--current {
  color: #fff;
  font-weight: 600;
}

/* ── bottom action row (voice) ── */

.media-viewer-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 20px 8px 32px;
  flex-shrink: 0;
  color: rgba(255, 255, 255, 0.6);
}

/* ── ellipsis menu drawer ── */

.media-viewer-menu-layer {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.media-viewer-menu-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  cursor: pointer;
}

.media-viewer-menu {
  position: relative;
  background: #1c1c1f;
  border-radius: 24px 24px 0 0;
  padding: 8px 8px calc(16px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
}

.media-viewer-menu-handle {
  width: 36px;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.25);
  margin: 4px auto 12px;
}

.media-viewer-menu-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: transparent;
  border: none;
  border-radius: var(--radius-md);
  color: #fff;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
}
.media-viewer-menu-row:hover { background: rgba(255, 255, 255, 0.06); }

/* ── Inline AI chat composer (Messages-parity pill) ─────────────────────
   The composer reuses chat.css's .chat-composer* classes wholesale; these
   rules only re-scope chat-pane layout assumptions to the detail column. */

/* Chat pane gives .chat-composer grid placement + gutter padding — the
   detail column provides its own spacing. */
.detail-ai-chat .chat-composer {
  padding: 0;
  max-width: none;
}

/* Desktop-only: mobile instead gets the docked Please AI composer bar in
   the toolbar (ai_chat.view_composer_bar / .ai-composer-dock — see
   new_reminder.css's responsive-split section). This selector is scoped to
   .detail-ai-chat specifically, so it doesn't touch the Messages pane's own
   .chat-composer on mobile. */
@media (max-width: 834px) {
  .detail-ai-chat .chat-composer {
    display: none;
  }
}

/* Desktop composer stays nested in-flow inside .detail-ai-chat, right
   below the messages list — same section as the reminder's checklist
   above it, not pinned/floating. (A fixed-to-viewport version of this was
   tried and reverted — parking that idea for later.) */

/* Mostly no pill overrides: in the stacked (mobile) layout the AI-chat
   section's --bg-secondary surface gives the --bg-primary pill its
   contrast, and the component keeps chat.css's exact chrome (including
   the 2px transparent → colored focus border) so it looks and feels
   identical to the Messages composer. The one exception is the desktop
   third column, where the pill becomes the mock's white "Chat" card — see
   the `.detail-ai-panel .chat-composer-form` rule in the panel section. */

/* Generic (non image/video) staged file chip inside the preview strip. */
.detail-ai-composer-file {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3xs);
  width: 80px;
  height: 80px;
  padding: var(--sp-xs);
  color: var(--text-secondary);
}

.detail-ai-composer-file-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--text-xs);
}

/* chat.css hardcodes the post-mount empty-state placeholder to
   "Type a message..." — keep the AI composer's own prompt (the pre-mount
   ::before already reads data-placeholder). */
.detail-ai-chat .chat-composer-milkdown .ProseMirror p.is-editor-empty:first-child::before,
.detail-ai-chat .chat-composer-milkdown .ProseMirror:empty::before {
  content: "Chat";
}

/* ── Desktop detail: editor column + Please AI third column ─────────────
   (Web mock 2026-09-24.) The columns wrapper takes over .nr-body's slot
   in the .nr-root flex column; on mobile it degrades to a plain column and
   the panel isn't rendered at all (the chat stays stacked inside .nr-body,
   exactly the historical layout). */
.nr-detail-columns {
  flex: 1;
  min-height: 0;
  display: flex;
}

.nr-detail-columns > .nr-body {
  flex: 1;
  min-width: 0;
}

/* Desktop-only chrome. `is_mobile()` picks the markup at render time; a
   live resize across 834 swaps it on the next render, and this covers the
   gap. */
@media (max-width: 834px) {
  .nr-detail-columns {
    flex-direction: column;
  }

  .detail-ai-panel,
  .detail-ai-entry,
  .detail-messages-pill {
    display: none;
  }
}

/* ── Please AI button (header action strip's trailing slot) ─────────────
   Closed (mock3): white disc, hairline edge, grey agent glyph. Open
   (mock4): black glyph in a static pastel halo, no edge (the halo gives
   it). Working (the agent latch is composing, open or closed): the halo
   breathes (iOS AIActionSurface timings, AIActionCircle.swift:158-193).
   The halo is a plain span, not an .icon, so the blur works. */
.detail-ai-entry {
  position: relative;
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: none;
  cursor: pointer;
}

.detail-ai-entry-halo {
  position: absolute;
  inset: -2px; /* 40px disc (mock fit: RMS 3.6/255) */
  border-radius: var(--radius-full);
  background: var(--gradient-pastel);
  filter: blur(2px);
  opacity: 0;
  transition: opacity 300ms ease-in-out; /* iOS isLit easeInOut(0.3) */
  pointer-events: none;
}

.detail-ai-entry-disc {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-tertiary); /* mock .06 */
  border-radius: var(--radius-full);
  background: var(--bg-primary);
  color: var(--text-secondary);
  transition: color 150ms ease, border-color 300ms ease-in-out;
}

@media (hover: hover) {
  .detail-ai-entry:hover .detail-ai-entry-disc {
    color: var(--text-primary);
  }
}

.detail-ai-entry[aria-expanded="true"] .detail-ai-entry-halo {
  opacity: 0.7;
}

.detail-ai-entry[aria-expanded="true"] .detail-ai-entry-disc {
  color: var(--text-primary);
}

.detail-ai-entry[aria-expanded="true"] .detail-ai-entry-disc,
.detail-ai-entry--working .detail-ai-entry-disc {
  border-color: transparent;
}

.detail-ai-entry--working .detail-ai-entry-halo {
  animation: detail-ai-entry-breathe 2.6s ease-in-out infinite alternate;
}

@keyframes detail-ai-entry-breathe {
  from {
    opacity: 0.5;
    transform: scale(0.97);
  }
  to {
    opacity: 1;
    transform: scale(1.05);
  }
}

@media (prefers-reduced-motion: reduce) {
  .detail-ai-entry--working .detail-ai-entry-halo {
    animation: none;
    opacity: 1;
    transform: scale(1.05);
  }
}

.detail-ai-entry:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: 2px;
}

/* Dark (no mock): --border-tertiary is #1A1A1A, invisible on black, so the
   closed disc takes the next hairline up; the pastel glow, drawn for a
   light panel, reads as a bright band at full strength. */
[data-theme="dark"]
  .detail-ai-entry:not([aria-expanded="true"]):not(.detail-ai-entry--working)
  .detail-ai-entry-disc {
  border-color: var(--border-secondary);
}
[data-theme="dark"] .detail-ai-panel-glow {
  opacity: 0.45;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme])
    .detail-ai-entry:not([aria-expanded="true"]):not(.detail-ai-entry--working)
    .detail-ai-entry-disc {
    border-color: var(--border-secondary);
  }
  :root:not([data-theme]) .detail-ai-panel-glow {
    opacity: 0.45;
  }
}

@media (min-width: 835px) {
  /* ── Main pane chrome (detail only) ──────────────────────────────────
     The mock's detail main pane is white edge to edge — no grey canvas
     (new_reminder.css's desktop .nr-root), no card chrome. */
  .nr-root.nr-root--detail {
    background: var(--bg-primary);
  }

  /* Breathing room around the editor now that the top nav is gone. The
     bottom is reserved for what floats over the pane's bottom corners —
     the accessory rail (52, bottom-left while editing) and the Messages
     pill (58, bottom-right) — so scrolled to the end, a long note's last
     line clears both (prod QA 2026-10-01: they sat on it). .nr-body's own
     bottom margin matches their 24 inset, so this is their height + a gap. */
  .nr-root--detail .nr-detail-columns > .nr-body {
    padding: var(--sp-xl) var(--sp-xl) calc(58px + var(--sp-sm));
  }

  /* Reading column: 692 max, centred; .nr-body's 24px side padding
     (above) keeps the gutters at max(24, (pane - 692) / 2) — 64.6 in
     mock3's 821 pane, 24 in mock4's 490 pane. Card padding-top 20 + body
     padding-top 24 = the title line box at y 44. */
  .nr-root.nr-root--detail .nr-reminder-card {
    width: 100%;
    max-width: 692px;
    margin-inline: auto;
    padding: var(--sp-lg) 0;
    border-radius: 0;
    background: none;
  }

  .nr-root--detail .reminder-heading-container {
    gap: var(--sp-sm); /* title box 84 → action row 96 */
    padding: 0 0 var(--sp-lg); /* row 132 → hairline 152 (mock 151) */
    border-bottom-color: var(--border-secondary); /* mock #E0E0E0 = .12 */
  }

  /* Ring 30 / stroke 2 in a 32 slot (48→80); the title starts at slot +
     12 gap = content-left + 44. */
  .nr-root--detail .reminder-heading-container .nr-completion-circle {
    width: 30px;
    height: 30px;
    margin: 5px 1px 0;
    border-color: var(--text-secondary); /* mock #8E8E8E */
  }

  .nr-root--detail .reminder-heading-container .nr-completion-circle:hover {
    border-color: var(--text-primary);
  }

  .nr-root--detail .reminder-heading-container .nr-completion-circle--done {
    width: 32px;
    height: 32px;
    margin: 4px 0 0;
  }

  /* 24px glyph → the 32 slot. */
  .nr-root--detail
    .reminder-heading-container
    .nr-completion-circle--done
    .icon {
    transform: scale(1.3333);
  }

  /* Flat tracking, as in the mocks (and iOS FontSystem's tracking 0 "for
     Figma parity"): Chromium applies SF Pro's size-specific tracking —
     measured +0.43px a letter at 32, −0.45 at 18, −0.23 at 15 (Apple's
     trak table) — and these letter-spacings cancel it. Segoe UI has no
     such tracking, so Windows renders these runs ~2.5% looser. */
  /* New toDo reads like the page it becomes (running notes: capture text
     styles didn't match detail) — same title and body type. */
  .nr-root .nr-title {
    font-size: var(--text-4xl); /* 32 */
    font-weight: 700;
    line-height: 40px;
    letter-spacing: -0.43px; /* flat tracking, above */
    min-height: 40px;
    max-height: 120px; /* still 3 lines */
  }

  .nr-root--detail .reminder-heading-container .thumb-stack {
    right: 0;
  }

  /* Deck clearance (iOS EditableTitle.swift:226-233, deckHeight 88;
     CaptureThumbnailStack.swift:22-26): the strip starts 8 under the
     deck — 44 + 84 + 12 = 140 = deck top 44 + 88 + 8. */
  .nr-root--detail
    .reminder-heading-container:has(.thumb-stack):has(.nr-action-strip)
    .nr-title-row {
    min-height: calc(88px + var(--sp-xs) - var(--sp-sm));
  }

  /* Card gap 8 + 12 = the body's first line 20 below the hairline. */
  .nr-root--detail .nr-body-editor .ProseMirror {
    padding: var(--sp-sm) 0 var(--sp-lg);
  }

  /* Colour stays --label-special (.detail-body-editor .ProseMirror). */
  .nr-root .detail-body-editor .ProseMirror {
    font-size: var(--text-xl);
    line-height: 25px;
    letter-spacing: 0.45px; /* flat tracking (.nr-title) */
  }

  /* Children that carried the old card's 24px inset. */
  .nr-root--detail .detail-audio-transcript {
    padding-inline: 0;
  }

  .nr-root--detail .nr-body .detail-reminders-list,
  .nr-root--detail .nr-body .detail-action-remind-wrapper {
    padding: 0;
  }

  .nr-root--detail .nr-save-indicator {
    margin-left: calc(-1 * var(--sp-xs));
  }

  /* ── The Please AI third column ──────────────────────────────────────
     Mock4: a flat bg-secondary column — no margin, radius, border or
     shadow. Mounted while closed ([hidden]) so the composer's lazy init,
     scroll and draft survive.

     Its width: --ai-panel-width is the drag-resize override (absolute px,
     persisted), set inline on .nr-root--detail. --ai-panel-w is what
     renders: that width (default 405, mock 405.1), narrowed so the main
     pane keeps 378 — the accessory rail (330) + 24 each side — down to
     the 300 floor. It resolves where it's used (the panel's width, the
     rail and pill insets below), each against the same pane-wide box, so
     all three agree. A drag past the clamp stops growing (the saved width
     still applies once the window is wide enough). */
  .nr-root--detail {
    --ai-panel-w: clamp(
      300px,
      calc(100% - 378px),
      var(--ai-panel-width, 405px)
    );
  }

  .detail-ai-panel {
    width: var(--ai-panel-w);
    flex-shrink: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    position: relative; /* anchors the resize handle and the glow */
    overflow: hidden;
    background: var(--bg-secondary);
  }

  /* The author display:flex above beats the UA [hidden] rule. */
  .detail-ai-panel[hidden] {
    display: none;
  }

  /* Drag-resize handle — invisible 5px strip along the panel's left
     edge; hairline on hover / during a drag. Dragging below the collapse
     threshold closes the panel (detail_msg.AiPanelResizeEnded). */
  .detail-ai-panel-resize-handle {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 5px;
    cursor: col-resize;
    z-index: 10;
  }

  @media (hover: hover) {
    .detail-ai-panel-resize-handle:hover {
      background: var(--border-primary);
    }
  }

  body.panel-resizing .detail-ai-panel-resize-handle {
    background: var(--border-primary);
  }

  /* Inside the panel the chat owns its own scroll: messages flex-grow and
     scroll independently, the composer docks at the panel bottom. This
     deliberately breaks the stacked layout's "single page-level scroll"
     rule — that rule still holds on mobile, where the chat stays in
     .nr-body. */
  /* position: relative, no z-index: the chat paints above the glow by DOM
     order (the glow comes first) without becoming a stacking context — a
     z-index here would trap the card sheet's modal layer (sheet.css,
     400/401) inside it, under the pill, rail and resize handle. */
  .detail-ai-panel .detail-ai-chat {
    position: relative;
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    margin-top: 0;
    padding: 0 var(--sp-xl) var(--sp-xl); /* composer 24 above the bottom */
    /* The stacked layout's `.nr-root .detail-ai-chat` paints bg-secondary;
       the panel's own surface shows through. */
    background: none;
  }

  /* No title bar in the mock; the header's button names the panel. */
  .detail-ai-panel .detail-ai-chat-title {
    display: none;
  }

  .detail-ai-panel .detail-ai-chat-body {
    flex: 1;
    min-height: 0;
    gap: var(--sp-sm); /* chips → composer 13 */
  }

  /* Top fade (mock ~112px, linear); short threads sit on the composer. */
  .detail-ai-panel .detail-ai-chat-messages {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding-top: var(--sp-xl);
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 112px);
    mask-image: linear-gradient(to bottom, transparent 0, #000 112px);
  }

  .detail-ai-panel .detail-ai-chat-messages > :first-child {
    margin-top: auto;
  }

  /* Agent prose ("What would you like to do next?" is agent text). */
  .detail-ai-panel .detail-ai-msg-body {
    font-size: var(--text-xl);
    line-height: 25px;
    letter-spacing: 0.45px; /* flat tracking (.nr-title) */
    color: var(--text-primary-vibrant);
  }

  /* The prose after a card run sits 48 below the last card (mock: card
     bottom 471 → question ink 540.8). */
  .detail-ai-panel .detail-ai-msg:has(.detail-ai-msg-cards) + .detail-ai-msg {
    margin-top: var(--sp-4xl);
  }

  .detail-ai-panel .detail-ai-msg-cards {
    gap: var(--sp-xxl); /* mock ~31 */
  }

  /* Rich cards (chat_card.css), panel only. */
  .detail-ai-panel .chat-card {
    border: none;
    border-radius: var(--radius-xl);
  }

  .detail-ai-panel .chat-card__footer {
    padding: var(--sp-md);
    gap: var(--sp-md);
  }

  /* Line boxes 25 / 20, not the inherited 1.5× (27 / 22.5): iOS
     subHeading / subline (FontSystem.swift) — title top → subtitle top
     28.8 (mock 28.2). */
  .detail-ai-panel .chat-card__title {
    font-size: var(--text-xl);
    font-weight: 500;
    line-height: 25px;
    letter-spacing: 0.45px; /* flat tracking (.nr-title) */
    color: var(--text-primary-vibrant);
  }

  .detail-ai-panel .chat-card__subtitle {
    font-size: var(--text-md);
    line-height: 20px;
    letter-spacing: 0.23px; /* flat tracking (.nr-title) */
  }

  .detail-ai-panel .chat-card__action {
    width: var(--size-5xl);
    height: var(--size-5xl);
    background: var(--accent-blue-elevated);
    color: var(--accent-blue);
  }

  .detail-ai-panel .chat-card__action .icon {
    transform: scale(1.2); /* 20 inline → 24 */
  }

  /* Suggestion chips (suggestion_pills.css), panel only: 56 tall, pitch
     72; question → first chip 16. */
  .detail-ai-panel .suggestion-pills {
    padding-top: 0;
  }

  /* 16 top and bottom around the 25 line (iOS pads 16 all round):
     57 tall, mock 57.6 / 57. The emoji → label gap is the mock's space
     (~5), not suggestion_pills.css's 12. */
  .detail-ai-panel .suggestion-pill {
    min-height: 0;
    padding: var(--sp-md) var(--sp-xl);
    gap: 5px;
  }

  .detail-ai-panel .suggestion-pill-emoji,
  .detail-ai-panel .suggestion-pill-label {
    font-size: var(--text-xl);
    line-height: 25px;
  }

  /* Composer → the mock's white "Chat" card. */
  .detail-ai-panel .chat-composer-form {
    min-height: 90px;
    background: var(--bg-primary);
    border-color: transparent;
    border-radius: var(--radius-xl);
  }

  /* Text hugs the top: the 90 min-height's slack goes under the line,
     not above it (chat.css aligns flex-end) — mock "Chat" ink 21 below
     the card's top edge. */
  .detail-ai-panel .chat-composer-input-wrap {
    align-items: flex-start;
    padding: var(--sp-md) var(--sp-md) 0;
  }

  .detail-ai-panel .chat-composer-milkdown {
    min-height: 25px;
    letter-spacing: 0.45px; /* flat tracking (.nr-title) */
  }

  .detail-ai-panel .chat-composer-milkdown:not(:has(.milkdown))::before,
  .detail-ai-panel .chat-composer-milkdown .ProseMirror {
    font-size: var(--text-xl);
    line-height: 25px;
    padding: 0;
    min-height: 25px;
  }

  .detail-ai-panel .chat-composer-toolbar {
    padding: var(--sp-xs) var(--sp-md) var(--sp-sm);
    gap: var(--sp-sm);
  }

  .detail-ai-panel .chat-composer-add,
  .detail-ai-panel .chat-composer-emoji-btn {
    width: 24px;
    height: 24px;
    color: var(--text-secondary);
  }

  .detail-ai-panel .chat-composer-send {
    width: 24px;
    height: 24px;
  }

  .detail-ai-panel .chat-composer-send:disabled {
    background: var(--bg-secondary); /* mock #F2F2F4 */
    color: var(--text-tertiary); /* mock #C4C3C8 */
  }

  /* Static pastel glow under the composer (mock: blue → green at 45% →
     blue; alpha falls off over ~96px). The mock's ends are tealer and
     duller than pure pastel-2, its middle a duller green: each stop mixes
     in the other two pastels (bottom row within ~10/255 of the mock). iOS
     draws it breathing at the top; the mock is still. */
  .detail-ai-panel-glow {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 96px;
    pointer-events: none;
    background: linear-gradient(
      90deg,
      color-mix(
          in srgb,
          var(--pastel-2) 70%,
          color-mix(in srgb, var(--pastel-3), var(--pastel-1))
        )
        0%,
      color-mix(
          in srgb,
          var(--pastel-3) 80%,
          color-mix(in srgb, var(--pastel-2), var(--pastel-1))
        )
        45%,
      color-mix(
          in srgb,
          var(--pastel-2) 70%,
          color-mix(in srgb, var(--pastel-3), var(--pastel-1))
        )
        100%
    );
    -webkit-mask-image: linear-gradient(
      to top,
      #000 0,
      rgba(0, 0, 0, 0.72) 32px,
      rgba(0, 0, 0, 0.28) 58px,
      rgba(0, 0, 0, 0.12) 70px,
      transparent 96px
    );
    mask-image: linear-gradient(
      to top,
      #000 0,
      rgba(0, 0, 0, 0.72) 32px,
      rgba(0, 0, 0, 0.28) 58px,
      rgba(0, 0, 0, 0.12) 70px,
      transparent 96px
    );
  }

  /* ── Accessory rail + Messages pill ──────────────────────────────────
     No card any more: both anchor 24 from the main pane's bottom edge —
     the pill on the right (the pane edge moves left by the panel's live,
     drag-resizable, clamped width while it is open), the rail on the
     left. */
  /* Bottom-LEFT of the main pane, level with the Messages pill on the
     right (running notes 2026-10-01: move it left to make room for
     floating messages) — so it neither stacks over the pill nor shifts
     with the AI panel. */
  .nr-root--detail .acc-rail {
    right: auto;
    left: var(--sp-xl);
    bottom: var(--sp-xl);
    transition: opacity 150ms ease;
  }

  /* The mocks (2026-09-24) show the reading state with no rail: the
     header strip now carries its media and ⋯ (share), leaving checklist
     and dictation, which are body-editing tools. So the rail shows while
     the title or body is being edited, or while it holds focus itself
     (Tab from the editor lands on it and keeps it up); at rest it is
     transparent and click-through. */
  .nr-root--detail:not(
      :has(
        .nr-title:focus,
        .detail-body-editor:focus-within,
        .acc-rail:focus-within
      )
    )
    .acc-rail {
    opacity: 0;
    pointer-events: none;
  }

  .nr-root--detail:has(.detail-ai-panel:not([hidden])) .detail-messages-pill {
    right: calc(var(--ai-panel-w) + var(--sp-xl));
  }

  /* Mock: 220×58 capsule — chat glyph, bold "Messages", the member pile;
     hairline edge + a soft float shadow. */
  .detail-messages-pill {
    position: absolute;
    right: var(--sp-xl);
    bottom: var(--sp-xl);
    z-index: 5;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3xs);
    height: 58px;
    padding: 0 var(--sp-md);
    border: 1px solid var(--border-float);
    border-radius: var(--radius-full);
    background: var(--bg-primary);
    box-shadow: var(--shadow-float);
    color: var(--text-primary);
    font: inherit;
    cursor: pointer;
  }

  .detail-messages-pill-label {
    font-size: var(--text-xl);
    font-weight: 700;
    line-height: 25px;
    letter-spacing: 0.45px; /* flat tracking (.nr-title) */
  }

  .detail-messages-pill-faces {
    display: inline-flex;
    margin-left: var(--sp-lg);
    --pile-overlap: 14px; /* mock pitch 9.6 */
  }

  @media (hover: hover) {
    .detail-messages-pill:hover {
      background: var(--bg-secondary);
    }

    .detail-messages-pill:hover .shared-pile {
      --pile-ring: var(--bg-secondary);
    }
  }

  .detail-messages-pill:focus-visible {
    outline: 2px solid var(--accent-blue);
    outline-offset: 2px;
  }
}

/* ── Save indicator (bottom of the editor card) ──────────────────────────
   The old header ✓'s replacement: quiet "Saved ✓" at rest, "Saving…"
   while a write is in flight (check pulses), tappable "Save" while
   drafts are dirty. Bottom-LEFT of the card: the accessory rail floats
   over the bottom-right corner, and the two must not overlap. The left
   margin aligns the pill's text with the editor's var(--sp-xl) content
   inset (minus the pill's own horizontal padding). */
.nr-save-indicator {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3xs);
  align-self: flex-start;
  margin-top: var(--sp-sm);
  margin-left: calc(var(--sp-xl) - var(--sp-xs));
  padding: 2px var(--sp-xs);
  border: none;
  border-radius: var(--radius-full);
  background: none;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 500;
  color: var(--text-secondary);
}

.nr-save-indicator-check {
  display: inline-flex;
  color: var(--accent-green);
}

.nr-save-indicator--saving .nr-save-indicator-check {
  animation: nr-save-pulse 1s ease-in-out infinite;
}

@keyframes nr-save-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.3;
  }
}

.nr-save-indicator--dirty {
  color: var(--text-secondary);
  cursor: pointer;
}

.nr-save-indicator--dirty .nr-save-indicator-check {
  color: var(--text-tertiary);
}

@media (hover: hover) {
  .nr-save-indicator--dirty:hover {
    background: var(--fill-elevated-quaternary);
    color: var(--text-primary);
  }
}

.nr-save-indicator--failed {
  color: var(--accent-red);
  cursor: pointer;
}

/* Public (unauthenticated) item page — /item/<id> for anonymous visitors.
   The content column reuses the signed-in detail page's .detail-* classes
   (see detail.css); this file only supplies the page wrapper and the
   sign-in footer link. */

.public-item-page {
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg-primary);
}

.public-item-page .detail-scroll {
  flex: 1;
  padding-top: var(--sp-xl);
}

.public-item-signin {
  padding: var(--sp-md);
  text-align: center;
  font-weight: 600;
  text-decoration: none;
  color: var(--accent-blue);
  border-top: 1px solid var(--border-primary);
}

/* Shared snapshot page — /s/<token>. After public_item.css: it reuses that
   file's .public-item-signin footer on the signed-out mount. */
/* Shared snapshot page — /s/<token>. The content column reuses the signed-in
   detail page's .detail-* classes (see detail.css) plus the reminder
   anatomy's .nr-reminder-card / .reminder-heading-container / thumbnail deck
   (see new_reminder.css); this file supplies only the page wrapper and the
   two states that wrap the content: the signed-out mount's sign-in footer
   (.public-item-signin, from public_item.css) and the single not-found
   rendering. */

.share-page {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  background: var(--bg-primary);
}

/* Signed-in mount: `.main-pane` is a BLOCK box (shell.css) with
   `overflow: hidden`, so the `flex: 1` above is inert there — the page would
   collapse to content height, `.detail-scroll` would never be bounded, and
   anything past the fold would be clipped with no document scroller to fall
   back on (base.css puts `overflow: hidden` on html/body). An explicit
   height is what every other main-pane child does (.chat, .search-page). */
.main-pane > .share-page {
  height: 100%;
}

.share-page .detail-scroll {
  flex: 1;
  padding-top: var(--sp-xl);
}

/* Signed-out mount: content plus the sign-in footer, owning the viewport.
   The signed-in mount renders the same .share-page inside the app shell. */
.share-page-anonymous {
  height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg-primary);
}

/* Centres only because the page now has a resolved height in both mounts —
   `height: 100%` under `.main-pane`, `flex: 1` under `.share-page-anonymous`.
   Without that these two are no-ops against an auto-height box. */
.share-page-unavailable {
  align-items: center;
  justify-content: center;
}

.share-unavailable-text {
  padding: var(--sp-xl);
  text-align: center;
  color: var(--text-secondary);
}

/* The Duplicate action, below the reminder card. Present for every
   visitor (see app/share_view.view) — the button styling itself mirrors
   .auth-submit's filled/muted-disabled treatment; the failure banner reuses
   .people-search-error (chat.css) rather than inventing a new one. */
.share-duplicate-row {
  padding: var(--sp-lg) var(--sp-xl) var(--sp-xxl);
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
}

.share-duplicate-btn {
  align-self: flex-start;
  padding: var(--sp-xs) var(--sp-lg);
  border: none;
  border-radius: var(--radius-full);
  background: var(--fg);
  color: var(--bg);
  font-size: var(--text-sm);
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

.share-duplicate-btn:disabled {
  background: var(--bg-secondary);
  color: var(--text-secondary);
  cursor: not-allowed;
}

/* The body, rendered from markdown into .detail-description (detail.css owns
   the list/heading/link/image rules). Two things the owner's editor gets from
   its ProseMirror inset and this static block would otherwise lack: the
   heading container's horizontal padding, so the text lines up under the
   title rather than at the card edge, and the body reading colour — iOS
   labelSpecial, the same as .detail-description (this rule loads later at
   equal specificity, so it must name the colour itself). */
.share-body {
  padding: var(--sp-lg) var(--sp-xl);
  color: var(--label-special);
}

/* The join page's attribution row sits inside the card above the heading;
   inset it the same way so name, title and body share one left edge. */
.nr-reminder-card > .join-inviter,
.nr-reminder-card > p.join-kicker {
  padding: 0 var(--sp-xl);
}

/* The thumbnail deck hangs off the heading container's top-right, out of
   flow (detail.css pulls .thumb-stack absolute so a short title doesn't get
   an 80px row). On the signed-in page the alert-pill row sits under the
   title and carries the hairline clear of the deck; these read-only pages
   render no pill row, so with a one-line title the hairline landed at
   ~60px and the 80px deck ran straight through it. Reserve the deck's
   height — the box, the few pixels the −7° fan and its shadow overhang,
   and the container's own bottom padding — so the line waits for it.
   Border-box: the min-height includes that padding, and the hairline is
   the border itself. A two-line title is taller than this and unaffected. */
.share-page .reminder-heading-container:has(.thumb-stack) {
  min-height: calc(80px + 10px + var(--sp-md));
}

/* Collaboration invite page — /join/<token>.

   Almost nothing lives here on purpose. The page is built on the share page's
   skeleton AND its anatomy (.share-page > .detail-scroll > .detail-content >
   .nr-reminder-card, reminder_heading with a thumbnail_stack deck, .share-body
   for the body, .share-duplicate-row / .share-duplicate-btn for the action), so
   the two anonymous link pages read as one product and share every rule that
   governs the card, the paddings, the scroll behaviour and the button.

   What follows is only what the share page has no equivalent for: the
   attribution row (a share is published content; an invitation comes from a
   person). There was an expiry line here too until collab links stopped
   expiring (2026-09-09); nothing replaced it. */

.join-inviter {
  margin-bottom: var(--sp-sm);
}

/* The line under the inviter's name, and the whole prompt on its own when the
   owner row could not be read — join_view.view_inviter renders it either way,
   so this must read correctly standing alone as well as stacked. */
.join-kicker {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: 1.3;
}

/* Standalone: it is a <p> rather than a <span>, and carries the spacing the
   author pill would otherwise have supplied. */
p.join-kicker {
  margin: 0 0 var(--sp-sm);
}

/* The iOS-only action row. On iOS this page is only ever seen by a visitor
   WITHOUT the app — an installed one takes the universal link before Safari
   loads — so the store leads and signing in is demoted. Both reuse
   .share-duplicate-btn; these rules only re-weight it. */

/* The iOS row is a STACK of three — action, explanation, alternative — where
   every other use of .share-duplicate-row holds a single button. Its
   content-width `align-self: flex-start` and one flat `--sp-xs` gap are tuned
   for that single button, and applied to three items they read as a cramped
   ragged pile on a phone. So the two actions stretch to the column and the
   sentence between them gets its own margins. */

/* An <a> needs the box back that <button> gave for free. */
.join-install-btn {
  align-self: stretch;
  display: block;
  text-align: center;
  text-decoration: none;
}

/* Muted fill rather than a new outline treatment: it's the same pill the
   disabled state already uses, so the page grows no new button vocabulary.
   Stretched to match the primary above it — two actions of different widths
   stacked left-aligned is what made this look unfinished. */
.join-secondary-btn {
  align-self: stretch;
  text-align: center;
  background: var(--bg-secondary);
  color: var(--fg);
}

/* Sits BETWEEN the two actions, so it needs air on both sides rather than the
   row's single gap — see join_view.view_join_action. */
.join-after-install {
  margin: var(--sp-xs) 0;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: 1.4;
}

/* Capture modal — overlay + dialog for the New Capture flow. */

.capture-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.capture-modal {
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  width: min(440px, calc(100vw - 48px));
  min-height: 480px;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: var(--sp-md);
  gap: var(--sp-xl);
}

.capture-modal-body {
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Header ------------------------------------------------------------------ */
.capture-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.capture-title {
  font-size: 16px;
  font-weight: 400;
  color: var(--text-primary);
  margin: 0;
}

.capture-close {
  background: transparent;
  border: none;
  color: var(--text-primary);
  cursor: pointer;
  padding: 0;
  width: 24px;
  height: 24px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
}
.capture-close:hover { background: var(--bg-secondary); }
.capture-close .icon { width: 14px; height: 14px; }

/* Drop zone --------------------------------------------------------------- */
.capture-dropzone {
  position: relative; /* contains the absolutely-positioned file input */
  background: rgba(0, 0, 0, 0.04); /* --bg-quaternary not available in tokens */
  border-radius: var(--radius-md);
  padding: 15px var(--sp-xl);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.capture-dropzone-icon {
  display: flex;
  align-items: center;
  color: var(--text-primary);
}

.capture-dropzone-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
}

/* Nested inside its parent label and sized to cover it fully so the user
   is physically tapping the file input itself — bypasses iOS Safari's
   label-for activation quirks entirely. The label needs position:relative. */
.capture-file-input-hidden {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  /* font-size: 100px pushes the "Choose File" text off-screen on some
     Android browsers that render it inside the input bounds */
  font-size: 100px;
}

.capture-dropzone-text {
  font-size: 14px;
  font-weight: 400;
  color: var(--text-primary);
  margin: 0;
  text-align: center;
}
.capture-dropzone-link {
  color: var(--accent-blue);
  text-decoration: none;
  cursor: pointer;
}
.capture-dropzone-link:hover {
  text-decoration: underline;
}
.capture-dropzone-formats {
  font-size: 12px;
  color: var(--text-secondary);
  margin: var(--sp-3xs) 0 0;
}

/* Uploads list ------------------------------------------------------------ */
.capture-uploads {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
}

.capture-upload {
  display: flex;
  gap: 12px;
  padding: 12px 14px;
  background: rgba(0, 0, 0, 0.7);
  border-radius: 12px;
  align-items: flex-start;
}

.capture-upload-type-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
  flex-shrink: 0;
  padding-top: 1px;
}

.capture-upload-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.capture-upload-header {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 8px;
  align-items: center;
}

.capture-upload-name {
  font-size: var(--text-sm);
  font-weight: 500;
  color: #ffffff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.capture-upload-remove {
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.5);
  padding: 0;
  width: 18px;
  height: 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.capture-upload-remove:hover { color: #ffffff; }

.capture-upload-percent {
  font-size: var(--text-xs);
  color: rgba(255, 255, 255, 0.5);
  font-variant-numeric: tabular-nums;
  min-width: 36px;
  text-align: right;
  flex-shrink: 0;
}
.capture-upload-percent--done {
  color: #22c55e;
}

.capture-upload-bar {
  height: 3px;
  background: rgba(255, 255, 255, 0.2);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.capture-upload-bar-fill {
  height: 100%;
  background: var(--accent-blue);
  transition: width 0.15s ease;
}
.capture-upload-bar-fill--done {
  background: #22c55e;
}

/* Image preview shown in drop mode after a file is selected */
.capture-drop-preview {
  position: relative;
  width: 100%;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-secondary);
  max-height: 240px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.capture-drop-preview-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  max-height: 240px;
  display: block;
}
.capture-drop-preview-change {
  position: absolute;
  bottom: var(--sp-xs);
  right: var(--sp-xs);
  padding: 4px 10px;
  font-size: var(--text-xs);
  font-weight: 500;
  color: #ffffff;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border-radius: var(--radius-full);
  cursor: pointer;
  user-select: none;
  transition: background 0.15s ease;
}
.capture-drop-preview-change:hover {
  background: rgba(0, 0, 0, 0.75);
}


/* Nav tabs (mode switches) ------------------------------------------------ */
.capture-nav {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-xs);
}

.capture-nav-title {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-tertiary);
  margin: 0;
}

.capture-nav-tabs {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 4px 0;
  gap: var(--sp-lg);
}

.capture-nav-tab {
  background: transparent;
  border: none;
  font: inherit;
  font-size: 16px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  padding: var(--sp-3xs) var(--sp-sm);
  border-radius: var(--radius-full);
  transition: color 0.12s, background 0.12s;
}
.capture-nav-tab:hover {
  color: var(--text-primary);
}
.capture-nav-tab--active {
  font-weight: 600;
  color: var(--text-primary);
  background: var(--bg-secondary);
}

/* Footer ------------------------------------------------------------------ */
.capture-footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-sm);
  padding-top: var(--sp-sm);
  border-top: 1px solid var(--border-primary);
}

.capture-btn {
  padding: var(--sp-xs) var(--sp-lg);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border-primary);
  font-family: inherit;
}
.capture-btn-cancel {
  background: var(--bg-primary);
  color: var(--text-primary);
}
.capture-btn-cancel:hover {
  background: var(--bg-subtle);
}
.capture-btn-save {
  background: var(--bg-body);
  color: var(--text-primary);
  border-color: var(--border-primary);
}
.capture-btn-save:hover:not(:disabled) {
  background: var(--bg-subtle);
}
.capture-btn-save-disabled {
  background: var(--bg-subtle);
  color: var(--text-secondary);
  border-color: var(--border-primary);
  cursor: not-allowed;
}


/* Mode forms -------------------------------------------------------------- */
.capture-link-form,
.capture-text-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
}

.capture-voice-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
}

.capture-camera-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  align-items: center;
}

/* Desktop webcam viewfinder + shutter ------------------------------------ */
.capture-webcam-wrap {
  width: 100%;
  position: relative; /* needed for the absolute-positioned flip button */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-md);
}

.capture-webcam-viewfinder {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-md);
  background: #000;
  display: block;
  object-fit: cover;
}

.capture-webcam-shutter {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 3px solid var(--border-primary);
  background: var(--bg-primary);
  cursor: pointer;
  position: relative;
  flex-shrink: 0;
  transition: border-color 0.12s;
}
.capture-webcam-shutter::after {
  content: "";
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background: var(--bg-disabled);
  transition: background 0.12s;
}
.capture-webcam-shutter--active::after {
  background: var(--accent-red);
}
.capture-webcam-shutter--active:hover::after {
  opacity: 0.8;
}
.capture-webcam-shutter:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* Camera flip button (desktop — overlaid bottom-right of webcam-wrap) ------- */
.capture-webcam-flip {
  position: absolute;
  bottom: var(--sp-md);
  right: var(--sp-md);
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.12s;
  flex-shrink: 0;
}
.capture-webcam-flip:hover { background: rgba(0, 0, 0, 0.65); }
.capture-webcam-flip .icon { color: #fff; }

/* Webcam photo preview + retake ------------------------------------------ */
.capture-webcam-preview {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-md);
}

.capture-webcam-preview-img {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-md);
  object-fit: cover;
  display: block;
}

.capture-webcam-retake {
  background: transparent;
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-sm);
  padding: var(--sp-xs) var(--sp-lg);
  cursor: pointer;
}
.capture-webcam-retake:hover {
  color: var(--text-primary);
  border-color: var(--text-secondary);
}

.capture-link-form label,
.capture-text-form label {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.capture-link-form input,
.capture-text-form input,
.capture-text-content {
  padding: var(--sp-sm);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text-primary);
  font: inherit;
  outline: none;
}
.capture-link-form input:focus,
.capture-text-form input:focus,
.capture-text-content:focus {
  border-color: var(--bg-secondary);
}

.capture-text-content {
  resize: vertical;
  min-height: 120px;
}

.capture-submit {
  padding: var(--sp-sm);
  border: none;
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}
.capture-submit:disabled { opacity: 0.4; cursor: not-allowed; }

/* Voice ------------------------------------------------------------------- */

/* Override the shared form layout for voice — needs a tighter gap */
.capture-voice-form {
  gap: var(--sp-sm);
  align-items: center;
}

/* Waveform canvas — fills available width with a fixed height */
.capture-voice-waveform {
  width: 100%;
  height: 160px;
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  display: block;
  cursor: pointer; /* scrub hint */
}

/* Taller canvas for the full-height mobile sheet */
.capture-voice-waveform--sheet {
  height: 200px;
}

/* Recording timer */
.capture-voice-timer {
  font-size: var(--text-xl);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.03em;
  color: var(--text-primary);
}

/* Controls row — play + record buttons side by side */
.capture-voice-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-sm);
}

/* Round icon button for play/pause */
.capture-voice-play-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1.5px solid var(--border-primary);
  background: var(--bg-primary);
  color: var(--text-primary);
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.capture-voice-play-btn:hover {
  background: var(--bg-secondary);
}

/* Big pill record / stop / re-record button */
.capture-voice-record-btn {
  padding: 12px 28px;
  border: none;
  border-radius: var(--radius-full);
  background: #ef4444;
  color: #ffffff;
  font: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s;
}
.capture-voice-record-btn:hover {
  background: #dc2626;
}
/* Pulsing glow while actively recording */
.capture-voice-record-btn--recording {
  animation: voice-record-pulse 1.4s ease-in-out infinite;
}

@keyframes voice-record-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
  50%       { box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); }
}

.capture-voice-hint,
.capture-camera-hint {
  text-align: center;
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

/* Camera file input ------------------------------------------------------- */
.capture-camera-input {
  display: block;
  margin: var(--sp-md) auto;
}

/* Sidebar button variant — used by "+ New Capture" row. ------------------- */
.sidebar-row-button {
  background: transparent;
  border: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  width: 100%;
  text-align: left;
}

/* Link preview card ------------------------------------------------------- */
.capture-link-preview {
  display: flex;
  gap: var(--sp-sm);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-secondary);
  animation: fadeIn 0.15s ease;
}

.capture-link-preview-image-wrap {
  flex-shrink: 0;
  width: 80px;
  height: 80px;
  overflow: hidden;
}

.capture-link-preview-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.capture-link-preview-body {
  flex: 1;
  min-width: 0;
  padding: var(--sp-sm);
  display: flex;
  flex-direction: column;
  gap: 4px;
  justify-content: center;
}

.capture-link-preview-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.capture-link-preview-desc {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  margin: 0;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* ─── Keyframe animations ────────────────────────────────────────────── */

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ─────────────────────────────────────────────────────────────────────────
   Mobile Capture Sheet (full-height dark overlay)
   Only rendered on mobile via the is_mobile() FFI check.
   ───────────────────────────────────────────────────────────────────────── */

.capture-sheet-mobile {
  /* Explicit edges instead of shorthand `inset` for older Safari compat */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  /* Contain all children — critical on iOS Safari to prevent rubber-band
     scroll leaking out of the fixed layer on both axes */
  overflow: hidden;
  /* Belt-and-suspenders: block overscroll bounce at the sheet level too */
  overscroll-behavior: none;
  /* iOS momentum scroll must never propagate to the document behind */
  touch-action: none;
  background: #000;
  z-index: 401;
  display: flex;
  flex-direction: column;
  /* Slide up on open */
  animation: sheet-slide-in 0.32s cubic-bezier(0.32, 0.72, 0, 1) forwards;
  /* Safe area bottom padding for home indicator */
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* ── Floating close button ──────────────────────────────────────────────── */

.capture-sheet-close {
  position: absolute;
  top: calc(16px + env(safe-area-inset-top, 0px));
  right: 16px;
  z-index: 10;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: none;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
}

/* ── Body (fills all space between close btn and tabs) ─────────────────── */

.capture-sheet-body {
  flex: 1;
  min-height: 0;
  /* Prevent children from overflowing the flex column on iOS Safari */
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* ── Tab bar ─────────────────────────────────────────────────────────────── */

.capture-sheet-tabs {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-around;
  padding: 10px 0 12px;
  background: #000;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.capture-sheet-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.4);
  cursor: pointer;
  padding: 4px 0;
  transition: color 0.15s;
}

.capture-sheet-tab--active {
  color: #fff;
  font-weight: 600;
}

.capture-sheet-tab-label {
  font-size: 10px;
  letter-spacing: 0.01em;
  line-height: 1;
}

/* ── Link mode ───────────────────────────────────────────────────────────── */

.capture-sheet-link {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* Hero: full-bleed OG image background */
.capture-sheet-hero {
  flex: 1;
  min-height: 0;
  position: relative;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 32px;
  /* Clip the background image and absolute overlays to this box */
  overflow: hidden;
  /* Gradient overlay so CTA text reads over bright images */
  background-color: #111;
}

.capture-sheet-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(0,0,0,0.1) 0%,
    rgba(0,0,0,0.0) 40%,
    rgba(0,0,0,0.35) 100%
  );
  pointer-events: none;
}

/* Empty state placeholder */
.capture-sheet-hero--empty {
  background-color: #111;
}

/* Decorative circle — only visible once an OG image is loaded, positioned
   32px above the capture tab bar at the bottom of the sheet. */
.capture-sheet-hero:not(.capture-sheet-hero--empty)::before {
  content: "";
  display: block;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: rgba(255,255,255,0.08);
  position: absolute;
  bottom: 32px;
  left: 50%;
  transform: translateX(-50%);
}

/* "Use this link" CTA pill */
.capture-sheet-cta {
  position: relative;
  z-index: 1;
  background: rgba(255, 255, 255, 0.92);
  color: #000;
  font-size: var(--text-base);
  font-weight: 600;
  padding: 10px 28px;
  border-radius: var(--radius-full);
  border: none;
  cursor: pointer;
  letter-spacing: -0.01em;
  box-shadow: 0 2px 16px rgba(0,0,0,0.35);
  transition: transform 0.12s, opacity 0.12s;
}

.capture-sheet-cta:active {
  transform: scale(0.97);
}

.capture-sheet-cta--disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* URL input bar */
.capture-sheet-url-bar {
  flex-shrink: 0;
  padding: 14px 16px 10px;
  background: #000;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.capture-sheet-url-input {
  display: block;
  width: 100%;
  background: transparent;
  border: none;
  outline: none;
  color: #fff;
  font-size: var(--text-lg);
  font-family: var(--font-sans);
  /* Monospace-ish feel for URLs */
  letter-spacing: -0.01em;
  caret-color: #fff;
  padding: 0;
  /* Re-enable touch interaction — parent sets touch-action: none to lock
     document scroll, but inputs need touch events for focus / text selection */
  touch-action: auto;
}

.capture-sheet-url-input::placeholder {
  color: rgba(255, 255, 255, 0.3);
}

/* ── Shared form layout (Text / Voice / Gallery / Camera) ────────────────── */

.capture-sheet-form {
  flex: 1;
  min-height: 0;
  /* Scroll the form content when the keyboard shrinks the viewport;
     overscroll-behavior: none stops bounce reaching the document AND the
     local rubber-band (site-wide no-bounce rule, see base.css). */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: none;
  touch-action: pan-y;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-md);
  padding: 56px 24px 24px; /* top pad clears close button */
  color: #fff;
}

.capture-sheet-input {
  width: 100%;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: var(--radius-md);
  color: #fff;
  font: inherit;
  font-size: 16px;
  padding: 12px 14px;
  outline: none;
}

.capture-sheet-input::placeholder {
  color: rgba(255,255,255,0.35);
}

.capture-sheet-input:focus {
  border-color: rgba(255,255,255,0.3);
}

.capture-sheet-textarea {
  width: 100%;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: var(--radius-md);
  color: #fff;
  font: inherit;
  font-size: 16px;
  padding: 12px 14px;
  outline: none;
  resize: none;
  min-height: 140px;
  touch-action: auto;
}

.capture-sheet-textarea::placeholder {
  color: rgba(255,255,255,0.35);
}

.capture-sheet-textarea:focus {
  border-color: rgba(255,255,255,0.3);
}

.capture-sheet-save-btn {
  width: 100%;
  padding: 14px;
  background: #fff;
  color: #000;
  font-size: var(--text-base);
  font-weight: 600;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: opacity 0.12s;
}

.capture-sheet-save-btn--disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* Voice recorder */
.capture-sheet-voice-indicator {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-sm);
  color: rgba(255,255,255,0.6);
}

.capture-sheet-voice-recording {
  color: #ff453a;
  animation: pulse 1.2s ease-in-out infinite;
}

.capture-sheet-voice-timer {
  font-size: var(--text-2xl);
  font-weight: 300;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
  color: #fff;
}

.capture-sheet-record-btn {
  padding: 12px 32px;
  background: rgba(255,255,255,0.1);
  color: #fff;
  border: 1px solid rgba(255,255,255,0.2);
  border-radius: var(--radius-full);
  font-size: var(--text-base);
  font-weight: 500;
  cursor: pointer;
}

/* Camera label row (label + flip toggle) ----------------------------------- */
.capture-sheet-camera-label-row {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-sm);
}

/* Flip camera button (mobile sheet) */
.capture-sheet-flip-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-full);
  color: rgba(255, 255, 255, 0.75);
  font: inherit;
  font-size: var(--text-sm);
  padding: 6px 14px 6px 10px;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.capture-sheet-flip-btn:active {
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
}
.capture-sheet-flip-btn .icon { color: rgba(255, 255, 255, 0.75); }

.capture-sheet-flip-label {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
}

/* Gallery / Camera file label — input is nested inside and overlays it */
.capture-sheet-file-label {
  position: relative; /* contains the absolute-positioned file input */
  overflow: hidden;   /* clips the input overlay to the label's border-radius */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-sm);
  padding: 40px 32px;
  border: 2px dashed rgba(255,255,255,0.18);
  border-radius: var(--radius-lg);
  color: rgba(255,255,255,0.6);
  font-size: var(--text-base);
  cursor: pointer;
  width: 100%;
  text-align: center;
  transition: border-color 0.15s, color 0.15s;
}

.capture-sheet-file-label:active {
  border-color: rgba(255,255,255,0.4);
  color: #fff;
}

.capture-sheet-error {
  color: #ff453a;
  font-size: var(--text-sm);
  text-align: center;
  margin: 0;
}

/* ── Text mode: Milkdown editor container ───────────────────────────────── */

/* Desktop (inside .capture-text-form) */
.capture-text-editor {
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  min-height: 128px;
  padding: var(--sp-sm);
  cursor: text;
  flex: 1;
  transition: border-color 0.12s;
  overflow-y: auto;
}

.capture-text-editor:focus-within {
  border-color: var(--bg-secondary);
  outline: none;
}

.capture-text-editor .ProseMirror {
  outline: none;
  min-height: 96px;
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--text-primary);
  word-break: break-word;
}

/* Placeholder */
.capture-text-editor .ProseMirror p.is-editor-empty::before {
  content: "Write your note...";
  color: var(--text-tertiary);
  float: left;
  height: 0;
  pointer-events: none;
}

/* Inline formatting */
.capture-text-editor .ProseMirror strong { font-weight: 700; }
.capture-text-editor .ProseMirror em { font-style: italic; }
.capture-text-editor .ProseMirror code {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.9em;
  background: var(--bg-secondary);
  border-radius: 3px;
  padding: 1px 4px;
}

/* Block elements */
.capture-text-editor .ProseMirror h2 {
  font-size: 1.15em;
  font-weight: 700;
  margin: 0.5em 0 0.25em;
  line-height: 1.3;
}
.capture-text-editor .ProseMirror h3 {
  font-size: 1.05em;
  font-weight: 600;
  margin: 0.5em 0 0.25em;
  line-height: 1.3;
}
.capture-text-editor .ProseMirror p {
  margin: 0 0 0.4em;
}
.capture-text-editor .ProseMirror p:last-child { margin-bottom: 0; }
.capture-text-editor .ProseMirror ul,
.capture-text-editor .ProseMirror ol {
  margin: 0 0 0.4em;
  padding-left: 1.4em;
}

/* Mobile sheet variant */
.capture-text-editor--sheet {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.12);
  flex: 1;
  min-height: 0;
  width: 100%;
  /* Re-enable scroll inside the editor on iOS */
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
}

.capture-text-editor--sheet:focus-within {
  border-color: rgba(255, 255, 255, 0.3);
}

.capture-text-editor--sheet .ProseMirror {
  color: #fff;
  min-height: 80px;
}

.capture-text-editor--sheet .ProseMirror p.is-editor-empty::before {
  content: "Write your note...";
  color: rgba(255, 255, 255, 0.35);
}

.capture-text-editor--sheet .ProseMirror code {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
}

/* Mobile sheet text form layout override — top-align content */
.capture-sheet-form--text {
  justify-content: flex-start;
  /* A bit less top padding since toolbar + editor take up full height */
  padding-top: 52px;
}

/* ── Camera mode sheet — full-bleed getUserMedia viewfinder ─────────────── */

.capture-sheet-form--camera {
  padding: 0;
  justify-content: flex-start;
  overflow: hidden;
}

/* Viewfinder container fills all available height */
.capture-sheet-webcam-wrap {
  position: relative;
  width: 100%;
  flex: 1;
  min-height: 0;
  background: #000;
}

/* Live preview video — covers the wrap div */
.capture-sheet-webcam-viewfinder {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Shutter button — centred at the bottom of the viewfinder */
.capture-sheet-webcam-shutter {
  position: absolute;
  bottom: 28px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  width: 68px;
  height: 68px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.85);
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color 0.12s;
}

.capture-sheet-webcam-shutter::after {
  content: "";
  position: absolute;
  inset: 7px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.3);
  transition: background 0.12s;
}

.capture-sheet-webcam-shutter--active::after {
  background: #fff;
}

.capture-sheet-webcam-shutter--active:active::after {
  opacity: 0.75;
}

.capture-sheet-webcam-shutter:disabled {
  cursor: not-allowed;
  opacity: 0.45;
}

/* Flip camera button — bottom right corner of the viewfinder */
.capture-sheet-webcam-flip {
  position: absolute;
  bottom: 40px;
  right: 24px;
  z-index: 2;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.12s;
}

.capture-sheet-webcam-flip:active { background: rgba(0, 0, 0, 0.65); }
.capture-sheet-webcam-flip .icon { color: #fff; }

/* Photo preview after shutter — full-bleed image + action buttons */
.capture-sheet-webcam-preview {
  position: relative;
  width: 100%;
  flex: 1;
  min-height: 0;
  background: #000;
  display: flex;
  flex-direction: column;
}

.capture-sheet-webcam-preview-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Action row overlaid at the bottom of the preview */
.capture-sheet-webcam-preview-actions {
  position: relative;
  z-index: 1;
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  padding: 20px 24px 28px;
  background: linear-gradient(to top, rgba(0,0,0,0.6) 0%, transparent 100%);
}

.capture-sheet-webcam-retake {
  align-self: center;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: var(--radius-full);
  color: #fff;
  font: inherit;
  font-size: var(--text-sm);
  padding: var(--sp-xs) var(--sp-xl);
  cursor: pointer;
  transition: background 0.12s;
}

.capture-sheet-webcam-retake:active {
  background: rgba(0, 0, 0, 0.65);
}

/* ============================================================================
   Chat feature: thread-view pane (header, messages list, composer).

   Layout: the chat module renders into the main-pane slot in the app shell,
   taking the full available height. The inner content is capped to a
   readable column width and centered.
   ========================================================================= */

.chat {
  display: grid;
  /* row 1: header | row 2: messages | row 3: mobile-edit cancel bar (collapses when empty) | row 4: composer */
  grid-template-rows: auto 1fr auto auto;
  /* Inherit height from the .main-pane flex chain rather than setting our
     own dvh value. The shell is sized via --vvh (visualViewport.height) in
     dom.mjs, so this correctly reflects the keyboard-adjusted visible area
     on iOS Safari. */
  height: 100%;
  width: 100%;
  position: relative;
  overflow: hidden;
  background: var(--bg-secondary);

}

/* Floating "jump to latest" pill that appears when a new message lands
   while the viewer is scrolled away from the bottom. */
.chat-jump-to-latest {
  position: absolute;
  left: 50%;
  bottom: 72px;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3xs);
  padding: var(--sp-xs) var(--sp-sm);
  background: var(--bg-elevated, var(--bg));
  color: var(--text-primary);
  border: 1px solid var(--border-primary);
  border-radius: 999px;
  font-size: 0.85rem;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 2;
}
@media (hover: hover) {
  .chat-jump-to-latest:hover {
    background: var(--bg-hover);
  }
}

/* Floating scroll-to-bottom button — appears when the user scrolls up.
   Right-aligned to the messages column, 56px above the composer. */
.chat-scroll-to-bottom {
  position: absolute;
  right: var(--sp-xl, 24px);
  bottom: 96px;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-primary, var(--bg-secondary));
  color: var(--text-primary);
  border-radius: var(--radius-xs, 8px);
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  z-index: 10;
  padding: 0;
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
}
.chat-scroll-to-bottom--hidden {
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
}
@media (hover: hover) {
  .chat-scroll-to-bottom:hover {
  background: var(--bg-hover, var(--bg-primary));
}
}

/* ── Header ───────────────────────────────────────────────────────────── */

/* The thread header is the standardized ui/page_header component
   (.page-header + slots, shell.css) — back link (desktop) / sidebar
   toggle (mobile) on the left, settings gear (+ group ⋮ menu) on the
   right. Scoped additions: pin it to the .chat grid's first row and keep
   the old .chat-header bottom inset above the messages list (the shared
   .page-header has no bottom padding). */
.chat > .page-header {
  grid-row: 1;
  padding-bottom: var(--sp-md);
}

/* Thread title is a <button> (tap → Chat Settings), not an <a>. Layout
   and typography come from .page-header-title / .page-header-title-link
   (shell.css); this only strips the native button chrome those
   <a>-oriented classes don't cover. */
.chat-header-title-link {
  background: transparent;
  border: none;
  font-family: inherit;
  line-height: inherit;
  padding: 0;
}

/* The settings gear and ⋮ menu buttons get their sizing/color/radius/
   hover from .header-icon-btn; the page-header right slot handles their
   alignment and spacing. */

/* ── Messages ─────────────────────────────────────────────────────────── */

.chat-messages {
  grid-row: 2;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--sp-xl);
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE/Edge */
  max-width: 800px;
  width: 100%;
  justify-self: center;
}
.chat-messages::-webkit-scrollbar {
  display: none; /* Chrome, Safari, Opera */
}

/* Each message row: avatar column + content stack. The whole row
   reverses for sent messages so the stack hugs the right edge. */
.chat-message-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-xs);
  max-width: 70%;
  min-width: 0; /* prevent flex item from overriding max-width with intrinsic size */
}

.chat-message-row.received {
  align-self: flex-start;
}

.chat-message-row.sent {
  align-self: flex-end;
  /* flex-direction: row-reverse; */
}

.chat-message-row.ai-bare {
  align-self: stretch;
  max-width: 100%;
}

/* Highlight when the current viewer is @-mentioned: a full-bleed band
   across the whole chat column in the elevated accent, with a solid bar
   in the primary accent on the left edge (Julian, 2026-09-24 — was the
   brand green). Both are the accent tokens, so a personalized accent
   (accent.css) carries through. The band carries the emphasis.
   Geometry: rows are normally capped at 70% width, so the mentioned row
   un-caps to full width and cancels the .chat-messages horizontal
   padding with negative margins (restored as padding so content stays
   aligned with its neighbors). Bubbles keep their own width via
   .chat-message-stack — the row stretching doesn't stretch them. */
.chat-message-row.mentioned-me {
  max-width: none;
  width: calc(100% + 2 * var(--sp-xl));
  align-self: stretch;
  margin-inline: calc(var(--sp-xl) * -1);
  padding-inline: calc(var(--sp-xl) - 3px) var(--sp-xl);
  padding-block: var(--sp-xs);
  border-left: 3px solid var(--accent-blue);
  border-radius: 0;
  background: var(--accent-blue-elevated);
}

/* Sent-side mentions (self-mention edge case) keep right alignment of
   the bubble inside the full-width band. */
.chat-message-row.sent.mentioned-me {
  justify-content: flex-end;
}

/* The row un-capping must not let bubbles grow past their normal
   width — restore the 70% cap at the stack level inside the band. */
.chat-message-row.mentioned-me .chat-message-stack {
  max-width: 70%;
}

/* Bubbles inside the band swap to the elevated fill so they read as a
   surface on top of the accent wash (token themes both modes; no filter —
   filters create a containing block that can mis-anchor bubble popovers). */
.chat-message-row.mentioned-me .chat-message-bubble {
  background: var(--fill-elevated-secondary);
}

.chat-timestamp-header {
  align-self: center;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  padding: var(--sp-sm) 0 var(--sp-3xs);
  text-align: center;
}

/* Fixed-width avatar column. Empty for continuation rows so bubbles
   indent under the group's avatar. */
.chat-message-avatar-slot {
  width: 28px;
  flex-shrink: 0;
  align-self: flex-start;
  display: flex;
  justify-content: center;
}

.chat-message-avatar-ai {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  background: var(--fg);
  color: var(--bg);
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 600;
}

/* The quote above a reply (iOS ChatReplyQuoteView): an outlined pill
   with no fill of its own, so it reads as a cut-out rather than a card.
   Pill-round at one line, settling at 20px corners when taller. Capped
   narrower than the bubble so it never runs past what it points at. */
.chat-reply-quote {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  max-width: min(100%, 420px);
  margin-bottom: var(--sp-3xs);
  padding: var(--sp-xs) var(--sp-sm);
  border: 0.5px solid var(--border-secondary);
  border-radius: 20px;
  background: none;
  color: var(--text-primary);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.chat-reply-quote:disabled {
  cursor: default;
}
.chat-reply-quote:not(:disabled):hover {
  background: var(--fill-elevated-quaternary);
}
.chat-reply-quote__name {
  font-size: var(--text-xs);
  font-weight: 600;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-reply-quote__text {
  font-size: var(--text-sm);
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.chat-reply-quote--deleted .chat-reply-quote__text {
  color: var(--text-secondary);
  font-style: italic;
}

/* A message a quote jumped to flashes briefly once it's on screen. */
.chat-message-row--jumped .chat-message-bubble {
  animation: chatJumpFlash 1.5s ease;
}
@keyframes chatJumpFlash {
  0%, 35% { box-shadow: 0 0 0 3px var(--accent-blue-elevated); }
  100%    { box-shadow: 0 0 0 3px transparent; }
}

/* Stack: tiny timestamp + bubble (and reaction wrapper). */
.chat-message-stack {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.chat-message-row.sent .chat-message-stack {
  align-items: flex-end;
}

.chat-message-tiny-time {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  padding: 0 var(--sp-xs);
  margin-bottom: var(--sp-3xs);
}

.chat-message-row.sent .chat-message-tiny-time {
  text-align: right;
}

/* Sender name + tiny time, inline, for the first message of a group (see
   chat.gleam's `header_el`/`show_sender_name`) — both received messages
   and the viewer's own sent messages. The name owns the row's spacing;
   the nested .chat-message-tiny-time keeps its own rules but no longer
   needs its own bottom margin since the row provides it. */
.chat-message-header-row {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3xs);
  margin-bottom: var(--sp-3xs);
}

.chat-message-header-row .chat-message-tiny-time {
  margin-bottom: 0;
  padding: 0;
}

.chat-message-sender-name {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-primary);
  padding-left: var(--sp-xs);
}

/* Bullet separator between the sender name and the tiny time — same
   footprint as .chat-message-tiny-time itself (exact spec, not just
   inherited), so it reads as part of that same tiny-time treatment. */
.chat-message-header-bullet {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  margin-bottom: 0;
  padding: 0;
}

/* "Edited" suffix — same size/colour as the time, italic only */
.chat-message-tiny-time__edited {
  font-style: italic;
  font-size: inherit;
  color: inherit;
}

.chat-message-bubble {
  padding: var(--sp-sm) var(--sp-md);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
  line-height: 1.5;
  overflow-wrap: anywhere; /* break long URLs/tokens at any character; also affects flex min-content */
  min-width: 0;
}

/* AI threads (daily brief, ephemeral, per-item) read a step larger —
   they're prose surfaces, not bubble chatter. Covers both the agent's
   bare messages and the viewer's sent bubbles in the same thread. */
.chat--ai .chat-message-bubble {
  font-size: var(--text-md);
}

/* Continuation bubbles inside a group sit a touch closer to the
   one above. */
.chat-message-row + .chat-message-row .chat-message-bubble {
  margin-top: var(--sp-3xs);
}

/* iOS ChatBubble fills: bubbleReceived #E5E5EA / #2C2C2E,
   bubbleSent #FFFFFF / #000000. */
.chat-message-row.received .chat-message-bubble {
  border-top-left-radius: var(--radius-sm);
  background: var(--bubble-received);
  color: var(--text-primary);
}

.chat-message-row.sent .chat-message-bubble {
  border-top-right-radius: var(--radius-sm);
  background: var(--bubble-sent);
  color: var(--text-primary);
}

/* AI bare: no bubble background; full-width content. Avatar slot
   stays so the row keeps its left-column alignment. */
.chat-message-row.ai-bare .chat-message-stack {
  width: 100%;
}
.chat-message-row.ai-bare .chat-message-bubble {
  padding: 0;
  background: transparent;
  border-radius: 0;
}
.chat-message-row:not(.ai-bare) + .chat-message-row.ai-bare {
  padding-top: var(--sp-md);
}
.chat-message-row.ai-bare .chat-message-text {
  line-height: 1.65;
}
.chat-message-row.ai-bare .chat-message-text p {
  margin: 0 0 var(--sp-sm) 0;
}

.chat-message-ai-bare .chat-message-text ul,
.chat-message-ai-bare .chat-message-text ol,
.chat-message-ai-bare .chat-message-text blockquote,
.chat-message-ai-bare .chat-message-text pre {
  margin-top: -4px;
  margin-right: 0;
  margin-bottom: var(--sp-sm);
  margin-left: 0;
}

/* ── Reactions ───────────────────────────────────────────────────────── */

.chat-message-bubble-wrapper {
  position: relative;
}

.chat-reaction-trigger {
  position: absolute;
  top: -22px;
  /* Anchor to the bubble's LEFT edge so the pill grows RIGHTWARD — the
     open side for received messages (they hug the pane's left). Pinning
     right: -4px grew the pill leftward, so any bubble narrower than the
     pill clipped it against the scroll container's left edge. */
  left: -4px;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-sm);
  border-radius: var(--radius-full);
  background: var(--bg);
  border: 1px solid var(--border-secondary);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  opacity: 0;
  /* Keep the hidden trigger out of tab order and pointer reach so keyboard
     users can't focus an invisible control. */
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.3s ease-in-out;
  color: var(--text-tertiary);
  white-space: nowrap;
}

.reaction-trigger__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: inherit;
}

.reaction-trigger__label {
  font-size: 13px;
  line-height: 1;
  color: inherit;
  font-weight: 500;
}

.chat-reaction-trigger:focus-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
@media (hover: hover) {
  .chat-message-bubble-wrapper:hover .chat-reaction-trigger {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
}

@media (hover: hover) {
  .chat-reaction-trigger:hover {
  background: var(--bg-subtle);
  color: var(--text-primary);
}
}

/* Sent messages hug the pane's right — mirror the anchor so the pill
   grows LEFTWARD into open space (a left anchor overflowed the right
   edge on bubbles narrower than the pill). */
.chat-message-row.sent .chat-reaction-trigger {
  left: auto;
  right: -4px;
}

/* Hide the trigger whenever the emoji pill is open for this message —
   the pill replaces it rather than overlaying it. */
.chat-message-bubble-wrapper:has(.emoji-picker-pill) .chat-reaction-trigger {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.chat-reaction-picker {
  position: absolute;
  top: -40px;
  left: 0;
  display: flex;
  gap: var(--sp-3xs);
  background: var(--bg);
  border: 1px solid var(--border-secondary);
  border-radius: var(--radius-lg);
  padding: var(--sp-3xs) var(--sp-xs);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  z-index: 10;
}

.chat-message-row.sent .chat-reaction-picker {
  left: auto;
  right: 0;
}

.chat-reaction-picker-btn {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-size: 16px;
  padding: 0;
  transition: background 0.1s ease, transform 0.1s ease;
}

@media (hover: hover) {
  .chat-reaction-picker-btn:hover {
  background: var(--bg-subtle);
  transform: scale(1.2);
}
}

.chat-reaction-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: var(--sp-3xs);
}

.chat-message-row.sent .chat-reaction-pills {
  justify-content: flex-end;
}

.chat-reaction-pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xxs);
  padding: var(--size-xxs) var(--size-sm);
  border-color: transparent;
  border-radius: var(--radius-full);
  background: var(--fill-elevated-secondary);
  font-size: var(--text-xs);
  cursor: pointer;
  touch-action: none; /* deliver pointer events immediately — required for long-press */
  transition: background 0.1s ease, border-color 0.1s ease;
}

/* Who reacted (chat.gleam view_reaction_pills): shown after a short
   deliberate hover — the delay is what keeps it from popping on a pointer
   passing over — and at once on keyboard focus. Hides immediately. */
.chat-reaction-names {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  padding: var(--size-sm) var(--size-md);
  border-radius: 12px;
  background: var(--bg-elevated-primary, var(--bg-primary));
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.3;
  max-width: 240px;
  width: max-content;
  white-space: normal;
  overflow-wrap: break-word;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.08s ease, visibility 0s linear 0.08s;
  z-index: 10;
  box-shadow: 1px 4px 12px 2px rgba(var(--neutral-black), 0.4);
}

.chat-message-row.sent .chat-reaction-names {
  left: auto;
  right: 0;
}

.chat-reaction-pill:focus-visible .chat-reaction-names {
  opacity: 1;
  visibility: visible;
  transition: none;
}

@media (hover: hover) {
  .chat-reaction-pill:hover .chat-reaction-names {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.12s ease 0.4s, visibility 0s linear 0.4s;
  }
}

.chat-reaction-pill.active {
  background: rgba(var(--color-blue), .2);
  background-blend-mode: var(--blend-mode);
}

@media (hover: hover) {
  .chat-reaction-pill:hover {
  background: var(--fill-elevated-primary);
  border-color: var(--border-secondary);
}
}

@media (hover: hover) {
  .chat-reaction-pill.active:hover {
  background: rgba(var(--color-blue), .4);
  border-color: var(--accent-blue);
}
}


/* BEM-style child spans — `.reaction-pill__emoji` holds the emoji glyph,
   `.reaction-pill__count` holds the numeric count. The pill itself uses
   inline-flex + gap to space them. `tabular-nums` keeps the count from
   jitter-shifting as digits change width. */
.reaction-pill__emoji,
.reaction-pill__count {
  display: inline-flex;
  align-items: center;
  line-height: 1;
}

.reaction-pill__emoji {
  font-size: 16px;
}

.reaction-pill__count {
  font-variant-numeric: tabular-nums;
}



/* ── Composer ─────────────────────────────────────────────────────────── */

.chat-composer {
  grid-row: 4;
  width: 100%;
  max-width: 800px;
  padding: 0 var(--sp-xl) var(--sp-xxl) var(--sp-xl);
  justify-self: center;
  position: relative;
}

/* ── @-mention picker ────────────────────────────────────────────────────── */

.chat-mention-picker {
  position: absolute;
  left: var(--sp-xl);
  right: var(--sp-xl);
  bottom: calc(100% - var(--sp-xxl));
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  max-height: 240px;
  overflow-y: auto;
  z-index: 20;
}

.chat-mention-picker-item {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-sm) var(--sp-md);
  cursor: pointer;
}
@media (hover: hover) {
    .chat-mention-picker-item:hover {
    background: var(--bg-subtle);
  }
}
/* Keyboard-highlighted row (↑/↓ navigation) — elevated secondary fill. */
.chat-mention-picker-item--active {
  background: var(--fill-elevated-secondary);
}
.chat-mention-picker-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.chat-mention-picker-name {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-mention-picker-username {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* On iOS the home-indicator bar sits below the safe area and overlaps the
   composer. A small bottom margin keeps the form above it. */
.ios .chat-composer {
  margin-bottom: var(--sp-4xs);
}

/* Mobile bottom spacing — two states:
   • Keyboard closed: clear the home indicator (safe-area inset, with a
     sp-md floor for devices without one).
   • Keyboard open (.kb-open, set by the --vvh updater in dom.mjs): the
     keyboard covers the home-indicator zone, but env() does NOT collapse
     — dropping to a slim pad stops the composer floating high above the
     keyboard. */
@media (max-width: 834px) {
  .chat-composer {
    padding-bottom: max(var(--sp-md), env(safe-area-inset-bottom));
  }
  .kb-open .chat-composer {
    padding-bottom: var(--sp-sm);
  }
  .kb-open.ios .chat-composer {
    margin-bottom: 0;
  }
}

/* Inner wrapper — takes up the flex space the textarea used to own */
.composer-inline-input-wrap {
  flex: 1;
  min-width: 0;
  position: relative;
}

/* Override flex: 1 on the textarea so it fills the block wrapper instead */
.composer-inline-input-wrap .chat-composer-input {
  flex: none;
  width: 100%;
}

/* When there's content and the composer is not focused: hide raw markdown
   so only the rendered overlay is visible. Caret-color is also cleared so
   the invisible cursor doesn't flicker when blurred. */
.composer-inline-input-wrap--has-content:not(:focus-within) .chat-composer-input {
  color: transparent;
  caret-color: transparent;
}

/* Rendered markdown overlay — absolutely positioned over the textarea.
   pointer-events: none lets clicks fall through to the textarea, which
   focuses it and triggers :focus-within to switch back to edit mode. */
.composer-inline-preview {
  position: absolute;
  top: 6px;       /* matches textarea padding-top */
  left: 0;
  right: 0;
  pointer-events: none;
  font-size: var(--text-lg);
  line-height: 20px;
  color: var(--text-primary);
  word-break: break-word;
  white-space: pre-wrap;
  overflow: hidden;
  max-height: 250px;
}

/* Strip paragraph margins that marked.parse adds */
.composer-inline-preview p {
  margin: 0;
}

/* Re-enable list bullets/numbers overridden by the base.css `ul, ol { list-style: none }` reset */
.composer-inline-preview ul,
.composer-inline-preview ol {
  padding-left: 1.4em;
}
.composer-inline-preview ul { list-style-type: disc; }
.composer-inline-preview ol { list-style-type: decimal; }

/* Switch to edit mode while the textarea has focus */
.composer-inline-input-wrap:focus-within .composer-inline-preview {
  display: none;
}

/* Unified pill — the form IS the composer box.
   Column layout: [strip?] [input] [toolbar] */
.chat-composer-form {
  display: flex;
  flex-direction: column;
  width: 100%;
  background: var(--bg-primary);
  border: 1px solid var(--border-secondary);
  border-radius: var(--radius-xl);
  transition: border-color 0.15s ease, background 0.15s ease;
  /* No overflow: hidden here — the emoji picker pops above the pill and
     must not be clipped. Corner clipping is handled on inner chips. */
  position: relative;
}

.chat-composer-form.drag-over {
  border-color: var(--bg-secondary);
  background: var(--bg-secondary);
}

/* Emoji picker — anchored above the pill, right-aligned */
.chat-composer-form .emoji-picker {
  position: absolute;
  bottom: calc(100% + var(--sp-sm));
  right: 0;
  width: 50%;
  max-width: none;
}

/* Input row — no background or border, just spacing */
.chat-composer-input-wrap {
  flex: 1;
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: var(--sp-xs);
  padding: var(--sp-sm) var(--sp-sm) 0 var(--sp-sm);
  min-width: 0;
}

.chat-composer-file-input {
  display: none;
}

/* Staged media preview strip — slides in at top of pill when an attachment
   is staged. Horizontal scroll for multiple chips. */
.chat-composer--mediaPreviewStrip {
  display: flex;
  flex-direction: row;
  gap: var(--sp-sm);
  overflow-x: auto;
  padding: var(--sp-sm) var(--sp-sm) 0 var(--sp-sm);
  scrollbar-width: none;
  /* Slide-up entrance animation */
  animation: composerStripIn 180ms ease;
}
.chat-composer--mediaPreviewStrip::-webkit-scrollbar {
  display: none;
}

/* "Replying to: <name> ✕" across the top of the pill (iOS
   ComposerReplyHeader): a header row over a hairline, the ✕ in a small
   filled circle flush with the hairline's end. */
.chat-composer-reply {
  display: flex;
  align-items: center;
  gap: var(--sp-3xs);
  margin: 0 var(--sp-md);
  padding: var(--sp-sm) 0;
  border-bottom: 0.5px solid var(--ui-borders);
  font-size: var(--text-sm);
  min-width: 0;
  animation: composerStripIn 180ms ease;
}
.chat-composer-reply__label {
  color: var(--text-secondary);
  flex-shrink: 0;
}
.chat-composer-reply__name {
  color: var(--text-primary);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-composer-reply__cancel {
  margin-left: auto;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border: none;
  border-radius: 50%;
  background: var(--fill-elevated-secondary);
  color: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
}
.chat-composer-reply__cancel:hover {
  color: var(--text-primary);
}

@keyframes composerStripIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Bottom toolbar row — attach | emoji | spacer | send */
.chat-composer-toolbar {
  display: flex;
  flex-direction: row;
  align-items: center;
  padding: var(--sp-xs) var(--sp-xs);
  gap: var(--sp-3xs);
}

.chat-composer-toolbar-spacer {
  flex: 1;
}

/* Attach (+) button — inside the pill, icon-only, no border */
.chat-composer-add {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  border: none;
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: color 120ms ease, background 120ms ease;
}
@media (hover: hover) {
  .chat-composer-add:hover {
  background: var(--bg-subtle);
  color: var(--text-primary);
}
}

.chat-composer--mediaPreview {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-primary);
  background: var(--bg-subtle);
  overflow: hidden;
  width: 80px;
  height: 80px;
}

.chat-composer-pending-image-thumb {
  display: block;
  width: 80px;
  height: 80px;
  object-fit: cover;
  border-radius: calc(var(--radius-md) - 1px);
}

/* Invisible button wrapper around the composer thumbnail — opens lightbox on tap */
.chat-composer-pending-image-preview-btn {
  display: block;
  padding: 0;
  margin: 0;
  border: none;
  background: none;
  cursor: zoom-in;
  width: 100%;
  height: 100%;
  line-height: 0;
}

.chat-composer-pending-image-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-full);
  border: none;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  line-height: 0;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

@media (hover: hover) {
  .chat-composer-pending-image-remove:hover {
  background: rgba(0, 0, 0, 0.75);
}
}

/* Textarea — transparent, no border, fills the pill */
.chat-composer-input {
  flex: 1;
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--text-primary);
  outline: none;
  min-width: 0;
  resize: none;
  max-height: 250px;
  overflow-y: auto;
  line-height: 20px;
  padding: 6px 0;
  margin: 0;
}
.chat-composer-input::placeholder {
  color: var(--text-secondary);
}

/* Emoji button — inside the toolbar row */
.chat-composer-emoji-btn {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
  padding: 0;
  border-radius: var(--radius-full);
  transition: color 120ms ease, background 120ms ease;
}
@media (hover: hover) {
  .chat-composer-emoji-btn:hover {
  background: var(--bg-subtle);
  color: var(--text-primary);
}
}

/* Send button — inside the toolbar row, right side */
.chat-composer-send {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  cursor: pointer;
  transition: background 0.15s ease;
  /* Enabled state */
  background: var(--bg-primary-inverse);
  color: var(--text-primary-inverse);
}

.chat-composer-send:disabled {
  background: var(--fill-tertiary);
  color: var(--text-secondary);
  cursor: default;
}

/* ── Empty / error state (e.g. failed message load) ──────────────────── */

.chat-empty {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  padding: var(--sp-xl);
}

/* (Header ⋮ dropdown removed — Delete chat lives in the Chat Settings
   sheet's footer, behind a confirmation.) */

/* ── Message attachments ── */

.chat-att {
  border-radius: var(--radius-md);
  overflow: hidden;
  margin-bottom: var(--sp-xs);
}

.chat-att:last-child { margin-bottom: 0; }

/* Image */
.chat-att-image {
  position: relative;
  max-width: 220px;
  overflow: hidden;
}

.chat-att-image-btn {
  display: block;
  padding: 0;
  border: none;
  background: transparent;
  cursor: zoom-in;
  width: 100%;
}

.chat-att-img {
  display: block;
  width: 100%;
  max-width: 100%; /* prevent full-res intrinsic size from breaking out of container */
  height: auto;
  border-radius: var(--radius-md);
}

/* Link unfurl — favicon only. Stream's "image" attachment type resolved to
   a small square icon rather than a real og:image, so it's shown inline
   next to the raw link text instead of as a big media card. */
.chat-att-favicon {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  max-width: 100%;
  text-decoration: none;
}
@media (hover: hover) {
  .chat-att-favicon:hover {
  text-decoration: none;
}
}

.chat-att-favicon-icon {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  object-fit: contain;
  border-radius: var(--radius-sm);
}

.chat-att-favicon-link {
  color: var(--accent-blue);
  font-size: var(--text-sm);
  overflow-wrap: anywhere;
}
@media (hover: hover) {
  .chat-att-favicon:hover .chat-att-favicon-link {
  text-decoration: underline;
}
}

/* Link unfurl — real og:image preview. The whole card is the image, linked
   to the source; no title/description text (tap the image to navigate). */
.chat-att-image-link {
  display: block;
  max-width: 280px;
}

.chat-att-image-link-img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-md);
}

/* Link unfurl — animated gif. Same aspect-ratio media treatment as the
   image-link card, plus a "View at: site.domain" caption overlaid at the
   bottom so the source (Tenor, Giphy, etc.) stays visible. */
.chat-att-gif {
  display: block;
  position: relative;
  max-width: 280px;
}

.chat-att-gif-img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-md);
}

.chat-att-gif-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: var(--sp-lg) var(--sp-sm) var(--sp-xs);
  background: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  font-size: var(--text-xs);
  pointer-events: none;
}

.chat-att-gif-caption-label {
  font-weight: 400;
  color: var(--text-secondary);
}

.chat-att-gif-caption-domain {
  font-weight: 600;
  color: var(--accent-blue);
}

/* Corner download button shared by image + video attachments. */
.chat-att-download {
  position: absolute;
  top: var(--sp-xs);
  right: var(--sp-xs);
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  border: none;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  opacity: 0;
  transition: opacity 120ms ease;
}

.chat-att-download:focus-visible {
  opacity: 1;
}
@media (hover: hover) {
  .chat-att-image:hover .chat-att-download,
  .chat-att-video:hover .chat-att-download {
  opacity: 1;
}
}

@media (hover: hover) {
  .chat-att-download:hover {
  background: rgba(0, 0, 0, 0.75);
}
}

/* Lightbox overlay for full-size image preview */
.chat-image-preview-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  cursor: zoom-out;
  padding: var(--sp-xl);
}

.chat-image-preview-img {
  max-width: 95%;
  max-height: 95%;
  object-fit: contain;
  border-radius: var(--radius-md);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}

.chat-image-preview-close {
  position: fixed;
  top: var(--sp-md);
  right: var(--sp-md);
  width: 36px;
  height: 36px;
  border-radius: var(--radius-full);
  border: none;
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

@media (hover: hover) {
  .chat-image-preview-close:hover {
  background: rgba(255, 255, 255, 0.25);
}
}

/* Video */
.chat-att-video {
  position: relative;
  max-width: 320px;
  overflow: hidden;
}

/* Click-to-preview thumbnail — same shape as .chat-att-image-btn, but with
   a centered play icon signalling it opens the fullscreen video lightbox
   rather than zooming a static image. */
.chat-att-video-btn {
  display: block;
  position: relative;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  width: 100%;
}

.chat-att-video-thumb {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius-md);
  background: #000;
}

.chat-att-video-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 48px;
  height: 48px;
  border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.chat-att-placeholder {
  width: 220px;
  height: 160px;
  background: var(--bg-subtle);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-tertiary);
}

/* File */
.chat-att-file {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-sm);
  background: var(--bg-subtle);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  color: inherit;
  cursor: pointer;
  text-align: left;
  width: 100%;
}

@media (hover: hover) {
  .chat-att-file:hover {
  background: var(--bg-elevated, var(--bg-subtle));
}
}

.chat-att-file-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.chat-att-file-name {
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-att-file-size {
  color: var(--text-secondary);
}

/* Link preview */
.chat-att-link {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-primary);
  text-decoration: none;
  color: inherit;
  max-width: 260px;
}
@media (hover: hover) {
  .chat-att-link:hover {
  border-color: var(--border-primary);
  text-decoration: none;
}
}

.chat-att-link-image {
  width: 100%;
  height: 120px;
  object-fit: cover;
}

.chat-att-link-body {
  padding: var(--sp-xs) var(--sp-sm);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.chat-att-link-title {
  font-size: var(--text-sm);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-att-link-desc {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Feed content card (Please-specific) */
.chat-att-content {
  display: flex;
  border: 1px solid var(--border-primary);
  text-decoration: none;
  color: inherit;
  max-width: 260px;
  gap: var(--sp-sm);
}
@media (hover: hover) {
  .chat-att-content:hover {
  border-color: var(--border-primary);
  text-decoration: none;
}
}

.chat-att-content-thumb {
  width: 80px;
  height: 80px;
  object-fit: cover;
  flex-shrink: 0;
}

.chat-att-content-thumb-placeholder {
  width: 80px;
  height: 80px;
  background: var(--bg-subtle);
  flex-shrink: 0;
}

.chat-att-content-body {
  padding: var(--sp-xs) var(--sp-xs) var(--sp-xs) 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.chat-att-content-category {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
}

.chat-att-content-title {
  font-size: var(--text-sm);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-att-content-likes {
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  color: var(--text-tertiary);
}

/* Message text alongside attachments */
.chat-message-text {
  margin: 0;
}

.chat-message-text:has(+ .chat-att) {
  margin-bottom: var(--sp-xs);
}

/* Links in chat message text use the cobalt accent */

.chat-message-text a {
  color: var(--accent-blue);
  text-decoration: none;
  overflow-wrap: anywhere;
}

@media (hover: hover) {
  .chat-message-text a:hover {
  text-decoration: underline;
}
}

/* ─── Video embed attachment ─────────────────────────────────────── */

/* Outer wrapper: left thread-line, gap, stacked content */
.chat-embed-wrapper {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding-left: calc(8px + var(--sp-xs));
  border-left: 2px solid var(--border);
  margin-bottom: var(--sp-xs);
}

/* When an embed follows message text, give it breathing room */
.chat-message-text + .chat-embed-wrapper {
  margin-top: 12px;
}

/* Brand logo — max-width 72px, display as block so the SVG scales */
.brand-logo {
  display: inline-flex;
  align-items: center;
  line-height: 0;
  margin-bottom: var(--sp-xs);
}

.brand-logo svg {
  width: 100%;
  height: auto;
}

.brand-logo-youtube {
  max-width: 72px;
}

.brand-logo-vimeo {
  max-width: 60px;
}

/* Header row: title + chevron toggle */
.chat-embed-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-xs);
  margin-bottom: 12px;
}

.chat-embed-title {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--accent-blue);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  flex: 1;
  min-width: 0;
}

.chat-embed-toggle {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 2px;
  cursor: pointer;
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  border-radius: var(--radius-sm);
}

@media (hover: hover) {
  .chat-embed-toggle:hover {
  color: var(--text-primary);
  background: var(--bg-secondary);
}
}

/* Collapsible area: iframe + link-out */
.chat-embed-collapsible {
  display: flex;
  flex-direction: column;
  gap: 12px;
  overflow: hidden;
  max-width: 420px;
  max-height: 500px;
  transition: max-height 0.3s ease;
}

.chat-embed-collapsible--collapsed {
  max-height: 0;
}

/* Responsive iframe container via aspect-ratio */
.chat-embed-player-wrap {
  position: relative;
  width: 100%;
  max-width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #000;
}

.chat-embed-iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
  border-radius: var(--radius-md);
}

/* "Open in YouTube" link-out row */
.chat-embed-linkout {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: var(--text-xs);
  color: var(--accent-blue);
  text-decoration: none;
  width: fit-content;
}

@media (hover: hover) {
  .chat-embed-linkout:hover {
  text-decoration: underline;
}
}

/* ============================================================================
   New Chat modal — contact picker overlay
   ========================================================================= */

.new-chat-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Must layer above the settings sheet (401 — sheet.css desktop block)
     when opened from the Add Members flow. */
  z-index: 410;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  /* When this overlay renders inside the chat-settings sheet's
     `.slide-up-sheet-shell` (Add Members flow), it inherits the parent's
     `pointer-events: none` and clicks fall through to the sheet beneath
     it. Re-enable here. */
  pointer-events: auto;
}

.new-chat-modal {
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  width: min(440px, calc(100vw - 48px));
  max-height: calc(100vh - 48px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Sheet drag handle: mobile-only (the modal becomes a slide-up sheet). */
.new-chat-handle {
  display: none;
}

@media (max-width: 834px) {
  /* Bottom-sheet presentation: pin the modal to the bottom edge and reuse
     the shared slide-up-sheet look + entrance animation (sheet.css). The
     drag-to-dismiss is attached on open via attach_sheet_drag. */
  .new-chat-overlay {
    align-items: flex-end;
  }
  .new-chat-modal {
    width: 100%;
    max-width: none;
    max-height: 95vh;
    min-height: 30vh;
    border: none;
    box-shadow: none;
    border-radius: 20px 20px 0 0;
    animation: sheet-slide-in 0.3s cubic-bezier(0.32, 0.72, 0, 1) forwards;
    will-change: height;
  }
  .new-chat-handle {
    display: flex;
  }
}

.new-chat-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-lg) var(--sp-lg) var(--sp-sm);
}

.new-chat-title {
  font-size: var(--text-lg);
  font-weight: 600;
  margin: 0;
}

.new-chat-close {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--text-tertiary);
  padding: var(--sp-3xs);
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
@media (hover: hover) {
  .new-chat-close:hover {
  background: var(--bg-subtle);
  color: var(--text-primary);
}
}

.new-chat-search {
  margin: 0 var(--sp-lg);
  padding: var(--sp-xs) var(--sp-sm);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  font-family: inherit;
  font-size: var(--text-lg); /* 16px */
  color: var(--text-primary);
  outline: none;
}
.new-chat-search:focus {
  border-color: var(--bg-secondary);
}
.new-chat-search::placeholder {
  color: var(--text-primary);
}

.new-chat-contacts {
  flex: 1;
  overflow-y: auto;
  list-style: none;
  margin: var(--sp-sm) 0 0;
  padding: 0 var(--sp-sm);
  min-height: 200px;
  max-height: 360px;
}

.new-chat-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-xl);
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

.people-search-error {
  padding: var(--sp-xs) var(--sp-lg) 0;
  color: var(--accent-red);
  font-size: var(--text-sm);
}

.new-chat-contact-row {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-md);
  cursor: pointer;
  user-select: none;
  border: 1px solid transparent;
}
@media (hover: hover) {
  .new-chat-contact-row:hover {
  background: var(--bg-subtle);
}
}

.new-chat-contact-selected {
  background: rgba(var(--accent-rgb, 0, 0, 0), 0.06);
  border-color: rgba(var(--accent-rgb, 0, 0, 0), 0.2);
}
@media (hover: hover) {
  .new-chat-contact-selected:hover {
  background: rgba(var(--accent-rgb, 0, 0, 0), 0.06);
  border-color: rgba(var(--accent-rgb, 0, 0, 0), 0.2);
}
}

.new-chat-contact-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.new-chat-contact-name {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.new-chat-contact-username {
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.new-chat-contact-check {
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
  flex-shrink: 0;
}

.new-chat-footer {
  padding: var(--sp-sm) var(--sp-lg) var(--sp-lg);
}

.new-chat-create {
  width: 100%;
  padding: var(--sp-xs) var(--sp-md);
  background: var(--accent);
  color: var(--accent-fg);
  border: none;
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  transition: opacity 120ms ease, background 120ms ease;
}
@media (hover: hover) {
  .new-chat-create:hover:not(:disabled) {
  opacity: 0.9;
}
}
.new-chat-create:disabled {
  background: var(--bg-secondary);
  color: var(--text-tertiary);
  cursor: not-allowed;
}

/* ── Share to chat ── */

.share-chat-spinner {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  animation: share-chat-pulse 1s ease-in-out infinite;
}

.share-chat-disabled {
  opacity: 0.5;
  pointer-events: none;
  cursor: default;
}

/* The three destination sources share one scroll region. */
/* Share picker — iOS GroupSharePickerView (bug bash 2026-09-29): one
   3-column grid of avatar tiles, group chats then people, a blue check
   badge on each pick. */
.share-grid {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  list-style: none;
  margin: 0;
  padding: var(--sp-sm) var(--sp-lg);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-md) var(--sp-sm);
  align-content: start;
}

.share-cell {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) 0;
  border: none;
  border-radius: var(--radius-md);
  background: none;
  color: var(--text-primary);
  font: inherit;
  cursor: pointer;
}
.share-cell:disabled {
  cursor: default;
  opacity: 0.6;
}
@media (hover: hover) {
  .share-cell:not(:disabled):hover {
    background: var(--bg-subtle);
  }
}

.share-cell-face {
  position: relative;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* A group without its own image. */
.share-cell-group {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-subtle);
  color: var(--text-secondary);
}

.share-cell-check {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-blue, #0a84ff);
  color: white;
  box-shadow: 0 0 0 2px var(--bg);
}

.share-cell-name {
  max-width: 100%;
  font-size: var(--text-sm);
  line-height: 1.25;
  text-align: center;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Nothing picked (share mode): the Copy link tile, centred. */
.share-empty-bar {
  justify-content: center;
}
.share-bar-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3xs);
  border: none;
  background: none;
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}
.share-bar-tile-icon {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-subtle);
}
@media (hover: hover) {
  .share-chat-selected:hover {
  background: rgba(var(--accent-rgb, 0, 0, 0), 0.06);
  border-color: rgba(var(--accent-rgb, 0, 0, 0), 0.2);
}
}

/* "Send in new group" secondary action above the primary Send button. */
.new-chat-secondary {
  width: 100%;
  padding: var(--sp-xs) var(--sp-md);
  margin-bottom: var(--sp-xs);
  background: transparent;
  color: var(--text-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}
@media (hover: hover) {
  .new-chat-secondary:not(:disabled):hover {
  background: var(--bg-subtle);
}
}
/* A send in flight ("Group collaboration" while its group is made). */
.new-chat-secondary:disabled {
  cursor: default;
  opacity: 0.5;
}

/* ── Typing indicator ── */

.chat-typing {
  min-height: 24px;
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: 0 var(--sp-xl);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.chat-typing-dots {
  display: flex;
  gap: 3px;
}

.chat-typing-dot {
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: var(--text-tertiary);
  animation: typing-bounce 1.2s ease infinite;
}

.chat-typing-dot:nth-child(2) { animation-delay: 0.2s; }
.chat-typing-dot:nth-child(3) { animation-delay: 0.4s; }

/* ── Markdown in messages ── */

.chat-message-text {
  line-height: 1.5;
}

.chat-message-text p {
  margin: 0 0 var(--sp-xs) 0;
}
.chat-message-text p:last-child {
  margin-bottom: 0;
}

.chat-message-text code {
  font-family: "SF Mono", "Fira Code", monospace;
  color: var(--code-text);
  font-size: 0.9em;
  background: var(--code-bg);
  padding: 1px 4px;
  border-radius: var(--radius-sm);
}

/* [data-theme="dark"] .chat-message-text code {
  background: rgba(255, 255, 255, 0.1);
}
[data-theme="light"] .chat-message-text code {
  background: rgba(0, 0, 0, 0.06);
} */

.chat-message-text pre {
  background: var(--code-bg);
  padding: var(--sp-sm);
  border-radius: var(--radius-md);
  overflow-x: auto;
  margin: var(--sp-xs) 0;
}

/* [data-theme="dark"] .chat-message-text pre {
  background: rgba(255, 255, 255, 0.08);
}
[data-theme="light"] .chat-message-text pre {
  background: rgba(0, 0, 0, 0.06);
} */

.chat-message-text pre code {
  background: none;
  padding: 0;
}

.chat-message-text strong { font-weight: 600; }


.chat-message-text ul,
.chat-message-text ol {
  margin: var(--sp-xs) 0;
  padding-left: var(--sp-lg);
}

.chat-message-text ul { list-style-type: disc; }
.chat-message-text ol { list-style-type: decimal; }

.chat-message-text blockquote {
  border-left: 3px solid var(--border-primary);
  padding-left: var(--sp-sm);
  color: var(--text-primary);
  margin: var(--sp-xs) 0;
  background: var(--bg-secondary);
}

.chat-message-text h1,
.chat-message-text h2,
.chat-message-text h3 {
  font-weight: 600;
  margin: var(--sp-xs) 0 var(--sp-3xs);
}
.chat-message-text h1 { font-size: var(--text-lg); }
.chat-message-text h2 { font-size: var(--text-base); }
.chat-message-text h3 { font-size: var(--text-sm); }

/* ── Face stack for group chat headers ── */

.chat-face-stack {
  display: flex;
  flex-direction: row;
}

.chat-face-stack .avatar,
.chat-face-stack .avatar-img {
  border: 2px solid var(--bg);
  margin-left: -8px;
}

.chat-face-stack .avatar:first-child,
.chat-face-stack .avatar-img:first-child {
  margin-left: 0;
}

.chat-header-ai-avatar {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  background: var(--fg);
  color: var(--bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* =============================================================================
   Message action menu
   ============================================================================= */

/* Ellipsis trigger — mirrors the .chat-reaction-trigger layout/reveal pattern */
.chat-message-menu-trigger {
  /* Absolutely positioned: centred on the top-right corner of the bubble */
  position: absolute;
  top: 0;
  right: 0;
  transform: translate(50%, -50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm, 6px);
  background: var(--bg-elevated, var(--bg));
  color: var(--text-secondary);
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0,0,0,0.10);
  /* Fade in/out */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0.3s ease, background 0.15s, color 0.15s;
  z-index: 1;
}
@media (hover: hover) {
  .chat-message-menu-trigger:hover {
  background: var(--bg-hover, var(--bg-secondary));
  color: var(--text-primary);
}
}

/* Show trigger on bubble hover — desktop only. Mobile uses the tap action
   sheet instead, so we leave the trigger permanently hidden there. */
@media (min-width: 835px) {
  .chat-message-row.sent .chat-message-bubble-wrapper:focus-within .chat-message-menu-trigger {
    opacity: 1;
    visibility: visible;
  }
@media (hover: hover) {
  .chat-message-row.sent .chat-message-bubble-wrapper:hover .chat-message-menu-trigger {
    opacity: 1;
    visibility: visible;
  }
}
}

/* Dropdown panel — appears above the bubble, right-aligned by default.
   positionMessageMenu() in dom.mjs adds --flip when the top would clip. */
.chat-message-menu-dropdown {
  position: absolute;
  bottom: calc(100% + 4px);
  right: 0;
  min-width: 184px;
  background: var(--bg-primary);
  border: 1px solid var(--border-secondary);
  border-radius: var(--radius-md);
  box-shadow: 0 4px 20px rgba(0,0,0,0.14);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: var(--sp-2xs, 4px) 0;
  z-index: 200;
  overflow: hidden;
}

/* Flip downward when the message is near the top of the chat container */
.chat-message-menu-dropdown--flip {
  bottom: auto;
  top: calc(100% + 4px);
}

/* Bubble-wrapper is the positioning context for the trigger + dropdown */
.chat-message-bubble-wrapper {
  position: relative;
}

.chat-message-menu-item {
  display: flex;
  border-radius: 0;
  align-items: center;
  gap: var(--sp-sm, 10px);
  width: 100%;
  padding: var(--sp-xs, 8px) var(--sp-sm, 12px);
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-size: var(--text-sm, 0.875rem);
  text-align: left;
  cursor: pointer;
  transition: background 0.2s ease-in-out;
}
@media (hover: hover) {
  .chat-message-menu-item:hover {
  background: var(--bg-secondary);
}
}
.chat-message-menu-item--danger {
  color: var(--accent-red);
}
@media (hover: hover) {
  .chat-message-menu-item--danger:hover {
  background: var(--accent-red-elevated);
}
}

.chat-message-menu-divider {
  height: 1px;
  background: var(--border-primary);
  margin: var(--sp-2xs, 4px) 0;
}

/* =============================================================================
   Emoji-only bubble
   ============================================================================= */

.chat-message-bubble--emoji-only {
  background: transparent;
  border: none;
  padding: 0;
  box-shadow: none;
}

.chat-message-bubble--emoji-only .chat-message-text,
.chat-message-bubble--emoji-only .chat-message-text p {
  font-size: 32px;
  line-height: 1.2;
}

/* =============================================================================
   Inline edit state
   ============================================================================= */

/* ── Full-width editing on desktop ─────────────────────────────────────────
   When a sent message enters edit mode, expand the row to the full readable
   column width so the textarea has maximum writing space.
   Scoped to desktop — mobile edit state uses the composer + cancel bar.
   ─────────────────────────────────────────────────────────────────────────── */
@media (min-width: 835px) {
  /* Expand the row itself — override max-width: 70% and align-self: flex-end */
  .chat-message-row.sent:has(.chat-message-bubble--editing) {
    max-width: 100%;
    width: 100%;
    align-self: stretch;
  }

  /* Stack grows to fill the row width minus the 28px avatar slot */
  .chat-message-row.sent:has(.chat-message-bubble--editing) .chat-message-stack {
    flex: 1;
    min-width: 0;
    align-items: stretch;
  }

  /* Bubble fills the stack; override the pre-measured min-width inline style */
  .chat-message-row.sent:has(.chat-message-bubble--editing) .chat-message-bubble--editing {
    width: 100%;
    min-width: 0 !important;
  }
}

.chat-message-bubble--editing {
  /* min-width is stamped as an inline style (pre-measured in dom.mjs) to
     match the original message row's width, preventing the editing bubble
     from collapsing narrower than the message being edited. The editor fills
     it via width: 100% and can grow beyond min-width for longer edits. */
  padding: 8px;
  overflow: visible;
  background: transparent;
  box-shadow: none;
  box-sizing: border-box;
}

/* Inline message-edit editor — Milkdown inside the editing bubble.
   Shares the .ProseMirror typography/block rules with the composer (see
   the "Milkdown composer" section below); this block is just the frame. */
.chat-edit-milkdown {
  display: block;
  width: 100%;
  box-sizing: border-box;
  min-height: 2.5em;
  max-height: 40vh;
  overflow-y: auto;
  padding: var(--sp-xs, 8px) var(--sp-sm, 12px);
  border: 1.5px solid var(--accent-blue);
  border-radius: var(--radius-md, 10px);
  background: var(--bg);
  color: var(--text-primary);
  font-size: var(--text-sm, 0.875rem);
  line-height: 1.5;
  cursor: text;
  outline: none;
}

/* Attachment strip — sits snugly below the textarea */
.chat-edit-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-xs, 8px);
  margin-top: var(--sp-xs, 8px);
  padding: 0 var(--sp-xs, 8px);
}

/* Individual thumbnail wrapper — relative so the remove btn can be absolute */
.chat-edit-att-thumb {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm, 6px);
  overflow: visible; /* let the button bleed outside */
  max-width: 120px;
}

/* Image / video-poster thumbnail */
.chat-edit-att-thumb-img {
  display: block;
  max-width: 120px;
  max-height: 120px;
  width: auto;
  height: auto;
  border-radius: var(--radius-sm, 6px);
  object-fit: cover;
}

/* Placeholder for non-visual types (file, link without image, embed) */
.chat-edit-att-thumb-placeholder {
  display: flex;
  align-items: center;
  gap: var(--sp-2xs, 4px);
  padding: var(--sp-xs, 8px);
  background: var(--bg-secondary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-sm, 6px);
  font-size: var(--text-xs, 0.8rem);
  color: var(--text-secondary);
  max-width: 120px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ✕ remove button — absolutely positioned at top-left, half outside */
.chat-edit-att-remove {
  position: absolute;
  top: -8px;
  left: -8px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-full, 999px);
  border: 1.5px solid var(--border-primary);
  background: var(--bg-elevated, var(--bg));
  color: var(--text-secondary);
  cursor: pointer;
  padding: 0;
  box-shadow: 0 1px 3px rgba(0,0,0,0.12);
  transition: background 0.15s, color 0.15s;
}
@media (hover: hover) {
  .chat-edit-att-remove:hover {
  background: var(--accent-red);
  color: #fff;
  border-color: var(--accent-red);
}
}

.chat-edit-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-xs, 8px);
  padding: var(--sp-2xs, 4px) var(--sp-xs, 8px) var(--sp-xs, 8px);
}

.chat-edit-cancel,
.chat-edit-save {
  padding: var(--sp-2xs, 4px) var(--sp-sm, 12px);
  border-radius: var(--radius-sm, 6px);
  font-size: var(--text-xs, 0.8rem);
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border-primary);
  transition: background 0.1s;
}
.chat-edit-cancel {
  background: transparent;
  color: var(--text-secondary);
}
@media (hover: hover) {
  .chat-edit-cancel:hover {
  background: var(--bg-hover, var(--bg-secondary));
}
}
.chat-edit-save {
  background: var(--bg-tertiary);
  color: var(--text-secondary);
}
@media (hover: hover) {
  .chat-edit-save:hover {
  background: var(--bg-primary-inverse);
  color: var(--text-primary-inverse);
}
}

/* ── Deleted message tombstone ── */
.chat-message-deleted {
  padding: var(--sp-xs) 0;
}

.chat-message-deleted-text {
  padding: var(--sp-xs) var(--sp-md);
  font-size: var(--text-base);
  font-style: italic;
  color: var(--text-secondary);
  margin: 0;
  pointer-events: none;
  user-select: none;
}

/* ── (edited) label ── */
.chat-message-edited-label {
  display: block;
  font-size: 0.75rem;
  font-style: italic;
  font-weight: 400;
  color: var(--text-secondary);
  margin-top: 2px;
  padding-left: 2px;
}

/* =============================================================================
   Delete confirmation modal
   ============================================================================= */

.chat-delete-confirm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 500;
}

.chat-delete-confirm-dialog {
  background: var(--bg-elevated, var(--bg));
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg, 14px);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.24);
  padding: var(--sp-lg, 24px);
  max-width: 360px;
  width: calc(100vw - 32px);
}

.chat-delete-confirm-title {
  font-size: var(--text-base, 1rem);
  font-weight: 600;
  color: var(--text-primary);
  margin-bottom: var(--sp-sm, 10px);
}

/* Preview container — scrollable to handle tall images */
.chat-delete-confirm-preview {
  background: var(--bg-secondary, var(--bg));
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-sm, 6px);
  padding: var(--sp-xs, 8px) var(--sp-sm, 12px);
  margin-bottom: var(--sp-md, 16px);
  max-height: 260px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs, 8px);
}

/* Text portion */
.chat-delete-confirm-preview-text {
  font-size: var(--text-sm, 0.875rem);
  color: var(--text-secondary);
  line-height: 1.5;
}

/* Attachment list */
.chat-delete-confirm-preview-attachments {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs, 8px);
}

/* Base for every attachment type in the modal */
.chat-delete-att-preview {
  display: flex;
  align-items: center;
  gap: var(--sp-xs, 8px);
  font-size: var(--text-sm, 0.875rem);
  color: var(--text-secondary);
}

/* Image */
.chat-delete-att-image {
  display: block;
}
.chat-delete-att-img {
  display: block;
  max-width: 100%;
  max-height: 180px;
  width: auto;
  height: auto;
  border-radius: var(--radius-sm, 6px);
  object-fit: cover;
}

/* Video — poster thumbnail or fallback label */
.chat-delete-att-video-label {
  display: flex;
  align-items: center;
  gap: var(--sp-xs, 8px);
  color: var(--text-tertiary);
}

/* File */
.chat-delete-att-file-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Link */
.chat-delete-att-link {
  gap: var(--sp-sm, 10px);
}
.chat-delete-att-link-img {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: var(--radius-xs, 4px);
  flex-shrink: 0;
}
.chat-delete-att-link-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Feed content */
.chat-delete-att-feed {
  gap: var(--sp-sm, 10px);
}
.chat-delete-att-feed-thumb {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: var(--radius-xs, 4px);
  flex-shrink: 0;
}
.chat-delete-att-feed-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Video embed */
.chat-delete-att-embed-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-delete-confirm-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-sm, 10px);
}

.chat-delete-confirm-btn {
  padding: var(--sp-xs, 8px) var(--sp-md, 16px);
  border-radius: var(--radius-sm, 6px);
  font-size: var(--text-sm, 0.875rem);
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border-primary);
  transition: background 0.1s, opacity 0.1s;
}
.chat-delete-confirm-cancel {
  background: transparent;
  color: var(--text-secondary);
}
@media (hover: hover) {
  .chat-delete-confirm-cancel:hover {
  background: var(--bg-hover, var(--bg-secondary));
}
}
.chat-delete-confirm-delete {
  background: var(--text-danger, #e53e3e);
  color: #fff;
  border-color: var(--text-danger, #e53e3e);
}
@media (hover: hover) {
  .chat-delete-confirm-delete:hover {
  opacity: 0.88;
}
}

/* =============================================================================
   Success / info toast
   ============================================================================= */

.chat-toast {
  position: fixed;
  bottom: var(--sp-xl, 24px);
  left: 50%;
  transform: translateX(-50%);
  padding: var(--sp-xs, 8px) var(--sp-md, 16px);
  background: var(--bg-elevated, var(--bg-primary));
  color: var(--text-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full, 999px);
  font-size: var(--text-sm, 0.875rem);
  white-space: nowrap;
  pointer-events: none;
  z-index: 200;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  animation: chat-toast-in 0.18s ease-out, chat-toast-out 0.3s ease-in 2.2s forwards;
}

/* ============================================================================
   Milkdown rich-text composer
   Milkdown mounts into .chat-composer-milkdown and injects .milkdown >
   .ProseMirror as the actual contenteditable surface.  We style those classes
   to match the previous textarea's look: transparent fill, same font, same
   padding, same scroll cap.
   ========================================================================= */

/* Mount point — flex item that stretches to fill the pill */
.chat-composer-milkdown {
  flex: 1;
  min-width: 0;
  min-height: 32px;
  cursor: text;
  outline: none; /* suppress native focus ring on the wrapper div */
}

/* Before ProseMirror has mounted (.milkdown child is absent), show a
   placeholder so the composer looks like a text input. Once the editor
   lands it fills the div and this pseudo-element disappears. */
.chat-composer-milkdown:not(:has(.milkdown))::before,
.chat-edit-milkdown:not(:has(.milkdown))::before {
  content: attr(data-placeholder);
  display: block;
  font-size: var(--text-sm);
  line-height: 20px;
  padding: 6px 0;
  color: var(--text-primary);
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Milkdown's outer wrapper div */
.chat-composer-milkdown .milkdown,
.chat-edit-milkdown .milkdown {
  all: unset;
  display: block;
  width: 100%;
}

/* ProseMirror's contenteditable div — the actual editable surface */
.chat-composer-milkdown .ProseMirror,
.chat-edit-milkdown .ProseMirror {
  outline: none;
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--text-primary);
  line-height: 20px;
  padding: 6px 0;
  margin: 0;
  min-height: 32px;
  max-height: 250px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Empty-state placeholder shown once Milkdown is mounted but the doc is
   empty. The `is-editor-empty` class is applied by our emptyPlaceholderPlugin
   decoration (milkdown_editor.mjs) whenever the doc has a single empty
   paragraph. We use float+height:0 (the Tiptap convention) so the trailing
   <br class="ProseMirror-trailingBreak"> ProseMirror inserts doesn't push
   the pseudo-element onto a second line. */
.chat-composer-milkdown .ProseMirror p.is-editor-empty:first-child::before,
.chat-edit-milkdown .ProseMirror p.is-editor-empty:first-child::before,
.chat-composer-milkdown .ProseMirror:empty::before,
.chat-edit-milkdown .ProseMirror:empty::before {
  content: "Write a message...";
  float: left;
  height: 0;
  color: var(--text-primary);
  pointer-events: none;
}

/* ProseMirror wraps bare text in <p> tags — strip the default margin */
.chat-composer-milkdown .ProseMirror p,
.chat-edit-milkdown .ProseMirror p {
  margin: 0;
}

/* Inline formatting */
.chat-composer-milkdown .ProseMirror strong { font-weight: 700; }
.chat-composer-milkdown .ProseMirror em     { font-style: italic; }

.chat-composer-milkdown .ProseMirror code,
.chat-edit-milkdown .ProseMirror code {
  font-family: "SF Mono", "Fira Code", monospace;
  color: var(--code-text);
  font-size: 0.9em;
  background: var(--code-bg);
  padding: 1px 4px;
  border-radius: var(--radius-sm);
}

.chat-composer-milkdown .ProseMirror a,
.chat-edit-milkdown .ProseMirror a {
  color: var(--accent-blue);
  text-decoration: underline;
}

/* Lists */
.chat-composer-milkdown .ProseMirror ul,
.chat-edit-milkdown .ProseMirror ul,
.chat-composer-milkdown .ProseMirror ol,
.chat-edit-milkdown .ProseMirror ol {
  margin: 2px 0;
  padding-left: 1.4em;
}
/* list-style re-enables must cover BOTH editor surfaces: base.css resets
   ul/ol to list-style none, and the edit bubble's editor was missing from
   these three rules — lists in chat-message-bubble--editing rendered
   without ::marker while the composer showed them fine. */
.chat-composer-milkdown .ProseMirror ul,
.chat-edit-milkdown .ProseMirror ul { list-style-type: disc; }
.chat-composer-milkdown .ProseMirror ol,
.chat-edit-milkdown .ProseMirror ol { list-style-type: decimal; }
.chat-composer-milkdown .ProseMirror li,
.chat-edit-milkdown .ProseMirror li { margin: 0; }

/* Block-level code fence */
.chat-composer-milkdown .ProseMirror pre,
.chat-edit-milkdown .ProseMirror pre {
  background: var(--code-bg);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
  overflow-x: auto;
  font-family: "SF Mono", "Fira Code", monospace;
  font-size: 0.9em;
  margin: 4px 0;
}
.chat-composer-milkdown .ProseMirror pre code,
.chat-edit-milkdown .ProseMirror pre code {
  background: none;
  padding: 0;
}

/* Blockquote */
.chat-composer-milkdown .ProseMirror blockquote,
.chat-edit-milkdown .ProseMirror blockquote {
  border-left: 3px solid var(--border-primary);
  padding-left: var(--sp-sm);
  color: var(--text-primary);
  margin: var(--sp-xs) 0;
  background: var(--bg-secondary);
}

/* Headings — keep them modest in a chat context */
.chat-composer-milkdown .ProseMirror h1,
.chat-edit-milkdown .ProseMirror h1,
.chat-composer-milkdown .ProseMirror h2,
.chat-edit-milkdown .ProseMirror h2,
.chat-composer-milkdown .ProseMirror h3,
.chat-edit-milkdown .ProseMirror h3 {
  font-weight: 700;
  margin: 2px 0;
  line-height: 1.3;
}

/* ProseMirror selection highlight */
.chat-composer-milkdown .ProseMirror ::selection,
.chat-edit-milkdown .ProseMirror ::selection {
  background: rgba(var(--color-blue), .3);
  background-blend-mode: var(--blend-mode);
}

/* ─── Reaction detail sheet content ─────────────────────────────────── */

/* Emoji tab bar */
.reaction-sheet__tabs {
  display: flex;
  gap: 12px; /* 12px between tabs */
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  padding-top: var(--sp-sm);
}
.reaction-sheet__tabs::-webkit-scrollbar {
  display: none;
}

.reaction-sheet__tab {
  display: inline-flex;
  align-items: center;
  gap: 2px; /* 2px between emoji and count */
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-sm); /* 8px */
  border: none;
  background: transparent;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.1s ease;
}

.reaction-sheet__tab.active {
  background: var(--bg-secondary);
}

.reaction-sheet__tab-emoji {
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}

.reaction-sheet__tab-count {
  font-size: var(--text-lg); /* 16px */
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

/* User list */
.reaction-sheet__users {
  margin-top: 24px; /* 24px gap from tab bar */
  display: flex;
  flex-direction: column;
  gap: 16px; /* 16px between user rows */
}

.reaction-sheet__user {
  display: flex;
  align-items: center;
  gap: 8px; /* 8px between avatar and name */
}

.reaction-sheet__user-name {
  font-size: var(--text-base); /* 14px */
  color: var(--text-primary);
  font-weight: 400;
}

/* ─── Keyframe animations ────────────────────────────────────────────── */

@keyframes share-chat-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 1; }
}

@keyframes typing-bounce {
  0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

@keyframes chat-toast-in {
  from { opacity: 0; transform: translateX(-50%) translateY(6px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

@keyframes chat-toast-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* ─── Media queries ──────────────────────────────────────────────────── */

@media (max-width: 834px) {
  /* The sidebar-toggle's mobile-only reveal is handled by the shared
     .page-header-sidebar-toggle pin (shell.css). */

  .chat-composer-form .emoji-picker {
    width: 100%;
    right: 0;
  }

  .chat-composer-input {
    font-size: var(--text-lg);
  }

  .chat-composer-milkdown .ProseMirror,
  .chat-edit-milkdown .ProseMirror {
    font-size: var(--text-lg);
  }

  .chat-scroll-to-bottom {
    bottom: 142px;
  }
}

@media (max-width: 600px) {
  .chat-embed-collapsible {
    max-width: 320px;
  }
    .chat-message-row {
    max-width: 90%;
  }
}


/* =============================================================================
   MOBILE MESSAGE ACTION SHEET
   ============================================================================= */

/* Override the default slide-up-sheet sizing for the action sheet — it
   should be auto-height rather than the fixed 70vh used by the reaction sheet. */
.msg-action-sheet {
  height: auto;
  min-height: unset;
  padding-bottom: env(safe-area-inset-bottom, 16px);
  padding-left: 8px !important;
  padding-right: 8px !important;
}

/* ── Content wrapper ──────────────────────────────────────────────────────── */
.msg-action-sheet .slide-up-sheet__content {
  padding: var(--sp-md) var(--sp-sm);
  /* Needed so the absolutely-positioned picker panel clips to this box */
  position: relative;
  overflow: hidden;
}

/* When the sheet is in emoji-picker mode its content becomes a flex column
   so the picker can fill all remaining height below the header.
   NOTE: use double-class selector (.msg-action-sheet.msg-action-sheet--picker)
   for specificity (0,2,0) so it beats .slide-up-sheet { height: 70vh } from
   sheet.css, which is imported later in index.css and would otherwise win the
   same-specificity cascade. */
.msg-action-sheet.msg-action-sheet--picker {
  height: 85vh;
  min-height: unset;
}

.msg-action-sheet--picker .slide-up-sheet__content {
  flex: 1;
  min-height: 0;
  padding-bottom: env(safe-area-inset-bottom, 16px);
}

/* ── Panel transitions (menu ↔ emoji picker) ──────────────────────────────── */
/* Both panels share the same transition so the swap is a smooth
   directional crossfade: normal slides out left while picker slides in
   from the right, and the reverse on the way back. */

.msg-action-sheet__panel {
  transition: opacity 230ms ease, transform 230ms ease;
}

/* Normal panel: visible by default */
.msg-action-sheet__panel--normal {
  opacity: 1;
  transform: translateX(0);
}
.msg-action-sheet--picker .msg-action-sheet__panel--normal {
  opacity: 0;
  transform: translateX(-28px);
  pointer-events: none;
}

/* Picker panel: hidden by default, absolutely fills the content box
   so it doesn't contribute to the sheet's auto-height in normal mode */
.msg-action-sheet__panel--picker {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  opacity: 0;
  transform: translateX(28px);
  pointer-events: none;
}
.msg-action-sheet--picker .msg-action-sheet__panel--picker {
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}

/* ── "Quick reactions" header ─────────────────────────────────────────────── */
.msg-action-sheet__reactions-header {
  display: block;
  font-size: var(--text-lg);      /* 16px */
  color: var(--text-secondary);
  text-align: center;
  margin-bottom: var(--sp-sm);    /* 12px gap to emoji row */
}

/* ── Quick emoji row ──────────────────────────────────────────────────────── */
.msg-action-sheet__quick-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--sp-xxl);   /* 32px gap to menu card */
}

.msg-action-sheet__quick-emoji {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-size: 24px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-full);
  transition: background 0.12s ease;
}

.msg-action-sheet__quick-emoji:active {
  background: var(--bg-secondary);
}

.msg-action-sheet__quick-more {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-full);
  transition: background 0.12s ease;
}

.msg-action-sheet__quick-more > .icon {
  height: 24px;
  width: 24px;
}

.msg-action-sheet__quick-more:active {
  background: var(--bg-secondary);
}

/* ── Menu card ────────────────────────────────────────────────────────────── */
.msg-action-sheet__menu {
  background: var(--bg-body);
  border-radius: var(--radius-lg);  /* 16px */
  padding: var(--sp-sm);            /* 12px all-around */
  display: flex;
  flex-direction: column;
  gap: var(--sp-xxl);               /* 32px between items */
}

/* ── Menu items ───────────────────────────────────────────────────────────── */
.msg-action-sheet__item {
  display: flex;
  align-items: center;
  gap: var(--sp-md);                /* 16px icon → label */
  background: none;
  border: none;
  padding: var(--sp-3xs) 0;         /* 4px vertical padding */
  cursor: pointer;
  text-align: left;
  color: var(--text-secondary);     /* icon color */
  width: 100%;
}

.msg-action-sheet__item .icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.msg-action-sheet__item span {
  font-size: var(--size-md);        /* 16px */
  color: var(--text-primary);
  font-weight: 400;
  line-height: 1.2;
}

/* Delete action — both icon and label are accent-red */
.msg-action-sheet__item--danger,
.msg-action-sheet__item--danger span {
  color: var(--accent-red);
}

/* ── In-sheet emoji picker header (back button + title) ──────────────────── */
.msg-action-sheet__picker-header {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-sm) 0 var(--sp-md);
  font-size: var(--text-lg);
  font-weight: 500;
  color: var(--text-primary);
}

.msg-action-sheet__picker-back {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  padding: 4px;
  cursor: pointer;
  color: var(--text-secondary);
  border-radius: var(--radius-full);
  flex-shrink: 0;
}
.msg-action-sheet__picker-back:active { opacity: 0.6; }

/* Override the floating-picker defaults when embedded inside the sheet */
.msg-action-sheet .emoji-picker,
.composer-emoji-sheet .emoji-picker {
  position: static;
  width: 100%;
  flex: 1;
  min-height: 0;
  max-height: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
  animation: none;
}

/* ── Composer emoji sheet (mobile) ──────────────────────────────────────────
   Slide-up sheet used instead of the floating popover on mobile.
   Reuses .slide-up-sheet base styles; overrides height to fill most of the
   screen so the full emoji grid is comfortably browsable.
   ─────────────────────────────────────────────────────────────────────────── */
.composer-emoji-sheet.slide-up-sheet {
  height: 85vh;
  min-height: unset;
  padding-bottom: env(safe-area-inset-bottom, 16px);
}

.composer-emoji-sheet .slide-up-sheet__content {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

/* ── Mobile edit cancel bar ────────────────────────────────────────────────
   Spans the full viewport width between the messages list and the composer.
   Appears only on mobile when a message is being edited (rendered by
   view_mobile_edit_cancel_bar; collapses to nothing when element.none()).
   ─────────────────────────────────────────────────────────────────────── */
.chat-cancel-edit__mobile {
  box-shadow: 0px 2px 4px rgba(0, 0, 0, .08);
  background: var(--bg-tertiary);
  grid-row: 3;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--sp-md);           /* 16px between icon and label */
  width: 100%;
  height: var(--size-3xl);     /* 40px total height */
  padding: 0 var(--sp-xl);
  background: var(--bg-body);
  border: none;
  border-radius: 0;
  color: var(--text-secondary);
  font-size: var(--text-lg);   /* 16px */
  cursor: pointer;
  box-sizing: border-box;
}

.chat-cancel-edit__mobile svg {
  width: var(--size-xl);       /* 24px */
  height: var(--size-xl);
  flex-shrink: 0;
}

.chat-cancel-edit__mobile:active {
  opacity: 0.7;
}

/* "Editing message" tiny-time label variant */
.chat-message-tiny-time--editing {
  color: var(--text-primary);
  font-style: italic;
}

/* ── Mention autocomplete picker ─────────────────────────────────────── */
.chat-mention-picker {
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  max-height: 240px;
  overflow-y: auto;
  margin-bottom: 8px;
}

.chat-mention-picker-item {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-sm) var(--sp-md);
  cursor: pointer;
}

@media (hover: hover) {
  .chat-mention-picker-item:hover {
  background: var(--bg-subtle);
}
}

.chat-mention-picker-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.chat-mention-picker-name {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-mention-picker-username {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-mention {
  color: var(--accent-blue);
  text-decoration: none;
}
@media (hover: hover) {
  .chat-mention:hover {
  text-decoration: underline;
}
}

/* ── Messages list (/chats, no thread open) ──────────────────────────────
   Shown on both mobile and desktop — desktop still has the sidebar's own
   chat list, but /chats now gets a real page instead of a blank
   placeholder (iOS parity mock).

   STRUCTURE PARITY: this whole block mirrors the Activity page's
   structural system (styles/notifications.css) — container geometry,
   header sizing, chip strip, body gaps, row shell and type scale.
   notifications.css is the source of truth; when a structural value
   changes there, change the mirrored value here too. Each rule notes the
   notifications.css rule it mirrors. */

/* The list itself lives in the sidebar's Chats mode (.sidebar-chats,
   styles/sidebar.css) — there is no standalone Messages page anymore. The
   chip strip, row shell and type scale below still mirror Activity. */

/* Filter chip strip — mirrors .notif-filter-chips. */
.chat-list-filters {
  display: flex;
  flex-direction: row;
  gap: var(--sp-sm);
  overflow-x: auto;
  /* The strip is a scroll container (overflow-x), so the app-wide
     `* { overscroll-behavior: none }` would make Chrome swallow VERTICAL
     wheel events over it instead of chaining them to .sidebar-scroll —
     a wheel dead-zone. Re-allow vertical chaining only; horizontal stays
     contained. */
  overscroll-behavior-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  flex-shrink: 0;
}

.chat-list-filters::-webkit-scrollbar {
  display: none;
}

/* Filter strip + compose on one row. The strip scrolls; compose doesn't. */
.chat-list-toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  flex-shrink: 0;
}

.chat-list-toolbar .chat-list-filters {
  flex: 1;
  min-width: 0;
}

/* Compose ("New Message") beside the filter strip — the Chats tab's own
   "new" action on desktop. */
.chat-list-compose {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
}

@media (hover: hover) {
  .chat-list-compose:hover {
    background: var(--fill-elevated-secondary);
  }
}

/* Mirrors .notif-chip (padding, radius, type, hover/active treatment). */
.chat-list-filter {
  flex-shrink: 0;
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-full);
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.1s, color 0.1s;
}
/* Filter tab highlights are elevated fill secondary (2026-09-24). They
   were bg-secondary — the canvas colour itself, so on the desktop list
   pane the active tab had no visible highlight at all. */
@media (hover: hover) {
  .chat-list-filter:hover {
  background: var(--fill-elevated-secondary);
  color: var(--text-primary);
}
}
.chat-list-filter-active {
  background: var(--fill-elevated-secondary);
  color: var(--text-primary);
  font-weight: 500;
}

/* Mirrors .notifications-empty. */
.chat-list-empty {
  padding: var(--sp-xxl) var(--sp-md);
  text-align: center;
  color: var(--text-secondary);
}

/* List wrapper — mirrors .notifications-list-wrap's structure but NOT its
   overflow: this wrapper must not be a scroll container. It lives inside
   .sidebar-scroll, which owns scrolling, and with the app-wide
   `* { overscroll-behavior: none }` (base.css) Chrome swallows wheel events
   over an overflow:auto element even when it has nothing to scroll — an
   inner `overflow-y: auto` here left the whole list wheel-dead in Chrome
   (Safari chains to the parent scroller regardless). */
.chat-list-rows {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow-x: clip;
}

/* Row shell — mirrors .notification (same padding, gap, hover).
   position: relative anchors the absolute unread dot in the left-padding
   zone, exactly like .notification-badge on Activity rows. */
.chat-list-row {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  padding: var(--sp-md) var(--sp-sm);
  position: relative;
  transition: background 0.1s;
  box-sizing: border-box;
  max-width: 100%;
  text-decoration: none;
  color: inherit;
  border-radius: var(--radius-sm);
}
@media (hover: hover) {
  /* Elevated fill secondary like every list-pane highlight (2026-09-24).
     --bg-subtle measured rgb(245,245,244) on the rgb(242,242,247) canvas
     — barely there — and rgb(23,23,23) on the dark canvas, darker than it. */
  .chat-list-row:hover {
  background: var(--fill-elevated-secondary);
  text-decoration: none;
}
}

/* Avatar slot — mirrors .notification-media's square (82px desktop /
   52px mobile) so Messages rows land at exactly the same height as
   Activity rows. The avatar helper inlines a fixed px size, so the
   container drives the real size via !important — same trick as
   .notification-media .avatar. */
.chat-list-avatar {
  position: relative;
  width: 40px;
  aspect-ratio: 1 / 1;
  flex-shrink: 0;
}

.chat-list-avatar .avatar,
.chat-list-avatar .avatar-img {
  width: 100% !important;
  height: 100% !important;
}

/* Group rows render the channel's custom image or, without one, the
   two-member stack; either fills the slot like a single avatar would. The
   blanket 100% rules above also match the member avatars inside the
   stack, but harmlessly — their percentages resolve against the
   .avatar-stack-member wrapper (66.7% of the slot), which is the size
   they want anyway. */
.chat-list-avatar .avatar-stack {
  width: 100% !important;
  height: 100% !important;
}

/* Unread count on the avatar (Julian, 2026-09-24) — DMs, mentions,
   replies; see chat.row_badge_count. iOS CountBadge geometry: an 18px
   capsule, min-width 18px so one digit is a circle and more grow
   sideways, 0 5px padding, 11px/600 tabular digits, "99+" cap. Colours
   are the rail badge's (.app-rail-badge): accent red, label primary
   inverse. A sibling of the avatar, not inside it, so no avatar clipping
   reaches it; -4px on both edges puts its centre near the circle's 45°
   edge point, overlapping the rim. aria-hidden — the row's
   .chat-list-row-sr text speaks the count. */
.chat-list-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  box-sizing: border-box;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--accent-red);
  color: var(--text-primary-inverse);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
}

/* AI chat rows: brand glyph in a neutral circle — mirrors
   .notification-media-icon-fallback, fills the avatar slot. */
.chat-list-ai-avatar {
  width: 100%;
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
}

/* Desktop: the list pane is the shell's canvas showing through, so the
   circle paints that canvas and blends with it as it did while the shell
   was bg-secondary (2026-09-25). In dark bg-secondary is #1C1C1E and the
   canvas #0C0C0C, which turned the circle into a lone disc on desktop dark
   only. The mobile drawer paints bg-secondary itself, so phones keep it. */
@media (min-width: 835px) {
  .chat-list-ai-avatar {
    background: var(--canvas-bg);
  }
}

/* Glyphs in the avatar slot render at size 0 (no inline width/height) and
   are sized here — mirrors .notification-media .icon. */
.chat-list-avatar .icon {
  width: 32px;
  height: 32px;
}

/* Mirrors .notification-body (column stack, --sp-3xs gap). */
/* Text clippers inside the row use `overflow: clip`, NOT `hidden`:
   hidden makes the element a scroll container, and under the app-wide
   `* { overscroll-behavior: none }` Chrome latches wheel events onto a
   scroll container whose content overflows (any truncated title/preview)
   instead of chaining them to .sidebar-scroll — leaving scroll stuck
   while the cursor sits over the text. clip clips identically (and
   text-overflow: ellipsis still applies) without creating a scroll
   container. */
.chat-list-row-text {
  flex: 1;
  min-width: 0;
  overflow: clip;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
}

/* Title + timestamp share one row — mirrors iOS ChatListRow's
   HStack { Text(name); Spacer(); Text(timeLabel) }: title on the leading
   edge, timestamp pushed to the trailing edge. `min-width: 0` on the title
   (not this row) is what lets the title ellipsize instead of the row
   overflowing — the timestamp is fixed-width content and never shrinks. */
.chat-list-row-top {
  display: flex;
  align-items: baseline;
  gap: var(--sp-sm);
}

/* Read/unread is carried by the whole row's typography and colour — there
   is no dot. Read rows keep a primary name over a secondary preview (iOS
   ChatListRow); .chat-list-row--unread lifts the preview to primary below. */
.chat-list-row-title {
  flex: 1;
  min-width: 0;
  font-size: var(--text-lg);
  font-weight: 500;
  color: var(--text-primary);
  overflow: clip; /* not `hidden` — see .chat-list-row-text */
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-list-row--unread .chat-list-row-title {
  font-weight: 700;
  color: var(--text-primary);
}

/* Spoken-only ", N unread" at the end of the title — the avatar badge's
   count for screen readers (the badge itself is aria-hidden). Absolutely
   positioned, so it takes no room in the title's line or its ellipsis. */
.chat-list-row-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* Timestamp — secondary in every state (iOS ChatListRow: no unread variant). */
.chat-list-row-timestamp {
  flex-shrink: 0;
  font-size: var(--text-xs);
  font-weight: 400;
  color: var(--text-secondary);
}


/* Last-message preview line, below the title+timestamp row — mirrors iOS's
   `messagePreview` (single line, lineLimit(1) there / ellipsis here). Only
   rendered by chat.gleam when `last_message_text` is non-empty, mirroring
   iOS's `if !lastMessage.isEmpty`. */
.chat-list-row-subtitle {
  max-width: 80%;
  overflow: clip; /* not `hidden` — see .chat-list-row-text */
  text-overflow: ellipsis;
  color: var(--text-secondary);
  white-space: nowrap;
}

/* "@sender: " prefix on the preview line — a soft blue in every state so
   it recedes behind the title and message instead of competing with them. */
.chat-list-row-sender {
  font-size: var(--text-base);
  color: var(--accent-blue);
}

.chat-list-row-msg {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.chat-list-row--unread .chat-list-row-msg {
  color: var(--text-primary);
}

/* ── Mobile overrides (≤ 768px) — mirrors the .notifications block ──── */

@media (max-width: 834px) {
  /* Mirrors .notification mobile row padding/gap. */
  .chat-list-row {
    padding: 16px 12px 16px 16px;
    gap: var(--sp-sm);
  }

  /* Mirrors .notification-media mobile square — aspect-ratio and the
     100%-sized children from the base rules handle the rest. */
  .chat-list-avatar {
    width: 52px;
  }

}

/* =============================================================================
   Received-message hover pill — [Add reaction | hairline | ⋮]
   ============================================================================= */

/* Split variant: the container keeps the pill chrome (position, border,
   reveal transitions from .chat-reaction-trigger); the inner segments own
   their padding and hover states so the hairline reads as a true divider. */
.chat-reaction-trigger--split {
  gap: 0;
  padding: 0;
  overflow: hidden;
}

/* Neutralise the whole-pill hover tint — hover feedback lives per-segment. */
@media (hover: hover) {
  .chat-reaction-trigger--split:hover {
  background: var(--bg-primary);
  color: var(--text-secondary);
}
}

.chat-reaction-trigger__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-sm);
  border: none;
  background: var(--bg-primary);
  color: inherit;
  cursor: pointer;
  border-radius: 0;
  white-space: nowrap;
  transition: .3s ease all;
}

@media (hover: hover) {
  .chat-reaction-trigger__btn:hover {
  background: var(--bg-secondary);
  color: var(--text-primary);
}
}

.chat-reaction-trigger__divider {
  height: 24px;
  width: 1px;
  background: var(--border-secondary);
}

/* ── Offline / delivery states ─────────────────────────────────────────────
   Chat is server-only: there is no local store behind it, so offline the
   surface can show what was last cached but cannot send, react, or receive.
   These make that legible instead of leaving a live-looking UI that silently
   never updates. */

.chat-offline-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-xs);
    padding: var(--sp-xs) var(--sp-sm);

    background: var(--fill-elevated-secondary);
    color: var(--text-secondary);
    font-size: var(--text-sm);
    line-height: 18px;
}

/* An in-flight message is dimmed — it hasn't landed yet, and shouldn't read
   as delivered.

   Scoped to `.sent` (the viewer's own outgoing messages) on purpose. The AI
   streaming placeholder is also modelled as `Sending` — it has no server id
   yet either, which correctly keeps reactions off it — but it is an INCOMING
   message being typed out, not an unsent one. Dimming it would fade every AI
   response while it streams and snap it to full opacity at the end. */
.chat-message-row.sent.chat-message-row--sending .chat-message-bubble-wrapper {
    opacity: 0.6;
}

/* A failed one is dimmed further and paired with the notice below. Left
   in place deliberately: silently deleting what someone typed because the
   network blinked would be worse than showing that it didn't send. */
.chat-message-row--failed .chat-message-bubble-wrapper {
    opacity: 0.5;
}

.chat-message-failed {
    display: flex;
    align-items: center;
    gap: var(--sp-3xs);
    margin-top: var(--sp-4xs);

    color: var(--accent-red);
    font-size: var(--text-xs);
    line-height: 16px;
}

/* Sent messages are right-aligned, and a failed send is always the viewer's
   own — so the notice hugs the same edge as the bubble it belongs to. */
.chat-message-row.sent .chat-message-failed {
    justify-content: flex-end;
}

.chat-message-failed-retry {
    padding: 0;
    border: none;
    background: none;

    color: var(--accent-blue);
    font: inherit;
    font-size: var(--text-xs);
    font-weight: 600;
    cursor: pointer;
}

@media (hover: hover) {
    .chat-message-failed-retry:hover {
      text-decoration: underline;
  }
}

/* ── New-chat landing (lazy ephemeral AI chat) ────────────────────────────
   Hosted at /chats/new and as the desktop /feed main pane. A hero (pastel
   agent glyph + greeting) centered in the pane, with a single-line
   composer directly beneath it. No channel exists yet — submitting mints
   one and auto-sends the draft. */

.chat-landing {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xl);
  padding: var(--sp-xl);
  background: var(--bg-primary);
}

.chat-landing .chat-offline-banner {
  position: absolute;
  top: var(--sp-md);
  left: 50%;
  transform: translateX(-50%);
}

.chat-landing-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-md);
}

.chat-landing-glyph .icon {
  background: var(--gradient-pastel);
}

.chat-landing-greeting {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--text-primary);
  text-align: center;
}

.chat-landing-composer {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  width: min(560px, 100%);
  padding: var(--sp-xs) var(--sp-xs) var(--sp-xs) var(--sp-lg);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
}

.chat-landing-composer:focus-within {
  border-color: var(--accent-blue);
}

.chat-landing-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: none;
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-lg);
  line-height: 1.4;
  padding: var(--sp-xs) 0;
}

.chat-landing-input:focus {
  outline: none;
}

.chat-landing-input::placeholder {
  color: var(--text-primary);
}

.chat-landing-send {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-full);
  background: var(--accent-blue);
  color: #ffffff;
  cursor: pointer;
}

.chat-landing-send:disabled {
  opacity: 0.4;
  cursor: default;
}

/* The Messages main pane with no conversation open (desktop /chats, and a
   chat route before its thread allocates). Layout and type are the shared
   .empty-pane (states.css, 2026-09-24). No background of its own: the
   main pane is background primary (Julian, 2026-09-24) and shows through;
   it painted the thread's grey canvas until then. */
.chat-thread-placeholder {
  height: 100%;
}

/* The conversation currently open in the main pane (sidebar chat list).
   Elevated fill secondary, the list pane's one highlight (Julian,
   2026-09-24); was elevated tertiary, fainter than a hovered row. */
.chat-list-row--selected,
.chat-list-row--selected:hover {
  background: var(--fill-elevated-secondary);
}

/* ── Desktop: the thread on the main pane's background primary ────────────
   Julian, 2026-09-24: the desktop main pane is background primary, the
   chat thread included. iOS's thread is the grey canvas with bubbleSent
   #FFFFFF / #000000 — background primary itself, so on a white pane the
   viewer's bubbles vanished. Desktop sent bubbles are elevated accent with
   accent-primary text instead; received keeps iOS's #E5E5EA / #2C2C2E.
   Phones follow iOS and keep the canvas and white bubbles. Last in the
   file on purpose: it overrides the bubble rule above at equal
   specificity by source order. */
@media (min-width: 835px) {
  .chat {
    background: var(--bg-primary);
  }

  .chat-message-row.sent .chat-message-bubble {
    background: var(--accent-blue-elevated);
    color: var(--accent-blue);
  }
}

/* ── Message requests ────────────────────────────────────────────────────── */
/* iOS ChatListView.requestsRow / ChatRequestsView / ChatRequestBar /
   ChatRequestPrivacyNotice parity. A request shows WHO, never WHAT. */

/* "Message requests" entry row atop the chat list (hidden at zero). */
.chat-requests-entry {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  width: 100%;
  padding: var(--sp-sm);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.chat-requests-entry:hover {
  background: var(--fill-elevated-secondary);
}

.chat-requests-entry-icon {
  position: relative;
  display: inline-flex;
  color: var(--text-secondary);
}

.chat-requests-entry-badge {
  position: absolute;
  top: -8px;
  right: -8px;
  box-sizing: border-box;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--accent-blue);
  color: #ffffff;
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
}

.chat-requests-entry-label {
  flex: 1;
  min-width: 0;
}

.chat-requests-entry-chevron {
  display: inline-flex;
  color: var(--text-tertiary);
}

/* The Requests inbox (sidebar). */
.chat-requests-header {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: 0 var(--sp-xs);
}

.chat-requests-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--text-primary);
  cursor: pointer;
}

.chat-requests-back:hover {
  background: var(--fill-elevated-secondary);
}

.chat-requests-title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--text-primary);
}

.chat-request-row-main {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
}

.chat-request-row-main > .chat-list-row {
  flex: 1;
  min-width: 0;
}

.chat-request-row-main > .chat-request-actions {
  padding-right: var(--sp-sm);
}

/* Decline (red X) + Accept (green check) circles. */
.chat-request-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  flex-shrink: 0;
}

.chat-request-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: opacity 0.1s;
}

.chat-request-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.chat-request-btn--decline {
  background: color-mix(in srgb, var(--accent-red) 14%, transparent);
  color: var(--accent-red);
}

.chat-request-btn--accept {
  background: color-mix(in srgb, var(--accent-green) 14%, transparent);
  color: var(--accent-green);
}

/* Inline decline confirmation — on the row, or in the request bar. */
.chat-request-confirm {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
  margin: 0 var(--sp-sm) var(--sp-sm);
  padding: var(--sp-md);
  border-radius: var(--radius-sm);
  background: var(--fill-elevated-secondary);
}

.chat-request-bar .chat-request-confirm {
  flex: 1;
  margin: 0;
}

.chat-request-confirm-title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--text-primary);
}

.chat-request-confirm-body {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 18px;
  color: var(--text-secondary);
}

.chat-request-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-xs);
  margin-top: var(--sp-xs);
}

.chat-request-confirm-btn {
  padding: var(--sp-xs) var(--sp-md);
  border: none;
  border-radius: var(--radius-full);
  background: var(--bg-primary);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}

.chat-request-confirm-btn--danger {
  background: var(--accent-red);
  color: #ffffff;
}

/* A request's conversation: notice in place of the timeline, request bar in
   place of the composer. */
.chat.chat--request {
  display: flex;
  flex-direction: column;
}

.chat-request-notice {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xs);
  padding: var(--sp-xl);
  text-align: center;
}

.chat-request-notice-icon {
  display: inline-flex;
  color: var(--text-tertiary);
}

.chat-request-notice-title {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text-secondary);
}

.chat-request-notice-body {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.chat-request-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  width: 100%;
  max-width: 800px;
  box-sizing: border-box;
  align-self: center;
  padding: var(--sp-sm) var(--sp-xl) var(--sp-xxl);
}

.chat-request-bar-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.chat-request-error {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-sm);
  background: color-mix(in srgb, var(--accent-red) 12%, transparent);
  color: var(--accent-red);
  font-size: var(--text-sm);
  line-height: 18px;
}

@media (max-width: 834px) {
  .chat-request-bar {
    padding: var(--sp-sm) var(--sp-md)
      max(var(--sp-md), env(safe-area-inset-bottom));
  }
}

/* The Chats toggle's unseen-request mark: a dot, never a count (iOS). Lives
   here with the rest of the request styling; the count badge it modifies is
   in sidebar.css. */
.sidebar-mode-badge.sidebar-mode-badge--dot {
  min-width: 8px;
  width: 8px;
  height: 8px;
  padding: 0;
}

/* Rich chat cards in message bubbles — see ui/chat_card. */
/* ═══════════════════════════════════════════════════════════════════════════
   Rich chat cards (ui/chat_card.gleam) — web port of iOS ChatCardView.
   Two layouts: hero (full-width media over a title footer) and compact
   (64px leading thumbnail). Plus the detail sheet hosted in the shared
   slide-up-sheet chrome.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Width-flexible like iOS ChatCardView (maxWidth: .infinity): the card
   fills whatever container hosts it — the full chat column on AI-bare
   rows, the bubble on bubbled rows. No fixed cap. */
.chat-card {
  display: flex;
  width: 100%;
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  text-align: left;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

/* Bare cards (no tap behavior) render as a div — keep the default cursor. */
div.chat-card {
  cursor: default;
}

@media (hover: hover) {
  a.chat-card:hover,
  button.chat-card:hover {
    background: var(--bg-subtle);
  }
}

.chat-card--hero {
  flex-direction: column;
}

.chat-card--compact {
  flex-direction: row;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-sm);
}

/* ── Media ── */

.chat-card__media--hero {
  width: 100%;
  height: 150px;
  flex-shrink: 0;
}

.chat-card__media--compact {
  width: 64px;
  height: 64px;
  flex-shrink: 0;
  border-radius: 10px;
  overflow: hidden;
}

.chat-card__thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Map placeholder — direct map integration is deferred, so the media slot
   is a muted block with a centered accent pin dot (echoes the iOS marker
   composite without tiles). */
.chat-card__map {
  position: relative;
  background: var(--bg-subtle);
}

.chat-card__map-pin {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 22px;
  height: 22px;
  transform: translate(-50%, -50%);
  border-radius: var(--radius-full);
  background: #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}

.chat-card__map-pin::after {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: var(--radius-full);
  background: var(--accent-blue);
}

/* ── Text + footer ── */

.chat-card__footer {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-md);
}

.chat-card__text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}

.chat-card__title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-primary);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.chat-card__subtitle {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ── Action circle ── */

.chat-card__action {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--brand-blue-12);
  color: var(--brand-blue);
  text-decoration: none;
}

@media (hover: hover) {
  .chat-card__action:hover {
    filter: brightness(0.95);
  }
}

/* ── Detail sheet ── */

.chat-card-sheet {
  display: flex;
  flex-direction: column;
  padding: var(--sp-sm) var(--sp-lg) var(--sp-lg);
}

.chat-card-sheet__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-sm);
  padding-bottom: var(--sp-sm);
}

.chat-card-sheet__heading {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.chat-card-sheet__title {
  margin: 0;
  font-size: var(--text-lg, 18px);
  font-weight: 600;
  color: var(--text-primary);
}

.chat-card-sheet__subtitle {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.chat-card-sheet__close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border: none;
  border-radius: var(--radius-full);
  background: var(--bg-subtle);
  color: var(--text-secondary);
  cursor: pointer;
  padding: 0;
}

.chat-card-sheet__rows {
  display: flex;
  flex-direction: column;
}

.chat-card-sheet__divider {
  height: 1px;
  background: var(--border-primary);
}

.chat-card-sheet__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-md);
  padding: var(--sp-sm) 0;
}

.chat-card-sheet__row-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.chat-card-sheet__row-label {
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.chat-card-sheet__row-value {
  font-size: 16px;
  color: var(--text-primary);
  overflow-wrap: anywhere;
  user-select: text;
  -webkit-user-select: text;
}

/* ============================================================================
   Chat Detail — settings/info page for a single chat thread.

   Layout mirrors the chat module: full-height column, max-width centered.
   ========================================================================= */

/* ── Layout ──────────────────────────────────────────────────────────── */

.chat-detail {
  display: flex;
  flex-direction: column;
  height: 100vh;   /* fallback */
  height: 100dvh;  /* dynamic viewport */
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
  padding: 0 var(--sp-xl);
}

/* ── Header ──────────────────────────────────────────────────────────── */

.chat-detail-header {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: 0 0 var(--sp-sm) 0;
  border-bottom: 1px solid var(--border-secondary);
}

.chat-detail-back {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-xs);
  color: var(--text-tertiary);
  text-decoration: none;
  font-size: var(--text-sm);
  padding: var(--sp-3xs) var(--sp-xs);
  border-radius: var(--radius-md);
}
@media (hover: hover) {
  .chat-detail-back:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}
}

.chat-detail-heading {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}

/* ── Avatar Section ──────────────────────────────────────────────────── */

.chat-detail-avatar-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--sp-xl) 0;
}

.chat-detail-avatar-wrapper {
  position: relative;
  cursor: pointer;
  border-radius: var(--radius-full);
  /* No overflow: hidden — .chat-detail-avatar-badge is absolutely
     positioned at the wrapper's bottom-right corner and needs to render
     outside the circular avatar's own bounds; hidden overflow clipped it. */
}

.chat-detail-avatar-img {
  width: 80px;
  height: 80px;
  border-radius: var(--radius-full);
  object-fit: cover;
  display: block;
}

.chat-detail-avatar-badge {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  border: 2px solid var(--bg);
}

.chat-detail-avatar-loading {
  position: absolute;
  bottom: 0;
  right: 0;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-full);
  background: var(--bg-tertiary);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--fill-elevated-secondary);
}

.chat-detail-avatar-file {
  display: none;
}

/* ── Name Section ────────────────────────────────────────────────────── */

.chat-detail-name-section {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) 0;
}

.chat-detail-name {
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}

.chat-detail-name-edit {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--text-tertiary);
  padding: var(--sp-3xs);
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-base);
  line-height: 1;
}
@media (hover: hover) {
  .chat-detail-name-edit:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}
}

.chat-detail-name-editing {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-xs) 0 var(--sp-md);
}

.chat-detail-name-input {
  padding: var(--sp-xs) var(--sp-sm);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--text-primary);
  outline: none;
  text-align: center;
  width: 240px;
  max-width: 100%;
}
.chat-detail-name-input:focus {
  border-color: var(--bg-secondary);
}

.chat-detail-name-actions {
  display: flex;
  gap: var(--sp-xs);
}

.chat-detail-name-actions button {
  padding: var(--sp-3xs) var(--sp-sm);
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  border: 1px solid var(--border-primary);
  background: var(--bg);
  color: var(--text-primary);
}
@media (hover: hover) {
  .chat-detail-name-actions button:hover {
  background: var(--bg-tertiary);
}
}

/* ── Members Section ─────────────────────────────────────────────────── */

.chat-detail-members {
  padding: var(--sp-md) 0;
}

.chat-detail-members-header {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding-bottom: var(--sp-sm);
}

.chat-detail-sheet .chat-detail-members-header  .title {
  font-size: var(--text-lg);
  color: var(--text-primary);
}

.chat-detail-member-count {
  font-size: var(--text-base);
  color: var(--text-secondary);
  font-weight: 500;
}

.chat-detail-add-member {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--text-secondary);
  padding: var(--sp-3xs);
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-lg);
  line-height: 1;
}
@media (hover: hover) {
  .chat-detail-add-member:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}
}

.chat-detail-member-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
}

.chat-detail-member-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-md);
}
@media (hover: hover) {
  .chat-detail-member-row:hover {
  background: var(--bg-tertiary);
}
}

.chat-detail-member-info,
.chat-detail-member-link {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  text-decoration: none;
  color: inherit;
  min-width: 0;
}
@media (hover: hover) {
  .chat-detail-member-info:hover,
  .chat-detail-member-link:hover {
  text-decoration: none;
}
}

.chat-detail-member-name {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--accent-blue);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat-detail-member-row.you .chat-detail-member-name {
  color: var(--text-primary);
  pointer-events: none;
}


.chat-detail-member-you, .chat-detail-member-row.owner .chat-detail-member-role {
    font-size: var(--text-sm);
    font-weight: 600;
    padding: var(--sp-xs) var(--sp-sm);
    border-radius: var(--radius-full);
    white-space: nowrap;
    flex-shrink: 0;
}

.chat-detail-member-you {
  color: var(--accent-blue);
  background: rgba(var(--color-blue), .1);
  background-blend-mode: var(--blend-mode);
}

.chat-detail-member-row.owner .chat-detail-member-role {
  color: var(--text-primary);
  background: rgba(var(--color-gray-2), .1);
  background-blend-mode: var(--blend-mode);
}

.chat-detail-remove-member {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--text-secondary);
  padding: var(--sp-3xs);
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-sm);
  line-height: 1;
}
@media (hover: hover) {
  .chat-detail-remove-member:hover {
  background: var(--bg-tertiary);
  color: var(--accent-red);
}
}

.chat-detail-sheet .chat-detail-member-row {
  padding-left: 0;
}

/* ── Actions ─────────────────────────────────────────────────────────── */

/* (Sheet actions live in .slide-up-sheet__footer > .sheet-footer__actions
   with the semantic .button.destructive / .button.danger variants —
   see styles/button.css and sheet.css.) */

/* ── Confirm Dialog ──────────────────────────────────────────────────── */

.chat-detail-confirm-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  /* Must layer above the settings sheet, which sits at the sheet tier
     (401 — see sheet.css desktop block). */
  z-index: 410;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  /* Rendered as a sibling of the settings sheet inside
     `.slide-up-sheet-shell`, which sets `pointer-events: none`. Without
     re-enabling here, clicks fall through to the sheet beneath and the
     Confirm/Cancel buttons aren't even hoverable. (Same fix as
     `.new-chat-overlay` in commit 30a8050.) */
  pointer-events: auto;
}

.chat-detail-confirm-dialog {
  background: var(--bg-secondary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: var(--sp-xl);
  width: min(400px, calc(100vw - 48px));
}

.chat-detail-confirm-dialog p {
  margin: 0 0 var(--sp-lg);
  font-size: var(--text-sm);
  color: var(--text-primary);
  line-height: 1.5;
}

.chat-detail-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-xs);
}

.chat-detail-confirm-cancel {
  padding: var(--sp-xs) var(--sp-md);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text-primary);
  font-family: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}
@media (hover: hover) {
  .chat-detail-confirm-cancel:hover {
  background: var(--bg-tertiary);
}
}

.chat-detail-confirm-confirm {
  padding: var(--sp-xs) var(--sp-md);
  border: none;
  border-radius: var(--radius-md);
  background: var(--accent-red);
  color: #ffffff;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}
@media (hover: hover) {
  .chat-detail-confirm-confirm:hover {
  opacity: 0.9;
}
}

/* ── Sheet chrome ────────────────────────────────────────────────────── */

/* Chat Settings renders in the shared adaptive slide-up-sheet chrome
   (sheet.css): bottom sheet ≤768px with drag-to-dismiss (attach_sheet_drag
   on #chat-detail-sheet), centered modal ≥769px. Only chat-specific
   sizing and inner layout live here. */

/* Desktop modal: keep the old .chat-detail-modal footprint (480px wide,
   capped height) instead of sheet.css's 560px Medium default. The
   two-class selector outspecifies sheet.css's single-class desktop width
   rule regardless of import order. */
@media (min-width: 835px) {
  .slide-up-sheet.chat-detail-sheet {
    width: min(480px, 92vw);
    max-height: min(640px, 80vh);
  }
}

/* Header row pinned above the scrollable body. */
.chat-detail-sheet .chat-detail-header {
  flex-shrink: 0;
}

/* Scrollable body — .slide-up-sheet__content supplies flex/overflow and
   the sheet panel supplies the horizontal inset (--sp-md); this adds the
   inner breathing room and hides the scrollbar like the old modal body. */
.chat-detail-body {
  padding: 0 var(--sp-xs) var(--sp-xl);
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.chat-detail-body::-webkit-scrollbar {
  display: none;
}

/* Close button (X) in modal header. */
.chat-detail-close {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--text-tertiary);
  padding: var(--sp-3xs);
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  margin-left: auto;
}
@media (hover: hover) {
  .chat-detail-close:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}
}

/* Member-count badge inside the sheet. */

.chat-detail-member-role {
  font-size: var(--text-xs);
  color: var(--text-tertiary);
  flex-shrink: 0;
}

.chat-detail-member-remove {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--text-secondary);
  padding: var(--sp-3xs);
  border-radius: var(--radius-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  flex-shrink: 0;
}
@media (hover: hover) {
  .chat-detail-member-remove:hover {
  background: var(--bg-tertiary);
  color: var(--accent-red);
}
}

.chat-detail-members-section {
  padding: var(--sp-md) 0;
}

.chat-detail-members-header {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding-bottom: var(--sp-sm);
}

.chat-detail-members-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
}

.chat-detail-name-save,
.chat-detail-name-cancel {
  padding: var(--sp-3xs) var(--sp-sm);
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  border: 1px solid var(--border-primary);
  background: var(--bg);
  color: var(--text-primary);
}
@media (hover: hover) {
  .chat-detail-name-save:hover,
  .chat-detail-name-cancel:hover {
  background: var(--bg-tertiary);
}
}

/* The thread-header settings gear is a plain page_header.icon_button
   (.header-icon-btn, shell.css); the former .chat-header-actions /
   .chat-header-detail-link chrome was retired with the ui/page_header
   migration. */

/* Block / Unblock on a member row (2026-09-18). A quiet text button beside
   the remove `x`; red while it would BLOCK, secondary while it would unblock. */
.chat-detail-member-block {
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--accent-red);
  font: inherit;
  font-size: var(--text-sm);
  padding: var(--sp-3xs) var(--sp-xs);
  border-radius: var(--radius-md);
  flex-shrink: 0;
}

.chat-detail-member-block--on {
  color: var(--text-secondary);
}

.chat-detail-member-block:disabled {
  opacity: 0.5;
  cursor: default;
}

@media (hover: hover) {
  .chat-detail-member-block:hover {
    background: var(--bg-tertiary);
  }
}

/* Search screen — query input + results grid. Results reuse .feed-grid
   and .feed-card from feed.css. */

/* Page root — mirrors .notifications-page: full-width shell hosting the
   standardized header above the column-constrained body. */
.search-page {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bg-secondary);
}

/* Desktop: the page sits beside the list pane, on the shell's canvas, and
   paints that canvas so the two read as one surface (2026-09-25). Phones
   keep bg-secondary, like the phone's other pages (.feed-page). */
@media (min-width: 835px) {
  .search-page {
    background: var(--canvas-bg);
  }
}

/* Same column as the feed (54vw / 900px, wider at mid-desktop, full-width
   on mobile) so the input, grid, and result rows all share one width —
   the input used to run 68vw while its content capped at 900px. */
.search {
  flex: 1;
  width: 54vw;
  /* Never wider than the main pane: a vw width ignores the rail and list
     pane beside it. At 1280 the mid-desktop 68vw (870px) only fit while the
     pane was 896; the 406px list pane (2026-09-24) left 810 and the column
     scrolled sideways. */
  max-width: min(900px, 100%);
  margin: 0 auto;
  padding-top: var(--sp-xs);
  padding-left: var(--sp-xs);
  padding-right: var(--sp-xs);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ── Header row: pill input ───────────────────────────────────────────── */

.search-header {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  margin-bottom: var(--sp-xl);
}

.search-input {
  flex: 1;
  height: 48px;
  padding: var(--sp-sm) var(--sp-md);
  border: 1px solid transparent;
  border-radius: 9999px;
  background: var(--bg-primary);
  color: var(--text-primary);
  font: inherit;
  outline: none;
  transition: border-color 120ms ease;
}

.search-input::placeholder {
  color: var(--text-primary);
}

.search-input:focus {
  border-color: var(--accent-blue);
}

.feed .search-input {
  color: var(--text-tertiary);
}

/* ── Scrollable body ──────────────────────────────────────────────────── */

.search-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: var(--sp-xl);
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.search-body::-webkit-scrollbar {
  display: none;
}

/* ── Empty / prompt state ─────────────────────────────────────────────── */

.search-prompt {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-md);
  color: var(--text-tertiary);
}

.search-prompt-icon {
  opacity: 0.4;
}

.search-prompt-text {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--text-base);
}

.search-empty,
.search-loading,
.search-error {
  padding: var(--sp-3xl) var(--sp-md);
  text-align: center;
  color: var(--text-secondary);
}

.search-error {
  color: var(--accent-red);
}

.search-result-count {
  text-align: center;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  padding: var(--sp-md) 0;
}

/* ── Results ──────────────────────────────────────────────────────────────
   Results are reminder rows (ui/timeline_list), same as the reminders list —
   see styles/timeline_row.css for the row itself.

   Deliberately NOT .feed-items-wrap: that is a scroll container, and
   .search-body already is one, so reusing it would nest a second scroller
   inside the first. This is a plain column; .search-body does the scrolling.

   Capped at the feed's column width so a result row is the same size as the
   same row on the reminders list — .search is wider (68vw) than .feed
   (54vw / 900px) because the search input wants the room, but the rows
   shouldn't inherit that. */
.search-results {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);

  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  padding-bottom: var(--sp-lg);
}

.search-results .tl-list {
  width: 100%;
}

/* ── Bucket grid (search entry state) ─────────────────────────────────────
   A responsive card grid of topic folders (iOS BucketGridView, desktop
   density): auto-fill gives 3–4 columns on desktop, degrading toward the
   iOS two-up on narrow windows. Cards speak the app's card language —
   bg-primary surface on the bg-secondary page, 16px radius, borderless,
   hover lift (see .feed-card) — with the iOS tint demoted from a
   whole-card wash to the count pill. Tints cycle over DISPLAY position
   (5 tints ⇒ grid neighbours never share one); purple fills the fifth
   slot (no teal token on web — and the old neutral fifth slot was
   bg-secondary, invisible against the page). */
.bucket-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--sp-md);
  width: 100%;
  max-width: 900px;
  margin: 0 auto;
  padding-bottom: var(--sp-lg);
}

.bucket-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: 16px;
  overflow: hidden;
  background: var(--bg-primary);
  transition: transform 0.15s ease;
}

@media (hover: hover) {
  .bucket-card:hover {
    transform: translateY(-1px);
  }
}

.bucket-card-tint-0 { --bucket-tint: var(--color-blue); }
.bucket-card-tint-1 { --bucket-tint: var(--color-orange); }
.bucket-card-tint-2 { --bucket-tint: var(--color-red); }
.bucket-card-tint-3 { --bucket-tint: var(--color-purple); }
.bucket-card-tint-4 { --bucket-tint: var(--color-green); }

.bucket-card-main {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-xs);
  width: 100%;
  min-height: 108px;
  padding: var(--sp-md);
  border: none;
  background: none;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: var(--text-primary);
}

.bucket-card-title {
  font-weight: 600;
  font-size: 1rem;
}

/* The card's tint carrier: the count as a tonal pill (accent at 12% over
   the card surface, accent text) — the iOS bucket colour, at feed-card
   levels of restraint. */
.bucket-card-count {
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(var(--bucket-tint), 0.12);
  font-size: 0.8rem;
  font-weight: 500;
  color: rgba(var(--bucket-tint), 1);
}

.bucket-card-suggested-pill {
  align-self: flex-start;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg-secondary);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}

.bucket-card-deck {
  display: flex;
  gap: var(--sp-xs);
  margin-top: auto;
  padding-top: var(--sp-xs);
}

.bucket-card-thumb {
  width: 44px;
  height: 44px;
  border-radius: 10px;
  object-fit: cover;
  background: var(--bg-secondary);
}

.bucket-card-date-badge {
  margin-top: auto;
  min-width: 40px;
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: 10px;
  background: var(--bg-secondary);
  font-size: 1.2rem;
  font-weight: 700;
  text-align: center;
}

/* Keep / No thanks on a suggested card — siblings of the select target,
   never nested inside it, so a tap can't both keep and open. */
.bucket-card-actions {
  display: flex;
  gap: var(--sp-sm);
  padding: 0 var(--sp-md) var(--sp-md);
}

.bucket-card-keep,
.bucket-card-dismiss {
  padding: 6px 14px;
  border: none;
  border-radius: 999px;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.bucket-card-keep {
  background: var(--text-primary);
  color: var(--bg-primary);
}

.bucket-card-dismiss {
  background: var(--bg-secondary);
  color: var(--text-secondary);
}

/* ── Browse sections (Today / Yesterday / …) ────────────────────────── */
.search-browse-section-title {
  margin: var(--sp-md) 0 var(--sp-xs);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-secondary);
}

/* ── Breakpoint gating: the entry state renders both the bucket grid and
   the desktop prompt; CSS picks one. On desktop the buckets live in the
   sidebar (Search mode), so the pane shows the prompt; on mobile the
   grid stays the entry surface (the sidebar is a drawer there). The
   empty-corpus prompt carries no modifier and shows everywhere. ─────── */

@media (min-width: 835px) {
  .search .bucket-grid {
    display: none;
  }
}

@media (max-width: 834px) {
  .search-prompt--desktop {
    display: none;
  }
}

/* ── Mid-desktop (769px – 1440px) — mirrors .feed ────────────────────── */

@media (min-width: 835px) and (max-width: 1440px) {
  .search {
    width: 68vw;
    max-width: min(68vw, 100%);
  }
}

/* ── Mobile (≤ 768px) — mirrors .feed ────────────────────────────────── */

@media (max-width: 834px) {
  .search {
    width: 100%;
    max-width: 100vw;
    padding: var(--sp-xl) var(--sp-md) var(--sp-sm);
  }
}

@media (max-width: 560px) {
  /* iOS two-up: auto-fill at 240px min would collapse a phone to one
     column; the grid stays two-up like BucketGridView instead. */
  .bucket-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-sm);
  }
}

/* Monogram avatar — circular badge with a letter on a deterministic
   desaturated background. Sizes are set inline via the `avatar()` helper
   so one CSS class supports arbitrary px sizes. */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  flex-shrink: 0;
  font-weight: 600;
  font-family: var(--font-sans);
  line-height: 1;
  color: #ffffff;
  user-select: none;
  letter-spacing: 0;
  text-transform: uppercase;
}

/* Muted/desaturated palette — picked so they sit quietly next to the
   monochrome icon set. Tuned for both light and dark modes. */
.avatar-color-1 { background: #8b7aa8; } /* dusty violet */
.avatar-color-2 { background: #7a9a8e; } /* sage */
.avatar-color-3 { background: #b58863; } /* terracotta */
.avatar-color-4 { background: #6e8caf; } /* slate blue */
.avatar-color-5 { background: #c08a6a; } /* warm tan */
.avatar-color-6 { background: #8a9a5b; } /* olive */
.avatar-color-7 { background: #a66e7e; } /* mauve */
.avatar-color-8 { background: #5f8a8b; } /* teal */

[data-theme="dark"] .avatar-color-1 { background: #6a5a82; }
[data-theme="dark"] .avatar-color-2 { background: #5a7568; }
[data-theme="dark"] .avatar-color-3 { background: #8a6547; }
[data-theme="dark"] .avatar-color-4 { background: #526b86; }
[data-theme="dark"] .avatar-color-5 { background: #8f6550; }
[data-theme="dark"] .avatar-color-6 { background: #687543; }
[data-theme="dark"] .avatar-color-7 { background: #7f5361; }
[data-theme="dark"] .avatar-color-8 { background: #476668; }

.avatar-img {
  border-radius: var(--radius-full);
  object-fit: cover;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
}

/* Only a BROKEN image renders pseudo-elements: its monogram, covering the
   alt text the browser would otherwise print (ui/avatar.gleam). */
.avatar-img::before {
  content: attr(data-monogram);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: inherit;
  color: #ffffff;
}

/* ─── Media queries ──────────────────────────────────────────────────── */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .avatar-color-1 { background: #6a5a82; }
  :root:not([data-theme]) .avatar-color-2 { background: #5a7568; }
  :root:not([data-theme]) .avatar-color-3 { background: #8a6547; }
  :root:not([data-theme]) .avatar-color-4 { background: #526b86; }
  :root:not([data-theme]) .avatar-color-5 { background: #8f6550; }
  :root:not([data-theme]) .avatar-color-6 { background: #687543; }
  :root:not([data-theme]) .avatar-color-7 { background: #7f5361; }
  :root:not([data-theme]) .avatar-color-8 { background: #476668; }
}

.avatar-error {
  position: relative;
  background: var(--bg-secondary);
  color: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  flex-shrink: 0;
}

.avatar-error-badge {
  position: absolute;
  bottom: -1px;
  right: -1px;
  background: var(--accent-red, #e53935);
  color: #fff;
  font-size: 8px;
  font-weight: 700;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* ── Group-chat avatar (iOS ChatAvatarStackView parity) ────────────────────
   The uploaded channel image when one is set — alone, a full circle — else
   two members pinned to opposite corners of the slot, each 2/3 of it
   (iOS's 32/48 ratio). See ui/avatar.chat_avatar_stack. */

/* vertical-align: top — an inline-block on the baseline leaves a descender
   gap under it, so a block parent grew ~5px taller than the circle (the
   list's 40px slot measured 40x45, and the row centred the box, not the
   circle). */
.avatar-stack {
  position: relative;
  display: inline-block;
  vertical-align: top;
  flex-shrink: 0;
}

.avatar-stack-member {
  position: absolute;
  width: 66.7%;
  height: 66.7%;
  display: flex;
}

/* The member box drives the real size (the helper's inline px is only a
   fallback/font seed, same convention as .chat-list-avatar's children). */
.avatar-stack-member .avatar,
.avatar-stack-member .avatar-img {
  width: 100% !important;
  height: 100% !important;
}

.avatar-stack-back {
  top: 0;
  left: 0;
}

/* Front avatar punches out of the back one with a surface-colored ring
   (iOS: ringWidth = max(1, size/48) behind a backgroundPrimary circle). */
.avatar-stack-front {
  bottom: 0;
  right: 0;
  border-radius: var(--radius-full);
  background: var(--bg-primary);
  box-shadow: 0 0 0 2px var(--bg-primary);
}

/* A custom image is the slot's only child: the circle carries a neutral
   fill so it reads as an avatar while the fetch is in flight (the member
   stack is never drawn behind it). On error the <img> dispatches the
   failed URL and the caller re-renders the member stack (one-way latch). */
.avatar-stack--custom {
  border-radius: var(--radius-full);
  background: var(--fill-secondary);
}

/* The custom image fills the whole slot. */
.avatar-stack-custom {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: var(--radius-full);
  object-fit: cover;
}

/* Error state + skeleton loaders — shared across all features. */

/* ── Error state ─────────────────────────────────────────────── */

.error-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-3xl) var(--sp-xl);
  text-align: center;
  color: var(--text-secondary);
}
.error-state .icon { color: var(--accent-red); }
.error-state-title {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--text-primary);
  margin: 0;
}
.error-state-message {
  font-size: var(--text-sm);
  max-width: 320px;
  margin: 0;
}

/* ── Skeleton loaders ────────────────────────────────────────── */

.skeleton-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  break-inside: avoid;
  margin-bottom: var(--sp-sm);
}

.skeleton-media {
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--bg-subtle);
  border-radius: var(--radius-lg);
  animation: skeleton-pulse 1.5s ease infinite;
}

.skeleton-hero {
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--bg-subtle);
  border-radius: var(--radius-lg);
  animation: skeleton-pulse 1.5s ease infinite;
}

.skeleton-line {
  height: 14px;
  background: var(--bg-subtle);
  border-radius: var(--radius-sm);
  animation: skeleton-pulse 1.5s ease infinite;
}

.skeleton-line-title { width: 70%; }
.skeleton-line-short { width: 40%; }
.skeleton-line-flex { flex: 1; }
.skeleton-line-full { width: 100%; }
.skeleton-line-medium { width: 60%; }

.skeleton-circle {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background: var(--bg-subtle);
  animation: skeleton-pulse 1.5s ease infinite;
  flex-shrink: 0;
}

.skeleton-row {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-sm) 0;
}

/* ── Boot screen ─────────────────────────────────────────────── */

.boot {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-lg);
  color: var(--text-primary);
}

.boot-spinner {
  display: flex;
  gap: var(--sp-xs);
}

.boot-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--text-tertiary);
  animation: boot-pulse 1.4s ease-in-out infinite both;
}
.boot-dot:nth-child(2) { animation-delay: 0.16s; }
.boot-dot:nth-child(3) { animation-delay: 0.32s; }

/* ─── Keyframe animations ────────────────────────────────────────────── */

@keyframes skeleton-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 0.8; }
}

@keyframes boot-pulse {
  0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
  40% { opacity: 1; transform: scale(1); }
}

/* ── Drop zone ─────────────────────────────────────────────────────────────
   Drag a photo in from the desktop. Applied to whole surfaces (the New
   Reminder page, a reminder's detail page) rather than a small target, so it
   needs a clearer affordance than the chat composer's background tint: a
   dashed inset outline that reads as "this whole thing will take the file".

   The overlay is a ::after so it can't disturb layout, and it's inert to the
   pointer — a drop zone that swallowed its own drop event would be a poor
   sort of drop zone. */

.nr-root.drag-over::after,
.detail.drag-over::after {
  content: "Drop to attach";
  position: fixed;
  inset: var(--sp-sm);
  z-index: 60;

  display: flex;
  align-items: center;
  justify-content: center;

  border: 2px dashed var(--accent-blue);
  border-radius: var(--radius-lg, 14px);
  background: rgba(var(--color-blue), 0.06);
  color: var(--accent-blue);

  font-size: var(--text-lg);
  font-weight: 600;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .nr-root.drag-over::after,
  .detail.drag-over::after {
    animation: drop-zone-in 0.12s ease-out;
  }
}

@keyframes drop-zone-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* ── Empty main pane (ui/empty_pane) ─────────────────────────────────────
   Nothing selected in the list beside it: a centred glyph over one line of
   text, filling the pane — glyph tertiary, words secondary (iOS
   CenteredStateView). Chat's "Select a conversation" placeholder until
   2026-09-24, now shared with Home (and Settings next).
   No background — the pane's own shows through. */
.empty-pane {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-sm);
  color: var(--text-tertiary);
}

.empty-pane-glyph {
  display: inline-flex;
}

.empty-pane-text {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--text-lg);
}

/* The way out under the text (the unknown-address page's "Go to Home"). */
.empty-pane-link {
  color: var(--accent-blue);
  font-size: var(--text-md);
  text-decoration: none;
}

.empty-pane-link:hover {
  text-decoration: underline;
}

/* Settings page styles */

/* Every Settings screen's column: content never wider than 600px
   (2026-09-24), centred in whatever the main pane leaves — on desktop
   that's everything right of the rail, since Settings has no list pane. */
.settings {
  box-sizing: border-box;
  width: 100%;
  max-width: calc(600px + 2 * var(--sp-xl));
  margin: 0 auto;
  padding: var(--sp-xl);
}

.settings-header {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  margin-bottom: var(--sp-xl);
}

/* Sidebar toggle is mobile-only in the settings header. */
@media (min-width: 835px) {
  .settings-header .sidebar-collapse-button {
    display: none;
  }
}


.settings-title {
  font-size: var(--text-4xl);
  font-weight: 700;
  margin: 0;
}

/* ── Profile header ─────────────────────────────────────────────────── */

.settings-profile {
  display: flex;
  justify-content: center;
  margin-bottom: 0;
}

/* iOS Avatar(size: .lg) = 80pt circle, centred, tap = change photo. */
.settings-profile-avatar {
  position: relative;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  overflow: hidden;
  flex-shrink: 0;
  cursor: pointer;
}

.settings-profile-avatar .avatar,
.settings-profile-avatar img.avatar {
  /* Size only. The old rule also forced display:block, which undid the
     monogram's inline-flex centring and pinned the letter top-left. */
  width: 80px !important;
  height: 80px !important;
  border-radius: 50% !important;
}

/* Camera overlay — hidden on desktop, shown on mobile via media query */
.settings-avatar-overlay {
  display: none;
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.35);
  align-items: center;
  justify-content: center;
  color: #fff;
  pointer-events: none; /* label handles the click */
}

.settings-avatar-input-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Form ───────────────────────────────────────────────────────────── */

.settings-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xl); /* 24px */
}

/* ── Row ────────────────────────────────────────────────────────────── */

.settings-row--btn {
  background: none;
  border: none;
  cursor: pointer;
  font: inherit;
  text-align: left;
  padding: 0;
}

.settings-row-input {
  flex: 1;
  height: var(--sp-3xl); /* 40px */
  padding: 0 var(--sp-sm);
  background: var(--bg-secondary);
  border: none;
  border-radius: var(--radius-lg);
  font: inherit;
  font-size: var(--text-lg);
  color: var(--text-primary);
  outline: none;
  min-width: 0;
}

.settings-row-input:focus {
  background: var(--bg-tertiary, var(--bg-secondary));
}

.settings-row-input:disabled {
  color: var(--text-tertiary);
  cursor: default;
}

.settings-row-right {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  margin-left: auto;
  color: var(--text-tertiary);
}

.settings-row-value {
  font-size: var(--text-lg);
  font-weight: 400;
  color: var(--text-secondary);
}

/* ── Privacy / visibility pill toggle ───────────────────────────────── */

.settings-privacy-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  background: transparent;
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  padding: 3px;
  gap: 0;
  cursor: pointer;
  font: inherit;
  transition: background 200ms ease, border-color 200ms ease;
}

/* Disabled (non-interactive) state */
.settings-privacy-toggle:disabled {
  opacity: 0.45;
  cursor: default;
}

/* On state (right / public) */
.settings-privacy-toggle--public {
  background: var(--bg-secondary);
  border-color: transparent;
}

.settings-privacy-toggle--public .settings-privacy-indicator {
  background: var(--bg);
}

/* Sliding indicator — sits behind the option labels (z-index 0).
   Default position: left half (Private active).
   Squish: when arriving at a side, the leading edge moves first and the
   trailing edge follows with a short delay, creating a stretch-then-snap
   feel. Each state declares the transition for arriving at that state. */
.settings-privacy-indicator {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  right: 50%;
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle, rgba(0, 0, 0, 0.06));
  border-radius: var(--radius-full);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.10);
  z-index: 0;
  /* Arriving at Private (left): left edge leads, right follows */
  transition:
    left  240ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,
    right 240ms cubic-bezier(0.4, 0, 0.2, 1) 50ms;
}

/* Public active: slide indicator to right half */
.settings-privacy-toggle--public .settings-privacy-indicator {
  left: 50%;
  right: 3px;
  /* Arriving at Public (right): right edge leads, left follows */
  transition:
    right 240ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,
    left  240ms cubic-bezier(0.4, 0, 0.2, 1) 50ms;
}

.settings-privacy-opt {
  position: relative;
  z-index: 1;
  flex: 1;
  padding: 5px 14px;
  border: none;
  background: transparent;
  border-radius: var(--radius-full);
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  color: var(--text-secondary);
  cursor: pointer;
  white-space: nowrap;
  transition: color 180ms ease, font-weight 180ms ease;
}

.settings-privacy-opt--active {
  font-weight: 600;
  color: var(--text-primary);
}

/* ── Error ──────────────────────────────────────────────────────────── */

.settings-error {
  padding: var(--sp-sm);
  margin-bottom: var(--sp-md);
  background: var(--danger-subtle, rgba(220, 38, 38, 0.08));
  color: var(--accent-red);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}

/* ── Push notifications row ─────────────────────────────────────────── */

.settings-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-xs) 0;
}

.settings-toggle-label {
  font-size: var(--text-lg);
  font-weight: 400;
  color: var(--text-secondary);
}

.settings-push-status,
.settings-push-denied,
.settings-push-error {
  font-size: var(--text-sm);
  color: var(--text-primary);
}

.settings-push-denied,
.settings-push-error {
  color: var(--accent-red);
}

.settings-push-btn,
.settings-push-disable-btn {
  padding: var(--sp-3xs) var(--sp-sm);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-md);
  background: var(--bg);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}

.settings-push-btn:hover,
.settings-push-disable-btn:hover {
  background: var(--bg-subtle);
}

.settings-push-active {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  font-size: var(--text-sm);
  color: var(--text-primary);
}

/* ── Save button ────────────────────────────────────────────────────── */

.settings-save-btn {
  align-self: flex-start;
  padding: var(--sp-xs) var(--sp-md);
  border: none;
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}

.settings-save-btn:hover {
  filter: brightness(0.95);
}

.settings-save-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* ── Divider ────────────────────────────────────────────────────────── */

.settings-divider {
  border: none;
  border-top: 1px solid var(--border-primary);
  margin: var(--sp-md) 0;
}

/* ── Sign-out ───────────────────────────────────────────────────────── */

.settings-signout-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-md);
  border: none;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-tertiary);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}

.settings-signout-btn:hover {
  color: var(--accent-red);
  background: var(--danger-subtle, rgba(220, 38, 38, 0.08));
}

/* ─── Mobile overrides (≤ 768px) ────────────────────────────────────── */

@media (max-width: 834px) {
  /* The avatar stays the same 80px circle on the phone — iOS uses one
     Avatar(size: .lg) at every width. The old 72px rounded-square rule and
     the name/handle/Change Photo rules it sat with are gone (2026-09-23);
     only the tap-to-change camera overlay is mobile-specific. */
  .settings-avatar-overlay {
    display: flex;
  }
}

/* ── Blocked users (/settings/blocked) ───────────────────────────────── */










/* ══════════════════════════════════════════════════════════════════════
   iOS Settings vocabulary (2026-09-23) — the grouped-inset-card language
   of PleaseUI's settingsCard() / SectionHeader / InputCellView, in web
   tokens. Numbers are iOS's: card radius Padding.xl 24, page inset
   Padding.md 16, row 19/12 padding on a 44 (Sizing.xxl) min height, label
   body 18, header subline 15/600, footer footnote 13. Where a token is one
   pixel off (Padding.lg 19 vs --sp-lg 20) the iOS number is hardcoded and
   says so. Rows carry NO leading glyph — iOS root rows do not.
   ══════════════════════════════════════════════════════════════════════ */

/* The settings pages paint the canvas themselves: .main-pane is
   bg-primary (the white sheet) and iOS's settings sit on the canvas. */
.settings-canvas {
  background: var(--canvas-bg);
  height: 100%;
  overflow-y: auto;
  box-sizing: border-box;
}

.settings-section {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs); /* 8px header → card, iOS Gap.xs */
}

/* Headers and footers sit 8px inside the card edge. iOS pads them
   Padding.xl (24) from the SCREEN while settingsCard() pads the cards
   Padding.md (16) from it, so the text lands 24 − 16 = 8 in from the
   card's edge. Web's column edge IS the card edge; until 2026-09-24 this
   was 24px from it — 16px right of iOS on every Settings screen. */
.settings-section-header {
  margin: 0;
  padding: 0 var(--sp-xs); /* 8px = iOS Padding.xl − Padding.md */
  font-size: var(--text-md); /* 15px */
  font-weight: 600;
  line-height: 20px;
  color: var(--text-secondary);
}

.settings-section-footer {
  margin: 0;
  padding: 0 var(--sp-xs); /* 8px, as the header above */
  font-size: var(--text-sm); /* 13px */
  line-height: 18px;
  color: var(--text-secondary);
}

.settings-card {
  display: flex;
  flex-direction: column;
  background: var(--bg-primary);
  border-radius: var(--radius-xl); /* 24px */
  overflow: hidden;
}

.settings-card-row {
  display: flex;
  align-items: center;
  gap: var(--sp-sm); /* 12px, iOS Gap.sm */
  min-height: 44px; /* iOS Sizing.xxl */
  padding: var(--sp-sm) 19px; /* iOS Padding.sm / Padding.lg (19, no token) */
  box-sizing: border-box;
  width: 100%;
  color: var(--text-primary);
  text-decoration: none;
}

/* Inset divider between rows, iOS Divider().padding(.horizontal, lg). */
.settings-card-row + .settings-card-row {
  position: relative;
}
.settings-card-row + .settings-card-row::before {
  content: "";
  position: absolute;
  top: 0;
  left: 19px;
  right: 19px;
  height: 1px;
  background: var(--border-tertiary);
}

.settings-card-row--btn {
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
@media (hover: hover) {
  .settings-card-row--btn:hover {
    background: var(--fill-elevated-secondary);
  }
}

.settings-card-row-label {
  flex: 1;
  min-width: 0;
  font-size: var(--text-xl); /* 18px, iOS body */
  font-weight: 400;
  line-height: 25px;
  color: var(--text-primary);
}

.settings-card-row-value {
  font-size: var(--text-xl);
  font-weight: 400;
  /* The label's 25px, not the inherited 1.5 (27px at 18px): rows with a
     value were 51px beside 49px label-only rows in the same card. */
  line-height: 25px;
  color: var(--text-secondary);
  text-align: right;
}
.settings-card-row-value--placeholder {
  color: var(--text-tertiary);
}

.settings-card-row-chevron {
  display: inline-flex;
  color: var(--text-secondary);
}

/* Checkmark rows (Theme, Look ahead): the filled check only on the
   selected row, accent blue, 24px frame — iOS InputCell.checkmark. */
.settings-card-row-check {
  display: inline-flex;
  width: 24px;
  height: 24px;
  align-items: center;
  justify-content: center;
  color: var(--accent-blue);
}

/* Native-looking switch, promoted from .integrations-switch and tinted
   green: iOS tints every settings Toggle brandGreen. */
.settings-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  width: 51px;
  height: 31px;
  padding: 2px;
  border: none;
  border-radius: var(--radius-full);
  background: var(--fill-elevated-secondary);
  cursor: pointer;
  transition: background 180ms ease;
}
.settings-switch:disabled {
  opacity: 0.5;
  cursor: default;
}
.settings-switch--on {
  background: var(--accent-green);
}
.settings-switch-thumb {
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  transform: translateX(0);
  transition: transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
}
.settings-switch--on .settings-switch-thumb {
  transform: translateX(20px);
}

/* Full-width capsule actions — iOS SettingsPillButton (40pt). The label
   is iOS bodyStyle, 18 REGULAR; it was 600 until 2026-09-24. */
.settings-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-xs);
  width: 100%;
  min-height: 40px;
  padding: 0 var(--sp-md);
  border: none;
  border-radius: var(--radius-full);
  font: inherit;
  font-size: var(--text-xl);
  font-weight: 400;
  cursor: pointer;
  background: var(--accent-blue-elevated);
  color: var(--accent-blue);
}
.settings-pill--hollow {
  background: transparent;
  border: 1px solid var(--border-secondary);
  color: var(--text-primary);
}
.settings-pill--danger {
  background: var(--accent-red-elevated);
  color: var(--accent-red);
}
.settings-pill:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Sub-screen chrome — iOS SettingsSubscreen: back arrow left, title
   centred, content below on the canvas. */
.settings-subscreen-header {
  display: flex;
  align-items: center;
  margin-bottom: var(--sp-xl);
}
.settings-subscreen-back {
  color: var(--text-primary);
}
.settings-subscreen-title {
  flex: 1;
  margin: 0;
  text-align: center;
  font-size: var(--text-lg); /* 16px */
  font-weight: 600;
}
.settings-subscreen-spacer {
  width: 32px; /* mirrors the back button so the title centres */
}
.settings-subscreen-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md); /* 16px between sub-screen blocks */
}

@media (max-width: 834px) {
  .settings {
    padding: var(--sp-md); /* iOS page inset 16 */
  }
}

/* Root page: 24px between blocks (iOS Gap.xl between settings cards). */
.settings-root-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xl);
}

/* Editable rows: label column then a bare input on the card — no pill,
   iOS InputCell.editable. */
.settings-card-row-label--column {
  flex: 0 0 128px; /* iOS InputCell.labelColumnWidth */
}
.settings-card-row-label--semibold {
  font-weight: 600;
}
.settings-card-row-leading {
  display: inline-flex;
  color: var(--text-primary);
}
.settings-card-row-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  font: inherit;
  font-size: var(--text-xl);
  line-height: 25px; /* one row height with the label-only rows */
  color: var(--text-primary);
  outline: none;
  padding: 0;
}
.settings-card-row-input::placeholder {
  color: var(--text-tertiary);
}

/* Permission rows: "label … STATUS", status bold and secondary — iOS's
   "Allowed" / "Not set" treatment. */
.settings-card-row-status {
  font-size: var(--text-xl);
  line-height: 25px; /* the label's, like .settings-card-row-value */
  font-weight: 600;
  color: var(--text-secondary);
  text-align: right;
}

/* Manage account: iOS's explainer is body text on the canvas, not a card,
   24px above the pill. Its 16 (Padding.md) is from the SCREEN, the cards'
   own inset, so it lines up with the pill below — no inset here. It was
   16px in from the column until 2026-09-24. */
.settings-explainer {
  margin: 0 0 var(--sp-xl);
  padding: 0;
  font-size: var(--text-xl);
  line-height: 25px;
  color: var(--text-primary);
}

/* Confirm modal, prefix "settings-confirm" (ui/confirm_modal). Mirrors
   detail's rules; each feature keeps its own scoped copy by design. */
.settings-confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
}
.settings-confirm-dialog {
  /* Elevated, not primary: since the merge the sign-out and Birthday
     modals sit over the root's cards, and in dark mode --bg-primary is the
     cards' own black. White either way in light. */
  background: var(--bg-elevated-primary);
  border-radius: var(--radius-lg);
  padding: var(--sp-xl);
  max-width: 360px;
  width: 90%;
  box-shadow: var(--shadow-lg, 0 8px 32px rgba(0, 0, 0, 0.2));
}
.settings-confirm-title {
  font-weight: 600;
  font-size: var(--text-lg);
  margin: 0 0 var(--sp-xs);
}
.settings-confirm-body {
  color: var(--text-secondary);
  margin: 0 0 var(--sp-lg);
  font-size: var(--text-md);
}
.settings-confirm-input {
  width: 100%;
  box-sizing: border-box;
  margin: var(--sp-xs) 0 var(--sp-lg);
  padding: var(--sp-xs) var(--sp-sm);
  border: 1px solid var(--border-secondary);
  border-radius: var(--radius-md);
  /* One step above the elevated dialog (same #F2F2F7 as before in light). */
  background: var(--bg-elevated-secondary);
  font: inherit;
  font-size: var(--text-lg);
  color: var(--text-primary);
  outline: none;
}
/* A validation line under the input (Birthday): tuck it up under the field
   and keep clear of the buttons below. */
.settings-confirm-input + .settings-footnote {
  margin: calc(-1 * var(--sp-sm)) 0 var(--sp-md);
}
.settings-confirm-actions {
  display: flex;
  gap: var(--sp-sm);
  justify-content: flex-end;
}
.settings-confirm-btn {
  padding: var(--sp-xs) var(--sp-md);
  border-radius: var(--radius-md);
  border: none;
  font: inherit;
  font-size: var(--text-md);
  font-weight: 500;
  cursor: pointer;
}
.settings-confirm-btn:disabled {
  /* The Settings pills' disabled treatment: a gate that isn't satisfied
     yet (the typed phrase) or a save in flight. */
  opacity: 0.5;
  cursor: default;
}
.settings-confirm-cancel {
  background: var(--fill-elevated-secondary);
  color: var(--text-primary);
}
.settings-confirm-confirm {
  background: var(--accent-blue);
  color: #fff;
}
.settings-confirm-delete {
  background: var(--accent-red);
  color: #fff;
}

/* Settings search — the mock's (2026-09-24) full-width capsule under the
   avatar, as tall as a card row, the magnifier leading at the rows' 19px
   inset. bg-primary, not bg-secondary: the page sits on the canvas, which
   IS the secondary colour. The input itself is bare inside the capsule;
   the capsule shows focus for it. */
.settings-search-field {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  box-sizing: border-box;
  min-height: 48px;
  padding: 0 19px;
  border-radius: var(--radius-full);
  background: var(--bg-primary);
  color: var(--text-primary);
  cursor: text;
}
.settings-search-field:focus-within {
  box-shadow: 0 0 0 2px var(--accent-blue);
}
.settings-search-field .icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
}
.settings-search {
  flex: 1;
  min-width: 0;
  height: 48px;
  padding: 0;
  border: none;
  background: transparent;
  font: inherit;
  font-size: var(--text-lg);
  color: var(--text-primary);
  outline: none;
}
.settings-search::placeholder {
  color: var(--text-primary);
}
/* iOS CenteredStateView: 16/600 secondary, centred. */
.settings-search-empty {
  margin: var(--sp-md) 0 0;
  text-align: center;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-secondary);
}

/* Loading / empty / failed copy on a sub-screen: iOS bodyStyle secondary. */
.settings-state {
  margin: 0;
  padding: var(--sp-xl) 0;
  text-align: center;
  font-size: var(--text-xl);
  color: var(--text-secondary);
}

/* Identity rows (Blocked users): name over @username beside a 36px avatar. */
.settings-card-row-identity {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.settings-card-row-identity .settings-card-row-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.settings-card-row-subline {
  font-size: var(--text-md); /* 15px */
  font-weight: 600;
  line-height: 20px;
  color: var(--text-secondary);
}
/* The small in-row pill (Unblock): iOS buttonLabelSmall 14/600 secondary
   on the secondary fill; dims while its request is in flight. */
.settings-pill--small {
  width: auto;
  min-height: 32px;
  padding: 0 var(--sp-md);
  font-size: 14px;
  font-weight: 600;
  color: var(--text-secondary);
  background: var(--bg-secondary);
}

/* ══════════════════════════════════════════════════════════════════════
   Settings › Integrations sub-screens (2026-09-24) — iOS
   GmailIntegrationSettingsView and CalendarSyncSettingsView on the card
   vocabulary above. Replaces integrations.css (the master-detail page,
   2026-09-11 → 2026-09-24) outright; nothing here is scoped to it.
   ══════════════════════════════════════════════════════════════════════ */

/* The Gmail screen's blocks sit 22px apart (iOS Gap.lg — no token; the
   sub-screen body's 16 is Gap.md, which Calendars keeps). */
.settings-gmail {
  display: flex;
  flex-direction: column;
  gap: 22px;
}

/* iOS titleSmall 20/600/25, textPrimary, on the canvas — the value
   statement above everything else. iOS pads it 24 from the screen like a
   section header, so it takes the header's 8 from the card edge and the
   two line up. No 20px type token exists. */
.settings-intro {
  margin: 0;
  padding: 0 var(--sp-xs);
  font-size: 20px;
  font-weight: 600;
  line-height: 25px;
  color: var(--text-primary);
}

/* Label tones for one-line status cards: "Loading…" and empty states in
   secondary, failures in iOS brandRed. */
.settings-card-row-label--secondary {
  color: var(--text-secondary);
}
.settings-card-row-label--danger {
  color: var(--accent-red);
}

/* iOS calloutRegular 16/400/21: the "What to know" rows. */
.settings-card-row-label--callout {
  font-size: var(--text-lg);
  line-height: 21px;
}

/* The account card's summary line: iOS footnote 13/18 secondary, 4px
   under the address (Gap.xxs). */
.settings-card-row-footnote {
  margin-top: var(--sp-3xs);
  font-size: var(--text-sm);
  line-height: 18px;
  color: var(--text-secondary);
}

/* The OAuth-return notice: text and dismiss top-aligned (iOS HStack
   alignment .top) so a two-line message keeps the X on its first line. */
.settings-notice {
  align-items: flex-start;
}
.settings-notice-dismiss {
  display: inline-flex;
  flex-shrink: 0;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-secondary);
  cursor: pointer;
}

/* A pill and its note, 8 apart (Gap.xs) — Connect's failure sits above
   its pill, Disconnect's below. No inset of its own: iOS pads the pills
   16 (Padding.md) from the screen, the same 16 settingsCard() pads the
   cards, so pill and card edges line up — as the column's do here. */
.settings-pill-block {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
}

/* iOS SettingsPillButton .tinted(accent.base) with a whiteCanvas label:
   Connect / Reconnect Gmail, the one primary action on the screen. It
   wears --accent-blue, which follows the chosen accent (accent.css,
   data-accent). */
.settings-pill--solid {
  background: var(--accent-blue);
  color: #fff;
}

/* iOS footnote 13/18 outside a card: the "unavailable" note, and the
   red failure lines (brandRed). */
.settings-footnote {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 18px;
  color: var(--text-secondary);
}
.settings-footnote--danger {
  color: var(--accent-red);
}

/* The 10px dot before each calendar's name. iOS fills it with the
   calendar's EventKit colour, or textSecondary for one this device doesn't
   have — every calendar, on web, whose wire carries no colour. The 19px
   after it (on top of the row's 12 gap) is the iOS toggle cell's own
   leading padding: iOS lays the dot BESIDE a whole padded cell, so the
   name starts 31 after the dot, not 12. */
.settings-card-row-dot {
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  margin-right: 19px;
  border-radius: 50%;
  background: var(--text-secondary);
}

/* Checkmark rows before the preferences load: iOS disables them; no hover
   wash and no pointer on a row that can't be picked. */
.settings-card-row--btn:disabled {
  background: none;
  cursor: default;
}

/* ── Accent Color additions (Cassie, bf45efa), restyled into the card
   vocabulary at the 2026-09-24 merge: the root row's disc, the preview,
   and the swatch card. Everything else her screens need is above. */

/* Accent colour: the dot is both the row icon and the swatch. */
.settings-accent-dot {
  display: inline-block;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-full);
  background: var(--swatch);
  flex-shrink: 0;
}

.settings-accent--default {
  --swatch: transparent;
  box-shadow: inset 0 0 0 2px var(--border-primary);
}

.settings-accent-preview {
  padding: var(--sp-md);
  border-radius: var(--radius-xl);
  background: var(--accent-blue-elevated);
  color: var(--text-primary);
}

.settings-accent-preview p {
  margin: 0 0 var(--sp-sm);
}

.settings-accent-preview-row {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
}

.settings-accent-badge {
  padding: 2px var(--sp-xs);
  border-radius: var(--radius-full);
  background: var(--accent-blue);
  color: var(--accent-fg-on-blue, #fff);
  font-size: var(--text-sm);
  font-weight: 500;
}

.settings-accent-glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-full);
  background: var(--accent-blue);
  color: #fff;
}

.settings-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-md);
  padding: var(--sp-md) 19px;
}

.settings-swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 0;
  border: none;
  background: none;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}

.settings-swatch .settings-accent-dot {
  width: 36px;
  height: 36px;
  outline: 2px solid transparent;
  outline-offset: 3px;
}

.settings-swatch--selected .settings-accent-dot {
  outline-color: var(--text-primary);
}

.settings-swatch--selected {
  color: var(--text-primary);
  font-weight: 500;
}

.settings-swatch:disabled {
  cursor: default;
}

/* The Accent Color row's disc sits in the value slot. */
.settings-card-row-value > .settings-accent-dot {
  display: block;
}

/* A value after the label column fills the row, so a trailing chevron
   (Birthday) sits at the far edge; text stays leading-aligned, iOS
   InputCell.standard. */
.settings-card-row-label--column + .settings-card-row-value {
  flex: 1;
  min-width: 0;
  text-align: left;
}

/* Processing badge on feed cards */

.feed-card-processing {
  display: flex;
  align-items: center;
  gap: var(--sp-3xs);
  padding: var(--sp-lg) var(--sp-sm);
  font-size: var(--text-xs);
  color: var(--text-tertiary);
}

.feed-card-processing-spinner {
  width: 12px;
  height: 12px;
  border: 1.5px solid var(--border-primary);
  border-top-color: var(--text-tertiary);
  border-radius: 50%;
  animation: processing-spin 0.8s linear infinite;
}

.feed-card-processing-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-tertiary);
  animation: processing-pulse 1.5s ease-in-out infinite;
}

.feed-card-processing-error .feed-card-processing-dot {
  background: var(--accent-red);
  animation: none;
}

.feed-card-processing-error {
  color: var(--accent-red);
}

.feed-card-processing-warning {
  color: #d97706;
}

.feed-card-processing-warning .feed-card-processing-dot {
  background: #d97706;
  animation: none;
}

/* Retry button inside errored processing badge */

.feed-card-retry-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3xs);
  margin-left: auto;
  padding: var(--sp-4xs) var(--sp-xs);
  border: 1px solid currentColor;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--accent-red);
  font-size: var(--text-xs);
  font-family: inherit;
  cursor: pointer;
  line-height: 1;
}

.feed-card-retry-btn:hover {
  opacity: 0.75;
}

.feed-card-retry-btn .icon {
  width: var(--size-sm);
  height: var(--size-sm);
}

/* Pill positioning override for processing/error cards —
   pull the pill out of absolute flow and let the container carry the offset */
.feed-card:has(.feed-card-processing) .feed-badge-pill {
  position: relative;
  top: auto;
  left: auto;
}

.feed-card:has(.feed-card-processing) .feed-card-link {
  padding-top: var(--sp-sm);
  padding-left: var(--sp-sm);
}

/* ─── Keyframe animations ────────────────────────────────────────────── */

@keyframes processing-spin {
  to { transform: rotate(360deg); }
}


@keyframes processing-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* client/styles/emoji_picker.css — scoped to the new chat emoji picker. */

/* Compact pill (hover state on bubbles) */
.emoji-picker-pill {
  position: absolute;
  top: -12px;
  /* Left-anchored so the pill grows RIGHTWARD — the open side for
     received messages (they hug the pane's left). Right-anchoring grew
     it leftward, clipping against the scroll container on bubbles
     narrower than the pill (same inversion the reaction trigger had). */
  left: -4px;
  display: flex;
  align-items: center;
  gap: var(--sp-3xs);
  padding: var(--sp-3xs) var(--sp-xs);
  background: var(--bg);
  border: 1px solid var(--border-subtle, var(--border-primary));
  border-radius: var(--radius-full);
  box-shadow: var(--shadow);
  z-index: 10;
  animation: emoji-picker-pill-open 0.12s ease-out;
}

@keyframes emoji-picker-pill-open {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Sent bubbles hug the pane's right — mirror the anchor so the pill
   grows LEFTWARD into open space. */
.chat-message-row.sent .emoji-picker-pill {
  left: auto;
  right: -4px;
}

.emoji-picker-pill-emoji {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  font-size: 18px;
  padding: 0;
  transition: background 0.1s ease, transform 0.1s ease;
}

@media (hover: hover) {
  .emoji-picker-pill-emoji:hover {
  background: var(--bg-subtle);
  transform: scale(1.15);
}
}

.emoji-picker-overflow {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  color: var(--text-tertiary);
  border-radius: var(--radius-full);
  cursor: pointer;
  padding: 0;
}

@media (hover: hover) {
  .emoji-picker-overflow:hover {
  background: var(--bg-subtle);
  color: var(--fg);
}
}

/* Drill-down panel — fixed positioning applied inline by view */
.emoji-picker {
  position: fixed;
  width: 336px;
  max-height: 420px;
  display: flex;
  flex-direction: column;
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  z-index: 100;
  overflow: hidden;
  animation: emoji-picker-open 0.12s ease-out;
}

/* Category strip at top */
.emoji-picker-category-strip {
  display: flex;
  justify-content: space-between;
  padding: var(--sp-sm) var(--sp-md);
  gap: var(--sp-xs);
}

.emoji-picker-category-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-3xs);
  background: none;
  border: none;
  color: var(--text-tertiary);
  cursor: pointer;
  border-radius: var(--radius-full);
}

.emoji-picker-category-btn-active {
  color: var(--text-primary);
  background: var(--bg-secondary);
}
@media (hover: hover) {
  .emoji-picker-category-btn:hover {
  color: var(--text-secondary);
  background: var(--bg-secondary);
}
}

.emoji-picker-category-btn:focus-visible {
  outline: 2px dashed var(--text-tertiary);
  outline-offset: 2px;
}

/* Search */
.emoji-picker-search-wrap {
  padding: var(--sp-md) var(--sp-sm);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-xs);
}

.emoji-picker-search {
  flex: 1;
  min-width: 0;
  padding: var(--sp-xs) var(--sp-md);
  background: var(--bg-subtle);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  font: inherit;
  font-size: var(--text-lg);
  color: var(--text-primary);
}

.emoji-picker-search::placeholder {
  color: var(--text-primary);
}

.emoji-picker-search:focus-visible {
  outline: 2px solid var(--bg-secondary);
  outline-offset: 0;
}

/* Global skin-tone button */
.emoji-picker-tone-global-btn {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 24px;
  cursor: pointer;
  padding: 0;
  color: inherit;
  transition: background 0.1s ease, border-color 0.1s ease;
}

@media (hover: hover) {
  .emoji-picker-tone-global-btn:hover {
  background: var(--bg-subtle);
}
}

.emoji-picker-tone-global-btn--active {
  background: var(--bg-subtle);
  border-color: var(--border-primary);
}
@media (hover: hover) {
  .emoji-picker-tone-global-btn--active:hover {
  background: var(--bg-subtle);
  border-color: var(--border-primary);
}
}

.emoji-picker-tone-global-btn:focus-visible {
  outline: 2px dashed var(--text-tertiary);
  outline-offset: 2px;
}

/* Global tone selector panel — wraps to its own row inside search-wrap */
.emoji-picker-global-tone-panel {
  flex-basis: 100%;
  display: flex;
  gap: var(--sp-3xs);
  padding-top: var(--sp-3xs);
  animation: emoji-picker-tone-open 0.08s ease-out;
}

.emoji-picker-global-tone-swatch {
  flex: 1;
  aspect-ratio: 1 / 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid transparent;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 20px;
  cursor: pointer;
  padding: 0;
  transition: background 0.1s ease, border-color 0.1s ease;
}

@media (hover: hover) {
  .emoji-picker-global-tone-swatch:hover {
  background: var(--bg-subtle);
}
}

.emoji-picker-global-tone-swatch--active {
  border-color: var(--fg);
}

@media (hover: hover) {
  .emoji-picker-global-tone-swatch--active:hover {
  background: var(--bg-subtle);
}
}

.emoji-picker-global-tone-swatch:focus-visible {
  outline: 2px dashed var(--text-tertiary);
  outline-offset: 2px;
}

/* Scroll region */
.emoji-picker-scroll {
  flex: 1;
  min-height: 0; /* required: without this a flex item won't shrink below its
                    content height, making scrollHeight === clientHeight and
                    disabling programmatic + touch scroll entirely */
  overflow-y: auto;
  scroll-behavior: smooth;
  padding: 0 var(--sp-md) var(--sp-md);
}

.emoji-picker-section {
  padding-top: var(--sp-sm);
}

.emoji-picker-section-header {
  position: sticky;
  top: 0;
  background: var(--bg-primary);
  padding: var(--sp-xs) 0;
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-secondary);
  margin: 0;
}

.emoji-picker-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--sp-3xs);
}

.emoji-picker-cell {
  aspect-ratio: 1 / 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 22px;
  cursor: pointer;
  padding: 0;
}

.emoji-picker-cell:focus-visible {
  background: var(--bg-subtle);
}
@media (hover: hover) {
  .emoji-picker-cell:hover {
  background: var(--bg-subtle);
}
}

.emoji-picker-cell:focus-visible {
  outline: none;
}

/* Empty search state */
.emoji-picker-empty {
  text-align: center;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  padding: var(--sp-xl) var(--sp-md);
}

.emoji-picker-results {
  padding-top: var(--sp-sm);
}

/* Tone popout */
.emoji-picker-tone-popout {
  position: fixed;
  display: flex;
  gap: var(--sp-3xs);
  padding: var(--sp-3xs) var(--sp-xs);
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  box-shadow: var(--shadow);
  z-index: 110;
  animation: emoji-picker-tone-open 0.08s ease-out;
}

.emoji-picker-tone-btn {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  border-radius: var(--radius-sm);
  font-size: 20px;
  cursor: pointer;
  padding: 0;
}

@media (hover: hover) {
  .emoji-picker-tone-btn:hover {
  background: var(--bg-subtle);
}
}

/* Reduced motion */

/* ─── Keyframe animations ────────────────────────────────────────────── */

@keyframes emoji-picker-pill-open {
  from { opacity: 0; transform: translateY(-2px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes emoji-picker-open {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes emoji-picker-tone-open {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ─── GIF tab button ─────────────────────────────────────────────────── */

/* GIF text button sits in the same strip as emoji category buttons */
.emoji-picker-gif-btn {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0 var(--sp-3xs);
  min-width: 36px;
}

/* ─── GIF panel ──────────────────────────────────────────────────────── */

/* 2-column masonry-style grid */
.emoji-picker-gif-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-xs);
  padding: var(--sp-xs);
}

.emoji-picker-gif-cell {
  display: block;
  width: 100%;
  padding: 0;
  border: none;
  background: var(--bg-secondary);
  border-radius: var(--radius-md);
  overflow: hidden;
  cursor: pointer;
  transition: opacity 0.1s;
}

@media (hover: hover) {
  .emoji-picker-gif-cell:hover {
  opacity: 0.85;
}
}

.emoji-picker-gif-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-md);
}

/* While a new search is in flight over existing results, keep the grid
   visible but dimmed — swapping to the spinner collapses the panel. */
.emoji-picker-gif-grid-loading {
  opacity: 0.55;
  transition: opacity 0.15s ease-out;
}

/* Loading spinner (first fetch only — no results to keep on screen).
   Fills the panel's scroll region so entering GIF mode doesn't collapse
   the picker to spinner height. */
.emoji-picker-gif-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 300px;
}

.emoji-picker-gif-spinner {
  width: 24px;
  height: 24px;
  border: 2.5px solid var(--border-primary);
  border-top-color: var(--text-secondary);
  border-radius: 50%;
  animation: emoji-picker-gif-spin 0.7s linear infinite;
}

@keyframes emoji-picker-gif-spin {
  to { transform: rotate(360deg); }
}

/* Empty state — same fill-height as the loading state, so "no results"
   doesn't collapse the panel either. */
.emoji-picker-gif-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 300px;
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

/* Attribution footer — required by Klipy's terms; the .icon mask inherits
   currentColor, so the mark themes with --text-tertiary. */
.emoji-picker-gif-attribution {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-xs) 0;
  border-top: 1px solid var(--border-primary);
  color: var(--text-tertiary);
}

.emoji-picker-gif-attribution .icon {
  width: 76px;
  height: 13px;
}

/* ─── Media queries ──────────────────────────────────────────────────── */

@media (max-width: 834px) {
  .emoji-picker-cell {
    font-size: var(--text-4xl);
  }
}

@media (prefers-reduced-motion: reduce) {
  .emoji-picker-scroll {
    scroll-behavior: auto !important;
  }

  .emoji-picker,
  .emoji-picker-tone-popout,
  .emoji-picker-global-tone-panel,
  .emoji-picker-pill,
  .emoji-picker-pill-emoji,
  .emoji-picker-cell,
  .emoji-picker-category-btn,
  .emoji-picker-tone-global-btn,
  .emoji-picker-global-tone-swatch,
  .emoji-picker-gif-spinner {
    animation: none !important;
    transition: none !important;
  }
}

/* ─── Slide-up sheet (iOS-style bottom drawer) ───────────────────────────────
   Reusable component for mobile bottom-sheet patterns.

   Usage:
     .slide-up-sheet-shell     — fixed full-viewport-height positioning shell.
                                  Wraps the backdrop + panel pair; see below
                                  for why this extra layer exists.
     .slide-up-sheet-backdrop  — full-screen dim/blur behind the sheet; tapping
                                  it should dispatch a dismiss message.
     .slide-up-sheet           — the sheet panel itself. fit-content height up
                                  to ~95vh, draggable further via JS (down to
                                  dismiss, up to expand — see dom.mjs).
     .slide-up-sheet__handle-bar — drag target at the top of the sheet.
     .slide-up-sheet__content    — scrollable content area inside the sheet.
     .slide-up-sheet__footer     — OPTIONAL pinned action row after the
                                   content. A plain flex sibling (the sheet
                                   is a flex column and __content owns the
                                   scrolling), so it stays put while content
                                   scrolls — the standardized home for any
                                   bottom-aligned sheet/modal actions.

   Markup shape at every call site (chat.gleam, detail.gleam, new_reminder.gleam,
   notifications.gleam, reminder_picker.gleam):
     html.div([class="slide-up-sheet-shell"], [
       html.div([class="slide-up-sheet-backdrop"], []),
       html.div([id=sheetId, class="slide-up-sheet ..."], [
         html.div([class="slide-up-sheet__handle-bar"], []),
         html.div([class="slide-up-sheet__content"], [...]),
         html.div([class="slide-up-sheet__footer"], [          // optional
           html.div([class="sheet-footer__actions"], [...actions]),
         ]),
       ]),
     ])

   Why the shell: `position: fixed; bottom: 0` on the panel directly anchors
   to the LAYOUT viewport, which iOS Safari does NOT shrink when its bottom
   toolbar is expanded — only the VISUAL viewport shrinks. That leaves the
   panel's bottom edge sitting underneath the toolbar, so however many
   pixels of its content render behind/covered by the browser chrome even
   when the panel's own height is correctly capped. The shell sidesteps this
   ambiguity entirely: it's `position: fixed; top: 0` (top-anchoring doesn't
   have this bug) with an explicit `height: var(--vvh)` — a JS-measured exact
   pixel value (dom.mjs, visualViewport.height), not a CSS relative unit — so
   its own box always matches the truly visible area. The panel is then a
   flex child bottom-aligned *within* that correctly-sized box, so it can
   never be positioned lower than what's actually on screen.

   The JS `attachSheetDrag(sheetId, onDismiss)` in dom.mjs manages drag
   behaviour: down past ~40% of the panel's height (or a fast flick) plays
   an exit animation and calls onDismiss; up past ~30% of the room left to
   the 95vh cap (or a fast upward flick) snaps the panel open to that cap.

   Adaptive presentation: on desktop viewports (≥ 769px, the complement of
   `isMobile()` in dom.mjs) the same markup renders as a centered modal that
   matches the ui/modal visual system (styles/modal.css — default overlay
   opacity 0.85 / blur 4px, Medium 560px panel width). The shell is
   neutralized (display: contents) so it doesn't affect this layout at all,
   the handle bar is hidden, the entrance animation is a fade/scale instead
   of the slide-up, and attachSheetDrag skips the drag listeners (see the
   desktop block at the bottom of this file and the media-query gate in
   dom.mjs).
──────────────────────────────────────────────────────────────────────────── */

.slide-up-sheet-shell {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--vvh, 100dvh);
  z-index: 401;
  display: flex;
  align-items: flex-end;
  /* The shell's own box has nothing to click on directly — the backdrop
     (which fills it via absolute inset:0) and the panel opt back in below. */
  pointer-events: none;
}

.slide-up-sheet-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  animation: sheet-backdrop-in 0.25s ease forwards;
  pointer-events: auto;
}

.slide-up-sheet {
  /* Flex child of .slide-up-sheet-shell now, not independently fixed — see
     the file-header comment for why. */
  position: relative;
  width: 100%;
  height: fit-content;
  /* Plain `vh` is sized against the browser's LARGEST possible viewport
     (chrome/toolbar hidden), not what's actually visible right now — on a
     real mobile browser with its address/tab bar showing, that overstates
     the available height, so a tall sheet's `fit-content` box can grow
     past the truly visible area and get clipped by the toolbar, cutting
     off content instead of just scrolling internally. `--vvh`
     (visualViewport.height, kept live by dom.mjs) is the shell's own
     "ground truth" fix for this same class of bug — reuse it here so the
     cap always matches the real visible viewport. Falls back to `dvh` if
     --vvh hasn't been set yet (matches shell.css's own fallback).
  */
  max-height: calc(var(--vvh, 100dvh) * 0.95);
  min-height: calc(var(--vvh, 100dvh) * 0.30);
  background: var(--bg-primary);
  border-radius: 20px 20px 0 0;
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  /* 16px horizontal padding per spec — uses --sp-md */
  padding: 0 var(--sp-md);
  overflow: hidden;
  color: var(--text-primary);
  animation: sheet-slide-in 0.3s cubic-bezier(0.32, 0.72, 0, 1) forwards;
  will-change: height;
}

/* The handle-bar div is the full 48px touch target; the visible pill is
   rendered via ::after so we can keep the hit area large without affecting
   the pill's visual size. overflow: hidden on the sheet clips anything
   outside, so we use a tall div + flex centering instead of negative margins. */
.slide-up-sheet__handle-bar {
  position: relative;
  width: 100%;
  height: 28px;
  flex-shrink: 0;
  cursor: grab;
  touch-action: none;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 8px;
}

.slide-up-sheet__handle-bar::after {
  content: "";
  display: block;
  width: 36px;
  height: 4px;
  background: var(--border);
  border-radius: var(--radius-full);
  pointer-events: none;
}

.slide-up-sheet__handle-bar:active {
  cursor: grabbing;
}

.slide-up-sheet__content {
  flex: 1;
  overflow-y: auto;
  /* Prevent content scroll from bubbling during drag — `none` (not
     `contain`) so the sheet doesn't rubber-band locally either, matching
     the site-wide no-bounce rule in base.css (which this element-level
     declaration would otherwise override). */
  overscroll-behavior: none;
}

/* Pinned action row — non-scrolling flex sibling below __content. Hairline
   separates it once the content scrolls; safe-area inset keeps actions
   above the iOS home indicator. Works unchanged in the desktop modal
   presentation (same flex skeleton). */
.slide-up-sheet__footer {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  /* Scroll-shadow home: while __content is actively scrolling (JS toggles
     --scrolling via attachSheetFooterShadow, trailing-debounced), an
     upward shadow separates the footer from the content sliding beneath
     it — then eases back out at rest. Replaces the old top hairline.
     The footer paints after __content in DOM order, so the shadow draws
     over the scrolled content. */
  box-shadow: 0 -11px 12px -19px rgba(0, 0, 0, 0.0);
  transition: box-shadow .5s ease all;
}

.slide-up-sheet__footer--scrolling {
  box-shadow: 0 -12px 24px -12px rgba(0, 0, 0, 0.22);
}

/* Actions nest here; it owns the inset the footer used to carry (the
   shadow needs the footer's box to span edge-to-edge). Actions sit
   HORIZONTALLY, sharing the row equally with a 12px gap — two-action
   sheets render side by side, a single action fills the width. */
.sheet-footer__actions {
  display: flex;
  align-items: stretch;
  gap: var(--sp-sm);
  padding: var(--sp-md) var(--sp-lg)
    calc(var(--sp-md) + env(safe-area-inset-bottom));
}
.sheet-footer__actions > * {
  flex: 1 1 0;
  min-width: 0;
}

/* ─── Keyframe animations ────────────────────────────────────────────────── */

@keyframes sheet-slide-in {
  from { transform: translateY(100%); }
  to   { transform: translateY(0);    }
}

@keyframes sheet-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Desktop entrance: subtle fade + scale (the slide-in stays mobile-only). */
@keyframes sheet-modal-in {
  from { opacity: 0; transform: scale(0.96); }
  to   { opacity: 1; transform: scale(1);    }
}

/* ─── Desktop (≥ 769px): centered modal presentation ─────────────────────────
   Mirrors the ui/modal system (.mo-backdrop-dark / .mo-panel / .mo-medium in
   modal.css): dark 0.85 overlay with 4px blur, centered panel, radius-lg,
   deep shadow, 560px default width. The drag handle is hidden and dom.mjs
   skips the drag listeners at this width, so dismissal is backdrop-click /
   ESC / explicit close buttons.

   NOTE: .notif-action-sheet stays display:none on desktop (two-class rule in
   notifications.css beats these single-class rules on specificity) — the
   notifications row menu deliberately uses a popover on desktop.
──────────────────────────────────────────────────────────────────────────── */

@media (min-width: 835px) {
  .slide-up-sheet-shell {
    /* Neutralize the mobile positioning shell entirely — its children render
       as if they were direct siblings of whatever contains it, so the
       centered-modal recipe below applies exactly as it did before the
       shell existed. This rewrite is purely a mobile-Safari-toolbar fix. */
    display: contents;
  }

  .slide-up-sheet-backdrop {
    position: fixed;
    inset: 0;
    /* Match modal.css .mo-backdrop-dark with the default_overlay() values
       (--mo: 0.85, --mb: 4px). */
    background: rgba(0, 0, 0, 0.85);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    /* display:contents on the shell drops its z-index:401 tier, leaving
       these fixed boxes at z-index:auto — so any later-DOM positioned
       element with an explicit z (composer chrome, jump-to-latest,
       scroll-to-bottom) painted above the backdrop and stayed clickable.
       Re-assert the mobile shell's tier explicitly. */
    z-index: 400;
  }

  /* The two-class variant is included so this beats later single-class
     variant rules (e.g. .nr-action-sheet's env() padding-bottom in
     new_reminder.css, imported after this file). */
  .slide-up-sheet,
  .slide-up-sheet.nr-action-sheet {
    position: fixed;
    /* Above its own backdrop (see z-index note there). */
    z-index: 401;
    /* inset:0 + margin:auto centers the fit-content panel on both axes. */
    inset: 0;
    margin: auto;
    width: min(92vw, 560px);
    height: fit-content;
    min-height: unset;
    max-height: 80vh; /* internal scroll stays on .slide-up-sheet__content */
    border-radius: var(--radius-lg);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.4);
    /* As .mo-panel: the elevated surface, or in dark mode a black panel
       disappears into the black backdrop (prod 2026-09-29: the ⋯ menu's
       rows floated with no panel). */
    background: var(--bg-elevated-primary, var(--bg-primary));
    padding: var(--sp-md);
    animation: sheet-modal-in 0.22s ease forwards;
    will-change: auto;
  }

  /* Menu-style sheets (action rows / pickers) read better narrower than
     the 560px Medium default. */
  .slide-up-sheet.nr-action-sheet,
  .slide-up-sheet.vis-sheet,
  .slide-up-sheet.nr-reminder-sheet {
    width: min(92vw, 420px);
  }

  /* Drag affordance is mobile-only. */
  .slide-up-sheet__handle-bar {
    display: none;
  }
}

/* ============================================================
   Modal / overlay system — ui/modal.gleam
   ============================================================
   CSS custom properties for runtime tuning (set via inline style):
     --mo   overlay opacity  (default 0.85)
     --mb   backdrop blur    (default 4px)

   Z-index ladder:
     detail confirm overlays  ~200
     this modal system        900
     chat image lightbox     1000
   ============================================================ */

/* ── root container ──────────────────────────────────────── */

.mo-root {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  /* CSS vars with fallbacks */
  --mo: 0.85;
  --mb: 4px;
}

/* ── backdrop ─────────────────────────────────────────────── */

.mo-backdrop {
  position: absolute;
  inset: 0;
  cursor: pointer;
}

.mo-backdrop-dark {
  background: rgba(0, 0, 0, var(--mo));
  backdrop-filter: blur(var(--mb));
  -webkit-backdrop-filter: blur(var(--mb));
}

/* ── panel ────────────────────────────────────────────────── */

.mo-panel {
  position: relative; /* sits above .mo-backdrop in stacking context */
  /* Elevated surface, not --bg-primary: in dark mode that's pure black,
     and the panel vanished into the 0.85-black backdrop (prod 2026-09-29). */
  background: var(--bg-elevated-primary, var(--bg-primary));
  border-radius: var(--radius-lg);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.4);
  overflow: hidden;
  max-height: 92vh;
  display: flex;
  flex-direction: column;
}

.mo-fullscreen {
  width: 100vw;
  height: 100vh;
  max-height: 100vh;
  border-radius: 0;
}

.mo-large {
  width: min(90vw, 1200px);
}

.mo-medium {
  width: min(92vw, 560px);
}

.mo-small {
  width: min(92vw, 380px);
}

/* ── close button ─────────────────────────────────────────── */

.mo-close-btn {
  position: absolute;
  top: var(--sp-sm);
  right: var(--sp-sm);
  z-index: 1;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: var(--radius-full);
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 120ms ease;
}

.mo-close-btn:hover {
  background: rgba(0, 0, 0, 0.55);
}

/* ── image lightbox ───────────────────────────────────────── */

.mo-lightbox {
  background: #000;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  color: rgba(255, 255, 255, 0.5);
  animation: mo-lb-in 0.3s ease-in-out;
}

@keyframes mo-lb-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes mo-lb-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

.mo-lb-header {
  display: flex;
  align-items: center;
  padding: 12px 8px 8px;
  flex-shrink: 0;
}

.mo-lb-header-spacer { flex: 1; }

.mo-lb-media {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: 0 16px;
}

.mo-lightbox-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
  border-radius: var(--radius-sm);
}

.mo-lightbox-video {
  background: #000;
}

.mo-lb-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 12px 8px 32px;
  flex-shrink: 0;
}

.mo-lb-btn {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-full);
  padding: 0;
  color: inherit;
  flex-shrink: 0;
  transition: background 120ms ease;
}
.mo-lb-btn:hover { background: rgba(255, 255, 255, 0.08); }

/* ── mobile adjustments ───────────────────────────────────── */

@media (max-width: 640px) {
  .mo-panel {
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    max-height: 96vh;
  }

  .mo-medium,
  .mo-large {
    width: 100vw;
    align-self: flex-end;
  }
}

/* Global ⌘K overlay — see app/palette. */
/* Command palette (⌘K) — global overlay, see app/palette.gleam.
   Rendered at the session root so it sits above every route; the toast
   (post-create confirmation) renders independently of the overlay. */

.palette-root {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  /* Panel sits in the upper third — reachable without eye travel, and
     stable while the result list below it grows and shrinks. */
  padding: 14vh var(--sp-md) var(--sp-md);
  box-sizing: border-box;
}

.palette-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.palette-panel {
  position: relative;
  width: min(560px, 100%);
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-lg, 12px);
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.28);
  overflow: hidden;
  animation: palette-in 0.12s ease-out;
}

@keyframes palette-in {
  from {
    opacity: 0;
    transform: translateY(-6px) scale(0.99);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .palette-panel {
    animation: none;
  }
}

.palette-input-row {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-sm) var(--sp-md);
  border-bottom: 1px solid var(--border-tertiary);
  flex-shrink: 0;
}

.palette-input-icon {
  display: inline-flex;
  align-items: center;
  color: var(--text-tertiary);
  flex-shrink: 0;
}

.palette-input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-size: var(--text-lg);
}

.palette-input::placeholder {
  color: var(--text-primary);
}

.palette-kbd {
  flex-shrink: 0;
  padding: 2px 6px;
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-sm, 6px);
  color: var(--text-tertiary);
  font-size: var(--text-xs, 11px);
  line-height: 1.4;
}

.palette-list {
  overflow-y: auto;
  padding: var(--sp-xs);
  scrollbar-width: thin;
}

.palette-section-title {
  padding: var(--sp-sm) var(--sp-sm) var(--sp-3xs);
  font-size: var(--text-xs, 11px);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}

.palette-entry {
  /* Button reset — match .sidebar-row's approach */
  font: inherit;
  border: none;
  background: none;
  cursor: pointer;
  box-sizing: border-box;
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  text-align: left;
  min-height: 40px;
}

/* Keyboard selection and hover share one look; hover also moves the
   selection (see view_entry's on_mouse_enter), so they never disagree. */
.palette-entry--selected {
  background: var(--fill-elevated-secondary);
}

.palette-entry-icon {
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--text-secondary);
}

.palette-entry-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--text-base);
}

.palette-entry-hint {
  flex-shrink: 0;
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

/* ── Post-create toast ──────────────────────────────────────────────────
   Same placement language as .chat-toast, but interactive: it carries the
   "View" link to the created reminder, so pointer-events stay on. */
.palette-toast {
  position: fixed;
  bottom: var(--sp-xl, 24px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-xs, 8px) var(--sp-md, 16px);
  background: var(--bg-elevated, var(--bg-primary));
  color: var(--text-primary);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full, 999px);
  font-size: var(--text-sm, 0.875rem);
  white-space: nowrap;
  z-index: 300;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  animation: palette-toast-in 0.18s ease-out;
}

@keyframes palette-toast-in {
  from {
    opacity: 0;
    transform: translate(-50%, 6px);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}

.palette-toast-link {
  color: var(--accent-blue);
  font-weight: 600;
  text-decoration: none;
}

@media (hover: hover) {
  .palette-toast-link:hover {
    text-decoration: underline;
  }
}

/* Mobile: pin the panel to the top with breathing room; 14vh of dead
   space above the keyboard is too much. */
@media (max-width: 834px) {
  .palette-root {
    padding: var(--sp-md);
  }

  .palette-panel {
    max-height: 70vh;
  }
}

/* Desktop home (brief hero + inline switcher) — reuses .palette-entry. */
/* Desktop home (app/home) — greeting, today's brief, and the inline quick
   switcher. Entry rows reuse the ⌘K overlay's .palette-entry styles
   (command_palette.css, imported before this file). */

.home {
  height: 100%;
  min-height: 0;
  overflow-y: auto;
  padding: var(--sp-2xl, 40px) var(--sp-xl);
  box-sizing: border-box;
}

.home-body {
  width: min(740px, 100%);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-lg);
}

.home-greeting {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: 700;
  color: var(--text-primary);
}

.home-offline {
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

/* ── Switcher column ───────────────────────────────────────────────────
   The switcher alone. Until 2026-09-24 it shared a 7:5 grid with the
   daily brief (.home-columns / .home-aside, split by an 800px container
   query on .home); the brief left this pane that day. */

.home-main {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
  min-width: 0;
}

/* ── Inline switcher ──────────────────────────────────────────────────── */

.home-switcher {
  display: flex;
  flex-direction: column;
  gap: var(--sp-md);
}

/* The pill. Same shape as the landing composer; the glow behind it is
   the brief's, blooming on focus. */
.home-switcher-pill-wrap {
  position: relative;
}

.home-switcher-glow {
  inset: -3px;
  border-radius: var(--radius-full);
  opacity: 0;
  transition: opacity 200ms ease;
}

.home-switcher-pill-wrap:focus-within .home-switcher-glow {
  opacity: 0.22;
}

.home-switcher-pill {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-xs);
  padding: var(--sp-xs) var(--sp-xs) var(--sp-xs) var(--sp-md);
  border: 1px solid var(--border-primary);
  border-radius: var(--radius-full);
  background: var(--bg-primary);
}

.home-switcher-pill:focus-within {
  border-color: transparent;
}

.home-switcher-pill .palette-input {
  font-size: var(--text-xl);
  padding: var(--sp-xs) 0;
}

/* The teaching line under the pill: same shortcut, same rows, anywhere. */
.home-switcher-hint {
  margin: calc(-1 * var(--sp-xs)) 0 0;
  padding: 0 var(--sp-md);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.home-switcher-hint .palette-kbd {
  margin: 0 2px;
  font-size: var(--text-xs);
  font-family: inherit;
  color: var(--text-secondary);
  background: var(--bg-secondary);
}

/* "Go": runs the selected row, for people who click rather than Enter.
   Mirrors .chat-landing-send. */
.home-switcher-go {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-full);
  background: var(--accent-blue);
  color: #ffffff;
  cursor: pointer;
  padding: 0;
}

@media (hover: hover) {
  .home-switcher-go:hover {
    filter: brightness(1.05);
  }
}

/* The list: the overlay's rows as short cards. Section titles stay for
   scannability. */
.home-switcher-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
}

.home-switcher-list .palette-section-title {
  padding: var(--sp-sm) var(--sp-xs) var(--sp-3xs);
}

.home-switcher-list .palette-entry {
  gap: var(--sp-sm);
  min-height: 48px;
  padding: var(--sp-xs) var(--sp-sm);
  border-radius: 14px;
  background: var(--bg-secondary);
  font-size: var(--text-lg);
  transition: background-color 120ms ease, box-shadow 120ms ease;
}

.home-switcher-list .palette-entry--selected {
  background: var(--fill-elevated-secondary);
  box-shadow: var(--shadow-sm);
}

/* The primary verb wears the brand green here too (rail parity). */
.home-switcher-list .palette-entry-icon--reminder .icon {
  background: var(--fill-gradient-green);
}

/* Row icons sit in a small white disc on the card. */
.home-switcher-list .palette-entry-icon {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-full);
  background: var(--bg-primary);
  color: var(--text-primary);
}

.home-switcher-list .palette-entry-icon .icon {
  width: 16px;
  height: 16px;
}

/* ── Brief hero ───────────────────────────────────────────────────────── */

.brief-hero {
  position: relative;
}

.brief-hero .brief-card-glow {
  border-radius: 24px;
}

/* A quieter halo than the timeline card's: home is a reading surface,
   and the hero is already the biggest thing on it. No breathing. */
.brief-hero .brief-card-glow--unread {
  animation: none;
  opacity: 0.26;
}

.brief-hero .brief-card-glow--read {
  opacity: 0.1;
}

.brief-hero-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-sm);
  width: 100%;
  padding: var(--sp-lg);
  border: none;
  border-radius: 20px;
  background: var(--bg-primary);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 150ms ease;
}

@media (hover: hover) {
  .brief-hero-card:hover {
    background: var(--fill-elevated-secondary);
  }
}

.brief-hero-head {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
}

.brief-hero-glyph {
  font-size: 22px;
  line-height: 1;
}

.brief-hero-title {
  margin: 0;
  flex: 1;
  min-width: 0;
  font-size: var(--text-xl);
  font-weight: 600;
  color: var(--text-primary);
}

.brief-hero-state {
  flex-shrink: 0;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.brief-hero--unread .brief-hero-state {
  background: var(--accent-blue);
  color: #ffffff;
}

.brief-hero-excerpt {
  margin: 0;
  font-size: var(--text-lg);
  line-height: 1.5;
  color: var(--text-primary);
}

.brief-hero-open {
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--accent-blue);
}

/* Read: a single line — reachable all day, no longer the headline. */
.brief-hero--read .brief-hero-card {
  padding: var(--sp-sm) var(--sp-md);
  flex-direction: row;
  align-items: center;
}

.brief-hero--read .brief-hero-head {
  flex: 1;
  min-width: 0;
}

.brief-hero--read .brief-hero-title {
  font-size: var(--text-lg);
}

/* New Reminder creation surface */

/* The per-visit key wrapper (client.view_route): layout-neutral, so
   .nr-root sits in the pane exactly as before. */
.nr-session {
  display: contents;
}

.nr-root {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--bg-body);
  position: relative;
}

/* No top nav on either reminder surface — back lives in the mobile
   accessory bar, and desktop commits via the rail's trailing circle
   (mock parity, matching the detail view). */

/* Full-width hero removed from reminder (container) views — media
   renders in the title-row thumbnail deck instead (iOS parity). */

/* ── Body ──────────────────────────────────────────────────── */

/* No horizontal padding on the container — each child carries its own
   so the hero above can bleed edge-to-edge. */
.nr-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding-top: var(--sp-md);
  padding-bottom: 0;
  gap: var(--sp-xs);
  /* Single page-level scroll container: the reminder card and (on detail)
     the AI chat section below it both flow here — neither owns its own
     independent scrollbox. The extra breathing room between the card and
     the AI chat section comes from .detail-ai-chat's own margin-top, same
     as before this was split into a separate box. */
  overflow-y: auto;
}
@media (max-width: 834px) {
  .nr-body { padding: calc(var(--sp-sm) + var(--sp-xs)) 0 0 0;
  }
}

/* Reminder-editing content (title/due/body/checklist) — its own card.
   height: fit-content sizes it exactly to its content (no max-height
   anywhere in this chain either), so it keeps growing as content (title
   lines, notes, checklist items) is added and never clips or scrolls
   internally. Kept as a dedicated wrapper (rather than styling .nr-body
   directly) so the detail page can render the AI chat section as an
   unboxed sibling below it instead of inside the same box. */
.nr-reminder-card {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--sp-xs);
  height: fit-content;
}

/* Heading block: title row + due pill in one container that owns the
   horizontal/vertical padding and the bottom hairline (replaces the old
   standalone .nr-divider below the pill). */
.reminder-heading-container {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-md); /* 16px title block → due pill */
  padding: 0 var(--sp-xl) var(--sp-md) var(--sp-xl);
  border-bottom: 1px solid var(--border-tertiary);
  flex-shrink: 0;
}

/* Title row: completion circle + textarea + optional thumbnail deck
   (.thumb-stack, styles in detail.css). The deck is pulled OUT OF FLOW
   (absolute, top-right of the heading container) so the row's height
   tracks the title text only — otherwise the 80px deck box forces a
   large gap between a short title and the due pill below. The rotated
   fan overhangs harmlessly. */
.nr-title-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-sm);
}

.reminder-heading-container .thumb-stack {
  position: absolute;
  top: 0;
  right: var(--sp-xl);
  margin-left: 0;
}

/* Reserve the deck's width so the title wraps before running under it. */
.reminder-heading-container:has(.thumb-stack) .nr-title {
  margin-right: 96px; /* 80px deck + 16px breathing room */
}

/* Due/reminder pill row — directly below the title block (iOS
   ReminderPill). Pill visuals come from the shared .due-pill component
   (pill.css); spacing/inset come from the heading container. */
/* One pill per alert, then "Add alert" (multi-alert): wrap onto further
   lines rather than overflow a narrow screen. */
.nr-due-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-xs);
}

/* iOS ReminderPill (detail variant): larger than the timeline-row pill —
   15/500/20 label, 12px/10px padding, 8px icon gap. Set/unset fills come
   from the shared .due-pill / .due-pill--empty rules. */
.nr-due-row .due-pill {
  padding: 10px 12px;
  gap: 8px;
}

.nr-due-row .due-pill > .due-pill-label {
  font-size: 15px;
  font-weight: 500;
  line-height: 20px;
}

/* (Hairline below the pill now lives on .reminder-heading-container's
   bottom border.) */

/* Header action strip (reminder_heading `actions`; desktop detail only).
   Starts under the title text: 32 circle slot + 8 (iOS titleTextInset 40;
   mock 40.1). Wraps rather than compresses (iOS FlowRowLayout); the
   trailing slot keeps ≥8 (iOS Spacer(minLength: Gap.xs)). */
.nr-action-strip {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-xs);
  padding-left: calc(var(--size-xxl) + var(--sp-xs));
}
.nr-action-strip-end {
  display: flex;
  flex-shrink: 0;
  margin-left: auto;
}
/* The set state (iOS ReminderPill capsule) sized to the 36px row. */
.nr-action-strip .due-pill {
  height: 36px;
  padding: 0 var(--sp-sm);
  gap: var(--sp-xs);
}
.nr-action-strip .due-pill > .due-pill-label {
  font-size: var(--text-md);
  font-weight: 500;
  line-height: 20px;
}

/* Round header action (ui/round_button) — iOS "label primary on
   background secondary" disc; 36px (mock) with a 22px glyph. */
.nr-round-btn {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: var(--bg-secondary); /* mock #F2F1F6 */
  color: var(--text-primary);
  cursor: pointer;
  transition: background 150ms ease;
}
@media (hover: hover) {
  .nr-round-btn:hover {
    background: var(--fill-elevated-secondary);
  }
}
/* base.css's bg-secondary outline vanishes on a bg-secondary disc. */
.nr-round-btn:focus-visible {
  outline: 2px solid var(--accent-blue);
  outline-offset: 2px;
}

.nr-completion-circle {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  border: 2px solid var(--text-tertiary);
  border-radius: var(--radius-full);
  margin-top: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  cursor: pointer;
  padding: 0;
  font: inherit;
  transition: border-color 150ms ease, background 150ms ease;
}
.nr-completion-circle:hover {
  border-color: var(--text-secondary);
}
/* Completed state: bare checkmark/thick glyph, label-tertiary — the
   button drops its ring and hugs the 24px svg (mock parity). */
.nr-completion-circle--done {
  border: none;
  background: transparent;
  color: var(--text-tertiary);
  padding: 0;
}
.nr-completion-circle--done:hover {
  border-color: transparent;
  color: var(--text-secondary);
}

/* Reminder list/picker section */
.nr-body .detail-reminders-list,
.nr-body .detail-action-remind-wrapper {
  padding: 0 var(--sp-xl);
}

.nr-title {
  flex: 1;
  min-width: 0;
  font-size: var(--text-3xl);
  font-weight: 600;
  color: var(--text-primary);
  background: transparent;
  border: none;
  outline: none;
  resize: none;
  font-family: inherit;
  line-height: 1.3;
  padding: 0;
  /* Auto-grow with content, capped at ~3 lines (matches
     .detail-title-editable's treatment and the iOS large title). */
  field-sizing: content;
  min-height: 1.3em;
  max-height: calc(3 * 1.3em);
  overflow: hidden;
}

.nr-title::placeholder { color: var(--text-secondary); }

/* Completed state — iOS EditableTitle parity: strikethrough +
   textPrimary → textSecondary. Title-only; due pill, body, and thumbnail
   deck are unaffected. */
.nr-title--done {
  text-decoration: line-through;
  color: var(--text-secondary);
}

/* Milkdown body editor mount. Typography and ProseMirror styling come
   from the shared .detail-body-editor rules (detail.css) — the mount div
   carries both classes.
   Flex sizing: `flex: 1 0 auto` (grow, NEVER shrink, content basis). The
   card is meant to look spacious by default (`.nr-reminder-card`'s
   min-height: 50vh) rather than hug tightly, so any leftover slack should
   read as more room to type — not dead unclickable padding below the
   editor — which is why the editor (not some inert spacer) is the child
   that claims it. Originally this was `flex: 1` (basis 0, shrink 1),
   which let the editor get squeezed toward its min-height once the AI
   chat filled the column; grow:1 + shrink:0 avoids that. */
.nr-body-editor {
  flex: 1 0 auto;
  min-height: 120px;
  /* Full-bleed editable surface: the inset moves INTO the ProseMirror
     node below so the entire remaining area is clickable/editable —
     with padding out here, clicks in the gutter landed on the mount
     div instead of the editor. */
  padding: 0;
  display: flex;
  flex-direction: column;
  cursor: text;
  outline: none;
}

/* Stretch the Milkdown chain to fill the mount (detail.css sets
   display:block — the flex column here wins for both surfaces since the
   mount carries .nr-body-editor on NR and detail alike). */
.nr-body-editor .milkdown {
  display: flex;
  flex-direction: column;
  flex: 1;
  width: 100%;
}

.nr-body-editor .ProseMirror {
  flex: 1;
  width: 100%;
  box-sizing: border-box;
  /* The visual inset the mount used to carry. */
  padding: var(--sp-lg) var(--sp-xl);
}

/* Override the detail editor's placeholder copy for this page. */
.nr-body-editor .ProseMirror p.is-editor-empty:first-child::before,
.nr-body-editor .ProseMirror:empty::before {
  content: "toDo";
}

/* ── Toolbar ───────────────────────────────────────────────── */

/* iOS CaptureBottomBar cluster layout:
     [back circle] [media/formatting capsule] [⋯ circle] [confirm circle]
   Grey circles + capsule on the surface fill; the confirm circle carries
   the green Done gradient. The detail surface reuses .nr-toolbar /
   .nr-toolbar-btn / .nr-toolbar-spacer with a flat button row (no capsule
   or circles), so those base rules must stay detail-compatible.

   Toolbar background continues its upper-most sibling's surface:
   New Reminder (body) → bg-primary; detail (AI-chat section above it)
   → bg-secondary via the .nr-root--detail modifier. */
.nr-toolbar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-sm);
  padding: var(--sp-sm) var(--sp-md) var(--sp-xl) var(--sp-md);
  padding-bottom: var(--sp-xl);
  flex-shrink: 0;
}

.nr-root--detail .nr-toolbar {
  background: var(--bg-secondary);
}

.nr-toolbar-spacer { flex: 1; }

/* Flat icon button — used bare on the detail toolbar and inside the
   New Reminder capsule (transparent fill either way). */
.nr-toolbar-btn {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  cursor: pointer;
  border-radius: var(--radius-full);
  color: var(--text-primary);
  padding: 0;
  font: inherit;
  flex-shrink: 0;
  transition: background 120ms ease;
}
.nr-toolbar-btn:hover:not(:disabled) { background: var(--bg-secondary); }
/* Formatting commands the editor FFI can't run yet render disabled. */
.nr-toolbar-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

/* Capsule mode-toggle chevron — narrower hit area, like iOS's 36pt
   capsuleChevronButton next to 44pt icon buttons. */
.nr-toolbar-btn--chevron {
  width: 32px;
}

/* Center capsule — the media-attach strip that flips to formatting. */
.nr-toolbar-capsule {
  min-width: fit-content;
  max-width: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 44px;
  padding: 0 var(--sp-xs);
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  overflow: hidden;
}

/* Circular grey buttons — leading back and trailing ⋯ (iOS 48pt → 44px). */
.nr-toolbar-circle {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  color: var(--text-primary);
  cursor: pointer;
  padding: 0;
  font: inherit;
  flex-shrink: 0;
  transition: filter 120ms ease, background 120ms ease;
}
.nr-toolbar-circle:hover:not(:disabled) { filter: brightness(0.96); }

/* The trailing circle renders as an <a> while it's the New Reminder CTA
   (keyboard down) — keep link chrome off the icon. */
a.nr-toolbar-circle,
a.nr-toolbar-circle:hover {
  text-decoration: none;
}

/* Trailing confirm circle — grey disabled + while there's nothing to
   save (iOS editing-empty quaternary fill), green gradient ✓ once
   content exists (same gradient family as the detail header's Done). */
.nr-toolbar-confirm:disabled {
  color: var(--text-tertiary);
  cursor: not-allowed;
}
.nr-toolbar-confirm--active {
  background: var(--fill-gradient);
  color: #ffffff;
}
/* Chained with .nr-toolbar-circle so this beats the grey circles'
   darkening hover rule (same element carries both classes). */
.nr-toolbar-circle.nr-toolbar-confirm--active:hover {
  filter: brightness(1.05);
}

/* ── Accessory bar — responsive split ───────────────────────────────── */
/* ui/accessory_bar renders BOTH the mobile cluster bar (.nr-toolbar) and
   the desktop floating rail (.acc-rail); width media queries gate which
   one shows (same visibility-pin pattern as .page-header).

   Both reminder surfaces (New Reminder + owner detail) render the
   component, so the desktop restructure below applies to every .nr-root. */

/* Mobile bar buttons — mock parity: white chrome with a hairline border
   (was solid bg-secondary fill). */
.nr-toolbar-circle,
.nr-toolbar-capsule {
  background: var(--bg-primary);
  border: 1px solid var(--border-tertiary);
}
/* The active confirm keeps its gradient fill (class order must not
   matter, so re-assert over the border rule above). */
.nr-toolbar-circle.nr-toolbar-confirm--active {
  background: var(--fill-gradient);
  border-color: transparent;
}

.acc-rail {
  display: none;
}

@media (min-width: 835px) {
  .nr-root .nr-toolbar {
    display: none;
  }

  /* Mobile-only feature (see ai_chat.view_composer_bar) — desktop keeps
     the static .acc-rail, so the whole dock (row + staged strip) hides. */
  .ai-composer-dock {
    display: none;
  }

  /* Horizontal action bar hovering over the editor column, aligned to
     its right edge: the shared flipping capsule + the standalone ⋯
     circle (mock parity). The base inset aligns with the creation
     surface's centered card (gutter = max(112px, half the space beyond
     960px), plus an inner inset); the detail surface re-anchors 24 from
     its main pane's edge — left of the open AI panel, above the Messages
     pill (see the :has() overrides in detail.css). */
  /* Both surfaces: the card stops var(--sp-xl) above the pane edge
     (.nr-body's bottom margin) — lift the bar so it floats fully inside
     the card instead of straddling its bottom edge. */
  .acc-rail {
    position: absolute;
    right: calc(max(112px, (100% - 960px) / 2) + var(--sp-lg));
    bottom: calc(var(--sp-xl) + var(--sp-lg));
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--sp-lg);
    z-index: 5;
  }

  /* Desktop scale for the shared capsule/circle chrome: a roomier 52px
     bar with generous icon spacing and a floating shadow. */
  .acc-rail .nr-toolbar-capsule {
    max-width: none;
    height: 52px;
    gap: var(--sp-md);
    padding: 0 var(--sp-md);
    box-shadow: var(--shadow);
  }

  .acc-rail .nr-toolbar-btn {
    width: 40px;
    height: 40px;
  }

  .acc-rail .nr-toolbar-circle {
    width: 52px;
    height: 52px;
    box-shadow: var(--shadow);
  }
}

/* Hidden multi-file input behind the accessory bar's folder action. */
.nr-attach-input {
  display: none;
}

/* Primary action in the capture surfaces (Save recording / Use photo) —
   token-based replacement for the old dark-theme .capture-sheet-save-btn. */
.nr-capture-save-btn {
  padding: var(--sp-sm) var(--sp-xl);
  background: var(--fill-gradient);
  color: #ffffff;
  font: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: filter 120ms ease;
}
.nr-capture-save-btn:hover {
  filter: brightness(1.05);
}

/* (Camera modal visuals come from detail.css's .camera-modal-body /
   .camera-viewfinder and capture.css's .capture-webcam-preview rules.) */

/* (Local media previews render through the shared .media-viewer chrome —
   styles in detail.css — and the shared image lightbox in modal.css.) */

/* ── Voice-memo capture overlay (iOS VoicePickerView parity) ────────── */
/* Fullscreen, PERMANENTLY DARK layer floating over the dimmed/blurred
   page — colors are pinned literals (iOS forces .dark on this modal), so
   the app theme must not leak in. Sits above the sheet layer (z 400/401). */
.voice-overlay {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  flex-direction: column;
  background: rgba(20, 20, 22, 0.72);
  -webkit-backdrop-filter: blur(24px);
  backdrop-filter: blur(24px);
}

/* Waveform zone — dotted midline (AudioWaveformLine idle) + timer. */
.voice-overlay__wave {
  flex: 3 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-md);
  padding: 0 var(--sp-md);
}
.voice-overlay__dotline {
  width: 100%;
  border-top: 2px dotted rgba(235, 235, 245, 0.35);
}
.voice-overlay__timer {
  font-variant-numeric: tabular-nums;
  font-size: var(--text-base);
  color: rgba(235, 235, 245, 0.6);
}

.voice-overlay__error {
  margin: 0;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--accent-red);
}

/* Record toggle (VoiceRecordButton): label over a 64px circle. */
.voice-overlay__record {
  flex: 2 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-md);
}
.voice-overlay__record-label {
  font-size: var(--text-base);
  color: rgba(235, 235, 245, 0.6);
}
/* Idle / rerecord: ghost outlined mic, white glyph. */
.voice-record-btn {
  width: 64px;
  height: 64px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: transparent;
  border: 1px solid rgba(235, 235, 245, 0.3);
  color: #ffffff;
  cursor: pointer;
  padding: 0;
  font: inherit;
}
/* Recording: red fill + detached outer ring (outline-offset = the 4px
   gap iOS draws with the padded overlay circle). */
.voice-record-btn--recording {
  background: var(--accent-red);
  border-color: transparent;
  outline: 1.5px solid var(--accent-red);
  outline-offset: 4px;
}

/* Bottom bar — Cancel / Done wide capsules (56px, fill the row). */
.voice-overlay__actions {
  display: flex;
  gap: var(--sp-md);
  padding: 0 var(--sp-md) calc(var(--sp-md) + env(safe-area-inset-bottom));
}
.voice-capsule {
  flex: 1;
  height: 56px;
  border: none;
  border-radius: var(--radius-full);
  font: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  cursor: pointer;
  background: rgba(118, 118, 128, 0.24);
  color: #ffffff;
}
/* Done — dark-pinned Inverse palette: dim while disabled, white/black
   once a take exists. */
.voice-capsule--done-disabled {
  background: rgba(118, 118, 128, 0.18);
  color: rgba(235, 235, 245, 0.3);
  cursor: not-allowed;
}
.voice-capsule--done {
  background: #ffffff;
  color: #000000;
}
.voice-capsule--done:hover {
  filter: brightness(0.95);
}

/* ── Desktop card shell (New Reminder) ──────────────────────────────── */
/* Mock: grey canvas, centered white content card, rail in the right
   gutter — no top chrome, matching the detail surface. The canvas is the
   shell's --canvas-bg (2026-09-25), so it runs on unbroken from the list
   pane beside it; bg-secondary matched only while the shell painted
   bg-secondary (#1C1C1E vs the canvas's #0C0C0C in dark). */
@media (min-width: 835px) {
  .nr-root {
    background: var(--canvas-bg);
  }

  /* .nr-body itself stays a plain (transparent, unboxed) scroll column,
     centered with gutters wide enough for the rail (112px each side at
     the narrow end) — both the reminder card and, on detail, the AI chat
     section below it line up under this same width. */
  .nr-root .nr-body {
    width: min(960px, calc(100% - 224px));
    margin: 0 auto var(--sp-xl);
  }

  /* Creation surface only (detail brings its own body padding): the top
     breathing room the old floating-header row used to provide, and —
     since there's no AI panel to share the row with — don't let the
     column flex-grow past its reading width (detail.css's
     `.nr-detail-columns > .nr-body { flex: 1 }` would otherwise stretch
     the card full-bleed; the auto margins then re-center it). */
  .nr-root:not(.nr-root--detail) .nr-body {
    padding-top: var(--sp-lg);
    flex: 0 1 auto;
  }

  /* The reminder card becomes the white, rounded box (mock parity) —
     moved off .nr-body so the AI chat section can render below it,
     unboxed, without inheriting the card background. */
  .nr-root .nr-reminder-card {
    background: var(--bg-primary);
    border-radius: var(--radius-xl);
    padding: var(--sp-xl) 0 var(--sp-lg);
  }
}

@media (max-width: 834px) {
  .nr-toolbar-capsule {
    height: var(--size-5xl);
  }
  .nr-toolbar-btn, .nr-toolbar-circle {
    height: var(--size-5xl);
    width: var(--size-5xl);
  }
}


@media (max-width: 488px) {
    .nr-toolbar-circle {
        height: var(--size-4xl);
        width: var(--size-4xl);
    }
    .nr-toolbar-capsule {
        height: var(--size-4xl);
    }

    .ai-composer-pill {
        min-height: var(--size-4xl);
    }

    .nr-toolbar-btn {
      height: var(--size-xxl);
      width: var(--size-xxl);
    }
}

/* ── Docked Please AI composer bar (mobile detail toolbar default) ───
   Replaces the .nr-toolbar-capsule + trailing circle with a docked chat
   composer when neither the title nor the body editor has focus (iOS
   EditorFocus / CaptureChatComposerBar parity — see ai_chat.gleam's
   `view_composer_bar`). Desktop is unaffected (hidden above via
   .ai-composer-dock).

   The row itself reuses .nr-toolbar for its outer chrome (padding,
   background, the bg-secondary swap on .nr-root--detail) and
   .nr-toolbar-circle / .nr-toolbar-confirm for the back/⋯/+ circles — no
   new rules needed for those. Everything below is new, dedicated markup
   so chat.css's chat-composer-* rules (the Messages composer) are never
   touched. The staged-attachment strip is the one exception: it reuses
   chat.css's pending-image chip classes for the chip itself (read-only —
   this file adds no chat.css overrides), only the scroll row wrapper
   below is new. */

.ai-composer-dock {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  position: relative;
  z-index: 1;
}

.ai-composer-dock-staged {
  display: flex;
  flex-direction: row;
  gap: var(--sp-sm);
  overflow-x: auto;
  padding: var(--sp-sm) var(--sp-md) 0 var(--sp-md);
  scrollbar-width: none;
}
.ai-composer-dock-staged::-webkit-scrollbar {
  display: none;
}

/* Fills the space the capsule normally takes — no max-width, unlike
   .nr-toolbar-capsule, since iOS's InputPill spans the row. */
.ai-composer-pill {
  flex: 1;
  min-width: 0;
  display: flex;
  /* Centered by default (empty/short single-line draft) — see
     .ai-composer-dock--grown below for the bottom-aligned, multi-line
     state. */
  align-items: center;
  gap: var(--sp-xs);
  /* Floor, not a cap — .ai-composer-pill-input grows the pill taller as
     the draft grows (like Messages/iMessage), rather than clipping it to
     a single line and scrolling the text inside a cramped box. */
  min-height: var(--size-5xl);
  padding: 0 var(--sp-xs) 0 8px;
  /* Fixed (not --radius-full): stays a full pill at the collapsed
     min-height, but doesn't stretch into an ellipse as the box grows —
     reads as a squircle instead. */
  border-radius: 32px;
  background: var(--bg-primary);
  border: 1px solid var(--border-tertiary);
}

/* Toggled by dom.mjs (autoResizeTextarea/resetTextarea) on the outer
   .ai-composer-dock once the textarea's own scrollHeight grows past a
   single line — i.e. the pill has actually gotten taller than its
   baseline, not just gained a couple of characters. Bottom-aligns the
   pill's attach/send icons and the dock row's ⋯ button to the composer's
   now-multi-line baseline (text still reads top-down, inherent to the
   textarea) instead of floating centered against the grown height.
   Stays applied until the draft is cleared entirely — see dom.mjs. */
.ai-composer-dock--grown .ai-composer-dock-row,
.ai-composer-dock--grown .ai-composer-pill {
  align-items: flex-end;
}

.ai-composer-dock--grown .ai-composer-pill {
  padding-bottom: 8px;
}

/* Tighter trailing padding once the send button is showing (iOS's
   InputPill: 6px vs the placeholder state's roomier trailing padding). */
.ai-composer-pill--can-send {
  padding-right: 8px;
}

.ai-composer-pill-file-input {
  display: none;
}

.ai-composer-pill-attach {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--fill-elevated-primary);
  color: var(--text-secondary);
  cursor: pointer;
}

.ai-composer-pill-input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  font-family: inherit;
  font-size: var(--text-sm);
  color: var(--text-primary);
  outline: none;
  resize: none;
  /* Grows with the draft (dom_ffi.auto_resize_textarea sets an explicit
     px height on input, matching scrollHeight) up to this cap, then
     scrolls internally — same generous grow-then-scroll convention as
     chat.css's .chat-edit-milkdown, instead of the old 96px cap that
     made multi-line drafts scroll in a cramped box almost immediately. */
  max-height: 40vh;
  overflow-y: auto;
  line-height: 20px;
  padding: 8px 0;
  margin: 0;
}
.ai-composer-pill-input::placeholder {
  color: var(--text-primary);
}
.ai-composer-pill-input:disabled {
  opacity: 0.6;
}

.ai-composer-pill-send {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  background: var(--bg-primary-inverse);
  color: var(--text-primary-inverse);
  transition: background 0.15s ease;
}
.ai-composer-pill-send:disabled {
  background: var(--bg-quarternary);
  color: var(--text-disabled);
  cursor: default;
}

/* ── Owner-container ⋮ action sheet ─────────────────────────────────── */

.nr-action-sheet {
  padding-bottom: env(safe-area-inset-bottom);
}

.nr-action-sheet__close {
  position: absolute;
  top: var(--sp-sm);
  right: var(--sp-md);
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  padding: 0;
}

.nr-action-row {
  display: flex;
  align-items: center;
  gap: var(--sp-md);
  width: 100%;
  padding: var(--sp-lg);
  background: transparent;
  border: none;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--text-primary);
}
/* First row sits under the close button / handle; no top divider. */
.nr-action-row:first-of-type {
  border-top: none;
}
.nr-action-row:hover {
  background: var(--bg-secondary);
}

.nr-action-row__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
  flex-shrink: 0;
}

.nr-action-row__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.nr-action-row__label {
  font-size: var(--text-base);
  font-weight: 500;
  color: var(--text-primary);
}

.nr-action-row__sublabel {
  font-size: var(--text-sm);
  color: var(--text-secondary-vibrant);
}

.nr-action-row__trailing {
  display: inline-flex;
  align-items: center;
  color: var(--text-tertiary);
  flex-shrink: 0;
  margin-left: var(--sp-sm);
}

