@social.io/catalog 1.3.0 → 2.0.0

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 (124) hide show
  1. package/.smartconfig.json +51 -0
  2. package/dist_bundle/bundle.js +5450 -3267
  3. package/dist_ts_web/00_commitinfo_data.js +3 -3
  4. package/dist_ts_web/elements/index.d.ts +20 -3
  5. package/dist_ts_web/elements/index.js +27 -7
  6. package/dist_ts_web/elements/sio-ai-card.d.ts +39 -0
  7. package/dist_ts_web/elements/sio-ai-card.js +229 -0
  8. package/dist_ts_web/elements/sio-avatar.d.ts +46 -0
  9. package/dist_ts_web/elements/sio-avatar.js +379 -0
  10. package/dist_ts_web/elements/sio-bucket-header.d.ts +36 -0
  11. package/dist_ts_web/elements/sio-bucket-header.js +197 -0
  12. package/dist_ts_web/elements/sio-button.d.ts +7 -3
  13. package/dist_ts_web/elements/sio-button.js +253 -152
  14. package/dist_ts_web/elements/sio-channel-rail.d.ts +37 -0
  15. package/dist_ts_web/elements/sio-channel-rail.js +275 -0
  16. package/dist_ts_web/elements/sio-chip.d.ts +25 -0
  17. package/dist_ts_web/elements/sio-chip.js +188 -0
  18. package/dist_ts_web/elements/sio-cmdk.d.ts +59 -0
  19. package/dist_ts_web/elements/sio-cmdk.js +471 -0
  20. package/dist_ts_web/elements/sio-combox.d.ts +55 -1
  21. package/dist_ts_web/elements/sio-combox.js +458 -261
  22. package/dist_ts_web/elements/sio-composer.d.ts +48 -0
  23. package/dist_ts_web/elements/sio-composer.js +357 -0
  24. package/dist_ts_web/elements/sio-conversation-selector.d.ts +41 -1
  25. package/dist_ts_web/elements/sio-conversation-selector.js +426 -238
  26. package/dist_ts_web/elements/sio-conversation-view.d.ts +34 -6
  27. package/dist_ts_web/elements/sio-conversation-view.js +738 -564
  28. package/dist_ts_web/elements/sio-dropdown-menu.d.ts +6 -6
  29. package/dist_ts_web/elements/sio-dropdown-menu.js +223 -169
  30. package/dist_ts_web/elements/sio-fab.d.ts +23 -5
  31. package/dist_ts_web/elements/sio-fab.js +259 -235
  32. package/dist_ts_web/elements/sio-glass-toolbar.d.ts +22 -0
  33. package/dist_ts_web/elements/sio-glass-toolbar.js +178 -0
  34. package/dist_ts_web/elements/sio-icon.d.ts +1 -0
  35. package/dist_ts_web/elements/sio-icon.js +254 -94
  36. package/dist_ts_web/elements/sio-image-lightbox.d.ts +3 -4
  37. package/dist_ts_web/elements/sio-image-lightbox.js +282 -191
  38. package/dist_ts_web/elements/sio-inset-list.d.ts +28 -0
  39. package/dist_ts_web/elements/sio-inset-list.js +191 -0
  40. package/dist_ts_web/elements/sio-kbd.d.ts +16 -0
  41. package/dist_ts_web/elements/sio-kbd.js +117 -0
  42. package/dist_ts_web/elements/sio-list-row.d.ts +55 -0
  43. package/dist_ts_web/elements/sio-list-row.js +462 -0
  44. package/dist_ts_web/elements/sio-message-input.d.ts +25 -4
  45. package/dist_ts_web/elements/sio-message-input.js +231 -226
  46. package/dist_ts_web/elements/sio-paper-thumb.d.ts +24 -0
  47. package/dist_ts_web/elements/sio-paper-thumb.js +293 -0
  48. package/dist_ts_web/elements/sio-pdf-viewer.d.ts +2 -0
  49. package/dist_ts_web/elements/sio-pdf-viewer.js +344 -244
  50. package/dist_ts_web/elements/sio-segmented.d.ts +32 -0
  51. package/dist_ts_web/elements/sio-segmented.js +217 -0
  52. package/dist_ts_web/elements/sio-step-timeline.d.ts +28 -0
  53. package/dist_ts_web/elements/sio-step-timeline.js +222 -0
  54. package/dist_ts_web/elements/sio-tab-bar.d.ts +36 -0
  55. package/dist_ts_web/elements/sio-tab-bar.js +232 -0
  56. package/dist_ts_web/elements/sio-unread-badge.d.ts +25 -0
  57. package/dist_ts_web/elements/sio-unread-badge.js +203 -0
  58. package/dist_ts_web/elements/sio-waveform.d.ts +34 -0
  59. package/dist_ts_web/elements/sio-waveform.js +304 -0
  60. package/dist_ts_web/elements/tokens.d.ts +70 -0
  61. package/dist_ts_web/elements/tokens.js +277 -0
  62. package/dist_ts_web/pages/firstpage.d.ts +5 -0
  63. package/dist_ts_web/pages/firstpage.js +21 -2
  64. package/dist_ts_web/pages/mainpage.d.ts +6 -0
  65. package/dist_ts_web/pages/mainpage.js +111 -39
  66. package/{license → license.md} +3 -1
  67. package/package.json +23 -29
  68. package/readme.hints.md +81 -0
  69. package/readme.md +85 -151
  70. package/ts_web/00_commitinfo_data.ts +2 -2
  71. package/ts_web/elements/index.ts +30 -7
  72. package/ts_web/elements/sio-ai-card.ts +175 -0
  73. package/ts_web/elements/sio-avatar.ts +295 -0
  74. package/ts_web/elements/sio-bucket-header.ts +157 -0
  75. package/ts_web/elements/sio-button.ts +149 -127
  76. package/ts_web/elements/sio-channel-rail.ts +243 -0
  77. package/ts_web/elements/sio-chip.ts +134 -0
  78. package/ts_web/elements/sio-cmdk.ts +461 -0
  79. package/ts_web/elements/sio-combox.ts +327 -204
  80. package/ts_web/elements/sio-composer.ts +306 -0
  81. package/ts_web/elements/sio-conversation-selector.ts +350 -228
  82. package/ts_web/elements/sio-conversation-view.ts +573 -457
  83. package/ts_web/elements/sio-dropdown-menu.ts +112 -130
  84. package/ts_web/elements/sio-fab.ts +192 -227
  85. package/ts_web/elements/sio-glass-toolbar.ts +140 -0
  86. package/ts_web/elements/sio-icon.ts +147 -25
  87. package/ts_web/elements/sio-image-lightbox.ts +62 -64
  88. package/ts_web/elements/sio-inset-list.ts +140 -0
  89. package/ts_web/elements/sio-kbd.ts +80 -0
  90. package/ts_web/elements/sio-list-row.ts +393 -0
  91. package/ts_web/elements/sio-message-input.ts +142 -221
  92. package/ts_web/elements/sio-paper-thumb.ts +244 -0
  93. package/ts_web/elements/sio-pdf-viewer.ts +58 -60
  94. package/ts_web/elements/sio-segmented.ts +183 -0
  95. package/ts_web/elements/sio-step-timeline.ts +179 -0
  96. package/ts_web/elements/sio-tab-bar.ts +195 -0
  97. package/ts_web/elements/sio-unread-badge.ts +147 -0
  98. package/ts_web/elements/sio-waveform.ts +239 -0
  99. package/ts_web/elements/tokens.ts +297 -0
  100. package/ts_web/pages/firstpage.ts +20 -1
  101. package/ts_web/pages/mainpage.ts +111 -39
  102. package/dist_bundle/bundle.js.map +0 -7
  103. package/dist_ts_web/elements/00colors.d.ts +0 -108
  104. package/dist_ts_web/elements/00colors.js +0 -137
  105. package/dist_ts_web/elements/00fonts.d.ts +0 -60
  106. package/dist_ts_web/elements/00fonts.js +0 -129
  107. package/dist_ts_web/elements/00tokens.d.ts +0 -123
  108. package/dist_ts_web/elements/00tokens.js +0 -176
  109. package/dist_ts_web/elements/sio-conversation-list.d.ts +0 -20
  110. package/dist_ts_web/elements/sio-conversation-list.js +0 -495
  111. package/dist_ts_web/elements/sio-recorder.d.ts +0 -60
  112. package/dist_ts_web/elements/sio-recorder.js +0 -173
  113. package/dist_ts_web/elements/sio-subwidget-conversations.d.ts +0 -16
  114. package/dist_ts_web/elements/sio-subwidget-conversations.js +0 -350
  115. package/dist_ts_web/elements/sio-subwidget-onboardme.d.ts +0 -7
  116. package/dist_ts_web/elements/sio-subwidget-onboardme.js +0 -243
  117. package/dist_watch/bundle.js +0 -81694
  118. package/dist_watch/bundle.js.map +0 -7
  119. package/dist_watch/index.html +0 -25
  120. package/npmextra.json +0 -19
  121. package/ts_web/elements/00colors.ts +0 -164
  122. package/ts_web/elements/00fonts.ts +0 -145
  123. package/ts_web/elements/00tokens.ts +0 -197
  124. package/ts_web/elements/sio-recorder.ts +0 -213
