@jant/core 0.6.15 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/bin/commands/import-site.js +7 -0
  2. package/dist/app-C75Zc8dD.js +6 -0
  3. package/dist/{app-C-oi_t8W.js → app-C7_4oab9.js} +480 -196
  4. package/dist/client/.vite/manifest.json +8 -8
  5. package/dist/client/_assets/chunks/{url-pLre2DM_.js → url-CU9pEbk5.js} +1 -1
  6. package/dist/client/_assets/{client-DxY5BFe8.js → client-Bq0vre8Y.js} +3 -3
  7. package/dist/client/_assets/client-D3VZyG4L.css +2 -0
  8. package/dist/client/_assets/{client-auth-CakPESv9.js → client-auth-F2rb9WfF.js} +1015 -977
  9. package/dist/{env-OHRKGcMj.js → env-BPYViDJJ.js} +1 -1
  10. package/dist/{export-CsFx6F_M.js → export-Dl5KCiEs.js} +93 -7
  11. package/dist/{github-api-BgSiE71w.js → github-api-BNF35Lkx.js} +1 -1
  12. package/dist/{github-app-BbklkFmU.js → github-app-DdiD-bC4.js} +1 -1
  13. package/dist/{github-sync-ppWXN3jb.js → github-sync-Bqg62IvV.js} +3 -3
  14. package/dist/{github-sync-Cq1pzzOI.js → github-sync-HdK2EgvJ.js} +3 -3
  15. package/dist/index.js +5 -5
  16. package/dist/node.js +6 -6
  17. package/dist/{url-BMYO-Zlt.js → url-CbLAtlZe.js} +517 -12
  18. package/package.json +1 -1
  19. package/src/__tests__/mise-config.test.ts +22 -0
  20. package/src/client/__tests__/compose-bridge.test.ts +252 -2
  21. package/src/client/__tests__/compose-shortcuts.test.ts +20 -0
  22. package/src/client/__tests__/toast.test.ts +65 -1
  23. package/src/client/components/__tests__/compose-format-convert.test.ts +0 -19
  24. package/src/client/components/__tests__/jant-command-palette.test.ts +51 -0
  25. package/src/client/components/__tests__/jant-compose-dialog.test.ts +2015 -318
  26. package/src/client/components/__tests__/jant-compose-editor.test.ts +53 -62
  27. package/src/client/components/__tests__/jant-compose-fullscreen.test.ts +35 -89
  28. package/src/client/components/compose-format-convert.ts +5 -1
  29. package/src/client/components/compose-types.ts +47 -3
  30. package/src/client/components/jant-command-palette.ts +12 -6
  31. package/src/client/components/jant-compose-dialog.ts +1464 -680
  32. package/src/client/components/jant-compose-editor.ts +225 -93
  33. package/src/client/components/jant-compose-fullscreen.ts +3 -5
  34. package/src/client/components/jant-post-menu.ts +91 -21
  35. package/src/client/compose-bridge.ts +143 -122
  36. package/src/client/compose-launch.ts +13 -0
  37. package/src/client/compose-shortcuts.ts +5 -1
  38. package/src/client/post-refresh.ts +135 -0
  39. package/src/client/tiptap/__tests__/link-input-rules.test.ts +121 -0
  40. package/src/client/tiptap/link-input-rules.ts +4 -4
  41. package/src/client/toast.ts +130 -38
  42. package/src/db/__tests__/backfill-thread-activity.test.ts +234 -0
  43. package/src/db/__tests__/thread-activity.test.ts +113 -0
  44. package/src/db/backfills/0005_split_thread_activity_from_quiet_replies.sql +68 -0
  45. package/src/db/migrations/0031_many_ego.sql +3 -0
  46. package/src/db/migrations/meta/0031_snapshot.json +2562 -0
  47. package/src/db/migrations/meta/_journal.json +7 -0
  48. package/src/db/migrations/pg/0029_rare_hairball.sql +3 -0
  49. package/src/db/migrations/pg/meta/0029_snapshot.json +3289 -0
  50. package/src/db/migrations/pg/meta/_journal.json +7 -0
  51. package/src/db/pg/schema.ts +18 -0
  52. package/src/db/schema.ts +20 -0
  53. package/src/db/thread-activity.ts +101 -0
  54. package/src/i18n/locales/public/en.po +89 -21
  55. package/src/i18n/locales/public/en.ts +1 -1
  56. package/src/i18n/locales/public/zh-Hans.po +88 -20
  57. package/src/i18n/locales/public/zh-Hans.ts +1 -1
  58. package/src/i18n/locales/public/zh-Hant.po +88 -20
  59. package/src/i18n/locales/public/zh-Hant.ts +1 -1
  60. package/src/lib/__tests__/feed.test.ts +168 -0
  61. package/src/lib/__tests__/markdown.test.ts +16 -0
  62. package/src/lib/__tests__/slug.test.ts +18 -0
  63. package/src/lib/__tests__/translit.test.ts +87 -0
  64. package/src/lib/__tests__/url.test.ts +53 -0
  65. package/src/lib/feed.ts +113 -11
  66. package/src/lib/hugo-markdown.ts +5 -0
  67. package/src/lib/link-preview.ts +25 -0
  68. package/src/lib/markdown-manager.ts +6 -1
  69. package/src/lib/post-display.ts +42 -9
  70. package/src/lib/timeline.ts +39 -9
  71. package/src/lib/tiptap-render.ts +4 -2
  72. package/src/lib/translit.ts +288 -0
  73. package/src/lib/url.ts +123 -12
  74. package/src/lib/view.ts +28 -0
  75. package/src/routes/api/__tests__/palette.test.ts +44 -0
  76. package/src/routes/api/posts.ts +13 -9
  77. package/src/routes/api/public/__tests__/posts.test.ts +38 -0
  78. package/src/routes/api/public/posts.ts +7 -0
  79. package/src/routes/compose.tsx +6 -2
  80. package/src/routes/feed/__tests__/sitemap.test.ts +68 -1
  81. package/src/routes/pages/__tests__/archive-params.test.ts +292 -0
  82. package/src/routes/pages/__tests__/featured.test.ts +6 -2
  83. package/src/routes/pages/__tests__/preview.test.ts +3 -1
  84. package/src/routes/pages/archive.tsx +65 -17
  85. package/src/routes/pages/page.tsx +5 -2
  86. package/src/services/__tests__/collection.test.ts +32 -1
  87. package/src/services/__tests__/post-timeline.test.ts +142 -11
  88. package/src/services/__tests__/post.test.ts +277 -0
  89. package/src/services/collection.ts +8 -10
  90. package/src/services/export-theme/assets/client-site.css +1 -1
  91. package/src/services/export.ts +3 -5
  92. package/src/services/path.ts +4 -1
  93. package/src/services/post.ts +297 -100
  94. package/src/services/search.ts +9 -0
  95. package/src/styles/components.css +0 -14
  96. package/src/styles/site-media.css +4 -4
  97. package/src/styles/tokens.css +62 -1
  98. package/src/styles/ui.css +1363 -1283
  99. package/src/types/entities.ts +5 -0
  100. package/src/types/props.ts +12 -0
  101. package/src/types/views.ts +6 -0
  102. package/src/ui/compose/ComposeDialog.tsx +17 -17
  103. package/src/ui/feed/LinkPreview.tsx +2 -7
  104. package/src/ui/feed/PostStatusBadges.tsx +42 -3
  105. package/src/ui/pages/ArchivePage.tsx +131 -24
  106. package/src/ui/pages/__tests__/ArchivePage.test.tsx +63 -0
  107. package/src/ui/shared/DraftPreviewBar.tsx +25 -9
  108. package/src/ui/shared/PostFooter.tsx +35 -12
  109. package/src/ui/shared/custom-icons.ts +5 -0
  110. package/src/ui/shared/post-article-attributes.ts +6 -0
  111. package/dist/app-BxCOR3Uc.js +0 -6
  112. package/dist/client/_assets/client-B_eGKN5p.css +0 -2
package/src/styles/ui.css CHANGED
@@ -55,7 +55,7 @@
55
55
  .draft-preview-bar-inner {
56
56
  display: flex;
57
57
  align-items: center;
58
- justify-content: center;
58
+ justify-content: space-between;
59
59
  gap: 0.7rem;
60
60
  min-height: 2.5rem;
61
61
  max-width: var(--layout-body-max-width);
@@ -66,6 +66,13 @@
66
66
  text-align: center;
67
67
  }
68
68
 
69
+ .draft-preview-bar-copy {
70
+ display: inline-flex;
71
+ align-items: center;
72
+ gap: 0.7rem;
73
+ min-width: 0;
74
+ }
75
+
69
76
  .draft-preview-bar-label {
70
77
  display: inline-flex;
71
78
  align-items: center;
@@ -90,10 +97,64 @@
90
97
  color: var(--site-text-secondary);
91
98
  }
92
99
 
100
+ .draft-preview-bar-action {
101
+ display: inline-flex;
102
+ flex: none;
103
+ align-items: center;
104
+ justify-content: center;
105
+ min-height: 1.9rem;
106
+ padding: 0.28rem 0.72rem;
107
+ border: 1px solid
108
+ color-mix(in srgb, var(--site-text-primary) 18%, transparent);
109
+ border-radius: 999px;
110
+ background: color-mix(
111
+ in srgb,
112
+ var(--site-elevated-bg) 78%,
113
+ var(--site-nav-hover-bg)
114
+ );
115
+ color: var(--site-text-primary);
116
+ font-size: var(--type-ui-caption);
117
+ font-weight: var(--fw-medium);
118
+ line-height: 1;
119
+ text-decoration: none;
120
+ cursor: pointer;
121
+ transition:
122
+ border-color 150ms ease,
123
+ background-color 150ms ease,
124
+ color 150ms ease,
125
+ transform 150ms ease;
126
+ }
127
+
128
+ .draft-preview-bar-action:hover {
129
+ border-color: color-mix(
130
+ in srgb,
131
+ var(--site-accent) 48%,
132
+ var(--site-divider)
133
+ );
134
+ background: var(--site-nav-hover-bg);
135
+ color: var(--site-accent);
136
+ }
137
+
138
+ .draft-preview-bar-action:active {
139
+ transform: translateY(1px);
140
+ }
141
+
142
+ .draft-preview-bar-action:focus-visible {
143
+ outline: 2px solid var(--site-accent);
144
+ outline-offset: 2px;
145
+ }
146
+
93
147
  @media (max-width: 480px) {
94
148
  .draft-preview-bar-inner {
149
+ align-items: center;
150
+ gap: 0.65rem;
151
+ text-align: left;
152
+ }
153
+
154
+ .draft-preview-bar-copy {
95
155
  flex-direction: column;
96
- gap: 0.18rem;
156
+ align-items: flex-start;
157
+ gap: 0.12rem;
97
158
  }
98
159
  }
99
160
 
@@ -2878,21 +2939,52 @@
2878
2939
  flex-shrink: 0;
2879
2940
  }
2880
2941
 
2942
+ /* One dot on both rails: a flat fill with a surface-coloured gap punched
2943
+ around it, so it reads as a bead threaded on the rail rather than a bulge in
2944
+ it. Same size, same colours, same punch — reading then rings it (below) and
2945
+ compose stops at the fill. What a post *is* comes from its card; the dot
2946
+ only says where it sits on the chain, and an accent fill says which one the
2947
+ view is about. */
2881
2948
  .thread-group,
2882
2949
  .compose-thread-layout {
2883
- --site-thread-marker-size: 10px;
2884
- --site-thread-marker-border-width: 2px;
2885
- --site-thread-marker-ring-width: 2px;
2886
- --site-thread-marker-hero-size: 14px;
2887
- --site-thread-marker-hero-border-width: 3px;
2950
+ --site-thread-marker-size: 7px;
2951
+ /* Kept at 7:1.5 with the marker. A 7px dot on a 1px hairline reads as a bead
2952
+ dropped onto a thread rather than the same stroke thickening; the timeline
2953
+ components this pattern comes from (Ant, CodyHouse) all run a 2px rail. */
2954
+ --site-thread-rail-line-width: 1.5px;
2955
+ /* The gap's ceiling is set by whichever dot has the rail on one side only —
2956
+ in compose that's the last post, where the rail is trimmed. One line end
2957
+ holds it instead of two, and around 12px it reads as floating loose. */
2958
+ --site-thread-marker-gap: 9px;
2888
2959
  --site-thread-marker-surface: var(--site-page-bg);
2960
+ /* "This is the post the view is about." Tinted toward the rail rather than
2961
+ the raw accent: every other accent-derived token in the system sits
2962
+ between 6% and 24% (see tokens.css), and a 7px shape at full accent was
2963
+ the single highest-contrast thing in an otherwise near-empty column. */
2964
+ --site-thread-marker-current: color-mix(
2965
+ in srgb,
2966
+ var(--site-accent) 90%,
2967
+ var(--site-threadline)
2968
+ );
2969
+ }
2970
+
2971
+ .compose-thread-layout {
2972
+ --site-thread-marker-surface: var(--compose-paper-bg);
2889
2973
  }
2890
2974
 
2891
- /* Thread group: timeline layout — vertical line with dot markers */
2975
+ /* Thread group: timeline layout — vertical line with dot markers.
2976
+ Reading rings its dots; compose doesn't. A compose dot sits beside a card
2977
+ edge, a format switcher and a "1/3", so a joint in the line is all it has to
2978
+ be. Reading's rail runs down an otherwise empty column, where a bare fill
2979
+ reads as a speck of stray threadline — the ring gives it a silhouette
2980
+ without making it any louder, since the tint is the same 16% accent every
2981
+ other halo in the system uses. Held off the fill by a band of surface so
2982
+ the two stay separate marks rather than one fuzzy dot. */
2892
2983
  .thread-group {
2893
2984
  --site-thread-rail-indent: 24px;
2894
2985
  --site-thread-rail-line-left: 5px;
2895
- --site-thread-rail-line-width: 2px;
2986
+ --site-thread-marker-ring-inset: 2px;
2987
+ --site-thread-marker-ring-width: 2px;
2896
2988
  position: relative;
2897
2989
  padding-left: var(--site-thread-rail-indent);
2898
2990
  }
@@ -2926,28 +3018,55 @@
2926
3018
 
2927
3019
  /* Dot marker at each post position — auto-centered on the rail line.
2928
3020
  Line ::before is relative to .thread-group, but dot ::before is relative
2929
- to .thread-item which sits inside the padding (indent), so we subtract it. */
3021
+ to .thread-item which sits inside the padding (indent), so we subtract it.
3022
+ Every length here is derived: a hardcoded half-marker is what once left the
3023
+ compose rail running down the edge of its own dots instead of through them. */
2930
3024
  .thread-item::before {
2931
3025
  content: "";
2932
3026
  position: absolute;
2933
3027
  left: calc(
2934
3028
  var(--site-thread-rail-line-left) + var(--site-thread-rail-line-width) /
2935
- 2 - 6px - var(--site-thread-rail-indent)
3029
+ 2 - var(--site-thread-marker-size) / 2 - var(--site-thread-rail-indent)
2936
3030
  );
2937
3031
  top: 50%;
2938
3032
  transform: translateY(-50%);
2939
- width: 12px;
2940
- height: 12px;
3033
+ width: var(--site-thread-marker-size);
3034
+ height: var(--site-thread-marker-size);
2941
3035
  border-radius: 50%;
2942
3036
  background-color: var(--site-threadline);
3037
+ /* Three concentric layers off one border box, painted first-on-top: a band
3038
+ of surface, the accent ring, then the rest of the punch-out. The punch's
3039
+ radius is unchanged, so the ring sits inside the gap the rail line was
3040
+ already broken for and nothing downstream (`.thread-context-shell`'s
3041
+ inset, the compose switcher's overhang) has to move for it. */
3042
+ box-shadow:
3043
+ 0 0 0 var(--site-thread-marker-ring-inset)
3044
+ var(--site-thread-marker-surface),
3045
+ 0 0 0
3046
+ calc(
3047
+ var(--site-thread-marker-ring-inset) +
3048
+ var(--site-thread-marker-ring-width)
3049
+ )
3050
+ var(--site-thread-dot-ring),
3051
+ 0 0 0 var(--site-thread-marker-gap) var(--site-thread-marker-surface);
2943
3052
  z-index: 1;
2944
3053
  }
2945
3054
 
3055
+ /* "This is the post the view is about": the feed preview's latest reply, a
3056
+ curated preview's highlighted segment, and the post you opened on a detail
3057
+ page. One signal for all three — accent fill on the same dot. Hero and
3058
+ curated also ran 4px larger; the full card beside them already says it, so
3059
+ size was a second signal doing the first one's job. */
3060
+ .thread-item-hero::before,
3061
+ .thread-item-curated::before,
3062
+ .thread-item-current::before {
3063
+ background-color: var(--site-thread-marker-current);
3064
+ }
3065
+
2946
3066
  .thread-group-preview,
2947
3067
  .thread-group-detail {
2948
3068
  --site-thread-rail-indent: 0px;
2949
3069
  --site-thread-rail-line-left: -27px;
2950
- --site-thread-rail-line-width: 1px;
2951
3070
  }
2952
3071
 
2953
3072
  @media (max-width: 760px) {
@@ -2958,6 +3077,12 @@
2958
3077
  }
2959
3078
  }
2960
3079
 
3080
+ /* The rail fades in at the top, where collapsed ancestor context begins: the
3081
+ thread continues above what this view shows, so the line shouldn't start on
3082
+ a hard edge. The bottom needs no matching fade — the line is cut at the last
3083
+ dot instead. Compose doesn't share the gradient: its rail is drawn per row
3084
+ so both ends can be trimmed, and a per-row gradient would fade once per
3085
+ post rather than once per thread. */
2961
3086
  .thread-group-preview::before,
2962
3087
  .thread-group-detail::before {
2963
3088
  background: linear-gradient(
@@ -2968,39 +3093,44 @@
2968
3093
  );
2969
3094
  }
2970
3095
 
3096
+ /* The rail ends at the last dot. Below it the line pointed at posts that
3097
+ aren't there — the thread is over, and compose already trims its own rail
3098
+ the same way. Painted over rather than shortened: the line is one box on the
3099
+ group so it can carry a single gradient, and only the last post knows where
3100
+ its own dot sits (its own 50%). `> ` scopes this to the group's own children
3101
+ — the ancestors inside `.thread-context-shell` have a last child too, and
3102
+ the rail must run straight through it. */
3103
+ .thread-group > .thread-item:last-child::after {
3104
+ content: "";
3105
+ position: absolute;
3106
+ left: calc(
3107
+ var(--site-thread-rail-line-left) - var(--site-thread-rail-indent)
3108
+ );
3109
+ top: 50%;
3110
+ bottom: 0;
3111
+ width: var(--site-thread-rail-line-width);
3112
+ background-color: var(--site-thread-marker-surface);
3113
+ }
3114
+
2971
3115
  .thread-group-preview .thread-item,
2972
3116
  .thread-group-detail .thread-item {
2973
3117
  padding: var(--site-thread-item-spacing) 0;
2974
3118
  }
2975
3119
 
2976
- .thread-group-preview .thread-item::before,
2977
- .thread-group-detail .thread-item::before {
2978
- left: calc(
2979
- var(--site-thread-rail-line-left) + var(--site-thread-rail-line-width) /
2980
- 2 - var(--site-thread-marker-size) / 2 - var(--site-thread-rail-indent)
2981
- );
2982
- width: var(--site-thread-marker-size);
2983
- height: var(--site-thread-marker-size);
2984
- border: var(--site-thread-marker-border-width) solid
2985
- var(--site-thread-marker-surface);
2986
- background-color: var(--site-threadline);
2987
- box-shadow: 0 0 0 var(--site-thread-marker-ring-width)
2988
- var(--site-thread-dot-ring);
2989
- }
2990
-
2991
3120
  /* Collapsible ancestor context wrapper.
2992
3121
  Server-rendered with `data-collapsed`. Crop and fade are always active
2993
3122
  while data-collapsed is set; JS only toggles the Show more/less button
2994
3123
  visibility based on real overflow. Negative left margin + matching
2995
3124
  padding extends the shell's box past the content edge so the dot
2996
- markers (which live at left:-31.5px on desktop) survive
2997
- `overflow: hidden`. `box-sizing: content-box` keeps the inner content
2998
- width identical to before. */
3125
+ markers survive `overflow: hidden`. `box-sizing: content-box` keeps the
3126
+ inner content width identical to before. The inset has to clear the dot's
3127
+ punched gap, not just its fill: on desktop the fill starts at -30px but the
3128
+ gap reaches -39px, so the old 40px cleared it by a single pixel. */
2999
3129
  .thread-context-shell {
3000
3130
  position: relative;
3001
3131
  box-sizing: content-box;
3002
- margin-left: -40px;
3003
- padding-left: 40px;
3132
+ margin-left: -48px;
3133
+ padding-left: 48px;
3004
3134
  transition: max-height 0.32s cubic-bezier(0.4, 0, 0.2, 1);
3005
3135
  }