@@ -1,19 +1,16 @@
1
1
  import {
2
2
  DeesElement,
3
- property,
4
3
  html,
4
+ property,
5
+ state,
5
6
  customElement,
6
- type TemplateResult,
7
- cssManager,
8
7
  css,
9
- unsafeCSS,
10
- state,
8
+ type TemplateResult,
11
9
  } from '@design.estate/dees-element';
12
10
 
13
- // Import design tokens
14
- import { colors, bdTheme } from './00colors.js';
15
- import { spacing, radius, shadows, transitions } from './00tokens.js';
16
- import { fontFamilies, typography } from './00fonts.js';
11
+ import { sioElementStyles } from './tokens.js';
12
+ import { SioComposer, type TSioComposerVariant, type TSioComposerAccent } from './sio-composer.js';
13
+ SioComposer;
17
14
 
18
15
  declare global {
19
16
  interface HTMLElementTagNameMap {
@@ -21,259 +18,183 @@ declare global {
21
18
  }
22
19
  }
23
20
 
21
+ /**
22
+ * sio-message-input — the widget's composer row, a thin shell around
23
+ * sio-composer (v7 SocialIOWidget.jsx composer).
24
+ *
25
+ * Adds the widget-level concerns on top of the composer primitive:
26
+ * a hidden file input behind the composer's attach button, the
27
+ * email-capture variant for the away state, and the keyboard-visibility
28
+ * focus events the combox uses on phablet.
29
+ *
30
+ * Events (bubbles, composed):
31
+ * send-message { text, attachments: File[] }
32
+ * files-selected { files: File[] }
33
+ * input-focus / input-blur — phablet keyboard handling
34
+ */
24
35
  @customElement('sio-message-input')
25
36
  export class SioMessageInput extends DeesElement {
26
37
  public static demo = () => html`
27
- <sio-message-input style="width: 600px;"></sio-message-input>
38
+ <div
39
+ style="display: grid; gap: 18px; max-width: 380px; padding: 24px; background: var(--sio-bg); font-family: var(--sio-font-sf);"
40
+ >
41
+ <div
42
+ style="font-size: 11px; font-weight: 700; letter-spacing: 0.06px; text-transform: uppercase; color: var(--sio-label-3);"
43
+ >
44
+ Widget composer
45
+ </div>
46
+ <div style="background: var(--sio-surface); border-radius: 16px; padding-top: 12px;">
47
+ <sio-message-input placeholder="Write a message…"></sio-message-input>
48
+ </div>
49
+
50
+ <div
51
+ style="font-size: 11px; font-weight: 700; letter-spacing: 0.06px; text-transform: uppercase; color: var(--sio-label-3);"
52
+ >
53
+ Email capture — team is away
54
+ </div>
55
+ <div style="background: var(--sio-surface); border-radius: 16px; padding-top: 12px;">
56
+ <sio-message-input variant="email"></sio-message-input>
57
+ </div>
58
+ </div>
28
59
  `;
29
60
 
61
+ /** field placeholder; message variant defaults to "Write a message…", email to "you@email.com" */
30
62
  @property({ type: String })
31
- public accessor placeholder: string = 'Type a message...';
63
+ public accessor placeholder = '';
32
64
 
33
- @property({ type: Boolean })
34
- public accessor disabled: boolean = false;
65
+ @property({ type: Boolean, reflect: true })
66
+ public accessor disabled = false;
35
67
 
36
- @state()
37
- private accessor messageText: string = '';
68
+ /** 'message' (default) or 'email' — the away-state email capture */
69
+ @property({ type: String })
70
+ public accessor variant: TSioComposerVariant = 'message';
71
+
72
+ /** send-button accent, forwarded to sio-composer */
73
+ @property({ type: String })
74
+ public accessor accent: TSioComposerAccent = 'tint';
75
+
76
+ /** show the composer's attach "+" button (opens the file picker) */
77
+ @property({ type: Boolean })
78
+ public accessor attach = false;
38
79
 
39
80
  @state()
40
81
  private accessor pendingAttachments: File[] = [];
41
82
 
42
83
  public static styles = [
43
- cssManager.defaultStyles,
84
+ ...sioElementStyles,
44
85
  css`
45
86
  :host {
46
87
  display: block;
47
- font-family: ${unsafeCSS(fontFamilies.sans)};
48
- }
49
-
50
- .input-container {
51
- display: flex;
52
- align-items: flex-end;
53
- background: ${bdTheme('secondary')};
54
- border: 1px solid ${bdTheme('border')};
55
- border-radius: 24px;
56
- padding: 2px;
57
- transition: all 200ms ease;
58
- overflow: hidden;
59
- }
60
-
61
- .input-container:focus-within {
62
- border-color: ${bdTheme('ring')};
63
- background: ${bdTheme('background')};
64
- }
65
-
66
- .message-input {
67
- flex: 1;
68
- min-height: 44px;
69
- max-height: 120px;
70
- padding: 12px 16px;
71
- background: transparent;
72
- border: none;
73
- font-size: 15px;
74
- color: ${bdTheme('foreground')};
75
- outline: none;
76
- resize: none;
77
- font-family: ${unsafeCSS(fontFamilies.sans)};
78
- line-height: 1.5;
79
- }
80
-
81
- .message-input::placeholder {
82
- color: ${bdTheme('mutedForeground')};
83
- }
84
-
85
- .action-buttons {
86
- display: flex;
87
- gap: 4px;
88
- align-items: center;
89
- padding-right: 2px;
90
- }
91
-
92
- .action-button {
93
- width: 40px;
94
- height: 40px;
95
- border-radius: 50%;
96
- border: none;
97
- background: transparent;
98
- color: ${bdTheme('mutedForeground')};
99
- cursor: pointer;
100
- display: flex;
101
- align-items: center;
102
- justify-content: center;
103
- transition: all 120ms ease;
104
- position: relative;
105
- outline: none;
106
- }
107
-
108
- .action-button:hover {
109
- color: ${bdTheme('foreground')};
110
- background: ${bdTheme('hsl(0 0% 0% / 0.05)', 'hsl(0 0% 100% / 0.05)')};
111
- }
112
-
113
- .action-button:active {
114
- transform: scale(0.95);
115
- }
116
-
117
- .action-button.attachment {
118
- opacity: 0.7;
88
+ font-family: var(--sio-font-sf);
119
89
  }
120
90
 
121
- .action-button.attachment:hover {
122
- opacity: 1;
123
- }
124
-
125
- .action-button.send {
126
- background: ${bdTheme('primary')};
127
- color: white;
128
- margin-left: 2px;
129
- }
130
-
131
- .action-button.send:hover {
132
- background: ${bdTheme('hsl(221 83% 49%)', 'hsl(217 91% 65%)')};
133
- }
134
-
135
- .action-button.send:disabled {
136
- opacity: 0.3;
137
- cursor: not-allowed;
138
- }
139
-
140
- .action-button.send:disabled:hover {
141
- background: ${bdTheme('primary')};
142
- transform: none;
91
+ :host([disabled]) {
92
+ pointer-events: none;
93
+ opacity: 0.5;
143
94
  }
144
95
 
145
96
  .file-input {
146
97
  display: none;
147
98
  }
148
-
149
- /* Icon styles */
150
- sio-icon {
151
- width: 20px;
152
- height: 20px;
153
- }
154
-
155
- .action-button.send sio-icon {
156
- width: 18px;
157
- height: 18px;
158
- }
159
99
  `,
160
100
  ];
161
101
 
162
102
  public render(): TemplateResult {
163
103
  return html`
164
- <div class="input-container">
165
- <textarea
166
- class="message-input"
167
- placeholder="${this.placeholder}"
168
- .value=${this.messageText}
169
- @input=${this.handleInput}
170
- @keydown=${this.handleKeyDown}
171
- ?disabled=${this.disabled}
172
- rows="1"
173
- ></textarea>
174
-
175
- <div class="action-buttons">
176
- <input
177
- type="file"
178
- class="file-input"
179
- id="fileInput"
180
- multiple
181
- accept="image/*,.pdf,.doc,.docx,.txt"
182
- @change=${this.handleFileSelect}
183
- />
184
- <button
185
- class="action-button attachment"
186
- @click=${this.openFileSelector}
187
- ?disabled=${this.disabled}
188
- >
189
- <sio-icon icon="paperclip"></sio-icon>
190
- </button>
191
- <button
192
- class="action-button send"
193
- ?disabled=${!this.messageText.trim() || this.disabled}
194
- @click=${this.sendMessage}
195
- >
196
- <sio-icon icon="send"></sio-icon>
197
- </button>
198
- </div>
199
- </div>
104
+ <sio-composer
105
+ variant=${this.variant}
106
+ accent=${this.accent}
107
+ ?attach=${this.attach}
108
+ ?auto-expand=${this.variant === 'message'}
109
+ placeholder=${this.variant === 'message' ? this.placeholder || 'Write a message…' : this.placeholder}
110
+ @composer-send=${this.handleComposerSend}
111
+ @composer-attach=${this.openFileSelector}
112
+ @focusin=${this.handleFocus}
113
+ @focusout=${this.handleBlur}
114
+ ></sio-composer>
115
+ <input
116
+ type="file"
117
+ class="file-input"
118
+ id="fileInput"
119
+ multiple
120
+ accept="image/*,.pdf,.doc,.docx,.txt"
121
+ @change=${this.handleFileSelect}
122
+ />
200
123
  `;
201
124
  }
202
125
 
203
- private handleInput(event: Event) {
204
- const textarea = event.target as HTMLTextAreaElement;
205
- this.messageText = textarea.value;
206
-
207
- // Auto-resize textarea
208
- textarea.style.height = 'auto';
209
- textarea.style.height = `${textarea.scrollHeight}px`;
210
- }
211
-
212
- private handleKeyDown(event: KeyboardEvent) {
213
- if (event.key === 'Enter' && !event.shiftKey) {
214
- event.preventDefault();
215
- this.sendMessage();
216
- }
217
- }
218
-
219
- private sendMessage() {
220
- if (!this.messageText.trim() && this.pendingAttachments.length === 0) {
221
- return;
222
- }
223
-
224
- this.dispatchEvent(new CustomEvent('send-message', {
225
- detail: {
226
- text: this.messageText,
227
- attachments: this.pendingAttachments
228
- },
229
- bubbles: true,
230
- composed: true
231
- }));
232
-
233
- // Clear input
234
- this.messageText = '';
126
+ private handleComposerSend = (event: CustomEvent<{ text: string }>) => {
127
+ // re-dispatch under the widget's send-message contract
128
+ event.stopPropagation();
129
+ this.dispatchEvent(
130
+ new CustomEvent('send-message', {
131
+ detail: {
132
+ text: event.detail.text,
133
+ attachments: this.pendingAttachments,
134
+ },
135
+ bubbles: true,
136
+ composed: true,
137
+ }),
138
+ );
235
139
  this.pendingAttachments = [];
236
-
237
- // Reset textarea height
238
- const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
239
- if (textarea) {
240
- textarea.style.height = 'auto';
241
- }
242
- }
243
-
244
- private openFileSelector() {
140
+ };
141
+
142
+ private handleFocus = () => {
143
+ this.dispatchEvent(
144
+ new CustomEvent('input-focus', {
145
+ bubbles: true,
146
+ composed: true,
147
+ }),
148
+ );
149
+ };
150
+
151
+ private handleBlur = () => {
152
+ this.dispatchEvent(
153
+ new CustomEvent('input-blur', {
154
+ bubbles: true,
155
+ composed: true,
156
+ }),
157
+ );
158
+ };
159
+
160
+ private openFileSelector = (event: Event) => {
161
+ event.stopPropagation();
245
162
  const fileInput = this.shadowRoot?.querySelector('#fileInput') as HTMLInputElement;
246
- if (fileInput) {
247
- fileInput.click();
248
- }
249
- }
163
+ fileInput?.click();
164
+ };
250
165
 
251
- private handleFileSelect(event: Event) {
166
+ private handleFileSelect = (event: Event) => {
252
167
  const input = event.target as HTMLInputElement;
253
168
  const files = Array.from(input.files || []);
254
-
169
+ if (files.length === 0) {
170
+ return;
171
+ }
172
+
255
173
  this.pendingAttachments = [...this.pendingAttachments, ...files];
256
-
257
- this.dispatchEvent(new CustomEvent('files-selected', {
258
- detail: { files },
259
- bubbles: true,
260
- composed: true
261
- }));
262
-
263
- input.value = ''; // Clear input for re-selection
264
- }
174
+
175
+ this.dispatchEvent(
176
+ new CustomEvent('files-selected', {
177
+ detail: { files },
178
+ bubbles: true,
179
+ composed: true,
180
+ }),
181
+ );
182
+
183
+ input.value = ''; // allow re-selection of the same file
184
+ };
265
185
 
266
186
  public focus() {
267
- const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
268
- textarea?.focus();
187
+ const field = this.shadowRoot
188
+ ?.querySelector('sio-composer')
189
+ ?.shadowRoot?.querySelector('.field') as HTMLElement | null;
190
+ field?.focus();
269
191
  }
270
192
 
271
193
  public clear() {
272
- this.messageText = '';
273
- this.pendingAttachments = [];
274
- const textarea = this.shadowRoot?.querySelector('.message-input') as HTMLTextAreaElement;
275
- if (textarea) {
276
- textarea.style.height = 'auto';
194
+ const composer = this.shadowRoot?.querySelector('sio-composer');
195
+ if (composer) {
196
+ composer.value = '';
277
197
  }
198
+ this.pendingAttachments = [];
278
199
  }
279
- }
200
+ }
@@ -0,0 +1,244 @@
1
+ import {
2
+ DeesElement,
3
+ html,
4
+ property,
5
+ customElement,
6
+ css,
7
+ type TemplateResult,
8
+ } from '@design.estate/dees-element';
9
+ import { sioElementStyles } from './tokens.js';
10
+
11
+ export type TSioPaperDirection = 'in' | 'out';
12
+
13
+ declare global {
14
+ interface HTMLElementTagNameMap {
15
+ 'sio-paper-thumb': SioPaperThumb;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * sio-paper-thumb — v7 physical-letter thumbnail (48×62, compact 38×50).
21
+ *
22
+ * Incoming letters render as cream scanned paper with a torn top edge;
23
+ * outgoing letters as lilac composed paper with a red-striped stamp square.
24
+ * An 18px direction badge (green ↓ in / letters-brown ↑ out) overhangs the
25
+ * bottom-right corner with a 2px surface ring.
26
+ */
27
+ @customElement('sio-paper-thumb')
28
+ export class SioPaperThumb extends DeesElement {
29
+ public static demo = () => html`
30
+ <div
31
+ style="display: flex; flex-direction: column; gap: 20px; max-width: 420px; padding: 20px; background: var(--sio-bg); border-radius: 18px; font-family: var(--sio-font-sf);"
32
+ >
33
+ <div
34
+ style="background: var(--sio-surface); border: 0.5px solid var(--sio-separator); border-radius: 18px; overflow: hidden;"
35
+ >
36
+ <div style="display: flex; gap: 12px; padding: 14px; border-bottom: 0.5px solid var(--sio-separator);">
37
+ <sio-paper-thumb direction="in"></sio-paper-thumb>
38
+ <div style="flex: 1; min-width: 0;">
39
+ <div style="display: flex; align-items: baseline; gap: 6px;">
40
+ <span
41
+ style="flex: 1; font-size: 15px; font-weight: 600; color: var(--sio-label); letter-spacing: -0.2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
42
+ >Finanzamt Berlin-Mitte</span
43
+ >
44
+ <span style="font-size: 12.5px; color: var(--sio-label-3); font-variant-numeric: tabular-nums;">Yesterday</span>
45
+ </div>
46
+ <div
47
+ style="font-size: 14px; font-weight: 500; color: var(--sio-label); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
48
+ >
49
+ Steuerbescheid 2024 — Festsetzung
50
+ </div>
51
+ <div style="font-size: 11px; color: var(--sio-label-3); margin-top: 6px;">Scanned · 3 pp</div>
52
+ </div>
53
+ </div>
54
+ <div style="display: flex; gap: 12px; padding: 14px;">
55
+ <sio-paper-thumb direction="out"></sio-paper-thumb>
56
+ <div style="flex: 1; min-width: 0;">
57
+ <div style="display: flex; align-items: baseline; gap: 6px;">
58
+ <span
59
+ style="flex: 1; font-size: 15px; font-weight: 500; color: var(--sio-label); letter-spacing: -0.2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
60
+ >To: Deutsche Rentenversicherung Bund</span
61
+ >
62
+ <span style="font-size: 12.5px; color: var(--sio-label-3); font-variant-numeric: tabular-nums;">Today</span>
63
+ </div>
64
+ <div
65
+ style="font-size: 14px; color: var(--sio-label-3); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
66
+ >
67
+ Widerspruch — Bescheid vom 14.03.2026
68
+ </div>
69
+ <div style="font-size: 11px; color: var(--sio-label-3); margin-top: 6px;">Mailed · Einschreiben · 2 pp</div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ <div style="display: flex; gap: 20px; align-items: flex-end;">
74
+ <div style="text-align: center;">
75
+ <sio-paper-thumb direction="in"></sio-paper-thumb>
76
+ <div style="font-size: 11px; color: var(--sio-label-3); margin-top: 8px;">in</div>
77
+ </div>
78
+ <div style="text-align: center;">
79
+ <sio-paper-thumb direction="out"></sio-paper-thumb>
80
+ <div style="font-size: 11px; color: var(--sio-label-3); margin-top: 8px;">out</div>
81
+ </div>
82
+ <div style="text-align: center;">
83
+ <sio-paper-thumb direction="in" compact></sio-paper-thumb>
84
+ <div style="font-size: 11px; color: var(--sio-label-3); margin-top: 8px;">in · compact</div>
85
+ </div>
86
+ <div style="text-align: center;">
87
+ <sio-paper-thumb direction="out" compact></sio-paper-thumb>
88
+ <div style="font-size: 11px; color: var(--sio-label-3); margin-top: 8px;">out · compact</div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ `;
93
+
94
+ /** 'in' = physically arrived + scanned (cream, torn edge); 'out' = composed + mailed (lilac, stamp). */
95
+ @property({ type: String, reflect: true })
96
+ public accessor direction: TSioPaperDirection = 'in';
97
+
98
+ /** 38×50 instead of 48×62, for cozy/compact list densities. */
99
+ @property({ type: Boolean, reflect: true })
100
+ public accessor compact = false;
101
+
102
+ public static styles = [
103
+ ...sioElementStyles,
104
+ css`
105
+ :host {
106
+ display: inline-block;
107
+ width: 48px;
108
+ height: 62px;
109
+ flex-shrink: 0;
110
+
111
+ /*
112
+ * Letter-paper artwork — theme-stable content colors transcribed from
113
+ * the v7 prototype. Scanned paper stays cream and stamps stay red in
114
+ * dark mode; these are illustration constants, not UI theme tokens.
115
+ */
116
+ --sio-paper-cream-top: #faf8f3;
117
+ --sio-paper-cream-bottom: #ede8de;
118
+ --sio-paper-lilac-top: #efebff;
119
+ --sio-paper-lilac-bottom: #d6ceff;
120
+ --sio-paper-ink-strong: rgba(0, 0, 0, 0.5);
121
+ --sio-paper-ink-faint: rgba(0, 0, 0, 0.18);
122
+ --sio-paper-edge: rgba(0, 0, 0, 0.12);
123
+ --sio-paper-stamp-a: #c94a3a;
124
+ --sio-paper-stamp-b: #e9664f;
125
+ --sio-paper-stamp-edge: #8a2e22;
126
+ --sio-paper-badge-fg: #ffffff;
127
+ }
128
+ :host([compact]) {
129
+ width: 38px;
130
+ height: 50px;
131
+ }
132
+
133
+ .thumb {
134
+ position: relative;
135
+ width: 100%;
136
+ height: 100%;
137
+ }
138
+
139
+ .paper {
140
+ position: absolute;
141
+ inset: 0;
142
+ border-radius: 4px;
143
+ overflow: hidden;
144
+ background: linear-gradient(180deg, var(--sio-paper-cream-top) 0%, var(--sio-paper-cream-bottom) 100%);
145
+ box-shadow: inset 0 0 0 0.5px var(--sio-paper-edge);
146
+ /* torn top edge — the letter was physically opened */
147
+ clip-path: polygon(
148
+ 0 4%, 5% 0, 13% 5%, 21% 1%, 29% 6%, 37% 2%, 45% 6%, 53% 1%,
149
+ 61% 5%, 69% 2%, 77% 6%, 85% 1%, 93% 5%, 100% 3%, 100% 100%, 0 100%
150
+ );
151
+ }
152
+ :host([direction='out']) .paper {
153
+ background: linear-gradient(180deg, var(--sio-paper-lilac-top) 0%, var(--sio-paper-lilac-bottom) 100%);
154
+ clip-path: none;
155
+ }
156
+
157
+ .addressBar {
158
+ position: absolute;
159
+ top: 7px;
160
+ left: 6px;
161
+ right: 14px;
162
+ height: 2px;
163
+ background: var(--sio-paper-ink-strong);
164
+ }
165
+ :host([compact]) .addressBar {
166
+ top: 5px;
167
+ left: 5px;
168
+ right: 11px;
169
+ }
170
+
171
+ .line {
172
+ position: absolute;
173
+ left: 6px;
174
+ right: 6px;
175
+ height: 1px;
176
+ background: var(--sio-paper-ink-faint);
177
+ }
178
+ :host([compact]) .line {
179
+ left: 5px;
180
+ right: 5px;
181
+ }
182
+
183
+ .stamp {
184
+ position: absolute;
185
+ top: 2px;
186
+ right: 2px;
187
+ width: 12px;
188
+ height: 9px;
189
+ border-radius: 1px;
190
+ background: repeating-linear-gradient(
191
+ 45deg,
192
+ var(--sio-paper-stamp-a) 0 2px,
193
+ var(--sio-paper-stamp-b) 2px 4px
194
+ );
195
+ border: 0.5px solid var(--sio-paper-stamp-edge);
196
+ }
197
+ :host([compact]) .stamp {
198
+ width: 10px;
199
+ height: 7px;
200
+ }
201
+
202
+ .badge {
203
+ position: absolute;
204
+ bottom: -4px;
205
+ right: -4px;
206
+ width: 18px;
207
+ height: 18px;
208
+ border-radius: 9px;
209
+ display: flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ font-size: 10px;
213
+ font-weight: 700;
214
+ line-height: 1;
215
+ color: var(--sio-paper-badge-fg);
216
+ background: var(--sio-green);
217
+ box-shadow: 0 0 0 2px var(--sio-surface);
218
+ }
219
+ :host([direction='out']) .badge {
220
+ background: var(--sio-ch-letters);
221
+ }
222
+ `,
223
+ ];
224
+
225
+ public render(): TemplateResult {
226
+ const lineCount = this.compact ? 5 : 6;
227
+ const lineTopStart = this.compact ? 12 : 15;
228
+ const lineGap = this.compact ? 5.5 : 6.5;
229
+ const lineTemplates: TemplateResult[] = [];
230
+ for (let k = 0; k < lineCount; k++) {
231
+ lineTemplates.push(html`<div class="line" style="top: ${lineTopStart + k * lineGap}px;"></div>`);
232
+ }
233
+ return html`
234
+ <div class="thumb" part="thumb">
235
+ <div class="paper">
236
+ <div class="addressBar"></div>
237
+ ${lineTemplates}
238
+ ${this.direction === 'out' ? html`<div class="stamp"></div>` : html``}
239
+ </div>
240
+ <div class="badge" aria-hidden="true">${this.direction === 'in' ? '↓' : '↑'}</div>
241
+ </div>
242
+ `;
243
+ }
244
+ }