3006
3136
 
@@ -3191,37 +3321,6 @@
3191
3321
  padding-top: 0.95rem;
3192
3322
  }
3193
3323
 
3194
- .thread-group-preview .thread-item-hero::before,
3195
- .compose-editor-row .compose-thread-dot {
3196
- left: calc(
3197
- var(--site-thread-rail-line-left) + var(--site-thread-rail-line-width) /
3198
- 2 - var(--site-thread-marker-hero-size) / 2 -
3199
- var(--site-thread-rail-indent)
3200
- );
3201
- width: var(--site-thread-marker-hero-size);
3202
- height: var(--site-thread-marker-hero-size);
3203
- border: var(--site-thread-marker-hero-border-width) solid
3204
- var(--site-thread-marker-surface);
3205
- background-color: var(--site-accent);
3206
- box-shadow: 0 0 0 var(--site-thread-marker-ring-width)
3207
- var(--site-thread-dot-ring);
3208
- }
3209
-
3210
- .thread-group-curated .thread-item-curated::before {
3211
- left: calc(
3212
- var(--site-thread-rail-line-left) + var(--site-thread-rail-line-width) /
3213
- 2 - var(--site-thread-marker-hero-size) / 2 -
3214
- var(--site-thread-rail-indent)
3215
- );
3216
- width: var(--site-thread-marker-hero-size);
3217
- height: var(--site-thread-marker-hero-size);
3218
- border: var(--site-thread-marker-hero-border-width) solid
3219
- var(--site-thread-marker-surface);
3220
- background-color: var(--site-accent);
3221
- box-shadow: 0 0 0 var(--site-thread-marker-ring-width)
3222
- var(--site-thread-dot-ring);
3223
- }
3224
-
3225
3324
  /* Compact text for thread replies */
3226
3325
  .feed-compact {
3227
3326
  @apply text-sm;
@@ -3232,6 +3331,28 @@
3232
3331
  scroll-margin-top: 80px;
3233
3332
  }
3234
3333
 
3334
+ /* --- Trailing draft in a thread (author-only) ---------------------------
3335
+ Rendered so the author sees the same thread end the server does. Reads as
3336
+ provisional — present enough to act on, quiet enough not to pass for
3337
+ published writing. */
3338
+
3339
+ .thread-detail-item article[data-post-draft] {
3340
+ opacity: 0.72;
3341
+ transition: opacity 0.18s ease;
3342
+ }
3343
+
3344
+ .thread-detail-item article[data-post-draft]:hover,
3345
+ .thread-detail-item article[data-post-draft]:focus-within {
3346
+ opacity: 1;
3347
+ }
3348
+
3349
+ /* A draft is not part of the published spine, so its marker is hollow: the
3350
+ rail reaches it, but the dot reads as an empty slot rather than another
3351
+ published stop. */
3352
+ .thread-item:has(article[data-post-draft])::before {
3353
+ background-color: var(--site-thread-marker-surface);
3354
+ }
3355
+
3235
3356
  /* When a child post has an ancestor shell above it (toggle button sits
3236
3357
  directly before the current post), leave enough room above the scroll
3237
3358
  landing so the user can see the Show more button + a peek of the
@@ -3242,23 +3363,6 @@
3242
3363
  scroll-margin-top: 120px;
3243
3364
  }
3244
3365
 
3245
- .thread-group-detail .thread-item-current::before {
3246
- background-color: var(--site-accent);
3247
- box-shadow:
3248
- 0 0 0 var(--site-thread-marker-border-width)
3249
- var(--site-thread-marker-surface),
3250
- 0 0 0
3251
- calc(
3252
- var(--site-thread-marker-border-width) +
3253
- var(--site-thread-marker-ring-width)
3254
- )
3255
- color-mix(
3256
- in srgb,
3257
- var(--site-accent) 18%,
3258
- var(--site-thread-marker-surface)
3259
- );
3260
- }
3261
-
3262
3366
  /* Quote block styling */
3263
3367
  .feed-quote {
3264
3368
  margin: 0;
@@ -3477,6 +3581,7 @@
3477
3581
  /* Show the container when at least one status is active */
3478
3582
  article[data-post-pinned] .post-status-badges,
3479
3583
  article[data-post-pinned-in-collection] .post-status-badges,
3584
+ article[data-post-draft] .post-status-badges,
3480
3585
  article[data-post-visibility="private"] .post-status-badges {
3481
3586
  display: flex;
3482
3587
  }
@@ -3507,6 +3612,38 @@
3507
3612
  display: inline-flex;
3508
3613
  }
3509
3614
 
3615
+ article[data-post-draft] .post-status-draft {
3616
+ display: inline-flex;
3617
+ }
3618
+
3619
+ /* The draft badge is a button — the shortest path from "I see an unfinished
3620
+ post" to "I'm finishing it". */
3621
+ .post-status-draft {
3622
+ padding: 0;
3623
+ border: 0;
3624
+ background: transparent;
3625
+ font: inherit;
3626
+ letter-spacing: inherit;
3627
+ cursor: pointer;
3628
+ transition: color 0.18s ease;
3629
+ }
3630
+
3631
+ .post-status-draft:hover,
3632
+ .post-status-draft:focus-visible {
3633
+ color: var(--site-text-primary);
3634
+ }
3635
+
3636
+ .post-status-draft:hover svg,
3637
+ .post-status-draft:focus-visible svg {
3638
+ opacity: 1;
3639
+ }
3640
+
3641
+ .post-status-draft:focus-visible {
3642
+ outline: 2px solid var(--site-accent);
3643
+ outline-offset: 3px;
3644
+ border-radius: 3px;
3645
+ }
3646
+
3510
3647
  .post-status-badge svg {
3511
3648
  width: 11px;
3512
3649
  height: 11px;
@@ -3954,6 +4091,14 @@
3954
4091
  padding: 0 0.22rem;
3955
4092
  }
3956
4093
 
4094
+ /* Every item in a group is conditional, so a group can end up with none —
4095
+ a draft reply hides both Featured and Pin. Match on element children so
4096
+ Lit's binding comments and the template's whitespace don't count as
4097
+ content, otherwise the empty group still draws its divider and padding. */
4098
+ .post-menu-section:not(:has(> *)) {
4099
+ display: none;
4100
+ }
4101
+
3957
4102
  .post-menu-section + .post-menu-section {
3958
4103
  margin-top: 0.08rem;
3959
4104
  padding-top: 0.3rem;
@@ -4704,7 +4849,12 @@
4704
4849
  }
4705
4850
  }
4706
4851
 
4852
+ /* The one composer that is not a floating sheet: it fills the viewport, so
4853
+ its paper is the page's, not the elevated surface's. Nothing sits behind
4854
+ it to be lifted away from. */
4707
4855
  .compose-page {
4856
+ --compose-paper-bg: var(--site-page-bg);
4857
+
4708
4858
  position: fixed;
4709
4859
  inset: 0;
4710
4860
  z-index: 50;
@@ -4773,19 +4923,18 @@
4773
4923
  .compose-page-shell .compose-dialog-inner {
4774
4924
  min-height: 0;
4775
4925
  max-height: none;
4926
+ padding-top: 0;
4776
4927
  border-radius: inherit;
4777
4928
  }
4778
4929
 
4779
- .compose-page-shell .compose-dialog-inner > jant-compose-editor,
4780
- .compose-page-shell .compose-thread-layout,
4781
- .compose-page-shell .compose-editor-row,
4782
- .compose-page-shell .compose-editor-row > jant-compose-editor {
4930
+ /* The page itself is the scroller here, so the composer's region gives up
4931
+ its own and grows to whatever it holds. */
4932
+ .compose-page-shell .compose-scroll {
4783
4933
  flex: 0 1 auto;
4784
- min-height: 0;
4934
+ overflow: visible;
4785
4935
  }
4786
4936
 
4787
4937
  .compose-page-shell .compose-body {
4788
- flex: 0 1 auto;
4789
4938
  min-height: clamp(8rem, 22vh, 12rem);
4790
4939
  }
4791
4940
 
@@ -4812,6 +4961,10 @@
4812
4961
  }
4813
4962
 
4814
4963
  .compose-page-shell .compose-dialog-inner-page {
4964
+ /* The card's own padding is the text column here, so nothing inside it
4965
+ adds a gutter of its own. */
4966
+ --compose-gutter: 0px;
4967
+
4815
4968
  padding: 20px 24px 16px;
4816
4969
  background-color: var(--compose-floating-bg);
4817
4970
  border: 1px solid var(--compose-control-border);
@@ -4822,40 +4975,25 @@
4822
4975
  overflow: visible;
4823
4976
  }
4824
4977
 
4825
- .compose-page-shell .compose-dialog-inner-page .compose-dialog-header {
4826
- display: grid;
4827
- grid-template-columns: 1fr auto;
4828
- align-items: center;
4829
- gap: 12px;
4830
- padding: 0 0 16px;
4831
- }
4832
-
4833
- .compose-page-shell
4834
- .compose-dialog-inner-page
4835
- .compose-dialog-header-center {
4836
- position: static;
4837
- transform: none;
4838
- justify-self: start;
4839
- min-width: 0;
4840
- }
4841
-
4842
- .compose-page-shell .compose-dialog-inner-page .compose-dialog-cancel {
4843
- display: none;
4844
- }
4845
-
4846
4978
  .compose-page-shell .compose-dialog-inner-page .compose-body {
4847
- padding: 22px 0 12px;
4979
+ padding: 22px 0 12px var(--compose-gutter);
4848
4980
  min-height: clamp(16rem, 36vh, 24rem);
4849
4981
  }
4850
4982
 
4983
+ /* Only the right is restated: the left comes from the card's own gutter,
4984
+ which is what every one of these rows already reads. */
4851
4985
  .compose-page-shell .compose-dialog-inner-page .compose-tools-row,
4852
4986
  .compose-page-shell .compose-dialog-inner-page .compose-add-thread-trigger {
4853
- padding-inline: 0;
4987
+ padding-inline-end: 0;
4854
4988
  background: transparent;
4855
4989
  }
4856
4990
 
4991
+ .compose-page-shell .compose-dialog-inner-page .compose-thread-post-header {
4992
+ padding-inline-end: 0;
4993
+ }
4994
+
4857
4995
  .compose-page-shell .compose-dialog-inner-page .compose-attachments-dock {
4858
- padding-inline: 0;
4996
+ padding-inline-end: 0;
4859
4997
  background: transparent;
4860
4998
  }
4861
4999
 
@@ -4864,7 +5002,15 @@
4864
5002
  bottom: 0;
4865
5003
  z-index: 5;
4866
5004
  margin-inline: 0;
4867
- padding: 14px 0 0;
5005
+ padding: 14px 0 0 var(--compose-gutter);
5006
+ background-color: inherit;
5007
+ }
5008
+
5009
+ /* Takes the page's gutter rather than the dialog's, so the toggles line up
5010
+ under Publish instead of sitting 12px inside it. */
5011
+ .compose-page-shell .compose-dialog-inner-page .compose-quick-actions-row {
5012
+ padding-inline: 0;
5013
+ padding-bottom: 2px;
4868
5014
  background-color: inherit;
4869
5015
  }
4870
5016
 
@@ -4889,8 +5035,8 @@
4889
5035
 
4890
5036
  .compose-dialog,
4891
5037
  .compose-page-shell > jant-compose-dialog {
4892
- --compose-title-input-size: var(--type-content-subtitle);
4893
- --compose-title-input-leading: var(--type-display-leading);
5038
+ --compose-title-input-size: calc(var(--type-content-body) * 1.2);
5039
+ --compose-title-input-leading: 1.35;
4894
5040
  --compose-quote-input-size: var(--type-content-subtitle);
4895
5041
  --compose-quote-input-leading: 1.32;
4896
5042
  --compose-inline-input-size: var(--type-ui-input);
@@ -4899,12 +5045,40 @@
4899
5045
  --compose-panel-control-size: var(--type-ui-control);
4900
5046
  --compose-summary-size: var(--type-ui-meta);
4901
5047
  --compose-primary-button-size: var(--type-ui-meta);
5048
+
5049
+ /* How far the format switcher is pulled left of the text column. Declared
5050
+ up here with the other compose tunables so one value covers the dialog,
5051
+ the /new page and every thread row at once. See `.compose-segmented` for
5052
+ what the endpoints look like and why 12px is the ceiling. */
5053
+ --compose-switcher-hang: 8px;
5054
+
5055
+ /* The composer's text column, measured from each row's own container.
5056
+ Every row starts its padding here and the control inside it hangs its
5057
+ box back out by its own optical inset, so what lands on the column is
5058
+ the glyph or the label rather than the box around it — the switcher's
5059
+ hang above is the same idea, named per control because each one is
5060
+ inset by a different amount.
5061
+
5062
+ It drops to 0 wherever something outside the editor already supplies the
5063
+ indent: a thread row's rail, or the /new card's own padding. Only the
5064
+ left is expressed this way; the right paddings stay per-row, because a
5065
+ thread row is indented on one side only. */
5066
+ --compose-gutter: 20px;
5067
+ }
5068
+
5069
+ /* The fullscreen sheet renders none of these rows today, but it does share
5070
+ `.compose-editor-row`. Give it the column too rather than leave the value
5071
+ undefined there: an undefined custom property drops the whole padding
5072
+ declaration that reads it instead of falling back to a sane number. */
5073
+ .compose-fullscreen-dialog {
5074
+ --compose-gutter: 20px;
4902
5075
  }
4903
5076
 
4904
5077
  @media (min-width: 700px) {
4905
5078
  .compose-dialog,
5079
+ .compose-fullscreen-dialog,
4906
5080
  .compose-page-shell > jant-compose-dialog {
4907
- --compose-title-input-size: var(--feed-note-title-size);
5081
+ --compose-gutter: 24px;
4908
5082
  }
4909
5083
  }
4910
5084
 
@@ -4924,7 +5098,7 @@
4924
5098
  box-shadow:
4925
5099
  0 8px 40px rgba(0, 0, 0, 0.08),
4926
5100
  0 1px 3px rgba(0, 0, 0, 0.04);
4927
- border: 1px solid var(--compose-control-border);
5101
+ border: 1px solid var(--site-raised-border);
4928
5102
  overflow: visible;
4929
5103
  }
4930
5104
 
@@ -4932,8 +5106,15 @@
4932
5106
  animation: compose-modal-in 0.32s cubic-bezier(0.22, 1, 0.36, 1) both;
4933
5107
  }
4934
5108
 
5109
+ /* The dim is only half of it. On a dark page there is no luminance left to
5110
+ take away — 0.3 → 0.7 alpha moves the dialog's contrast against its own
5111
+ backdrop by 0.05 — so the blur carries the separation instead. It reads as
5112
+ depth rather than darkness, which is the one cue that still works when the
5113
+ page and the sheet are three percent apart. */
4935
5114
  .compose-dialog::backdrop {
4936
5115
  background-color: rgba(0, 0, 0, 0.3);
5116
+ backdrop-filter: blur(3px);
5117
+ -webkit-backdrop-filter: blur(3px);
4937
5118
  }
4938
5119
 
4939
5120
  .compose-dialog jant-compose-dialog {
@@ -4953,10 +5134,15 @@
4953
5134
  background: transparent;
4954
5135
  }
4955
5136
 
5137
+ /* The composer opens straight onto the first post's header row now that the
5138
+ title bar is gone, so the top gutter it used to provide lives here. It
5139
+ cannot go on the row itself: single-post mode's wrapper is
5140
+ `display: contents` and generates no box to pad. */
4956
5141
  .compose-dialog-inner {
4957
5142
  display: flex;
4958
5143
  flex-direction: column;
4959
5144
  max-height: calc(100dvh - var(--compose-margin-top) - 20px);
5145
+ padding-top: 10px;
4960
5146
  position: relative;
4961
5147
  background-color: var(--compose-paper-bg);
4962
5148
  border-radius: inherit;
@@ -4976,12 +5162,31 @@
4976
5162
  pointer-events: none;
4977
5163
  }
4978
5164
 
4979
- .compose-dialog-inner > jant-compose-editor {
5165
+ /* The composer's one scroll region. Every block inside it — editor body,
5166
+ attachments, per-post toolbar — flows at its natural height and scrolls
5167
+ together, so there is a single answer to "what scrolls?" in every mode.
5168
+ Only the action row below it stays pinned. Nesting a second scroller in
5169
+ here (a fixed-height body, a capped attachment dock) is what forced the
5170
+ old previews to be small; don't reintroduce one. */
5171
+ .compose-scroll {
4980
5172
  flex: 1;
4981
5173
  min-height: 0;
5174
+ overflow-y: auto;
5175
+ overflow-x: hidden;
5176
+ }
5177
+
5178
+ .compose-scroll::-webkit-scrollbar {
5179
+ width: 3px;
5180
+ }
5181
+
5182
+ .compose-scroll::-webkit-scrollbar-thumb {
5183
+ background-color: var(--site-divider);
5184
+ border-radius: 4px;
5185
+ }
5186
+
5187
+ .compose-scroll > jant-compose-editor {
4982
5188
  display: flex;
4983
5189
  flex-direction: column;
4984
- overflow: hidden;
4985
5190
  position: relative;
4986
5191
  }
4987
5192
 
@@ -5059,144 +5264,63 @@
5059
5264
 
5060
5265
  /* --- Header ------------------------------------------------------------- */
5061
5266
 
5062
- .compose-dialog-header {
5063
- display: flex;
5064
- align-items: center;
5065
- justify-content: space-between;
5066
- min-height: 3.6rem;
5067
- padding: 12px 14px;
5068
- background-color: var(--compose-paper-bg);
5069
- border-top-left-radius: inherit;
5070
- border-top-right-radius: inherit;
5071
- position: relative;
5072
- z-index: 4;
5073
- }
5074
-
5075
- @media (min-width: 700px) {
5076
- .compose-dialog-header {
5077
- padding: 14px 18px;
5078
- }
5079
- }
5267
+ /* Leaves the composer, so unlike the per-post × it never hides until hover:
5268
+ on a phone this and the options sheet are the only ways out. */
5269
+ .compose-close-btn {
5270
+ --compose-dialog-icon-paper-fill: var(--compose-paper-bg);
5080
5271
 
5081
- .compose-dialog-cancel {
5082
- min-height: 44px;
5083
- min-width: 72px;
5084
- padding: 10px 14px;
5272
+ display: inline-flex;
5273
+ align-items: center;
5274
+ justify-content: center;
5275
+ margin-left: auto;
5276
+ width: 28px;
5277
+ height: 28px;
5278
+ border-radius: 8px;
5085
5279
  border: none;
5086
- background: none;
5087
- border-radius: 10px;
5280
+ background: transparent;
5088
5281
  color: var(--site-text-secondary);
5089
- font-size: var(--type-ui-control);
5090
- font-weight: var(--fw-medium);
5091
5282
  cursor: pointer;
5092
- transition: color 0.15s ease-out;
5093
5283
  flex-shrink: 0;
5094
- text-align: left;
5284
+ opacity: 0.6;
5285
+ transition:
5286
+ opacity 0.15s ease,
5287
+ background-color 0.13s ease,
5288
+ color 0.13s ease;
5095
5289
  }
5096
5290
 
5097
- .compose-dialog-cancel:hover {
5291
+ .compose-close-btn:hover {
5292
+ opacity: 1;
5293
+ background-color: var(--compose-control-bg);
5098
5294
  color: var(--site-text-primary);
5099
5295
  }
5100
5296
 
5101
- .compose-dialog-header-center {
5102
- position: absolute;
5103
- left: 50%;
5104
- transform: translateX(-50%);
5105
- width: max-content;
5106
- max-width: calc(100% - 9.5rem);
5107
- }
5108
-
5109
- .compose-dialog-header-actions {
5297
+ .compose-edit-loading-close {
5110
5298
  display: flex;
5111
- align-items: center;
5112
- gap: 0;
5113
- flex-shrink: 0;
5114
- min-width: 44px;
5115
5299
  justify-content: flex-end;
5116
5300
  }
5117
5301
 
5118
- .compose-dialog-title {
5119
- font-weight: var(--fw-semibold);
5120
- font-size: var(--type-ui-control);
5121
- line-height: 1.2;
5122
- }
5302
+ /* Treat touch-first browsers as mobile here too. iOS Safari can expose a
5303
+ * desktop-sized layout viewport for dialogs while still behaving like phone UI. */
5304
+ @media (max-width: 700px),
5305
+ (max-height: 760px),
5306
+ (hover: none) and (pointer: coarse) {
5307
+ .compose-dialog {
5308
+ --compose-margin-top: 0px;
5123
5309
 
5124
- .compose-dialog-header-btn {
5125
- --compose-dialog-icon-paper-fill: var(--compose-paper-bg);
5126
-
5127
- display: flex;
5128
- align-items: center;
5129
- justify-content: center;
5130
- width: 44px;
5131
- height: 44px;
5132
- border-radius: 12px;
5133
- border: none;
5134
- background: none;
5135
- color: var(--site-text-secondary);
5136
- cursor: pointer;
5137
- transition:
5138
- background-color 0.15s ease-out,
5139
- color 0.15s ease-out;
5140
- }
5141
-
5142
- .compose-dialog-header-btn:hover {
5143
- --compose-dialog-icon-paper-fill: var(--compose-control-bg);
5144
- color: var(--site-text-primary);
5145
- background-color: var(--compose-control-bg);
5146
- }
5147
-
5148
- .compose-dialog-draft-btn {
5149
- color: color-mix(
5150
- in srgb,
5151
- var(--site-text-primary) 48%,
5152
- var(--site-text-secondary)
5153
- );
5154
- }
5155
-
5156
- .compose-dialog-header-icon {
5157
- display: block;
5158
- width: 26px;
5159
- height: 26px;
5160
- --icon-stroke: 1.32;
5161
- flex-shrink: 0;
5162
- overflow: visible;
5163
- }
5164
-
5165
- .compose-dialog-draft-btn .compose-dialog-header-icon {
5166
- --icon-stroke: 1.24;
5167
- }
5168
-
5169
- .compose-dialog-draft-btn:hover {
5170
- color: var(--site-text-primary);
5171
- }
5172
-
5173
- .compose-dialog-header-btn:disabled {
5174
- opacity: 0.3;
5175
- cursor: not-allowed;
5176
- }
5177
-
5178
- /* Treat touch-first browsers as mobile here too. iOS Safari can expose a
5179
- * desktop-sized layout viewport for dialogs while still behaving like phone UI. */
5180
- @media (max-width: 700px),
5181
- (max-height: 760px),
5182
- (hover: none) and (pointer: coarse) {
5183
- .compose-dialog {
5184
- --compose-margin-top: 0px;
5185
-
5186
- position: fixed;
5187
- inset: 0;
5188
- box-sizing: border-box;
5189
- width: 100vw;
5190
- min-width: 100vw;
5191
- max-width: 100vw;
5192
- height: 100dvh;
5193
- min-height: 100dvh;
5194
- max-height: 100dvh;
5195
- margin: 0;
5196
- border: none;
5197
- border-radius: 0;
5198
- box-shadow: none;
5199
- }
5310
+ position: fixed;
5311
+ inset: 0;
5312
+ box-sizing: border-box;
5313
+ width: 100vw;
5314
+ min-width: 100vw;
5315
+ max-width: 100vw;
5316
+ height: 100dvh;
5317
+ min-height: 100dvh;
5318
+ max-height: 100dvh;
5319
+ margin: 0;
5320
+ border: none;
5321
+ border-radius: 0;
5322
+ box-shadow: none;
5323
+ }
5200
5324
 
5201
5325
  .compose-dialog-inner {
5202
5326
  height: 100dvh;
@@ -5212,29 +5336,13 @@
5212
5336
  padding-left: calc(20px + env(safe-area-inset-left));
5213
5337
  }
5214
5338
 
5215
- .compose-dialog-inner > jant-compose-editor,
5216
- .compose-thread-layout,
5217
- .compose-editor-row,
5218
- .compose-editor-row > jant-compose-editor {
5219
- flex: 0 1 auto;
5220
- }
5221
-
5222
- .compose-dialog-inner > jant-compose-editor,
5223
- .compose-editor-row > jant-compose-editor {
5224
- max-height: calc(
5225
- 100dvh - 13rem - env(safe-area-inset-top) - env(safe-area-inset-bottom)
5226
- );
5227
- }
5228
-
5229
- .compose-dialog-header {
5230
- padding-top: calc(12px + env(safe-area-inset-top));
5231
- padding-right: calc(14px + env(safe-area-inset-right));
5232
- padding-left: calc(14px + env(safe-area-inset-left));
5233
- border-bottom: 1px solid var(--compose-control-border);
5339
+ /* Fullscreen on a phone, so the composer's own first row sits under the
5340
+ notch — it takes the inset the title bar used to. */
5341
+ .compose-dialog-inner {
5342
+ padding-top: calc(10px + env(safe-area-inset-top));
5234
5343
  }
5235
5344
 
5236
5345
  .compose-body {
5237
- flex: 0 1 auto;
5238
5346
  min-height: clamp(8rem, 22vh, 12rem);
5239
5347
  padding-right: calc(20px + env(safe-area-inset-right));
5240
5348
  padding-left: calc(20px + env(safe-area-inset-left));
@@ -5261,92 +5369,100 @@
5261
5369
  }
5262
5370
  }
5263
5371
 
5264
- /* --- Format switcher with animated pill --------------------------------- */
5372
+ /* --- Format switcher ----------------------------------------------------- */
5373
+
5374
+ /* One grouped control — a tinted track with a pill on the active format.
5375
+ The strip's own inset (track padding + item padding) is 16px, so its box
5376
+ and its first label can never both sit on the text column: whatever the
5377
+ strip is pulled left by, the other edge is 16px away from the text. There
5378
+ is no value that aligns both, only a choice of which one to favour, so the
5379
+ amount is a knob rather than a derived number:
5265
5380
 
5381
+ 0px strip's tinted box on the text edge, "Note" 16px inside it
5382
+ 8px neither edge exact; the tint is light enough that the perceived
5383
+ edge sits between them, which is why this is the default
5384
+ 16px "Note" on the text edge, the tint hanging a full inset out
5385
+
5386
+ 12px is the hard ceiling: in a thread row the rail dot ends 12px short of
5387
+ the content edge, and past that the tint runs over the dot. */
5266
5388
  .compose-segmented {
5389
+ --compose-switcher-track-pad: 3px;
5390
+ --compose-switcher-item-pad: 13px;
5391
+
5267
5392
  display: inline-flex;
5268
- background-color: var(--compose-control-bg);
5269
- border-radius: 10px;
5270
- padding: 3px;
5393
+ flex: none;
5394
+ gap: 1px;
5395
+ padding: var(--compose-switcher-track-pad);
5396
+ margin-inline-start: calc(-1 * var(--compose-switcher-hang));
5397
+ /* Paints over the thread rail dot, not under it. The dot carries a
5398
+ surface-coloured ring (`--site-thread-marker-gap`) to break the rail line
5399
+ around itself, and that ring reaches further right than the dot does —
5400
+ far enough to clip the strip's left edge once it hangs. Both sit at
5401
+ z-index 1, so the strip wins on document order. Covering ring pixels is
5402
+ harmless here: the line it protects runs down the dot's centre, well left
5403
+ of anywhere the strip reaches. */
5271
5404
  position: relative;
5272
- box-shadow: inset 0 0 0 1px var(--compose-control-border);
5405
+ z-index: 1;
5406
+ border-radius: calc(var(--compose-switcher-track-pad) + 8px);
5407
+ background-color: var(--compose-control-bg);
5273
5408
  font-family: var(--font-ui);
5274
5409
  }
5275
5410
 
5276
- .compose-format-pill {
5277
- position: absolute;
5278
- top: 3px;
5279
- left: 3px;
5280
- width: calc((100% - 6px) / 3);
5281
- height: calc(100% - 6px);
5282
- background-color: var(--compose-paper-bg);
5283
- border-radius: 7px;
5284
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
5285
- transition: left 0.28s cubic-bezier(0.22, 1, 0.36, 1);
5286
- z-index: 0;
5287
- }
5288
-
5289
- .compose-format-pill-link {
5290
- left: calc(3px + (100% - 6px) / 3);
5291
- }
5292
-
5293
- .compose-format-pill-quote {
5294
- left: calc(3px + (100% - 6px) * 2 / 3);
5295
- }
5296
-
5297
5411
  .compose-segmented-item {
5298
- position: relative;
5299
- z-index: 1;
5300
- flex: 1;
5301
- padding: 5px 0.95rem;
5302
- border-radius: 8px;
5412
+ flex: none;
5413
+ padding: 5px var(--compose-switcher-item-pad);
5303
5414
  border: none;
5415
+ border-radius: 8px;
5304
5416
  background: transparent;
5305
5417
  color: var(--site-text-secondary);
5306
5418
  font-size: var(--type-ui-hint);
5307
5419
  font-weight: var(--fw-medium);
5308
5420
  line-height: 1.2;
5309
- cursor: pointer;
5310
- transition: color 0.2s ease;
5311
5421
  letter-spacing: 0.01em;
5422
+ cursor: pointer;
5423
+ transition:
5424
+ color 0.15s ease,
5425
+ background-color 0.15s ease,
5426
+ box-shadow 0.15s ease;
5312
5427
  }
5313
5428
 
5314
- @media (min-width: 700px) {
5315
- .compose-segmented-item {
5316
- padding: 0.4rem 1.15rem;
5317
- font-size: var(--type-ui-control);
5318
- }
5429
+ .compose-segmented-item:hover:not(.compose-segmented-item-active) {
5430
+ color: var(--site-text-primary);
5319
5431
  }
5320
5432
 
5321
5433
  .compose-segmented-item-active {
5322
5434
  color: var(--site-text-primary);
5323
- font-weight: var(--fw-medium);
5435
+ background-color: var(--compose-paper-bg);
5436
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
5324
5437
  }
5325
5438
 
5326
5439
  /* --- Body --------------------------------------------------------------- */
5327
5440
 
5441
+ /* Grows with its content — `.compose-scroll` does the scrolling. The floor is
5442
+ a target to click into on an empty post, so it yields once the post has an
5443
+ attachment: below a full-size preview the same space reads as a gap between
5444
+ the text and the picture. */
5328
5445
  .compose-body {
5329
- flex: 1;
5330
- min-height: 0;
5331
- overflow-y: auto;
5332
- padding: 16px 20px 12px;
5446
+ min-height: 72px;
5447
+ padding: 16px 20px 12px var(--compose-gutter);
5333
5448
  display: flex;
5334
5449
  flex-direction: column;
5335
5450
  }
5336
5451
 
5337
- @media (min-width: 700px) {
5338
- .compose-body {
5339
- padding: 18px 24px 12px;
5340
- }
5452
+ jant-compose-editor:has(.compose-attachments-dock) .compose-body {
5453
+ min-height: 0;
5341
5454
  }
5342
5455
 
5343
- .compose-body::-webkit-scrollbar {
5344
- width: 3px;
5456
+ jant-compose-editor:has(.compose-attachments-dock)
5457
+ .compose-tiptap-body
5458
+ .tiptap {
5459
+ min-height: 0;
5345
5460
  }
5346
5461
 
5347
- .compose-body::-webkit-scrollbar-thumb {
5348
- background-color: var(--site-divider);
5349
- border-radius: 4px;
5462
+ @media (min-width: 700px) {
5463
+ .compose-body {
5464
+ padding: 18px 24px 12px var(--compose-gutter);
5465
+ }
5350
5466
  }
5351
5467
 
5352
5468
  .compose-field-enter {
@@ -5372,22 +5488,25 @@
5372
5488
  color: var(--site-text-secondary);
5373
5489
  }
5374
5490
 
5491
+ /* An optional field sitting directly above the one people came to type in:
5492
+ the empty state should recede, not announce itself. */
5493
+ .compose-note-title::placeholder,
5494
+ .compose-link-title::placeholder {
5495
+ color: color-mix(in srgb, var(--site-text-secondary) 55%, transparent);
5496
+ font-weight: 400;
5497
+ }
5498
+
5375
5499
  .compose-input:focus::placeholder {
5376
5500
  opacity: 0.6;
5377
5501
  }
5378
5502
 
5379
5503
  /* --- Note mode ---------------------------------------------------------- */
5380
5504
 
5381
- .compose-note-title-row {
5382
- display: flex;
5383
- align-items: center;
5384
- gap: 10px;
5385
- margin-bottom: 14px;
5386
- animation: compose-fade-up 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
5387
- }
5388
-
5389
5505
  .compose-note-title {
5390
- flex: 1;
5506
+ width: 100%;
5507
+ /* Sole owner of the title-to-body gap now that the first block's lead-in
5508
+ margin is gone — one place to tune instead of two that stack. */
5509
+ margin-bottom: 12px;
5391
5510
  font-family: var(--font-heading);
5392
5511
  font-size: var(--compose-title-input-size);
5393
5512
  font-weight: var(--type-heading-weight);
@@ -5396,30 +5515,6 @@
5396
5515
  text-wrap: pretty;
5397
5516
  }
5398
5517
 
5399
- @media (min-width: 700px) {
5400
- .compose-note-title {
5401
- font-size: var(--compose-title-input-size);
5402
- }
5403
- }
5404
-
5405
- .compose-note-title-dismiss {
5406
- border: none;
5407
- background: transparent;
5408
- color: var(--site-text-secondary);
5409
- cursor: pointer;
5410
- font-size: var(--type-ui-hint);
5411
- padding: 6px 8px;
5412
- border-radius: 8px;
5413
- flex-shrink: 0;
5414
- line-height: 1;
5415
- opacity: 0.6;
5416
- transition: opacity 0.15s ease;
5417
- }
5418
-
5419
- .compose-note-title-dismiss:hover {
5420
- opacity: 1;
5421
- }
5422
-
5423
5518
  .compose-body-input {
5424
5519
  width: 100%;
5425
5520
  padding: 0;
@@ -5480,17 +5575,11 @@
5480
5575
  font-weight: var(--type-heading-weight);
5481
5576
  letter-spacing: var(--type-heading-tracking);
5482
5577
  line-height: var(--compose-title-input-leading);
5483
- margin-bottom: 16px;
5578
+ margin-bottom: 10px;
5484
5579
  padding-left: 2px;
5485
5580
  text-wrap: pretty;
5486
5581
  }
5487
5582
 
5488
- @media (min-width: 700px) {
5489
- .compose-link-title {
5490
- font-size: var(--compose-title-input-size);
5491
- }
5492
- }
5493
-
5494
5583
  /* --- Quote mode --------------------------------------------------------- */
5495
5584
 
5496
5585
  .compose-quote-wrap {
@@ -5561,15 +5650,17 @@
5561
5650
  font-weight: var(--fw-regular);
5562
5651
  line-height: var(--compose-quote-input-leading);
5563
5652
  letter-spacing: -0.02em;
5564
- min-height: 9rem;
5653
+ /* Four lines of the quote's own type — enough to hold most pulled passages
5654
+ without opening a well. Stated in lines so it tracks the two leadings
5655
+ this input takes (1.32 in compose, 1.42 in a reply) instead of needing a
5656
+ pixel value per breakpoint. The `rows` attribute is set to match. */
5657
+ min-height: calc(var(--compose-quote-input-leading) * 4em);
5565
5658
  padding-top: 0.5rem;
5566
5659
  text-wrap: pretty;
5567
5660
  }
5568
5661
 
5569
5662
  @media (min-width: 700px) {
5570
5663
  .compose-quote-text {
5571
- font-size: var(--compose-quote-input-size);
5572
- min-height: 9.5rem;
5573
5664
  padding-top: 0.55rem;
5574
5665
  }
5575
5666
  }
@@ -5638,7 +5729,9 @@
5638
5729
 
5639
5730
  .compose-url-input {
5640
5731
  border-bottom: 1px solid transparent;
5732
+ font-family: var(--font-mono);
5641
5733
  font-size: var(--compose-inline-input-size);
5734
+ color: var(--site-accent);
5642
5735
  line-height: 1.4;
5643
5736
  padding-block: 0.1rem;
5644
5737
  transition:
@@ -5735,16 +5828,11 @@
5735
5828
  margin-left: 4px;
5736
5829
  }
5737
5830
 
5831
+ /* Plain background, no top fade: nothing scrolls underneath it any more —
5832
+ the dock travels with its post inside `.compose-scroll`. */
5738
5833
  .compose-attachments-dock {
5739
- flex-shrink: 0;
5740
- padding: 8px 20px 4px;
5741
- background:
5742
- linear-gradient(
5743
- 180deg,
5744
- color-mix(in srgb, var(--compose-paper-bg) 0%, transparent) 0%,
5745
- var(--compose-paper-bg) 18px
5746
- ),
5747
- var(--compose-paper-bg);
5834
+ padding: 8px 20px 4px var(--compose-gutter);
5835
+ background-color: var(--compose-paper-bg);
5748
5836
  }
5749
5837
 
5750
5838
  @media (min-width: 700px) {
@@ -5832,26 +5920,31 @@
5832
5920
 
5833
5921
  /* --- Tools row ---------------------------------------------------------- */
5834
5922
 
5923
+ /* Belongs to its post, not to the dialog: it sits under that post's content
5924
+ and scrolls away with it, so a thread shows one toolbar per block. */
5835
5925
  .compose-tools-row {
5836
- padding: 6px 10px;
5926
+ /* Half the difference between the 44px button and the 20px glyph inside
5927
+ it. The row starts on the text column and its first control hangs back
5928
+ out by this much, so the icon lands on the column while the button keeps
5929
+ its full touch target. 12px is also the ceiling for anything hanging out
5930
+ of a thread row — see `.compose-segmented`, whose knob has the same job
5931
+ for a control with a different inset. */
5932
+ --compose-tool-hang: 12px;
5933
+
5934
+ padding: 6px 10px 6px var(--compose-gutter);
5837
5935
  display: flex;
5838
5936
  align-items: center;
5839
- flex-shrink: 0;
5840
5937
  gap: 4px;
5841
5938
  background-color: var(--compose-paper-bg);
5842
5939
  }
5843
5940
 
5844
- .compose-thread-layout .compose-tools-row {
5845
- padding-inline-start: 0;
5846
- }
5847
-
5848
- .compose-thread-layout .compose-tools-row .compose-tool-btn:first-child {
5849
- margin-inline-start: -10px;
5941
+ .compose-tools-row > :first-child {
5942
+ margin-inline-start: calc(-1 * var(--compose-tool-hang));
5850
5943
  }
5851
5944
 
5852
5945
  @media (min-width: 700px) {
5853
5946
  .compose-tools-row {
5854
- padding: 6px 16px;
5947
+ padding: 6px 16px 6px var(--compose-gutter);
5855
5948
  gap: 4px;
5856
5949
  }
5857
5950
  }
@@ -5863,37 +5956,64 @@
5863
5956
  justify-content: center;
5864
5957
  width: 44px;
5865
5958
  height: 44px;
5959
+ /* The button keeps its 44px touch target; only the paint is inset, so the
5960
+ chip a hovered or switched-on tool wears is 36px and sits closer to the
5961
+ glyph than to its neighbours. `background-clip` shrinks the corner radii
5962
+ with the box, which is why 12px here reads as 8px on the chip. */
5963
+ padding: 4px;
5964
+ background-clip: content-box;
5866
5965
  border-radius: 12px;
5867
5966
  border: none;
5868
- background: transparent;
5967
+ background-color: transparent;
5869
5968
  cursor: pointer;
5870
5969
  transition: all 0.18s ease;
5871
5970
  flex-shrink: 0;
5872
- color: var(--site-text-secondary);
5971
+ color: var(--compose-tool-ink);
5873
5972
  }
5874
5973
 
5974
+ /* One weight for the whole row, stated here rather than per icon, so the
5975
+ stroked glyphs and the star's outline read as one set. */
5875
5976
  .compose-tool-icon {
5876
- --icon-stroke: 1.46;
5877
5977
  display: block;
5878
5978
  width: 20px;
5879
5979
  height: 20px;
5880
5980
  flex-shrink: 0;
5881
5981
  overflow: visible;
5982
+ stroke-width: 1.42;
5882
5983
  }
5883
5984
 
5884
5985
  .compose-tool-btn:hover {
5885
5986
  background-color: var(--compose-control-bg);
5886
5987
  }
5887
5988
 
5989
+ /* On is a real chip — the shape people already read as "this control is
5990
+ switched on" — just quieter than the one that was tried first. That one
5991
+ paired the chip with full-strength ink, so the whole button lit up at
5992
+ once; here the glyph is left in the row's faded ink and the chip does the
5993
+ talking on its own, a little under the row's hover tint. It is the only
5994
+ edge in the row, so it needs no help to be found. */
5888
5995
  .compose-tool-btn-active {
5889
- color: var(--site-text-primary);
5996
+ background-color: color-mix(
5997
+ in srgb,
5998
+ var(--compose-control-bg) 85%,
5999
+ var(--compose-paper-bg)
6000
+ );
6001
+ }
6002
+
6003
+ /* Hover has to stay a step above a button that already carries a chip. */
6004
+ .compose-tool-btn-active:hover {
5890
6005
  background-color: var(--compose-control-bg-strong);
5891
6006
  }
5892
6007
 
6008
+ /* A tool that has grown a label is reading matter now, so it carries text's
6009
+ weight — the row's faded ink would put "Add more" under the 4.5:1 floor. */
5893
6010
  .compose-tool-btn-add {
5894
6011
  width: auto;
5895
6012
  gap: 4px;
5896
- padding: 0 10px 0 8px;
6013
+ /* Same 4px of inset paint as the icon-only buttons; only the sides open up
6014
+ to make room for the label. */
6015
+ padding: 4px 10px 4px 8px;
6016
+ color: var(--site-text-secondary);
5897
6017
  }
5898
6018
 
5899
6019
  .compose-tool-label {
@@ -5902,12 +6022,11 @@
5902
6022
  white-space: nowrap;
5903
6023
  }
5904
6024
 
5905
- .compose-tool-sep {
5906
- width: 1px;
5907
- height: 16px;
5908
- background-color: var(--site-divider);
5909
- margin: 0 2px;
5910
- border-radius: 1px;
6025
+ /* Was a hairline. The two groups (insert something / mark something up) are
6026
+ still worth telling apart, but air does it without drawing another edge
6027
+ into the paper. */
6028
+ .compose-tool-gap {
6029
+ width: 8px;
5911
6030
  flex-shrink: 0;
5912
6031
  }
5913
6032
 
@@ -5921,7 +6040,6 @@
5921
6040
  width: 44px;
5922
6041
  height: 44px;
5923
6042
  border-radius: 12px;
5924
- color: var(--site-text-secondary);
5925
6043
  }
5926
6044
 
5927
6045
  .compose-tool-btn-view:hover,
@@ -5950,7 +6068,7 @@
5950
6068
  /* --- Action row --------------------------------------------------------- */
5951
6069
 
5952
6070
  .compose-action-row {
5953
- padding: 10px 12px 14px;
6071
+ padding: 10px 12px 14px var(--compose-gutter);
5954
6072
  display: flex;
5955
6073
  align-items: center;
5956
6074
  gap: 10px;
@@ -5961,7 +6079,10 @@
5961
6079
  border-bottom-right-radius: inherit;
5962
6080
  }
5963
6081
 
6082
+ /* The quick toggles take over the action row's bottom edge when present, so
6083
+ the two read as one block rather than a strip stuck underneath. */
5964
6084
  .compose-action-row:has(+ .compose-quick-actions-row) {
6085
+ padding-bottom: 4px;
5965
6086
  border-bottom-left-radius: 0;
5966
6087
  border-bottom-right-radius: 0;
5967
6088
  }
@@ -5974,17 +6095,50 @@
5974
6095
  justify-content: flex-end;
5975
6096
  }
5976
6097
 
6098
+ .compose-quick-actions-row {
6099
+ display: flex;
6100
+ justify-content: flex-end;
6101
+ gap: 0.5rem;
6102
+ padding: 2px 12px 12px;
6103
+ background-color: var(--compose-paper-bg);
6104
+ border-bottom-left-radius: inherit;
6105
+ border-bottom-right-radius: inherit;
6106
+ }
6107
+
5977
6108
  @media (min-width: 700px) {
5978
6109
  .compose-action-row {
5979
- padding: 12px 18px 16px;
6110
+ padding: 12px 18px 16px var(--compose-gutter);
6111
+ }
6112
+
6113
+ .compose-action-row:has(+ .compose-quick-actions-row) {
6114
+ padding-bottom: 4px;
6115
+ }
6116
+
6117
+ .compose-quick-actions-row {
6118
+ padding: 2px 18px 14px;
5980
6119
  }
5981
6120
  }
5982
6121
 
5983
6122
  .compose-collection-select {
6123
+ /* Optically, not geometrically, centered: the glyph meets the left padding
6124
+ at a vertex and sits inset in its own box, so equal padding leaves the
6125
+ icon side looking open and the label side pinched against the edge.
6126
+ Tune the pill's side padding here and in the touch block below — both
6127
+ set these two values rather than re-declaring `padding`, so neither
6128
+ silently shadows the other. They live on the wrapper because the hang
6129
+ below is measured from them and has to apply to the flex item the action
6130
+ row lays out, not to the trigger inside it. */
6131
+ --compose-collection-pad-icon: 0.56rem;
6132
+ --compose-collection-pad-label: 0.85rem;
6133
+
5984
6134
  display: inline-flex;
5985
6135
  position: relative;
5986
6136
  min-width: 0;
5987
6137
  max-width: 100%;
6138
+ /* Hangs back by the trigger's icon-side inset plus its 1px transparent
6139
+ border, so it is the layers glyph that lands on the text column rather
6140
+ than the pill's edge. */
6141
+ margin-inline-start: calc(-1 * (var(--compose-collection-pad-icon) + 1px));
5988
6142
  }
5989
6143
 
5990
6144
  .compose-collection-select[data-open] {
@@ -5994,14 +6148,18 @@
5994
6148
  .compose-collection-trigger {
5995
6149
  display: flex;
5996
6150
  align-items: center;
5997
- gap: 0.62rem;
6151
+ gap: 0.38rem;
5998
6152
  min-width: 0;
5999
6153
  max-width: 100%;
6000
- min-height: 2.45rem;
6001
- border: 1px solid var(--compose-control-border);
6002
- background: var(--compose-control-bg);
6154
+ min-height: 1.95rem;
6155
+ /* No fill and no edge at rest — it was the one bordered pill left in the
6156
+ composer, which made picking a collection look like the thing to do. It
6157
+ keeps a transparent border so the box does not resize on hover. */
6158
+ border: 1px solid transparent;
6159
+ background: transparent;
6003
6160
  border-radius: 999px;
6004
- padding: 0.24rem 0.72rem 0.24rem 0.28rem;
6161
+ padding: 0.2rem var(--compose-collection-pad-label) 0.2rem
6162
+ var(--compose-collection-pad-icon);
6005
6163
  cursor: pointer;
6006
6164
  transition:
6007
6165
  background-color 0.18s ease,
@@ -6012,12 +6170,12 @@
6012
6170
  font-size: var(--type-ui-control);
6013
6171
  }
6014
6172
 
6173
+ /* The same flat wash the tools row uses on hover — no lift, no edge. A chip
6174
+ that rises off the paper is what made this read as the loud control. */
6015
6175
  .compose-collection-trigger:hover,
6016
6176
  .compose-collection-trigger[data-open="true"] {
6017
6177
  color: var(--site-text-primary);
6018
- background: var(--compose-control-bg-strong);
6019
- border-color: color-mix(in srgb, var(--site-text-primary) 10%, transparent);
6020
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
6178
+ background: var(--compose-control-bg);
6021
6179
  }
6022
6180
 
6023
6181
  .compose-collection-trigger[data-selected="true"] {
@@ -6028,39 +6186,16 @@
6028
6186
  );
6029
6187
  }
6030
6188
 
6031
- .compose-collection-trigger-icon {
6032
- display: inline-flex;
6033
- align-items: center;
6034
- justify-content: center;
6035
- width: 1.65rem;
6036
- height: 1.65rem;
6037
- border-radius: 999px;
6038
- background: color-mix(
6039
- in srgb,
6040
- var(--compose-paper-bg) 72%,
6041
- var(--compose-control-bg)
6042
- );
6043
- color: color-mix(
6044
- in srgb,
6045
- var(--site-text-primary) 58%,
6046
- var(--site-text-secondary)
6047
- );
6048
- flex-shrink: 0;
6049
- }
6050
-
6051
- .compose-collection-trigger-svg,
6052
6189
  .compose-collection-search-icon,
6053
6190
  .compose-collection-add-svg,
6054
6191
  .compose-collection-option-check-circle,
6055
6192
  .compose-collection-option-plus-circle,
6056
6193
  .compose-collection-option-svg,
6057
- .compose-collection-chevron,
6058
6194
  .compose-collection-option-check {
6059
6195
  display: block;
6060
6196
  overflow: visible;
6061
6197
  }
6062
6198
 
6063
- .compose-collection-trigger-svg,
6064
6199
  .compose-collection-option-svg {
6065
6200
  width: 0.92rem;
6066
6201
  height: 0.92rem;
@@ -6068,6 +6203,26 @@
6068
6203
  stroke-width: var(--icon-stroke);
6069
6204
  }
6070
6205
 
6206
+ /* Bare on the pill rather than in its own tinted circle: the pill is already
6207
+ a container, and a chip inside it read as a button inside a button. */
6208
+ .compose-collection-trigger-svg {
6209
+ display: block;
6210
+ overflow: visible;
6211
+ width: 0.86rem;
6212
+ height: 0.86rem;
6213
+ flex-shrink: 0;
6214
+ opacity: 0.75;
6215
+ --icon-stroke: 1.4;
6216
+ stroke-width: var(--icon-stroke);
6217
+ }
6218
+
6219
+ .compose-collection-trigger:hover .compose-collection-trigger-svg,
6220
+ .compose-collection-trigger[data-open="true"] .compose-collection-trigger-svg,
6221
+ .compose-collection-trigger[data-selected="true"]
6222
+ .compose-collection-trigger-svg {
6223
+ opacity: 1;
6224
+ }
6225
+
6071
6226
  .compose-collection-label {
6072
6227
  display: block;
6073
6228
  flex: 1 1 auto;
@@ -6079,32 +6234,18 @@
6079
6234
  font-weight: var(--fw-medium);
6080
6235
  }
6081
6236
 
6082
- .compose-collection-chevron {
6083
- width: 0.78rem;
6084
- height: 0.78rem;
6085
- flex-shrink: 0;
6086
- opacity: 0.46;
6087
- --icon-stroke: 1.38;
6088
- stroke-width: var(--icon-stroke);
6089
- transition:
6090
- transform 0.18s ease,
6091
- opacity 0.18s ease;
6092
- }
6093
-
6094
- .compose-collection-trigger[data-open="true"] .compose-collection-chevron {
6095
- transform: rotate(180deg);
6096
- opacity: 0.74;
6097
- }
6098
-
6237
+ /* Desktop sizing. Every dimension here has a roomier counterpart in the
6238
+ touch block near the end of the file — a picker aimed with a cursor can be
6239
+ tighter than one aimed with a thumb. */
6099
6240
  .compose-collection-select [data-popover] {
6100
6241
  z-index: 100;
6101
- min-width: 16rem;
6102
- max-width: 20rem;
6242
+ min-width: 13.5rem;
6243
+ max-width: 17rem;
6103
6244
  }
6104
6245
 
6105
6246
  .compose-collection-popover {
6106
- padding: 0.56rem 0.62rem 0.5rem;
6107
- border-radius: 1.08rem;
6247
+ padding: 0.44rem 0.48rem 0.4rem;
6248
+ border-radius: 0.95rem;
6108
6249
  border: 1px solid var(--compose-control-border);
6109
6250
  background: var(--compose-floating-bg);
6110
6251
  box-shadow:
@@ -6114,16 +6255,16 @@
6114
6255
  }
6115
6256
 
6116
6257
  .compose-collection-popover-header {
6117
- padding: 0.24rem 0 0.6rem;
6258
+ padding: 0.18rem 0 0.44rem;
6118
6259
  }
6119
6260
 
6120
6261
  .compose-collection-search-shell {
6121
6262
  display: flex;
6122
6263
  align-items: center;
6123
- gap: 0.52rem;
6124
- min-height: 2.8rem;
6125
- padding: 0 0.95rem;
6126
- border-radius: 0.92rem;
6264
+ gap: 0.45rem;
6265
+ min-height: 2.25rem;
6266
+ padding: 0 0.75rem;
6267
+ border-radius: 0.75rem;
6127
6268
  border: 1px solid var(--compose-control-border);
6128
6269
  background: var(--compose-control-bg);
6129
6270
  color: var(--site-text-secondary);
@@ -6146,8 +6287,8 @@
6146
6287
  }
6147
6288
 
6148
6289
  .compose-collection-search-icon {
6149
- width: 1.15rem;
6150
- height: 1.15rem;
6290
+ width: 0.98rem;
6291
+ height: 0.98rem;
6151
6292
  flex-shrink: 0;
6152
6293
  --icon-stroke: 1.3;
6153
6294
  stroke-width: var(--icon-stroke);
@@ -6174,7 +6315,7 @@
6174
6315
  display: flex;
6175
6316
  flex-direction: column;
6176
6317
  gap: 0.06rem;
6177
- max-height: 15rem;
6318
+ max-height: 12.5rem;
6178
6319
  overflow-y: auto;
6179
6320
  padding: 0.08rem;
6180
6321
  }
@@ -6183,15 +6324,15 @@
6183
6324
  width: 100%;
6184
6325
  display: flex;
6185
6326
  align-items: center;
6186
- gap: 0.75rem;
6327
+ gap: 0.6rem;
6187
6328
  min-width: 0;
6188
- min-height: 3rem;
6329
+ min-height: 2.35rem;
6189
6330
  padding-top: 0;
6190
6331
  padding-bottom: 0;
6191
- padding-left: 0.8rem !important;
6192
- padding-right: 0.46rem !important;
6332
+ padding-left: 0.62rem !important;
6333
+ padding-right: 0.4rem !important;
6193
6334
  border: none;
6194
- border-radius: 0.88rem;
6335
+ border-radius: 0.7rem;
6195
6336
  background: transparent;
6196
6337
  background-image: none !important;
6197
6338
  cursor: pointer !important;
@@ -6231,8 +6372,8 @@
6231
6372
  display: inline-flex;
6232
6373
  align-items: center;
6233
6374
  justify-content: center;
6234
- width: 1.32rem;
6235
- height: 1.32rem;
6375
+ width: 1.12rem;
6376
+ height: 1.12rem;
6236
6377
  flex-shrink: 0;
6237
6378
  }
6238
6379
 
@@ -6283,7 +6424,7 @@
6283
6424
  }
6284
6425
 
6285
6426
  .compose-collection-empty {
6286
- padding: 0.95rem 0.7rem 1.05rem;
6427
+ padding: 0.75rem 0.6rem 0.85rem;
6287
6428
  color: var(--site-text-secondary);
6288
6429
  font-size: var(--type-ui-caption);
6289
6430
  line-height: 1.5;
@@ -6291,8 +6432,8 @@
6291
6432
  }
6292
6433
 
6293
6434
  .compose-collection-footer {
6294
- margin-top: 0.35rem;
6295
- padding: 0.35rem 0.22rem 0.12rem;
6435
+ margin-top: 0.28rem;
6436
+ padding: 0.28rem 0.18rem 0.1rem;
6296
6437
  border-top: 1px solid
6297
6438
  color-mix(in srgb, var(--site-divider) 50%, transparent);
6298
6439
  }
@@ -6300,12 +6441,12 @@
6300
6441
  .compose-collection-add-action {
6301
6442
  display: flex;
6302
6443
  align-items: center;
6303
- gap: 0.75rem;
6444
+ gap: 0.6rem;
6304
6445
  width: 100%;
6305
- min-height: 3rem;
6306
- padding: 0 0.8rem;
6446
+ min-height: 2.35rem;
6447
+ padding: 0 0.62rem;
6307
6448
  border: none;
6308
- border-radius: 1rem;
6449
+ border-radius: 0.7rem;
6309
6450
  background: transparent;
6310
6451
  cursor: pointer;
6311
6452
  text-align: left;
@@ -6386,28 +6527,7 @@
6386
6527
  min-width: 0;
6387
6528
  }
6388
6529
 
6389
- .compose-quick-actions-row {
6390
- display: flex;
6391
- justify-content: flex-end;
6392
- gap: 0.5rem;
6393
- padding: 2px 12px 12px;
6394
- background-color: var(--compose-paper-bg);
6395
- border-bottom-left-radius: inherit;
6396
- border-bottom-right-radius: inherit;
6397
- }
6398
-
6399
- .compose-action-row:has(+ .compose-quick-actions-row) {
6400
- padding-bottom: 4px;
6401
- }
6402
-
6403
6530
  @media (min-width: 700px) {
6404
- .compose-quick-actions-row {
6405
- padding: 2px 18px 14px;
6406
- }
6407
-
6408
- .compose-action-row:has(+ .compose-quick-actions-row) {
6409
- padding-bottom: 4px;
6410
- }
6411
6531
  }
6412
6532
 
6413
6533
  .compose-publish-quick-toggle {
@@ -6470,83 +6590,6 @@
6470
6590
  cursor: not-allowed;
6471
6591
  }
6472
6592
 
6473
- .compose-publish-summaries {
6474
- display: flex;
6475
- flex-wrap: wrap;
6476
- justify-content: flex-end;
6477
- gap: 0.5rem;
6478
- width: 100%;
6479
- }
6480
-
6481
- .compose-publish-summary {
6482
- display: inline-flex;
6483
- align-items: center;
6484
- gap: 0.48rem;
6485
- min-height: 2.15rem;
6486
- max-width: min(100%, 17rem);
6487
- padding: 0.38rem 0.74rem;
6488
- border: 1px solid var(--compose-control-border);
6489
- border-radius: 999px;
6490
- background-color: color-mix(
6491
- in srgb,
6492
- var(--compose-control-bg) 82%,
6493
- var(--compose-paper-bg)
6494
- );
6495
- color: color-mix(
6496
- in srgb,
6497
- var(--site-text-primary) 70%,
6498
- var(--site-text-secondary)
6499
- );
6500
- font-size: var(--compose-summary-size);
6501
- font-weight: var(--fw-medium);
6502
- line-height: 1.1;
6503
- cursor: pointer;
6504
- white-space: nowrap;
6505
- transition:
6506
- background-color 0.18s ease,
6507
- border-color 0.18s ease,
6508
- color 0.18s ease,
6509
- transform 0.18s ease;
6510
- }
6511
-
6512
- .compose-publish-summary:hover:not(:disabled) {
6513
- border-color: color-mix(in srgb, var(--site-text-primary) 12%, transparent);
6514
- background-color: color-mix(
6515
- in srgb,
6516
- var(--compose-control-bg-strong) 84%,
6517
- var(--compose-paper-bg)
6518
- );
6519
- color: var(--site-text-primary);
6520
- transform: translateY(-1px);
6521
- }
6522
-
6523
- .compose-publish-summary:disabled {
6524
- opacity: 0.6;
6525
- cursor: progress;
6526
- }
6527
-
6528
- .compose-publish-summary-icon {
6529
- display: inline-flex;
6530
- align-items: center;
6531
- justify-content: center;
6532
- width: 1rem;
6533
- height: 1rem;
6534
- flex-shrink: 0;
6535
- }
6536
-
6537
- .compose-publish-summary-icon svg {
6538
- display: block;
6539
- width: 100%;
6540
- height: 100%;
6541
- overflow: visible;
6542
- }
6543
-
6544
- .compose-publish-summary-text {
6545
- min-width: 0;
6546
- overflow: hidden;
6547
- text-overflow: ellipsis;
6548
- }
6549
-
6550
6593
  .compose-publish-group {
6551
6594
  position: relative;
6552
6595
  display: inline-flex;
@@ -6559,10 +6602,17 @@
6559
6602
  z-index: 100;
6560
6603
  }
6561
6604
 
6562
- .compose-publish-single,
6563
- .compose-publish-main,
6564
- .compose-publish-toggle {
6565
- min-height: 2.8rem;
6605
+ /* Sized against the row it leads, not in isolation. Measured: Publish 33px,
6606
+ the options trigger 33px, the collection pill 30.5px — Publish and the
6607
+ options circle share a height so they read as one pair at the end of the
6608
+ row, and the collection pill sits just under them. Touch gets the roomier
6609
+ set back further down: 33px is a fine mouse target and a poor thumb one. */
6610
+ .compose-publish-main {
6611
+ /* `em`, not `rem`: the box is a multiple of its own label, so the ratio
6612
+ holds if the button's type ever scales. The reference is 2.4em (36px
6613
+ around a 15px label); ours sits a step under, which is the only place we
6614
+ leave it — every other number in this button is traced. */
6615
+ min-height: 2.2em;
6566
6616
  border: none;
6567
6617
  background: transparent;
6568
6618
  cursor: pointer;
@@ -6572,28 +6622,36 @@
6572
6622
  box-shadow 0.18s ease;
6573
6623
  }
6574
6624
 
6575
- .compose-publish-single,
6625
+ /* Shape traced from threads.com's Post button, measured off the reference at
6626
+ 2x: 1px border, 8px radius, 18px side padding, ~15px bold label, content
6627
+ width with no `min-width` — the reference sizes to its word. Height is the
6628
+ one number we take a step under; see above.
6629
+
6630
+ Where we part from it: threads outlines Post in *both* states, and we only
6631
+ borrow that for disabled (below). Once there is something to post the
6632
+ button fills with ink rather than firming up an outline — a composer this
6633
+ flat can carry one solid object, and it should be the send. Weight and
6634
+ tracking hold across both states so the box does not resize the moment the
6635
+ post becomes postable. */
6576
6636
  .compose-publish-main {
6577
6637
  display: inline-flex;
6578
6638
  align-items: center;
6579
6639
  justify-content: center;
6580
6640
  gap: 0.3rem;
6581
6641
  white-space: nowrap;
6582
- min-width: 6rem;
6583
6642
  padding: 0 18px;
6584
6643
  background-color: var(--site-text-primary);
6585
6644
  color: var(--site-elevated-bg);
6586
6645
  font-size: var(--compose-primary-button-size);
6587
- font-weight: var(--fw-semibold);
6588
- letter-spacing: 0.01em;
6646
+ font-weight: var(--fw-bold);
6647
+ letter-spacing: 0;
6589
6648
  box-shadow: none;
6590
6649
  }
6591
6650
 
6592
- .compose-publish-single:hover:not(:disabled),
6593
6651
  .compose-publish-main:hover:not(:disabled) {
6594
6652
  background-color: color-mix(
6595
6653
  in srgb,
6596
- var(--site-text-primary) 94%,
6654
+ var(--site-text-primary) 92%,
6597
6655
  var(--site-elevated-bg)
6598
6656
  );
6599
6657
  }
@@ -6602,138 +6660,32 @@
6602
6660
  display: inline-flex;
6603
6661
  align-items: stretch;
6604
6662
  overflow: hidden;
6605
- border-radius: 999px;
6606
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
6663
+ border-radius: 8px;
6607
6664
  }
6608
6665
 
6609
6666
  .compose-publish-buttons .compose-publish-main {
6610
- border-radius: 999px 0 0 999px;
6611
- }
6612
-
6613
- .compose-publish-buttons-inactive .compose-publish-toggle:not(:disabled) {
6614
- color: color-mix(
6615
- in srgb,
6616
- var(--site-text-secondary) 92%,
6617
- var(--site-elevated-bg)
6618
- );
6619
- background-color: color-mix(
6620
- in srgb,
6621
- var(--compose-control-bg) 88%,
6622
- var(--compose-paper-bg)
6623
- );
6624
- --compose-publish-divider-color: color-mix(
6625
- in srgb,
6626
- var(--site-divider) 72%,
6627
- transparent
6628
- );
6629
- }
6630
-
6631
- .compose-publish-buttons-inactive
6632
- .compose-publish-toggle:not(:disabled):hover,
6633
- .compose-publish-buttons-inactive
6634
- .compose-publish-toggle[aria-expanded="true"]:not(:disabled) {
6635
- color: color-mix(
6636
- in srgb,
6637
- var(--site-text-primary) 68%,
6638
- var(--site-text-secondary)
6639
- );
6640
- background-color: color-mix(
6641
- in srgb,
6642
- var(--compose-control-bg-strong) 88%,
6643
- var(--compose-paper-bg)
6644
- );
6645
- }
6646
-
6647
- .compose-publish-buttons-inactive .compose-publish-toggle-chevron {
6648
- opacity: 0.62;
6649
- }
6650
-
6651
- .compose-publish-toggle {
6652
- display: inline-flex;
6653
- align-items: center;
6654
- justify-content: center;
6655
- position: relative;
6656
- width: 2.75rem;
6657
- color: var(--site-elevated-bg);
6658
- background-color: var(--site-text-primary);
6659
- border-radius: 0 999px 999px 0;
6660
- flex-shrink: 0;
6661
- --compose-publish-divider-color: color-mix(
6662
- in srgb,
6663
- var(--site-elevated-bg) 14%,
6664
- transparent
6665
- );
6666
- }
6667
-
6668
- .compose-publish-toggle::before {
6669
- content: "";
6670
- position: absolute;
6671
- left: 0;
6672
- top: 50%;
6673
- width: 1px;
6674
- height: 1rem;
6675
- transform: translateY(-50%);
6676
- background-color: var(--compose-publish-divider-color);
6677
- opacity: 0.7;
6678
- }
6679
-
6680
- .compose-publish-toggle:hover:not(:disabled),
6681
- .compose-publish-toggle[aria-expanded="true"]:not(:disabled) {
6682
- background-color: color-mix(
6683
- in srgb,
6684
- var(--site-text-primary) 94%,
6685
- var(--site-elevated-bg)
6686
- );
6687
- }
6688
-
6689
- .compose-publish-toggle-chevron {
6690
- opacity: 0.78;
6691
- display: block;
6692
- width: 0.92rem;
6693
- height: 0.92rem;
6694
- overflow: visible;
6695
- --icon-stroke: 1.46;
6696
- stroke-width: var(--icon-stroke);
6697
- }
6698
-
6699
- .compose-publish-single:focus-visible,
6700
- .compose-publish-main:focus-visible,
6701
- .compose-publish-toggle:focus-visible,
6702
- .compose-publish-summary:focus-visible,
6703
- .compose-publish-chip:focus-visible {
6704
- outline: 2px solid
6705
- color-mix(in srgb, var(--site-text-primary) 22%, transparent);
6706
- outline-offset: 2px;
6667
+ border-radius: 8px;
6707
6668
  }
6708
6669
 
6709
- .compose-publish-single:disabled:not(.compose-publish-single-loading),
6670
+ /* Nothing postable yet. Both channels step back together: the frame to
6671
+ 7% (1.10:1, the reference's #f4f4f4 on white) and the label to 46%
6672
+ (2.12:1, its #b2b2b2). Inactive controls are exempt from the contrast
6673
+ floors, which is the only reason a label may sit this far under 4.5:1. */
6710
6674
  .compose-publish-main:disabled:not(.compose-publish-main-loading) {
6711
6675
  opacity: 1;
6712
- color: var(--site-text-secondary);
6713
- background-color: color-mix(
6714
- in srgb,
6715
- var(--compose-control-bg) 88%,
6716
- var(--compose-paper-bg)
6717
- );
6718
- box-shadow: none;
6719
- }
6720
-
6721
- .compose-publish-toggle:disabled:not(.compose-publish-toggle-loading) {
6722
- opacity: 1;
6723
- color: var(--site-text-secondary);
6724
- background-color: color-mix(
6725
- in srgb,
6726
- var(--compose-control-bg) 88%,
6727
- var(--compose-paper-bg)
6728
- );
6729
- border-left-color: color-mix(in srgb, var(--site-divider) 80%, transparent);
6676
+ color: color-mix(in srgb, var(--site-text-secondary) 46%, transparent);
6677
+ background-color: transparent;
6678
+ box-shadow: inset 0 0 0 1px
6679
+ color-mix(in srgb, var(--site-text-secondary) 7%, transparent);
6680
+ cursor: not-allowed;
6730
6681
  }
6731
6682
 
6732
- .compose-publish-single-loading:disabled,
6733
- .compose-publish-main-loading:disabled,
6734
- .compose-publish-toggle-loading:disabled {
6735
- opacity: 1;
6736
- cursor: progress;
6683
+ /* Pointing at it says so rather than doing nothing: the frame comes up a
6684
+ step, but the label stays faded and the cursor stays barred, so the hover
6685
+ never promises a press it will not accept. */
6686
+ .compose-publish-main:disabled:not(.compose-publish-main-loading):hover {
6687
+ box-shadow: inset 0 0 0 1px
6688
+ color-mix(in srgb, var(--site-text-secondary) 18%, transparent);
6737
6689
  }
6738
6690
 
6739
6691
  .compose-publish-panel {
@@ -6810,163 +6762,398 @@
6810
6762
  padding-left: calc(1rem + env(safe-area-inset-left));
6811
6763
  }
6812
6764
 
6813
- .compose-publish-section {
6765
+ /* ── Options sheet ──────────────────────────────────────────────────
6766
+ A vertical list read top to bottom, not a toolbar of chips: muted
6767
+ group label, one full-width row per setting, value or control on the
6768
+ right. Every choice carries its own hint so they can be compared
6769
+ before picking one. */
6770
+
6771
+ .compose-sheet {
6814
6772
  display: flex;
6815
6773
  flex-direction: column;
6816
- gap: 0.625rem;
6817
6774
  }
6818
6775
 
6819
- .compose-publish-section-header {
6776
+ .compose-sheet-label {
6777
+ margin: 0;
6778
+ padding: 0.35rem 0.35rem 0.3rem;
6779
+ color: var(--site-text-secondary);
6780
+ font-size: 0.79rem;
6781
+ font-weight: var(--fw-semibold);
6782
+ }
6783
+
6784
+ .compose-sheet-row {
6785
+ --compose-icon-paper-fill: var(--compose-paper-bg);
6786
+
6820
6787
  display: flex;
6821
- align-items: flex-start;
6822
- justify-content: space-between;
6823
- gap: 0.75rem;
6788
+ align-items: center;
6789
+ gap: 0.7rem;
6790
+ width: 100%;
6791
+ padding: 0.5rem 0.45rem;
6792
+ border: none;
6793
+ border-radius: 10px;
6794
+ background: transparent;
6795
+ color: var(--site-text-primary);
6796
+ text-align: left;
6797
+ cursor: pointer;
6798
+ transition: background-color 0.15s ease;
6799
+ }
6800
+
6801
+ .compose-sheet-row:hover:not(:disabled) {
6802
+ --compose-icon-paper-fill: var(--compose-control-bg);
6803
+ background-color: var(--compose-control-bg);
6804
+ }
6805
+
6806
+ /* Sized to the visibility glyphs it sits in a list with, so the session rows
6807
+ at the foot of the panel read as the same kind of row. */
6808
+ .compose-sheet-row-icon {
6809
+ display: block;
6810
+ width: 0.98rem;
6811
+ height: 0.98rem;
6812
+ --icon-stroke: 1.3;
6813
+ stroke-width: var(--icon-stroke);
6814
+ flex-shrink: 0;
6815
+ overflow: visible;
6816
+ color: var(--site-text-secondary);
6817
+ }
6818
+
6819
+ .compose-sheet-row:disabled {
6820
+ cursor: not-allowed;
6821
+ opacity: 0.55;
6822
+ }
6823
+
6824
+ .compose-sheet-row:focus-visible {
6825
+ outline: 2px solid
6826
+ color-mix(in srgb, var(--site-text-primary) 22%, transparent);
6827
+ outline-offset: 1px;
6824
6828
  }
6825
6829
 
6826
- .compose-publish-section-copy {
6830
+ .compose-sheet-main {
6831
+ display: flex;
6832
+ flex-direction: column;
6833
+ gap: 0.08rem;
6834
+ flex: 1;
6827
6835
  min-width: 0;
6828
6836
  }
6829
6837
 
6830
- .compose-publish-section-label {
6831
- color: color-mix(
6832
- in srgb,
6833
- var(--site-text-primary) 84%,
6834
- var(--site-text-secondary)
6835
- );
6836
- font-size: var(--compose-panel-label-size);
6838
+ .compose-sheet-title {
6839
+ font-size: 0.9rem;
6837
6840
  font-weight: var(--fw-medium);
6838
- letter-spacing: 0.01em;
6839
- line-height: 1.24;
6840
6841
  }
6841
6842
 
6842
- .compose-publish-section-hint {
6843
- margin-top: 0.25rem;
6843
+ .compose-sheet-sub {
6844
6844
  color: var(--site-text-secondary);
6845
- font-size: var(--compose-panel-hint-size);
6845
+ font-size: 0.75rem;
6846
+ line-height: 1.4;
6847
+ }
6848
+
6849
+ .compose-sheet-check,
6850
+ .compose-sheet-caret {
6851
+ display: inline-flex;
6852
+ flex-shrink: 0;
6853
+ align-items: center;
6854
+ }
6855
+
6856
+ .compose-sheet-caret {
6857
+ color: var(--site-text-secondary);
6858
+ opacity: 0.75;
6859
+ }
6860
+
6861
+ .compose-sheet-hint {
6862
+ margin: 0;
6863
+ color: var(--site-text-secondary);
6864
+ font-size: 0.75rem;
6846
6865
  line-height: 1.45;
6847
- text-wrap: pretty;
6848
6866
  }
6849
6867
 
6850
- .compose-publish-section-action {
6868
+ .compose-sheet-divider {
6869
+ height: 1px;
6870
+ margin: 0.45rem 0.1rem;
6871
+ background-color: var(--site-divider);
6872
+ }
6873
+
6874
+ .compose-sheet-row-switch {
6875
+ cursor: default;
6876
+ }
6877
+
6878
+ .compose-sheet-row-switch > .input[role="switch"] {
6851
6879
  flex-shrink: 0;
6852
- min-height: 2.1rem;
6853
- padding: 0.42rem 0.78rem;
6854
- border: 1px solid var(--compose-control-border);
6880
+ }
6881
+
6882
+ /* ── Per-post date / permalink ──────────────────────────────────────
6883
+ Sits on the post it describes, not in the publish panel. Quiet until the
6884
+ post is hovered or focused, or the values are non-default. Uses
6885
+ margin-left:auto so appearing and disappearing never shifts the × next
6886
+ to it. */
6887
+
6888
+ .compose-post-meta {
6889
+ position: relative;
6890
+ margin-left: auto;
6891
+ /* Without this the flex item cannot shrink past its content's min-content
6892
+ width, so the header row overflows on narrow screens instead of
6893
+ ellipsising the permalink. */
6894
+ min-width: 0;
6895
+ opacity: 0;
6896
+ transition: opacity 0.18s ease;
6897
+ }
6898
+
6899
+ /* Narrow screens show the pill outright instead of revealing it. "Quiet until
6900
+ approached" needs a pointer to approach with, and this width is mostly
6901
+ touch, where there is none — an invisible-until-focused control is just a
6902
+ missing one.
6903
+
6904
+ It must also stay in the layout. Gating `display` on
6905
+ `jant-compose-editor:focus-within` made pressing the pill destroy it:
6906
+ pointerdown moves focus off the editor, the pill unmounts before mouseup,
6907
+ and a press that starts and ends on different elements never becomes a
6908
+ click. The pill was unclickable at this width — only `.click()` from script
6909
+ could open the panel. */
6910
+ @media (max-width: 760px) {
6911
+ .compose-post-meta {
6912
+ opacity: 1;
6913
+ }
6914
+ }
6915
+
6916
+ /* Only the first trailing control claims the free space. Two `auto` margins
6917
+ would split it and strand the pill mid-row. */
6918
+ .compose-post-meta ~ .compose-thread-post-remove,
6919
+ .compose-post-meta ~ .compose-close-btn {
6920
+ margin-left: 0;
6921
+ }
6922
+
6923
+ /* Scoped to the editor element rather than a layout row: single-post mode
6924
+ has no `.compose-editor-row` wrapper to hang this off. */
6925
+ jant-compose-editor:hover .compose-post-meta,
6926
+ jant-compose-editor:focus-within .compose-post-meta,
6927
+ .compose-post-meta-set,
6928
+ .compose-post-meta-open,
6929
+ .compose-post-meta:hover,
6930
+ .compose-post-meta:focus-within {
6931
+ opacity: 1;
6932
+ }
6933
+
6934
+ /* A lone post shows its pill outright. "Quiet until approached" only pays
6935
+ off when several rows would each carry one — with a single pill there is
6936
+ no noise to save, and hiding it made the date/permalink fade out exactly
6937
+ when focus moved to the publish controls, where that metadata matters
6938
+ most. The child combinator is the single-post detector: only that mode
6939
+ renders the editor as a direct child of `.compose-scroll`; reply and
6940
+ thread rows sit inside `.compose-editor-row` and stay quiet. */
6941
+ .compose-scroll > jant-compose-editor .compose-post-meta {
6942
+ opacity: 1;
6943
+ }
6944
+
6945
+ /* Defaults are the common case, so at rest this is a caption rather than a
6946
+ control: no chrome, and dimmer than the format selector it shares the row
6947
+ with. The outline arrives on hover, once it is being aimed at. */
6948
+ .compose-post-meta-pill {
6949
+ display: inline-flex;
6950
+ align-items: center;
6951
+ gap: 5px;
6952
+ max-width: min(17rem, 52vw);
6953
+ min-width: 0;
6954
+ padding: 3px 10px 3px 8px;
6955
+ border: 1px solid transparent;
6855
6956
  border-radius: 999px;
6856
- background-color: var(--compose-control-bg);
6857
- color: color-mix(
6858
- in srgb,
6859
- var(--site-text-primary) 74%,
6860
- var(--site-text-secondary)
6861
- );
6862
- font-size: var(--compose-panel-control-size);
6863
- font-weight: var(--fw-medium);
6864
- line-height: 1;
6957
+ background-color: transparent;
6958
+ color: color-mix(in srgb, var(--site-text-secondary) 62%, transparent);
6959
+ font-family: var(--font-ui);
6960
+ font-size: var(--type-ui-caption);
6961
+ white-space: nowrap;
6865
6962
  cursor: pointer;
6866
6963
  transition:
6867
- color 0.16s ease,
6868
- background-color 0.16s ease,
6869
- border-color 0.16s ease,
6870
- transform 0.16s ease;
6964
+ background-color 0.15s ease,
6965
+ border-color 0.15s ease,
6966
+ color 0.15s ease;
6871
6967
  }
6872
6968
 
6873
- .compose-publish-section-action:hover {
6969
+ .compose-post-meta-pill:hover,
6970
+ .compose-post-meta-pill[aria-expanded="true"] {
6971
+ border-color: var(--compose-control-border);
6972
+ background-color: var(--compose-control-bg);
6874
6973
  color: var(--site-text-primary);
6875
- border-color: color-mix(in srgb, var(--site-text-primary) 14%, transparent);
6876
- background-color: var(--compose-control-bg-strong);
6877
- transform: translateY(-1px);
6878
6974
  }
6879
6975
 
6880
- .compose-publish-section-action:focus-visible {
6881
- outline: 2px solid
6882
- color-mix(in srgb, var(--site-text-primary) 18%, transparent);
6883
- outline-offset: 2px;
6976
+ /* A date or permalink the author chose is real information, not a default —
6977
+ it gets the chrome back and full contrast. */
6978
+ .compose-post-meta-set .compose-post-meta-pill {
6979
+ border-color: color-mix(in srgb, var(--site-text-primary) 32%, transparent);
6980
+ background-color: var(--compose-paper-bg);
6981
+ color: var(--site-text-primary);
6982
+ font-weight: var(--fw-medium);
6884
6983
  }
6885
6984
 
6886
- .compose-publish-switch-row {
6887
- display: flex;
6888
- align-items: flex-start;
6889
- justify-content: space-between;
6890
- gap: 0.75rem;
6891
- cursor: pointer;
6985
+ .compose-post-meta-icon {
6986
+ flex-shrink: 0;
6987
+ opacity: 0.85;
6892
6988
  }
6893
6989
 
6894
- .compose-publish-switch-row > .input[role="switch"] {
6895
- flex-shrink: 0;
6896
- margin-top: 0.1rem;
6990
+ .compose-post-meta-sep {
6991
+ color: color-mix(in srgb, currentcolor 45%, transparent);
6897
6992
  }
6898
6993
 
6899
- .compose-publish-divider {
6900
- height: 1px;
6901
- margin: 0.75rem 0;
6902
- background-color: color-mix(
6903
- in srgb,
6904
- var(--site-text-primary) 8%,
6905
- transparent
6906
- );
6994
+ /* Only the permalink gives ground when space runs out — the date is short and
6995
+ already as terse as it gets. Without `overflow: hidden` a shrunk flex item
6996
+ still paints its full text, so a long slug used to run under the date. */
6997
+ .compose-post-meta-value {
6998
+ flex-shrink: 0;
6999
+ min-width: 0;
7000
+ overflow: hidden;
6907
7001
  }
6908
7002
 
6909
- /* --- Visibility segmented control --------------------------------------- */
7003
+ .compose-post-meta-value-slug {
7004
+ flex-shrink: 1;
7005
+ text-overflow: ellipsis;
7006
+ }
6910
7007
 
6911
- .compose-publish-chips {
7008
+ /* Fixed, not absolute. The pill it hangs from lives inside `.compose-scroll`,
7009
+ and an absolute panel is clipped by that box — an empty post makes the box
7010
+ shorter than the panel every time, so the footer went missing and focusing
7011
+ the panel scrolled the format switcher off the top to compensate. Fixed
7012
+ leaves the scroller behind; `_updatePostMetaPanelLayout` supplies top/left
7013
+ and the max-height, since only script can know where the pill ended up. */
7014
+ .compose-post-meta-panel {
7015
+ position: fixed;
7016
+ z-index: 100;
7017
+ top: 0;
7018
+ left: 0;
6912
7019
  display: flex;
7020
+ flex-direction: column;
7021
+ gap: 0.85rem;
7022
+ width: min(20rem, calc(100vw - 3rem));
7023
+ /* A window too short for the whole panel scrolls the panel, never crops it.
7024
+ Unset until script measures, so the panel is never briefly 1px tall. */
7025
+ max-height: var(--compose-post-meta-panel-max-height, none);
7026
+ overflow-y: auto;
7027
+ overscroll-behavior: contain;
7028
+ padding: 0.8rem 0.85rem 0.85rem;
7029
+ border: 1px solid var(--compose-control-border);
6913
7030
  border-radius: 12px;
6914
- padding: 4px;
6915
- background-color: var(--compose-control-bg);
6916
- box-shadow: inset 0 0 0 1px var(--compose-control-border);
7031
+ /* Same surface treatment as .compose-publish-panel — it is the same kind
7032
+ of floating panel, and --compose-floating-bg is what adapts to dark. */
7033
+ background-color: var(--compose-floating-bg);
7034
+ box-shadow:
7035
+ 0 10px 28px rgba(0, 0, 0, 0.08),
7036
+ 0 1px 3px rgba(0, 0, 0, 0.05);
6917
7037
  }
6918
7038
 
6919
- .compose-publish-chip {
6920
- flex: 1;
6921
- display: inline-flex;
6922
- align-items: center;
6923
- justify-content: center;
6924
- gap: 0.3rem;
6925
- min-height: 2.45rem;
6926
- padding: 0.46rem 0.5rem;
6927
- border-radius: 10px;
7039
+ /* It takes focus when it opens so the dialog is announced and Tab lands in
7040
+ the fields, but it is a container, not a control — no focus ring. */
7041
+ .compose-post-meta-panel:focus {
7042
+ outline: none;
7043
+ }
7044
+
7045
+ /* ── Date / permalink form ──────────────────────────────────────────
7046
+ Two settings, so they are both just open: a drill row per field meant
7047
+ the panel restated each value directly above the input showing it, and
7048
+ changed height every time one was opened. */
7049
+
7050
+ .compose-meta-field {
7051
+ display: flex;
7052
+ flex-direction: column;
7053
+ gap: 0.4rem;
7054
+ }
7055
+
7056
+ .compose-meta-field-head {
7057
+ display: flex;
7058
+ align-items: baseline;
7059
+ justify-content: space-between;
7060
+ gap: 0.6rem;
7061
+ min-height: 1.15rem;
7062
+ }
7063
+
7064
+ .compose-meta-label {
7065
+ color: var(--site-text-primary);
7066
+ font-size: 0.82rem;
7067
+ font-weight: var(--fw-medium);
7068
+ }
7069
+
7070
+ /* The panel's own way out. Escape and a click outside both close it, but
7071
+ neither announces itself, and on touch there is no Escape key at all. It
7072
+ spans the panel's padding so the divider reads as the foot of the panel
7073
+ rather than a line drawn inside it. */
7074
+ .compose-post-meta-footer {
7075
+ display: flex;
7076
+ justify-content: flex-end;
7077
+ margin: -0.1rem -0.85rem -0.85rem;
7078
+ padding: 0.4rem 0.6rem 0.5rem;
7079
+ border-top: 1px solid
7080
+ color-mix(in srgb, var(--site-divider) 60%, transparent);
7081
+ }
7082
+
7083
+ .compose-post-meta-open {
7084
+ z-index: 100;
7085
+ }
7086
+
7087
+ /* Options trigger: its own button past Publish, never a chevron welded to
7088
+ it. A split button reads as one button. */
7089
+ .compose-options-trigger {
7090
+ display: inline-grid;
7091
+ flex-shrink: 0;
7092
+ place-items: center;
7093
+ width: 2.2rem;
7094
+ height: 2.2rem;
6928
7095
  border: none;
7096
+ border-radius: 999px;
6929
7097
  background: transparent;
6930
- cursor: pointer;
6931
- white-space: nowrap;
6932
7098
  color: var(--site-text-secondary);
7099
+ cursor: pointer;
6933
7100
  transition:
6934
7101
  background-color 0.18s ease,
6935
- box-shadow 0.18s ease,
6936
7102
  color 0.18s ease;
6937
7103
  }
6938
7104
 
6939
- .compose-publish-chip:hover:not(:disabled):not(
6940
- .compose-publish-chip-selected
6941
- ) {
7105
+ .compose-options-trigger:hover:not(:disabled),
7106
+ .compose-options-trigger-open {
7107
+ background-color: var(--compose-control-bg);
6942
7108
  color: var(--site-text-primary);
6943
7109
  }
6944
7110
 
6945
- .compose-publish-chip:disabled {
6946
- cursor: default;
6947
- opacity: 0.72;
7111
+ .compose-options-trigger:disabled {
7112
+ cursor: not-allowed;
7113
+ opacity: 0.5;
6948
7114
  }
6949
7115
 
6950
- .compose-publish-chip-selected {
6951
- background-color: var(--compose-paper-bg);
6952
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
6953
- color: var(--site-text-primary);
7116
+ .compose-options-trigger:focus-visible {
7117
+ outline: 2px solid
7118
+ color-mix(in srgb, var(--site-text-primary) 22%, transparent);
7119
+ outline-offset: 2px;
6954
7120
  }
6955
7121
 
6956
- .compose-publish-chip-label {
6957
- font-size: 0.875rem;
6958
- font-weight: var(--fw-medium);
6959
- letter-spacing: 0.005em;
6960
- line-height: 1.28;
7122
+ .compose-options-trigger-icon {
7123
+ width: 1.35rem;
7124
+ height: 1.35rem;
6961
7125
  }
6962
7126
 
6963
- .compose-publish-chip-hint {
7127
+ /* Sits on the field's label row, so it reads as an aside to that label rather
7128
+ than a second thing to do. It only appears once there is a value to undo,
7129
+ which is why it can afford to be this quiet. */
7130
+ .compose-publish-section-action {
7131
+ flex-shrink: 0;
7132
+ padding: 0;
7133
+ border: none;
7134
+ background: none;
6964
7135
  color: var(--site-text-secondary);
6965
- font-size: var(--compose-panel-hint-size);
6966
- line-height: 1.45;
6967
- text-wrap: pretty;
7136
+ font-size: 0.75rem;
7137
+ line-height: 1.2;
7138
+ text-underline-offset: 2px;
7139
+ cursor: pointer;
7140
+ transition: color 0.16s ease;
7141
+ }
7142
+
7143
+ .compose-publish-section-action:hover {
7144
+ color: var(--site-text-primary);
7145
+ text-decoration: underline;
7146
+ }
7147
+
7148
+ .compose-publish-section-action:focus-visible {
7149
+ outline: 2px solid
7150
+ color-mix(in srgb, var(--site-text-primary) 18%, transparent);
7151
+ outline-offset: 3px;
7152
+ border-radius: 4px;
6968
7153
  }
6969
7154
 
7155
+ /* --- Visibility segmented control --------------------------------------- */
7156
+
6970
7157
  .compose-publish-visibility-icon {
6971
7158
  display: inline-flex;
6972
7159
  align-items: center;
@@ -7079,90 +7266,46 @@
7079
7266
  background-color: var(--compose-control-border);
7080
7267
  }
7081
7268
 
7269
+ /* One line, no card: a card holding a chip holding the slug was three nested
7270
+ frames for what is a single tap-to-accept. */
7082
7271
  .compose-slug-suggestion {
7083
- display: grid;
7084
- grid-template-columns: minmax(0, 1fr) auto;
7085
- align-items: center;
7086
- gap: 0.65rem;
7272
+ display: flex;
7273
+ align-items: baseline;
7274
+ gap: 0.4rem;
7087
7275
  width: 100%;
7088
- padding: 0.62rem 0.68rem;
7089
- border: 1px solid var(--compose-control-border);
7090
- border-radius: 10px;
7091
- background-color: color-mix(
7092
- in srgb,
7093
- var(--compose-control-bg) 72%,
7094
- var(--compose-paper-bg)
7095
- );
7096
- box-shadow:
7097
- inset 0 1px 0 color-mix(in srgb, var(--compose-paper-bg) 88%, transparent),
7098
- 0 1px 2px rgba(0, 0, 0, 0.03);
7276
+ margin: -0.05rem -0.35rem;
7277
+ padding: 0.3rem 0.35rem;
7278
+ border: none;
7279
+ border-radius: 7px;
7280
+ background: none;
7099
7281
  cursor: pointer;
7100
7282
  text-align: left;
7101
- transition:
7102
- background-color 0.16s ease,
7103
- border-color 0.16s ease,
7104
- transform 0.16s ease,
7105
- box-shadow 0.16s ease;
7283
+ transition: background-color 0.16s ease;
7106
7284
  }
7107
7285
 
7108
7286
  .compose-slug-suggestion:hover {
7109
- border-color: color-mix(in srgb, var(--site-text-primary) 14%, transparent);
7110
- background-color: color-mix(
7111
- in srgb,
7112
- var(--compose-control-bg-strong) 78%,
7113
- var(--compose-paper-bg)
7114
- );
7115
- transform: translateY(-1px);
7116
- box-shadow:
7117
- inset 0 1px 0 color-mix(in srgb, var(--compose-paper-bg) 88%, transparent),
7118
- 0 4px 14px rgba(0, 0, 0, 0.045);
7287
+ background-color: var(--compose-control-bg);
7119
7288
  }
7120
7289
 
7121
7290
  .compose-slug-suggestion:focus-visible {
7122
7291
  outline: 2px solid
7123
7292
  color-mix(in srgb, var(--site-text-primary) 18%, transparent);
7124
- outline-offset: 2px;
7125
- }
7126
-
7127
- .compose-slug-suggestion-copy {
7128
- min-width: 0;
7129
- display: flex;
7130
- align-items: center;
7131
- flex-wrap: wrap;
7132
- gap: 0.45rem;
7293
+ outline-offset: 1px;
7133
7294
  }
7134
7295
 
7135
7296
  .compose-slug-suggestion-label {
7297
+ flex-shrink: 0;
7136
7298
  color: var(--site-text-secondary);
7137
- font-size: var(--type-ui-micro);
7138
- font-weight: var(--fw-medium);
7139
- letter-spacing: 0.01em;
7140
- line-height: 1.2;
7141
- }
7142
-
7143
- .compose-slug-suggestion-chip {
7144
- display: inline-flex;
7145
- align-items: center;
7146
- min-width: 0;
7147
- max-width: 100%;
7148
- padding: 0.25rem 0.48rem;
7149
- border-radius: 999px;
7150
- border: 1px solid var(--compose-control-border);
7151
- background-color: color-mix(
7152
- in srgb,
7153
- var(--compose-paper-bg) 92%,
7154
- var(--compose-control-bg)
7155
- );
7156
- box-shadow: inset 0 1px 0
7157
- color-mix(in srgb, var(--compose-paper-bg) 88%, transparent);
7299
+ font-size: 0.75rem;
7300
+ line-height: 1.3;
7158
7301
  }
7159
7302
 
7160
7303
  .compose-slug-suggestion-value {
7161
- display: block;
7304
+ min-width: 0;
7162
7305
  color: var(--site-text-primary);
7163
7306
  font-size: var(--type-ui-caption);
7164
7307
  font-weight: var(--fw-medium);
7165
- line-height: 1.2;
7308
+ line-height: 1.3;
7166
7309
  font-family:
7167
7310
  ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
7168
7311
  "Courier New", monospace;
@@ -7173,101 +7316,61 @@
7173
7316
 
7174
7317
  .compose-slug-suggestion-icon {
7175
7318
  display: inline-flex;
7319
+ flex-shrink: 0;
7176
7320
  align-items: center;
7177
- justify-content: center;
7178
- width: 1.65rem;
7179
- height: 1.65rem;
7180
- border-radius: 999px;
7181
- background-color: color-mix(
7182
- in srgb,
7183
- var(--compose-paper-bg) 88%,
7184
- var(--compose-control-bg)
7185
- );
7186
- color: color-mix(
7187
- in srgb,
7188
- var(--site-text-primary) 72%,
7189
- var(--site-text-secondary)
7190
- );
7321
+ margin-left: auto;
7322
+ color: var(--site-text-secondary);
7323
+ transform: translateY(0.1em);
7191
7324
  transition:
7192
7325
  transform 0.16s ease,
7193
- background-color 0.16s ease,
7194
7326
  color 0.16s ease;
7195
7327
  }
7196
7328
 
7197
7329
  .compose-slug-suggestion:hover .compose-slug-suggestion-icon {
7198
- transform: translateX(1px);
7199
- background-color: color-mix(
7200
- in srgb,
7201
- var(--compose-paper-bg) 66%,
7202
- var(--compose-control-bg-strong)
7203
- );
7330
+ transform: translate(2px, 0.1em);
7204
7331
  color: var(--site-text-primary);
7205
7332
  }
7206
7333
 
7207
- .compose-publish-slug-field,
7208
- .compose-publish-date-field {
7209
- display: flex;
7210
- flex-direction: column;
7211
- gap: 0.55rem;
7212
- }
7213
-
7334
+ /* Scaled to the panel it sits in. The old field was 3rem tall with a 14px
7335
+ radius, a gradient, an inset highlight and a drop shadow — more chrome than
7336
+ the dialog's own controls carry. */
7214
7337
  .compose-publish-slug-input-wrap,
7215
7338
  .compose-publish-date-input-wrap {
7216
7339
  display: flex;
7217
7340
  align-items: center;
7218
- gap: 0.78rem;
7219
- min-height: 3rem;
7220
- padding: 0 0.95rem;
7341
+ gap: 0.5rem;
7342
+ min-height: 2.3rem;
7343
+ padding: 0 0.6rem;
7221
7344
  border: 1px solid var(--compose-control-border);
7222
- border-radius: 14px;
7223
- background:
7224
- linear-gradient(
7225
- 180deg,
7226
- color-mix(in srgb, var(--compose-paper-bg) 94%, transparent),
7227
- color-mix(in srgb, var(--compose-control-bg) 16%, transparent)
7228
- ),
7229
- color-mix(in srgb, var(--compose-paper-bg) 97%, var(--compose-control-bg));
7230
- box-shadow:
7231
- inset 0 1px 0 color-mix(in srgb, var(--compose-paper-bg) 92%, transparent),
7232
- 0 1px 2px rgba(0, 0, 0, 0.02);
7345
+ border-radius: 9px;
7346
+ background-color: color-mix(
7347
+ in srgb,
7348
+ var(--compose-control-bg) 55%,
7349
+ var(--compose-paper-bg)
7350
+ );
7233
7351
  transition:
7234
7352
  border-color 0.16s ease,
7235
- background-color 0.16s ease,
7236
7353
  box-shadow 0.16s ease;
7237
7354
  }
7238
7355
 
7239
7356
  .compose-publish-slug-input-wrap:focus-within,
7240
7357
  .compose-publish-date-input-wrap:focus-within {
7241
- border-color: color-mix(in srgb, var(--site-text-primary) 16%, transparent);
7242
- background:
7243
- linear-gradient(
7244
- 180deg,
7245
- color-mix(in srgb, var(--compose-paper-bg) 96%, transparent),
7246
- color-mix(in srgb, var(--compose-control-bg-strong) 24%, transparent)
7247
- ),
7248
- color-mix(
7249
- in srgb,
7250
- var(--compose-paper-bg) 96%,
7251
- var(--compose-control-bg-strong)
7252
- );
7253
- box-shadow:
7254
- inset 0 1px 0 color-mix(in srgb, var(--compose-paper-bg) 94%, transparent),
7255
- 0 0 0 3px color-mix(in srgb, var(--site-text-primary) 6%, transparent);
7358
+ border-color: color-mix(in srgb, var(--site-text-primary) 22%, transparent);
7359
+ box-shadow: 0 0 0 3px
7360
+ color-mix(in srgb, var(--site-text-primary) 7%, transparent);
7361
+ }
7362
+
7363
+ /* The message under the field is red; the field it belongs to should say so
7364
+ too, rather than leaving the reader to pair them up. */
7365
+ .compose-publish-slug-input-wrap:has([aria-invalid="true"]),
7366
+ .compose-publish-date-input-wrap:has([aria-invalid="true"]) {
7367
+ border-color: color-mix(in srgb, var(--destructive) 42%, transparent);
7256
7368
  }
7257
7369
 
7258
7370
  .compose-publish-slug-prefix {
7259
7371
  flex-shrink: 0;
7260
- min-width: 1.25rem;
7261
- padding-right: 0.78rem;
7262
- border-right: 1px solid
7263
- color-mix(in srgb, var(--site-text-primary) 8%, transparent);
7264
- color: color-mix(
7265
- in srgb,
7266
- var(--site-text-primary) 52%,
7267
- var(--site-text-secondary)
7268
- );
7269
- font-size: var(--compose-panel-control-size);
7270
- font-weight: var(--fw-medium);
7372
+ color: var(--site-text-secondary);
7373
+ font-size: var(--type-ui-caption);
7271
7374
  line-height: 1;
7272
7375
  font-family:
7273
7376
  ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
@@ -7283,9 +7386,8 @@
7283
7386
  padding: 0;
7284
7387
  box-shadow: none;
7285
7388
  color: var(--site-text-primary);
7286
- font-size: var(--compose-inline-input-size);
7287
- font-weight: var(--fw-medium);
7288
- line-height: 1.35;
7389
+ font-size: var(--type-ui-caption);
7390
+ line-height: 1.4;
7289
7391
  font-family:
7290
7392
  ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
7291
7393
  "Courier New", monospace;
@@ -7295,13 +7397,56 @@
7295
7397
  font-family: inherit;
7296
7398
  }
7297
7399
 
7400
+ /* The browser paints `yyyy-mm-dd` in the value's own colour, so an untouched
7401
+ field reads as a date that was chosen. Only the empty state is dimmed. */
7402
+ .compose-publish-date-input-empty::-webkit-datetime-edit {
7403
+ color: var(--site-text-secondary);
7404
+ opacity: 0.75;
7405
+ }
7406
+
7407
+ /* Replaced by .compose-publish-date-picker, which drives the same picker from
7408
+ a target the height of the field instead of a ~13px UA glyph. */
7409
+ .compose-publish-date-input::-webkit-calendar-picker-indicator {
7410
+ display: none;
7411
+ }
7412
+
7413
+ .compose-publish-date-picker {
7414
+ display: inline-grid;
7415
+ flex-shrink: 0;
7416
+ place-items: center;
7417
+ width: 1.7rem;
7418
+ height: 1.7rem;
7419
+ margin-right: -0.35rem;
7420
+ border: none;
7421
+ border-radius: 7px;
7422
+ background: transparent;
7423
+ color: var(--site-text-secondary);
7424
+ cursor: pointer;
7425
+ transition:
7426
+ background-color 0.16s ease,
7427
+ color 0.16s ease;
7428
+ }
7429
+
7430
+ .compose-publish-date-picker:hover {
7431
+ background-color: var(--compose-control-bg-strong);
7432
+ color: var(--site-text-primary);
7433
+ }
7434
+
7435
+ .compose-publish-date-picker:focus-visible {
7436
+ outline: 2px solid
7437
+ color-mix(in srgb, var(--site-text-primary) 22%, transparent);
7438
+ outline-offset: 1px;
7439
+ }
7440
+
7441
+ .compose-publish-date-picker-icon {
7442
+ width: 0.95rem;
7443
+ height: 0.95rem;
7444
+ }
7445
+
7298
7446
  .compose-publish-slug-input::placeholder,
7299
7447
  .compose-publish-date-input::placeholder {
7300
- color: color-mix(
7301
- in srgb,
7302
- var(--site-text-secondary) 82%,
7303
- var(--compose-paper-bg)
7304
- );
7448
+ color: var(--site-text-secondary);
7449
+ opacity: 0.75;
7305
7450
  }
7306
7451
 
7307
7452
  .compose-publish-slug-input:focus,
@@ -7322,21 +7467,13 @@
7322
7467
  color: var(--destructive);
7323
7468
  }
7324
7469
 
7470
+ /* The URL a slug resolves to is a confirmation, not a control — a bordered
7471
+ box for it made a third framed thing in a panel with two fields. */
7325
7472
  .compose-publish-slug-preview {
7326
- display: flex;
7327
- flex-wrap: wrap;
7328
- align-items: baseline;
7329
- gap: 0.04rem;
7330
- padding: 0.48rem 0.62rem;
7331
- border-radius: 9px;
7332
- border: 1px solid var(--compose-control-border);
7333
- background-color: color-mix(
7334
- in srgb,
7335
- var(--compose-control-bg) 58%,
7336
- var(--compose-paper-bg)
7337
- );
7338
- font-size: var(--type-ui-caption);
7339
- line-height: 1.35;
7473
+ margin: 0;
7474
+ color: var(--site-text-secondary);
7475
+ font-size: var(--type-ui-micro);
7476
+ line-height: 1.4;
7340
7477
  word-break: break-word;
7341
7478
  font-family:
7342
7479
  ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
@@ -7344,19 +7481,11 @@
7344
7481
  }
7345
7482
 
7346
7483
  .compose-publish-slug-preview-origin {
7347
- color: color-mix(
7348
- in srgb,
7349
- var(--site-text-secondary) 92%,
7350
- var(--compose-paper-bg)
7351
- );
7352
- font-size: var(--type-ui-micro);
7353
- font-weight: var(--fw-normal);
7484
+ opacity: 0.75;
7354
7485
  }
7355
7486
 
7356
7487
  .compose-publish-slug-preview-path {
7357
7488
  color: var(--site-text-primary);
7358
- font-size: var(--type-ui-caption);
7359
- font-weight: var(--fw-medium);
7360
7489
  overflow-wrap: anywhere;
7361
7490
  }
7362
7491
 
@@ -7455,10 +7584,13 @@
7455
7584
  transform: scale(1.02);
7456
7585
  }
7457
7586
 
7587
+ /* Sized to be looked at, not to leave room for a pinned toolbar. Nothing
7588
+ clips the dock any more, so a tall preview just makes `.compose-scroll`
7589
+ longer. The dvh half keeps it from filling a short window on its own. */
7458
7590
  .compose-attachment-img {
7459
7591
  display: block;
7460
7592
  border-radius: var(--media-radius, 0.5rem);
7461
- max-height: min(160px, 18dvh);
7593
+ max-height: min(420px, 46dvh);
7462
7594
  object-fit: contain;
7463
7595
  }
7464
7596
 
@@ -7515,7 +7647,7 @@
7515
7647
  cropped from the top (object-fit: cover) instead of shrinking to a sliver. */
7516
7648
  .compose-attachment:only-child .compose-attachment-img {
7517
7649
  max-width: 100%;
7518
- max-height: min(200px, 22dvh);
7650
+ max-height: min(460px, 52dvh);
7519
7651
  min-width: 48px;
7520
7652
  object-fit: cover;
7521
7653
  object-position: center top;
@@ -7526,9 +7658,9 @@
7526
7658
  aspect-ratio: 4 / 3;
7527
7659
  }
7528
7660
 
7529
- /* Multiple images: fixed height, cover */
7661
+ /* Multiple images: one shared height so the row reads as a strip, cover */
7530
7662
  .compose-attachment:not(:only-child) .compose-attachment-img {
7531
- height: 200px;
7663
+ height: min(340px, 40dvh);
7532
7664
  width: auto;
7533
7665
  min-width: 120px;
7534
7666
  object-fit: cover;
@@ -7867,18 +7999,21 @@
7867
7999
  --compose-thread-padding-left: 16px;
7868
8000
  --compose-thread-padding-right: 16px;
7869
8001
  --compose-thread-gap: 12px;
7870
- --compose-thread-rail-left: calc(var(--compose-thread-padding-left) + 5px);
7871
- --site-thread-marker-surface: var(--compose-paper-bg);
8002
+ /* Fixed track every rail child sits in and centres its circle inside, so the
8003
+ line's x depends on the track alone and no dot size can pull it off centre.
8004
+ The constant this replaced was half the *feed's* 10px marker and stayed
8005
+ behind when compose dropped to 7px, leaving the line down the dot's edge. */
8006
+ --compose-thread-rail-width: 10px;
8007
+ --compose-thread-rail-left: calc(
8008
+ var(--compose-thread-padding-left) + var(--compose-thread-rail-width) / 2
8009
+ );
7872
8010
  display: flex;
7873
8011
  flex-direction: column;
7874
- flex: 1;
7875
- min-height: 0;
7876
- overflow: hidden;
7877
8012
  position: relative;
7878
8013
  }
7879
8014
 
7880
8015
  .compose-reply-compose-layout {
7881
- --compose-title-input-size: var(--type-content-subtitle);
8016
+ --compose-title-input-size: calc(var(--type-content-body) * 1.2);
7882
8017
  --compose-title-input-leading: 1.35;
7883
8018
  --compose-quote-input-size: calc(var(--type-content-body) * 1.06);
7884
8019
  --compose-quote-input-leading: 1.42;
@@ -7901,8 +8036,9 @@
7901
8036
  width: 1.55rem;
7902
8037
  }
7903
8038
 
8039
+ /* Height comes from the shared four-line floor, which already reads this
8040
+ layout's own leading and type size — only the lead-in changes here. */
7904
8041
  .compose-reply-compose-layout .compose-quote-text {
7905
- min-height: 6.5rem;
7906
8042
  padding-top: 0.35rem;
7907
8043
  }
7908
8044
 
@@ -7917,28 +8053,68 @@
7917
8053
  left: 1rem;
7918
8054
  width: 1.6rem;
7919
8055
  }
8056
+ }
7920
8057
 
7921
- .compose-reply-compose-layout .compose-quote-text {
7922
- min-height: 6.5rem;
7923
- padding-top: 0.35rem;
7924
- }
8058
+ /* The rail is drawn one row at a time rather than as a single strip down the
8059
+ layout, so the two ends can be trimmed: it starts at the first dot and
8060
+ stops at the last post's. Under the last post it used to keep going —
8061
+ that's the tallest row in the layout, so it was a long line pointing at
8062
+ nothing. */
8063
+ /* Distance from a row's top edge to the centre of its dot: where the rail
8064
+ has to start or stop, and where the dot itself has to land. The dot derives
8065
+ its own margin from this pair (see `.compose-thread-dot`) rather than
8066
+ restating it, so resizing the marker cannot drift the line off the dot. */
8067
+ .compose-thread-layout > * {
8068
+ position: relative;
8069
+ --compose-thread-row-padding-top: 0px;
8070
+ --compose-thread-dot-center: calc(0.65rem + 5px);
8071
+ }
8072
+
8073
+ .compose-thread-layout > .compose-editor-row {
8074
+ --compose-thread-row-padding-top: 14px;
8075
+ --compose-thread-dot-center: calc(14px + 1rem + 7px);
8076
+ }
8077
+
8078
+ .compose-thread-compose-layout > .compose-editor-row {
8079
+ --compose-thread-row-padding-top: 4px;
8080
+ --compose-thread-dot-center: calc(4px + 1.05rem + 7px);
7925
8081
  }
7926
8082
 
7927
- /* Continuous vertical line through the rail */
7928
- .compose-thread-layout::before {
8083
+ .compose-thread-layout > *::before {
7929
8084
  content: "";
7930
8085
  position: absolute;
7931
8086
  left: var(--compose-thread-rail-left);
7932
8087
  top: 0;
7933
8088
  bottom: 0;
7934
- width: 1px;
7935
- background: linear-gradient(
7936
- 180deg,
7937
- transparent 0,
7938
- color-mix(in srgb, var(--site-threadline) 85%, transparent) 8%,
7939
- color-mix(in srgb, var(--site-threadline) 55%, transparent) 100%
8089
+ width: var(--site-thread-rail-line-width);
8090
+ margin-left: calc(var(--site-thread-rail-line-width) / -2);
8091
+ border-radius: 1px;
8092
+ /* Matched to the weight of the reading rail's gradient rather than its
8093
+ implementation. That one runs 85% → 55% down its length, so ~70% flat
8094
+ reads at the same strength here. It can't literally share the gradient:
8095
+ this rail is drawn per row so both ends can be trimmed, and a per-row
8096
+ gradient would fade in once per post instead of once per thread. */
8097
+ background-color: color-mix(
8098
+ in srgb,
8099
+ var(--site-threadline) 70%,
8100
+ transparent
7940
8101
  );
7941
- z-index: 0;
8102
+ }
8103
+
8104
+ .compose-thread-layout > :first-child::before {
8105
+ top: var(--compose-thread-dot-center);
8106
+ }
8107
+
8108
+ /* The last post ends the rail. `.compose-thread-add-row` sits below it and is
8109
+ a placeholder, not a post, so its dashed dot is left unconnected. */
8110
+ .compose-thread-layout
8111
+ > .compose-editor-row:not(:has(~ .compose-editor-row))::before {
8112
+ bottom: auto;
8113
+ height: var(--compose-thread-dot-center);
8114
+ }
8115
+
8116
+ .compose-thread-layout > .compose-thread-add-row::before {
8117
+ display: none;
7942
8118
  }
7943
8119
 
7944
8120
  /* Row containing dot + reply context */
@@ -7952,152 +8128,72 @@
7952
8128
 
7953
8129
  /* Row containing dot + editor */
7954
8130
  .compose-editor-row {
8131
+ /* The rail already indents this row from outside the editor, so nothing
8132
+ inside it adds a gutter of its own. */
8133
+ --compose-gutter: 0px;
8134
+
7955
8135
  display: flex;
7956
8136
  align-items: flex-start;
7957
8137
  gap: var(--compose-thread-gap);
7958
8138
  padding-right: var(--compose-thread-padding-right);
7959
8139
  padding-left: var(--compose-thread-padding-left);
7960
- flex: 1;
7961
- min-height: 0;
7962
- overflow: hidden;
8140
+ padding-top: 4px;
8141
+ padding-bottom: 4px;
7963
8142
  }
7964
8143
 
8144
+ /* `flex: 1` here is horizontal — it claims the width left by the rail dot.
8145
+ The row's height comes from the editor's own content; nothing in this
8146
+ chain clips or scrolls, that is `.compose-scroll`'s job. */
7965
8147
  .compose-editor-row > jant-compose-editor {
7966
8148
  flex: 1;
7967
- min-height: 0;
8149
+ min-width: 0;
7968
8150
  display: flex;
7969
8151
  flex-direction: column;
7970
- align-self: stretch;
7971
- overflow: hidden;
7972
8152
  position: relative;
7973
8153
  }
7974
8154
 
7975
- /* Reduce editor body left padding — the thread rail already provides indent */
7976
- .compose-editor-row .compose-body {
7977
- padding-left: 0;
7978
- }
7979
-
7980
8155
  .compose-editor-row .compose-attachments-dock {
7981
- flex: 0 1 auto;
7982
- min-height: 0;
7983
- max-height: min(240px, 34dvh);
7984
- overflow-y: auto;
7985
- padding-inline: 0;
8156
+ padding-inline-end: 0;
7986
8157
  background: transparent;
7987
8158
  }
7988
8159
 
7989
- .compose-reply-compose-layout .compose-editor-row .compose-attachments-dock {
7990
- --compose-reply-attachment-width: 96px;
7991
- --compose-reply-attachment-height: 72px;
7992
-
7993
- max-height: min(116px, 18dvh);
7994
- padding-block: 4px 2px;
7995
- }
7996
-
7997
- .compose-reply-compose-layout .compose-editor-row .compose-attachments {
7998
- gap: 6px;
7999
- padding-block: 0;
8000
- }
8001
-
8002
- .compose-reply-compose-layout .compose-editor-row .compose-attachment {
8003
- gap: 2px;
8004
- max-width: none;
8005
- }
8006
-
8007
- .compose-reply-compose-layout
8008
- .compose-editor-row
8009
- .compose-attachment:has(.compose-attachment-preview-fallback) {
8010
- width: var(--compose-reply-attachment-width);
8011
- }
8012
-
8013
- .compose-reply-compose-layout
8014
- .compose-editor-row
8015
- .compose-attachment:only-child
8016
- .compose-attachment-img,
8017
- .compose-reply-compose-layout
8018
- .compose-editor-row
8019
- .compose-attachment:not(:only-child)
8020
- .compose-attachment-img {
8021
- width: var(--compose-reply-attachment-width);
8022
- height: var(--compose-reply-attachment-height);
8023
- min-width: 0;
8024
- max-width: var(--compose-reply-attachment-width);
8025
- max-height: var(--compose-reply-attachment-height);
8026
- object-fit: cover;
8027
- object-position: center;
8028
- }
8029
-
8030
- .compose-reply-compose-layout
8031
- .compose-editor-row
8032
- .compose-attachment-preview-fallback,
8033
- .compose-reply-compose-layout
8034
- .compose-editor-row
8035
- .compose-attachment-processing {
8036
- width: var(--compose-reply-attachment-width);
8037
- height: var(--compose-reply-attachment-height);
8038
- aspect-ratio: auto;
8039
- }
8040
-
8041
- .compose-reply-compose-layout
8042
- .compose-editor-row
8043
- .compose-attachment-preview-fallback
8044
- svg {
8045
- width: 28px;
8046
- height: 28px;
8047
- }
8048
-
8049
- .compose-reply-compose-layout
8050
- .compose-editor-row
8051
- .compose-attachment-file-card,
8052
- .compose-reply-compose-layout
8053
- .compose-editor-row
8054
- .compose-attachment:not(:only-child)
8055
- .compose-attachment-file-card {
8056
- width: 112px;
8057
- height: var(--compose-reply-attachment-height);
8058
- gap: 4px;
8059
- padding: 8px 10px;
8060
- }
8061
-
8062
- .compose-reply-compose-layout .compose-editor-row .compose-attachment-alt {
8063
- padding: 1px 3px;
8064
- font-size: var(--type-ui-micro);
8065
- line-height: 1.2;
8066
- }
8067
-
8068
- .compose-reply-compose-layout .compose-editor-row .compose-attachment-remove {
8069
- top: 4px;
8070
- right: 4px;
8071
- width: 20px;
8072
- height: 20px;
8073
- }
8074
-
8075
- /* Dot marker for thread positions */
8160
+ /* Dot marker for thread positions. Sits above the rail so it reads as a joint
8161
+ in the line, and takes its offset from the row's declared dot centre so the
8162
+ two ends of the rail always meet it. The element is the full rail track and
8163
+ only centres the circle inside it, which is what keeps the line through the
8164
+ dot's middle no matter how the marker is resized. */
8076
8165
  .compose-thread-dot {
8077
- width: var(--site-thread-marker-size);
8166
+ position: relative;
8167
+ z-index: 1;
8168
+ display: flex;
8169
+ justify-content: center;
8170
+ width: var(--compose-thread-rail-width);
8078
8171
  height: var(--site-thread-marker-size);
8079
- margin-top: 0.65rem;
8080
- border-radius: 50%;
8081
- border: var(--site-thread-marker-border-width) solid
8082
- var(--site-thread-marker-surface);
8083
- background-color: var(--site-threadline);
8172
+ margin-top: calc(
8173
+ var(--compose-thread-dot-center) - var(--compose-thread-row-padding-top) -
8174
+ var(--site-thread-marker-size) / 2
8175
+ );
8084
8176
  flex-shrink: 0;
8085
8177
  align-self: flex-start;
8086
- box-shadow: 0 0 0 var(--site-thread-marker-ring-width)
8087
- var(--site-thread-dot-ring);
8088
- z-index: 1;
8089
8178
  }
8090
8179
 
8091
- .compose-editor-row .compose-thread-dot {
8092
- margin-top: 1rem;
8093
- margin-left: calc(
8094
- (var(--site-thread-marker-size) - var(--site-thread-marker-hero-size)) / 2
8095
- );
8180
+ .compose-thread-dot::before {
8181
+ content: "";
8182
+ width: var(--site-thread-marker-size);
8183
+ height: var(--site-thread-marker-size);
8184
+ border-radius: 50%;
8185
+ background-color: var(--site-threadline);
8186
+ box-shadow: 0 0 0 var(--site-thread-marker-gap)
8187
+ var(--site-thread-marker-surface);
8188
+ transition: background-color 0.15s ease;
8096
8189
  }
8097
8190
 
8098
- /* In thread compose mode, dot aligns with the post header row */
8099
- .compose-thread-compose-layout .compose-editor-row .compose-thread-dot {
8100
- margin-top: 1.05rem;
8191
+ /* Every post in a thread is equally new, so accenting all of them says nothing
8192
+ — it just makes the rail loud. The accent marks where you are on the chain
8193
+ instead: the one post holding the cursor. The reply context above stays on
8194
+ the line's own colour. */
8195
+ .compose-editor-row.is-current .compose-thread-dot::before {
8196
+ background-color: var(--site-thread-marker-current);
8101
8197
  }
8102
8198
 
8103
8199
  .compose-reply-context {
@@ -8107,12 +8203,15 @@
8107
8203
  overflow: hidden;
8108
8204
  position: relative;
8109
8205
  padding: 0.08rem 0 0.18rem;
8110
- transition: max-height 0.3s ease;
8111
8206
  }
8112
8207
 
8208
+ /* "Show more" means show it: expanding hands the post its full height rather
8209
+ than a taller box with a second scrollbar in it. That inner scrollbar is
8210
+ what made the parent's pictures thumbnails — there was never room for one
8211
+ at full size. Length is the toggle's job now, not the preview's. */
8113
8212
  .compose-reply-context.expanded {
8114
- max-height: min(400px, 35dvh);
8115
- overflow-y: auto;
8213
+ max-height: none;
8214
+ overflow: visible;
8116
8215
  }
8117
8216
 
8118
8217
  .compose-reply-context-body {
@@ -8167,21 +8266,26 @@
8167
8266
  background-color: transparent;
8168
8267
  }
8169
8268
 
8269
+ /* The parent reads at the same scale as the reply being written — the same
8270
+ ceiling as `.compose-attachment-img`, the same radius token — so the two
8271
+ halves of the exchange look like the same material. This is the only size
8272
+ rule for pictures in the context: it covers both the ones inside the post's
8273
+ own HTML and the media strip below it, which sits in here too. */
8170
8274
  .compose-reply-context-body img,
8171
8275
  .compose-reply-context-body video {
8172
8276
  width: auto !important;
8173
8277
  height: auto !important;
8174
- max-height: 120px;
8175
- border-radius: 6px;
8278
+ max-height: min(420px, 46dvh);
8279
+ border-radius: var(--media-radius, 0.5rem);
8176
8280
  object-fit: contain;
8177
8281
  background-image: none !important;
8178
8282
  }
8179
8283
 
8180
8284
  .compose-reply-context-media {
8181
8285
  display: flex;
8182
- gap: 6px;
8286
+ gap: 8px;
8183
8287
  flex-wrap: wrap;
8184
- margin-top: 6px;
8288
+ margin-top: 8px;
8185
8289
  }
8186
8290
 
8187
8291
  .compose-reply-context-media-link {
@@ -8189,12 +8293,6 @@
8189
8293
  cursor: pointer;
8190
8294
  }
8191
8295
 
8192
- .compose-reply-context-media-img {
8193
- max-height: 80px;
8194
- border-radius: 6px;
8195
- object-fit: contain;
8196
- }
8197
-
8198
8296
  .compose-reply-fade {
8199
8297
  position: absolute;
8200
8298
  bottom: 0;
@@ -8212,7 +8310,8 @@
8212
8310
  padding-top: 4px;
8213
8311
  padding-right: var(--compose-thread-padding-right);
8214
8312
  padding-left: calc(
8215
- var(--compose-thread-padding-left) + var(--compose-thread-gap) + 10px
8313
+ var(--compose-thread-padding-left) + var(--compose-thread-gap) +
8314
+ var(--compose-thread-rail-width)
8216
8315
  );
8217
8316
  font-size: var(--type-thread-context-meta);
8218
8317
  color: var(--site-text-secondary);
@@ -8312,14 +8411,26 @@
8312
8411
  padding: 0.78rem 0.9rem;
8313
8412
  }
8314
8413
 
8315
- .compose-collection-trigger {
8316
- min-height: 2.65rem;
8317
- padding-right: 0.82rem;
8414
+ /* Touch keeps the roomier set: the reference's 36px box is tuned for a
8415
+ cursor, and a thumb needs the millimetres back. Width still comes from
8416
+ the word — only the box around it grows. */
8417
+ .compose-publish-main {
8418
+ min-height: 2.8em;
8419
+ padding-inline: 22px;
8318
8420
  }
8319
8421
 
8320
- .compose-collection-trigger-icon {
8321
- width: 1.75rem;
8322
- height: 1.75rem;
8422
+ .compose-options-trigger {
8423
+ width: 2.5rem;
8424
+ height: 2.5rem;
8425
+ }
8426
+
8427
+ .compose-collection-select {
8428
+ --compose-collection-pad-icon: 0.68rem;
8429
+ --compose-collection-pad-label: 0.85rem;
8430
+ }
8431
+
8432
+ .compose-collection-trigger {
8433
+ min-height: 2.5rem;
8323
8434
  }
8324
8435
 
8325
8436
  .compose-collection-select [data-popover] {
@@ -8327,13 +8438,24 @@
8327
8438
  max-width: min(20rem, calc(100vw - 1rem));
8328
8439
  }
8329
8440
 
8441
+ .compose-collection-search-icon {
8442
+ width: 1.15rem;
8443
+ height: 1.15rem;
8444
+ }
8445
+
8446
+ .compose-collection-options {
8447
+ max-height: 15rem;
8448
+ }
8449
+
8330
8450
  .compose-collection-popover {
8331
8451
  padding: 0.62rem 0.68rem 0.58rem;
8332
8452
  }
8333
8453
 
8334
8454
  .compose-collection-search-shell {
8455
+ gap: 0.52rem;
8335
8456
  min-height: 3rem;
8336
8457
  padding: 0 1rem;
8458
+ border-radius: 0.92rem;
8337
8459
  }
8338
8460
 
8339
8461
  .compose-collection-options {
@@ -8352,12 +8474,20 @@
8352
8474
  height: 1.5rem;
8353
8475
  }
8354
8476
 
8355
- .compose-thread-layout {
8356
- padding-top: 0.75rem;
8477
+ .compose-collection-add-action {
8478
+ gap: 0.82rem;
8479
+ min-height: 3rem;
8480
+ padding: 0 0.92rem;
8481
+ border-radius: 1rem;
8482
+ }
8483
+
8484
+ .compose-collection-footer {
8485
+ margin-top: 0.35rem;
8486
+ padding: 0.35rem 0.22rem 0.12rem;
8357
8487
  }
8358
8488
 
8359
- .compose-thread-layout::before {
8360
- top: 0.75rem;
8489
+ .compose-thread-layout {
8490
+ padding-top: 0.75rem;
8361
8491
  }
8362
8492
 
8363
8493
  .compose-reply-context {
@@ -8380,35 +8510,14 @@
8380
8510
  }
8381
8511
  }
8382
8512
 
8383
- /* --- Thread compose layout (multi-post, scrollable) --------------------- */
8384
-
8385
- /* Scrollable variant for composing a new thread (vs reply thread which is
8386
- fixed-height). Each editor item grows naturally; the whole layout scrolls. */
8387
- .compose-thread-compose-layout {
8388
- overflow-y: auto;
8389
- overflow-x: hidden;
8390
- }
8391
-
8392
- .compose-thread-compose-layout .compose-editor-row {
8393
- flex: none;
8394
- overflow: visible;
8395
- align-items: flex-start;
8396
- padding-top: 4px;
8397
- padding-bottom: 4px;
8398
- }
8399
-
8400
- .compose-thread-compose-layout .compose-editor-row > jant-compose-editor {
8401
- overflow: visible;
8402
- min-width: 0;
8403
- }
8513
+ /* --- Thread compose layout (multi-post) --------------------------------- */
8404
8514
 
8405
- .compose-thread-compose-layout .compose-body {
8406
- flex: none;
8407
- overflow-y: visible;
8408
- min-height: 72px;
8409
- }
8515
+ /* `.compose-thread-compose-layout` carries no layout rules of its own: the
8516
+ grow-naturally-and-scroll-as-one behaviour it used to opt into is now what
8517
+ every mode does, under `.compose-scroll`. It survives as a hook for the
8518
+ rail geometry above.
8410
8519
 
8411
- /* Focused thread post gets a subtle left accent. Use an inset box-shadow, not
8520
+ Focused thread post gets a subtle left accent. Use an inset box-shadow, not
8412
8521
  a border: a border adds 2px of layout width only while focused, so switching
8413
8522
  focus between thread posts shifted each one sideways ("jitter"). box-shadow
8414
8523
  paints in the same spot without affecting layout. */
@@ -8416,7 +8525,8 @@
8416
8525
  box-shadow: inset 2px 0 0 transparent;
8417
8526
  }
8418
8527
 
8419
- /* "Add to thread" row inside thread compose layout */
8528
+ /* "Add to thread" on the rail — thread compose, and a reply before it has
8529
+ grown a second post. */
8420
8530
  .compose-thread-add-row {
8421
8531
  display: flex;
8422
8532
  align-items: center;
@@ -8427,92 +8537,63 @@
8427
8537
  padding-bottom: 10px;
8428
8538
  }
8429
8539
 
8540
+ /* Fills the rail track exactly, so it lands on the same centre line as the
8541
+ post dots without any offset of its own. */
8430
8542
  .compose-thread-add-dot {
8431
- width: 10px;
8432
- height: 10px;
8543
+ width: var(--compose-thread-rail-width);
8544
+ height: var(--compose-thread-rail-width);
8433
8545
  border-radius: 50%;
8434
8546
  border: 1.5px dashed
8435
8547
  color-mix(in srgb, var(--site-threadline) 60%, transparent);
8436
8548
  flex-shrink: 0;
8437
8549
  margin-top: 1px;
8438
- margin-left: calc(
8439
- (var(--site-thread-marker-hero-size) - var(--site-thread-marker-size)) / 2
8440
- );
8441
- }
8442
-
8443
- .compose-thread-add-btn {
8444
- display: inline-flex;
8445
- align-items: center;
8446
- gap: 6px;
8447
- font-size: var(--type-ui-caption);
8448
- font-family: var(--font-ui);
8449
- color: var(--site-text-secondary);
8450
- padding: 4px 12px 4px 9px;
8451
- border-radius: 20px;
8452
- border: 1.5px dashed
8453
- color-mix(in srgb, var(--site-threadline) 70%, transparent);
8454
- background: transparent;
8455
- cursor: pointer;
8456
- transition:
8457
- color 0.15s ease,
8458
- background-color 0.15s ease,
8459
- border-color 0.15s ease;
8460
- }
8461
-
8462
- .compose-thread-compose-layout .compose-thread-add-btn {
8463
- margin-inline-start: -8px;
8464
- }
8465
-
8466
- .compose-thread-add-btn:hover:not(:disabled) {
8467
- color: var(--site-text-primary);
8468
- background-color: var(--compose-control-bg);
8469
- border-color: var(--site-threadline);
8470
- border-style: solid;
8471
8550
  }
8472
8551
 
8473
- .compose-thread-add-btn:disabled,
8474
- .compose-add-thread-btn:disabled {
8475
- opacity: 0.4;
8476
- cursor: default;
8477
- }
8478
-
8479
- /* "Add to thread" trigger below single-post editor */
8552
+ /* "Add to thread" trigger with no rail under it — a single post, which has
8553
+ no next slot to point at yet. */
8480
8554
  .compose-add-thread-trigger {
8481
- padding: 1px 10px 4px;
8555
+ padding: 1px 10px 4px var(--compose-gutter);
8482
8556
  display: flex;
8483
8557
  align-items: center;
8484
8558
  }
8485
8559
 
8486
8560
  @media (min-width: 700px) {
8487
8561
  .compose-add-thread-trigger {
8488
- padding-inline: 16px;
8562
+ padding-inline: var(--compose-gutter) 16px;
8489
8563
  }
8490
8564
  }
8491
8565
 
8492
- .compose-add-thread-btn {
8566
+ /* No glyph of its own: on the rail the dashed dot already says "next slot",
8567
+ and on a single post the row's position says it. The label is all that is
8568
+ left, so it sits at the row's own quiet weight — and hangs back by its own
8569
+ side padding so the label, not the hover chip, starts on the text column. */
8570
+ .compose-thread-add-btn {
8571
+ --compose-add-hang: 8px;
8572
+
8493
8573
  display: inline-flex;
8494
8574
  align-items: center;
8495
- gap: 6px;
8496
8575
  font-size: var(--type-ui-caption);
8497
8576
  font-family: var(--font-ui);
8498
8577
  color: var(--site-text-secondary);
8499
- padding: 4px 12px 4px 9px;
8500
- border-radius: 20px;
8501
- border: 1.5px dashed
8502
- color-mix(in srgb, var(--site-threadline) 70%, transparent);
8578
+ padding: 5px var(--compose-add-hang);
8579
+ margin-inline-start: calc(-1 * var(--compose-add-hang));
8580
+ border-radius: 9px;
8581
+ border: none;
8503
8582
  background: transparent;
8504
8583
  cursor: pointer;
8505
8584
  transition:
8506
8585
  color 0.15s ease,
8507
- background-color 0.15s ease,
8508
- border-color 0.15s ease;
8586
+ background-color 0.15s ease;
8509
8587
  }
8510
8588
 
8511
- .compose-add-thread-btn:hover:not(:disabled) {
8589
+ .compose-thread-add-btn:hover:not(:disabled) {
8512
8590
  color: var(--site-text-primary);
8513
8591
  background-color: var(--compose-control-bg);
8514
- border-color: var(--site-threadline);
8515
- border-style: solid;
8592
+ }
8593
+
8594
+ .compose-thread-add-btn:disabled {
8595
+ opacity: 0.4;
8596
+ cursor: default;
8516
8597
  }
8517
8598
 
8518
8599
  /* --- Thread post header: format tag switcher + × on right --------------- */
@@ -8520,8 +8601,45 @@
8520
8601
  .compose-thread-post-header {
8521
8602
  display: flex;
8522
8603
  align-items: center;
8523
- justify-content: space-between;
8524
- padding: 6px 0 4px;
8604
+ /* The format selector will not shrink, so on a narrow screen let the
8605
+ date/permalink pill drop to its own line rather than clip. */
8606
+ flex-wrap: wrap;
8607
+ row-gap: 4px;
8608
+ gap: 8px;
8609
+ /* Left is the shared text column: the switcher hangs back out of it by its
8610
+ own item padding, so it is the label that starts on the edge. Right keeps
8611
+ the tools row's 16px so × and the date pill stay over the tool buttons
8612
+ bracketing the editor from below. */
8613
+ padding: 10px 16px 2px var(--compose-gutter);
8614
+ }
8615
+
8616
+ .compose-editor-row .compose-thread-post-header {
8617
+ padding-block: 6px 4px;
8618
+ }
8619
+
8620
+ /* "1/3" position marker. Identity comes before attribute, so it leads the
8621
+ row; it pairs visually with the thread rail dot to its left. */
8622
+ .compose-post-position {
8623
+ flex-shrink: 0;
8624
+ padding: 1px 7px;
8625
+ border-radius: 999px;
8626
+ background: var(--compose-control-bg);
8627
+ color: var(--site-text-secondary);
8628
+ font-size: 0.68rem;
8629
+ font-variant-numeric: tabular-nums;
8630
+ letter-spacing: 0.01em;
8631
+ }
8632
+
8633
+ /* "Editing" — the one thing a compose title ever said that you could not read
8634
+ off the composer itself: this post is already live. New / reply / thread
8635
+ all announce themselves through the parent post, the 2/3 markers and the
8636
+ submit label, so only this case earns a word.
8637
+ It takes the position marker's weight rather than an accent tint: it sits
8638
+ in that marker's slot, and everything else in this row that is not the
8639
+ format selector is deliberately quiet. Only the numerals are reset. */
8640
+ .compose-post-badge {
8641
+ font-variant-numeric: normal;
8642
+ letter-spacing: 0.02em;
8525
8643
  }
8526
8644
 
8527
8645
  /* Reply / edit-reply compose (a .compose-editor-row directly under
@@ -8536,7 +8654,8 @@
8536
8654
  padding-top: 14px;
8537
8655
  }
8538
8656
 
8539
- .compose-thread-post-header + .compose-body {
8657
+ /* The header now sits above every post, so it owns the gap to the body. */
8658
+ .compose-editor-row .compose-thread-post-header + .compose-body {
8540
8659
  padding-top: 8px;
8541
8660
  }
8542
8661
 
@@ -8544,55 +8663,12 @@
8544
8663
  padding-top: 12px;
8545
8664
  }
8546
8665
 
8547
- /* Thread format selector: pill-tag style (lighter than the main segmented) */
8548
- .compose-thread-segmented {
8549
- background: transparent;
8550
- box-shadow: none;
8551
- padding: 0;
8552
- gap: 4px;
8553
- }
8554
-
8555
- .compose-thread-segmented .compose-format-pill {
8556
- display: none;
8557
- }
8558
-
8559
- .compose-thread-segmented .compose-segmented-item {
8560
- flex: none;
8561
- font-size: var(--type-ui-caption);
8562
- padding: 3px 10px;
8563
- border-radius: 20px;
8564
- border: 1.5px solid
8565
- color-mix(in srgb, var(--site-text-secondary) 22%, transparent);
8566
- letter-spacing: 0.01em;
8567
- transition:
8568
- border-color 0.15s ease,
8569
- color 0.15s ease,
8570
- background-color 0.15s ease;
8571
- }
8572
-
8573
- .compose-thread-segmented
8574
- .compose-segmented-item:hover:not(.compose-segmented-item-active) {
8575
- border-color: color-mix(
8576
- in srgb,
8577
- var(--site-text-secondary) 50%,
8578
- transparent
8579
- );
8580
- color: var(--site-text-primary);
8581
- background: transparent;
8582
- }
8583
-
8584
- .compose-thread-segmented .compose-segmented-item-active {
8585
- color: var(--site-text-primary);
8586
- font-weight: var(--fw-medium);
8587
- border-color: color-mix(in srgb, var(--site-text-primary) 55%, transparent);
8588
- background: color-mix(in srgb, var(--site-text-primary) 7%, transparent);
8589
- }
8590
-
8591
8666
  /* Remove button (×) — hidden by default, revealed on thread post hover/focus */
8592
8667
  .compose-thread-post-remove {
8593
8668
  display: inline-flex;
8594
8669
  align-items: center;
8595
8670
  justify-content: center;
8671
+ margin-left: auto;
8596
8672
  width: 28px;
8597
8673
  height: 28px;
8598
8674
  border-radius: 8px;
@@ -8622,11 +8698,6 @@
8622
8698
  color: var(--site-text-primary);
8623
8699
  }
8624
8700
 
8625
- /* In thread compose, toolbar padding matches thread indent */
8626
- .compose-thread-compose-layout .compose-tools-row {
8627
- padding-inline-start: 0;
8628
- }
8629
-
8630
8701
  /* --- Alt text overlay panel (Threads-style, covers entire dialog) ------- */
8631
8702
 
8632
8703
  .compose-alt-panel {
@@ -8798,11 +8869,18 @@
8798
8869
  }
8799
8870
 
8800
8871
  .compose-draft-meta {
8872
+ display: flex;
8873
+ align-items: center;
8874
+ gap: 0.35rem;
8801
8875
  font-size: var(--type-ui-caption);
8802
8876
  color: var(--site-text-secondary);
8803
8877
  margin-top: 4px;
8804
8878
  }
8805
8879
 
8880
+ .compose-draft-format {
8881
+ font-weight: 500;
8882
+ }
8883
+
8806
8884
  .compose-draft-more {
8807
8885
  width: 40px;
8808
8886
  height: 40px;
@@ -9293,9 +9371,14 @@
9293
9371
  min-height: 0;
9294
9372
  }
9295
9373
 
9374
+ /* The floor is two lines, written as two lines. It is a target to click into
9375
+ on an empty post, not a reserved shape, so it should follow the type
9376
+ instead of a pixel count that goes stale when the leading moves. Every
9377
+ compose text surface shares it: a note's body and the "thoughts" under a
9378
+ link or a quote all open the same way and grow from there. */
9296
9379
  .compose-tiptap-body .tiptap {
9297
9380
  outline: none;
9298
- min-height: 120px;
9381
+ min-height: calc(var(--type-body-leading) * 2em);
9299
9382
  min-width: 0;
9300
9383
  max-width: 100%;
9301
9384
  overflow-x: auto;
@@ -9311,14 +9394,6 @@
9311
9394
  line-height: var(--type-body-leading);
9312
9395
  }
9313
9396
 
9314
- .compose-tiptap-thoughts .tiptap {
9315
- min-height: 72px;
9316
- }
9317
-
9318
- .compose-tiptap-thoughts-quote .tiptap {
9319
- min-height: 56px;
9320
- }
9321
-
9322
9397
  .compose-tiptap-link .tiptap {
9323
9398
  color: var(--site-text-primary);
9324
9399
  }
@@ -9379,12 +9454,12 @@
9379
9454
  margin-bottom: 1rem;
9380
9455
  }
9381
9456
 
9382
- /* In inline-format compose (reply / edit-reply / thread — the editors wrapped
9383
- in .compose-editor-row), the format header sits right above the body, so the
9384
- first block's 1.4rem top margin reads as a gap between the format pills and
9385
- the input. Drop it there so the text hugs the header. Standalone new/edit
9386
- editors keep their natural lead-in. */
9387
- .compose-editor-row .compose-tiptap-body .tiptap > :first-child {
9457
+ /* The first block's 1.4rem top margin is a lead-in that only made sense when
9458
+ the body was the first thing in the editor. Every editor now renders a
9459
+ format header above it — and notes a title field too — so it reads as a gap
9460
+ instead. (This used to be scoped to .compose-editor-row, back when
9461
+ standalone new/edit editors did start with the body.) */
9462
+ .compose-tiptap-body .tiptap > :first-child {
9388
9463
  margin-top: 0;
9389
9464
  }
9390
9465
 
@@ -11215,12 +11290,16 @@
11215
11290
  var(--site-page-bg) 88%,
11216
11291
  var(--site-elevated-bg) 12%
11217
11292
  );
11293
+ }
11218
11294
 
11219
- overflow: visible;
11295
+ /* Scoped to the collapsed state. Unscoped it also outranked
11296
+ `.compose-reply-context.expanded` — same specificity, declared later — so
11297
+ fullscreen's "Show more" expanded a box that was still pinned at 140px. */
11298
+ .compose-fullscreen-thread-layout .compose-reply-context:not(.expanded) {
11299
+ max-height: 140px;
11220
11300
  }
11221
11301
 
11222
11302
  .compose-fullscreen-thread-layout .compose-reply-context {
11223
- max-height: 140px;
11224
11303
  padding: 0.08rem 0 0.08rem;
11225
11304
  }
11226
11305
 
@@ -11234,12 +11313,7 @@
11234
11313
  color: var(--site-text-secondary);
11235
11314
  }
11236
11315
 
11237
- .compose-fullscreen-thread-layout .compose-thread-dot {
11238
- margin-top: 0.6rem;
11239
- }
11240
-
11241
11316
  .compose-fullscreen-editor-row {
11242
- overflow: visible;
11243
11317
  padding-top: 1.5rem;
11244
11318
  }
11245
11319
 
@@ -11255,10 +11329,9 @@
11255
11329
  gap: 0.7rem;
11256
11330
  }
11257
11331
 
11258
- .compose-fullscreen-editor-row .compose-thread-dot {
11259
- margin-top: 0.95rem;
11260
- }
11261
-
11332
+ /* Stand-in for a hidden title: reads as the field's own placeholder, and
11333
+ clicking it swaps in the real input. Fullscreen has no toolbar to put the
11334
+ `T` toggle on, so this is the way back to a title from here. */
11262
11335
  .compose-fullscreen-title-placeholder {
11263
11336
  display: block;
11264
11337
  width: 100%;
@@ -11490,6 +11563,13 @@
11490
11563
 
11491
11564
  /* --- View toggle -------------------------------------------------------- */
11492
11565
 
11566
+ .archive-toolbar-toggles {
11567
+ display: flex;
11568
+ align-items: center;
11569
+ gap: 6px;
11570
+ flex-shrink: 0;
11571
+ }
11572
+
11493
11573
  .archive-view-toggle {
11494
11574
  display: flex;
11495
11575
  align-items: center;