tfrere HF Staff commited on
Commit
9f7fae9
·
1 Parent(s): 15dff0c

feat: align component CSS with research-article-template

Browse files

- Note: left border accent style with color-mix variants (info/success/danger)
- QuoteBlock: decorative open-quote mark, large text, author/source footer
- Accordion: template-matching border, bg, chevron, open state highlight
- Sidenote: muted text, generous padding (matches Sidenote.astro)
- Reference: proper spacing and caption styling
- Stack: responsive breakpoints matching template (768/1100px)
- Tables: zebra stripes, template header bg, vertical dividers
- Wide/fullWidth: proper overflow layout in editor
- Add PDF download button to hero meta section (using showPdf flag)
- Update publisher HTML styles for all components to match template

Made-with: Cursor

backend/src/publisher/html-renderer.ts CHANGED
@@ -131,77 +131,145 @@ ${css.print}
131
  @keyframes spin-cw { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
132
  @keyframes spin-ccw { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
133
 
134
- /* Note component */
135
  div[data-component="note"] {
136
- border: 1px solid var(--border-color);
137
- border-radius: 8px;
138
- padding: 1rem 1.25rem;
139
- margin: 1em 0;
140
- background: var(--surface-bg, rgba(0,0,0,0.02));
 
141
  }
142
- div[data-component="note"][data-variant="warning"] {
143
- border-color: #f59e0b;
144
- background: rgba(245,158,11,0.06);
145
  }
146
- div[data-component="note"][data-variant="danger"] {
147
- border-color: #ef4444;
148
- background: rgba(239,68,68,0.06);
149
  }
150
- div[data-component="note"][data-variant="success"] {
151
- border-color: #22c55e;
152
- background: rgba(34,197,94,0.06);
 
 
 
 
 
 
153
  }
154
  div[data-component="note"] > *:first-child { margin-top: 0; }
155
  div[data-component="note"] > *:last-child { margin-bottom: 0; }
156
 
157
- /* Quote component */
158
  div[data-component="quoteBlock"] {
159
- border-left: 3px solid var(--primary-color, var(--border-color));
160
- padding: 1rem 1.25rem;
161
- margin: 1.5em 0;
162
- font-style: italic;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  color: var(--muted-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  }
165
  div[data-component="quoteBlock"] > *:first-child { margin-top: 0; }
166
  div[data-component="quoteBlock"] > *:last-child { margin-bottom: 0; }
167
 
168
- /* Sidenote component */
169
  div[data-component="sidenote"] {
170
- font-size: 0.85em;
171
  color: var(--muted-color);
172
- border-left: 2px solid var(--border-color);
173
- padding-left: 0.75rem;
174
  margin: 1em 0;
175
  }
176
  div[data-component="sidenote"] > *:first-child { margin-top: 0; }
177
  div[data-component="sidenote"] > *:last-child { margin-bottom: 0; }
178
 
179
- /* Reference wrapper */
180
  div[data-component="reference"] {
181
- margin: 1.5em 0;
 
 
 
 
 
 
182
  }
183
  div[data-component="reference"] > *:first-child { margin-top: 0; }
184
  div[data-component="reference"] > *:last-child { margin-bottom: 0; }
185
 
186
- /* Stack / multi-column layout */
187
  div[data-component="stack"] {
188
  display: grid;
189
  gap: 1rem;
190
- margin: 1.5em 0;
 
 
 
191
  }
192
- div[data-component="stack"][data-layout="2-column"] { grid-template-columns: 1fr 1fr; }
193
- div[data-component="stack"][data-layout="3-column"] { grid-template-columns: 1fr 1fr 1fr; }
194
- div[data-component="stack"][data-layout="4-column"] { grid-template-columns: 1fr 1fr 1fr 1fr; }
 
 
195
  div[data-component="stack"][data-gap="small"] { gap: 0.5rem; }
196
  div[data-component="stack"][data-gap="large"] { gap: 2rem; }
197
- div[data-type="stack-column"] { min-width: 0; }
198
  div[data-type="stack-column"] > *:first-child { margin-top: 0; }
199
  div[data-type="stack-column"] > *:last-child { margin-bottom: 0; }
200
- @media (max-width: 640px) {
201
- div[data-component="stack"] { grid-template-columns: 1fr !important; }
 
 
 
 
 
 
 
 
 
202
  }
203
 
204
- /* Wide / full-width helpers for published content (if not already from _layout.css) */
205
  div[data-component="wide"] {
206
  width: min(1100px, 100vw - 64px);
207
  margin-left: 50%;
@@ -212,34 +280,55 @@ div[data-component="fullWidth"] {
212
  margin-left: calc(50% - 50vw);
213
  }
214
 
215
- /* Accordion as details/summary */
216
  details[data-component="accordion"] {
217
  border: 1px solid var(--border-color);
218
- border-radius: 8px;
 
219
  padding: 0;
220
- margin: 1em 0;
 
 
 
 
221
  }
222
-
223
  details[data-component="accordion"] > summary {
224
- padding: 0.75rem 1rem;
 
 
 
 
225
  cursor: pointer;
226
  font-weight: 600;
227
  color: var(--text-color);
228
  list-style: none;
229
  user-select: none;
230
  }
231
-
 
 
232
  details[data-component="accordion"] > summary::-webkit-details-marker { display: none; }
233
-
234
- details[data-component="accordion"] > summary::before {
235
- content: "\\25B8";
236
  display: inline-block;
237
- margin-right: 0.5rem;
238
- transition: transform 0.2s;
 
 
 
 
 
 
239
  }
240
-
241
- details[data-component="accordion"][open] > summary::before { transform: rotate(90deg); }
242
- details[data-component="accordion"] > .accordion-content { padding: 0 1rem 1rem; }
 
 
 
 
 
243
 
244
  /* Footer */
245
  .footer {
 
131
  @keyframes spin-cw { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
132
  @keyframes spin-ccw { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
133
 
134
+ /* Note component - aligned with template Note.astro */
135
  div[data-component="note"] {
136
+ background: var(--surface-bg);
137
+ border-left: 2px solid var(--border-color);
138
+ border-top-right-radius: 8px;
139
+ border-bottom-right-radius: 8px;
140
+ padding: 20px 18px;
141
+ margin: var(--block-spacing-y, 1em) 0;
142
  }
143
+ div[data-component="note"][variant="info"] {
144
+ border-left-color: #f39c12;
145
+ background: color-mix(in oklab, #f39c12 10%, var(--surface-bg));
146
  }
147
+ div[data-component="note"][variant="success"] {
148
+ border-left-color: #2ecc71;
149
+ background: color-mix(in oklab, #2ecc71 8%, var(--surface-bg));
150
  }
151
+ div[data-component="note"][variant="danger"] {
152
+ border-left-color: #e74c3c;
153
+ background: color-mix(in oklab, #e74c3c 8%, var(--surface-bg));
154
+ }
155
+ div[data-component="note"] .note__title {
156
+ font-size: 16px;
157
+ font-weight: 600;
158
+ color: var(--text-color);
159
+ margin-bottom: 6px;
160
  }
161
  div[data-component="note"] > *:first-child { margin-top: 0; }
162
  div[data-component="note"] > *:last-child { margin-bottom: 0; }
163
 
164
+ /* Quote component - aligned with template Quote.astro */
165
  div[data-component="quoteBlock"] {
166
+ position: relative;
167
+ margin: 32px 0;
168
+ max-width: 600px;
169
+ padding: 0;
170
+ border: none;
171
+ background: none;
172
+ }
173
+ div[data-component="quoteBlock"]::before {
174
+ content: '\\201C';
175
+ position: absolute;
176
+ top: -24px;
177
+ left: -30px;
178
+ font-size: 8rem;
179
+ font-weight: 400;
180
+ color: var(--text-color);
181
+ opacity: 0.05;
182
+ z-index: -1;
183
+ line-height: 1;
184
+ pointer-events: none;
185
+ }
186
+ div[data-component="quoteBlock"] .quote-text {
187
+ font-size: 1.5rem;
188
+ line-height: 1.4;
189
+ font-weight: 400;
190
+ letter-spacing: -0.01em;
191
+ color: var(--text-color);
192
+ }
193
+ div[data-component="quoteBlock"] .quote-footer {
194
+ font-size: 0.875rem;
195
  color: var(--muted-color);
196
+ margin-top: 12px;
197
+ }
198
+ div[data-component="quoteBlock"] .quote-author::before {
199
+ content: '\\2014\\00A0';
200
+ font-style: normal;
201
+ }
202
+ div[data-component="quoteBlock"] .quote-author {
203
+ font-weight: 500;
204
+ font-style: italic;
205
+ color: var(--text-color);
206
+ opacity: 0.85;
207
+ }
208
+ div[data-component="quoteBlock"] .quote-source {
209
+ font-weight: 500;
210
+ font-style: italic;
211
+ color: var(--text-color);
212
+ opacity: 0.85;
213
  }
214
  div[data-component="quoteBlock"] > *:first-child { margin-top: 0; }
215
  div[data-component="quoteBlock"] > *:last-child { margin-bottom: 0; }
216
 
217
+ /* Sidenote component - aligned with template Sidenote.astro */
218
  div[data-component="sidenote"] {
219
+ font-size: 0.9rem;
220
  color: var(--muted-color);
221
+ padding: 0 30px;
 
222
  margin: 1em 0;
223
  }
224
  div[data-component="sidenote"] > *:first-child { margin-top: 0; }
225
  div[data-component="sidenote"] > *:last-child { margin-bottom: 0; }
226
 
227
+ /* Reference wrapper - aligned with template Reference.astro */
228
  div[data-component="reference"] {
229
+ margin: 0 0 var(--spacing-4, 1rem);
230
+ }
231
+ div[data-component="reference"] .reference__caption {
232
+ text-align: left;
233
+ font-size: 0.9rem;
234
+ color: var(--muted-color);
235
+ margin-top: 6px;
236
  }
237
  div[data-component="reference"] > *:first-child { margin-top: 0; }
238
  div[data-component="reference"] > *:last-child { margin-bottom: 0; }
239
 
240
+ /* Stack / multi-column layout - aligned with template Stack.astro */
241
  div[data-component="stack"] {
242
  display: grid;
243
  gap: 1rem;
244
+ margin: var(--block-spacing-y, 1.5em) 0;
245
+ width: 100%;
246
+ max-width: 100%;
247
+ box-sizing: border-box;
248
  }
249
+ div[data-component="stack"][data-layout="2-column"] { grid-template-columns: repeat(2, 1fr); }
250
+ div[data-component="stack"][data-layout="3-column"] { grid-template-columns: repeat(3, 1fr); }
251
+ div[data-component="stack"][data-layout="4-column"] { grid-template-columns: repeat(4, 1fr); }
252
+ div[data-component="stack"][data-layout="auto"] { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
253
+ div[data-component="stack"]:not([data-layout]) { grid-template-columns: repeat(2, 1fr); }
254
  div[data-component="stack"][data-gap="small"] { gap: 0.5rem; }
255
  div[data-component="stack"][data-gap="large"] { gap: 2rem; }
256
+ div[data-type="stack-column"] { min-width: 0; overflow: hidden; word-wrap: break-word; overflow-wrap: break-word; }
257
  div[data-type="stack-column"] > *:first-child { margin-top: 0; }
258
  div[data-type="stack-column"] > *:last-child { margin-bottom: 0; }
259
+ @media (max-width: 768px) {
260
+ div[data-component="stack"][data-layout="2-column"],
261
+ div[data-component="stack"][data-layout="3-column"],
262
+ div[data-component="stack"][data-layout="4-column"],
263
+ div[data-component="stack"][data-layout="auto"],
264
+ div[data-component="stack"]:not([data-layout]) { grid-template-columns: 1fr !important; }
265
+ }
266
+ @media (min-width: 769px) and (max-width: 1100px) {
267
+ div[data-component="stack"][data-layout="3-column"],
268
+ div[data-component="stack"][data-layout="4-column"],
269
+ div[data-component="stack"][data-layout="auto"] { grid-template-columns: repeat(2, 1fr) !important; }
270
  }
271
 
272
+ /* Wide / full-width helpers */
273
  div[data-component="wide"] {
274
  width: min(1100px, 100vw - 64px);
275
  margin-left: 50%;
 
280
  margin-left: calc(50% - 50vw);
281
  }
282
 
283
+ /* Accordion - aligned with template Accordion.astro */
284
  details[data-component="accordion"] {
285
  border: 1px solid var(--border-color);
286
+ border-radius: var(--table-border-radius, 8px);
287
+ background: var(--surface-bg);
288
  padding: 0;
289
+ margin: 0 0 var(--spacing-4, 1em);
290
+ transition: box-shadow 180ms ease, border-color 180ms ease;
291
+ }
292
+ details[data-component="accordion"][open] {
293
+ border-color: color-mix(in oklab, var(--border-color), var(--primary-color) 20%);
294
  }
 
295
  details[data-component="accordion"] > summary {
296
+ display: flex;
297
+ align-items: center;
298
+ justify-content: space-between;
299
+ gap: 4px;
300
+ padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);
301
  cursor: pointer;
302
  font-weight: 600;
303
  color: var(--text-color);
304
  list-style: none;
305
  user-select: none;
306
  }
307
+ details[data-component="accordion"][open] > summary {
308
+ border-bottom: 1px solid var(--border-color);
309
+ }
310
  details[data-component="accordion"] > summary::-webkit-details-marker { display: none; }
311
+ details[data-component="accordion"] > summary::marker { content: ""; }
312
+ details[data-component="accordion"] > summary::after {
313
+ content: '';
314
  display: inline-block;
315
+ width: 0.5em;
316
+ height: 0.5em;
317
+ border-right: 2px solid currentColor;
318
+ border-bottom: 2px solid currentColor;
319
+ transform: rotate(-45deg);
320
+ transition: transform 220ms ease;
321
+ opacity: 0.6;
322
+ flex-shrink: 0;
323
  }
324
+ details[data-component="accordion"][open] > summary::after {
325
+ transform: rotate(45deg);
326
+ }
327
+ details[data-component="accordion"] > .accordion-content {
328
+ padding: 8px;
329
+ }
330
+ details[data-component="accordion"] > .accordion-content > *:first-child { margin-top: 0; }
331
+ details[data-component="accordion"] > .accordion-content > *:last-child { margin-bottom: 0; }
332
 
333
  /* Footer */
334
  .footer {
frontend/src/editor/components/WrapperView.tsx CHANGED
@@ -14,12 +14,11 @@ import { NodeViewWrapper, NodeViewContent } from "@tiptap/react";
14
  import type { NodeViewProps } from "@tiptap/react";
15
  import type { ComponentDef, ComponentField } from "./registry";
16
 
17
- // Variant color mapping for the Note component
18
  const VARIANT_COLORS: Record<string, { border: string; bg: string; icon: string }> = {
19
- neutral: { border: "rgba(255,255,255,0.12)", bg: "rgba(255,255,255,0.03)", icon: "📝" },
20
- info: { border: "rgba(59,130,246,0.35)", bg: "rgba(59,130,246,0.06)", icon: "ℹ️" },
21
- success: { border: "rgba(34,197,94,0.35)", bg: "rgba(34,197,94,0.06)", icon: "✅" },
22
- danger: { border: "rgba(239,68,68,0.35)", bg: "rgba(239,68,68,0.06)", icon: "⚠️" },
23
  };
24
 
25
  function FieldInput({
@@ -115,21 +114,50 @@ export function makeWrapperView(def: ComponentDef) {
115
 
116
  const emoji = (node.attrs.emoji as string) || colors?.icon || def.icon;
117
 
118
- const borderColor = colors?.border || "rgba(255,255,255,0.1)";
119
- const bgColor = colors?.bg || "rgba(255,255,255,0.02)";
120
 
121
  const isAccordion = def.name === "accordion";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
  return (
124
  <NodeViewWrapper
125
  data-component={def.name}
126
- style={{
127
- border: `1px solid ${borderColor}`,
128
- borderRadius: 8,
129
- background: bgColor,
130
- margin: "0.75em 0",
131
- overflow: "hidden",
132
- }}
133
  >
134
  {/* Header chrome - not editable by ProseMirror */}
135
  <div
@@ -138,8 +166,8 @@ export function makeWrapperView(def: ComponentDef) {
138
  display: "flex",
139
  alignItems: "center",
140
  gap: 8,
141
- padding: "8px 12px",
142
- borderBottom: collapsed ? "none" : `1px solid ${borderColor}`,
143
  userSelect: "none",
144
  cursor: isAccordion ? "pointer" : "default",
145
  }}
@@ -193,7 +221,7 @@ export function makeWrapperView(def: ComponentDef) {
193
  <div
194
  style={{
195
  display: collapsed ? "none" : "block",
196
- padding: "8px 12px",
197
  }}
198
  >
199
  <NodeViewContent className="component-content" />
 
14
  import type { NodeViewProps } from "@tiptap/react";
15
  import type { ComponentDef, ComponentField } from "./registry";
16
 
 
17
  const VARIANT_COLORS: Record<string, { border: string; bg: string; icon: string }> = {
18
+ neutral: { border: "var(--border-color)", bg: "var(--surface-bg)", icon: "📝" },
19
+ info: { border: "#f39c12", bg: "color-mix(in oklab, #f39c12 10%, var(--surface-bg))", icon: "💡" },
20
+ success: { border: "#2ecc71", bg: "color-mix(in oklab, #2ecc71 8%, var(--surface-bg))", icon: "✅" },
21
+ danger: { border: "#e74c3c", bg: "color-mix(in oklab, #e74c3c 8%, var(--surface-bg))", icon: "⚠️" },
22
  };
23
 
24
  function FieldInput({
 
114
 
115
  const emoji = (node.attrs.emoji as string) || colors?.icon || def.icon;
116
 
117
+ const borderColor = colors?.border || "var(--border-color)";
118
+ const bgColor = colors?.bg || "var(--surface-bg)";
119
 
120
  const isAccordion = def.name === "accordion";
121
+ const isNote = def.name === "note";
122
+ const isQuote = def.name === "quoteBlock";
123
+ const isSidenote = def.name === "sidenote";
124
+
125
+ const wrapperStyle: React.CSSProperties = isNote
126
+ ? {
127
+ borderLeft: `2px solid ${borderColor}`,
128
+ borderTopRightRadius: 8,
129
+ borderBottomRightRadius: 8,
130
+ background: bgColor,
131
+ padding: 0,
132
+ margin: "0.75em 0",
133
+ }
134
+ : isQuote
135
+ ? {
136
+ position: "relative" as const,
137
+ margin: "32px 0",
138
+ maxWidth: 600,
139
+ }
140
+ : isSidenote
141
+ ? {
142
+ borderRadius: 8,
143
+ padding: 0,
144
+ margin: "0.75em 0",
145
+ fontSize: "0.9rem",
146
+ color: "var(--muted-color)",
147
+ }
148
+ : {
149
+ border: `1px solid ${borderColor}`,
150
+ borderRadius: "var(--table-border-radius, 8px)",
151
+ background: bgColor,
152
+ margin: "0.75em 0",
153
+ overflow: "hidden",
154
+ };
155
 
156
  return (
157
  <NodeViewWrapper
158
  data-component={def.name}
159
+ data-variant={variant || undefined}
160
+ style={wrapperStyle}
 
 
 
 
 
161
  >
162
  {/* Header chrome - not editable by ProseMirror */}
163
  <div
 
166
  display: "flex",
167
  alignItems: "center",
168
  gap: 8,
169
+ padding: isNote ? "8px 18px" : isQuote ? "0" : "8px 12px",
170
+ borderBottom: collapsed || isQuote ? "none" : isNote ? "none" : `1px solid ${borderColor}`,
171
  userSelect: "none",
172
  cursor: isAccordion ? "pointer" : "default",
173
  }}
 
221
  <div
222
  style={{
223
  display: collapsed ? "none" : "block",
224
+ padding: isNote ? "0 18px 12px" : isQuote ? "0" : isSidenote ? "0 30px" : "8px 12px",
225
  }}
226
  >
227
  <NodeViewContent className="component-content" />
frontend/src/editor/frontmatter/FrontmatterHero.tsx CHANGED
@@ -28,7 +28,7 @@ export function FrontmatterHero({ store }: FrontmatterHeroProps) {
28
  const hasAuthors = data.authors.length > 0;
29
  const hasAffiliations = data.affiliations.length > 0;
30
  const multipleAffiliations = data.affiliations.length > 1;
31
- const hasMeta = hasAuthors || data.published || data.doi;
32
 
33
  return (
34
  <div>
@@ -145,6 +145,18 @@ export function FrontmatterHero({ store }: FrontmatterHeroProps) {
145
  inline
146
  />
147
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
148
  </div>
149
  </header>
150
  )}
 
28
  const hasAuthors = data.authors.length > 0;
29
  const hasAffiliations = data.affiliations.length > 0;
30
  const multipleAffiliations = data.affiliations.length > 1;
31
+ const hasMeta = hasAuthors || data.published || data.doi || data.showPdf;
32
 
33
  return (
34
  <div>
 
145
  inline
146
  />
147
  </div>
148
+
149
+ {/* PDF cell */}
150
+ {data.showPdf && (
151
+ <div className="meta-container-cell meta-container-cell--pdf">
152
+ <h3>PDF</h3>
153
+ <p style={{ margin: 0, lineHeight: 1 }}>
154
+ <a className="button" href="#" onClick={(e) => e.preventDefault()}>
155
+ Download PDF
156
+ </a>
157
+ </p>
158
+ </div>
159
+ )}
160
  </div>
161
  </header>
162
  )}
frontend/src/styles/article.css CHANGED
@@ -200,40 +200,51 @@
200
  margin: 2em 0;
201
  }
202
 
203
- /* Tables */
204
  .tiptap table {
205
  border-collapse: collapse;
 
206
  width: 100%;
207
  margin: 1em 0;
208
  overflow: hidden;
209
- border-radius: 6px;
210
- border: 1px solid var(--border-light);
 
211
  }
212
 
213
  .tiptap th,
214
  .tiptap td {
215
- padding: 0.5rem 0.75rem;
216
- border: 1px solid var(--border-light);
 
217
  text-align: left;
218
- font-size: 0.875rem;
 
219
  min-width: 80px;
220
  }
221
 
 
 
 
 
 
222
  .tiptap th {
223
- background: var(--code-bg);
224
- color: var(--muted-color);
225
  font-weight: 600;
226
- font-size: 0.8125rem;
227
- text-transform: uppercase;
228
- letter-spacing: 0.03em;
229
  }
230
 
231
  .tiptap td {
232
  color: var(--text-color);
233
  }
234
 
235
- .tiptap tr:hover td {
236
- background: var(--bg-hover);
 
 
 
 
237
  }
238
 
239
  /* Links - aligned with template (_base.css .content-grid main a) */
@@ -413,7 +424,10 @@
413
  font-style: italic;
414
  }
415
 
416
- /* Custom component blocks */
 
 
 
417
  .component-content > *:first-child {
418
  margin-top: 0;
419
  }
@@ -422,6 +436,81 @@
422
  margin-bottom: 0;
423
  }
424
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  /* Glossary inline node */
426
  .glossary-node {
427
  position: relative;
 
200
  margin: 2em 0;
201
  }
202
 
203
+ /* Tables - aligned with template _table.css */
204
  .tiptap table {
205
  border-collapse: collapse;
206
+ table-layout: auto;
207
  width: 100%;
208
  margin: 1em 0;
209
  overflow: hidden;
210
+ border-radius: var(--table-border-radius, 6px);
211
+ border: 1px solid var(--border-color);
212
+ background: var(--surface-bg);
213
  }
214
 
215
  .tiptap th,
216
  .tiptap td {
217
+ padding: 6px 8px;
218
+ border-bottom: 1px solid var(--border-color);
219
+ border-right: 1px solid var(--border-color);
220
  text-align: left;
221
+ font-size: 15px;
222
+ vertical-align: top;
223
  min-width: 80px;
224
  }
225
 
226
+ .tiptap th:last-child,
227
+ .tiptap td:last-child {
228
+ border-right: none;
229
+ }
230
+
231
  .tiptap th {
232
+ background: var(--table-header-bg, var(--code-bg));
 
233
  font-weight: 600;
234
+ padding-top: 10px;
235
+ padding-bottom: 10px;
 
236
  }
237
 
238
  .tiptap td {
239
  color: var(--text-color);
240
  }
241
 
242
+ .tiptap tbody tr:nth-child(odd) td {
243
+ background: var(--table-row-odd-bg, rgba(255,255,255,0.02));
244
+ }
245
+
246
+ .tiptap tbody tr:last-child td {
247
+ border-bottom: none;
248
  }
249
 
250
  /* Links - aligned with template (_base.css .content-grid main a) */
 
424
  font-style: italic;
425
  }
426
 
427
+ /* -----------------------------------------------------------------------
428
+ Custom component blocks (aligned with research-article-template)
429
+ ----------------------------------------------------------------------- */
430
+
431
  .component-content > *:first-child {
432
  margin-top: 0;
433
  }
 
436
  margin-bottom: 0;
437
  }
438
 
439
+ /* --- Note --- */
440
+ .tiptap [data-component="note"] .component-content {
441
+ font-size: 0.95rem;
442
+ }
443
+
444
+ /* --- Quote block (decorative) --- */
445
+ .tiptap [data-component="quoteBlock"] {
446
+ position: relative;
447
+ }
448
+
449
+ .tiptap [data-component="quoteBlock"]::before {
450
+ content: '\201C';
451
+ position: absolute;
452
+ top: -24px;
453
+ left: -30px;
454
+ font-size: 8rem;
455
+ font-family: var(--default-font-family);
456
+ font-weight: 400;
457
+ color: var(--text-color);
458
+ opacity: 0.05;
459
+ z-index: -1;
460
+ line-height: 1;
461
+ pointer-events: none;
462
+ }
463
+
464
+ .tiptap [data-component="quoteBlock"] .component-content {
465
+ font-size: 1.5rem;
466
+ line-height: 1.4;
467
+ font-weight: 400;
468
+ letter-spacing: -0.01em;
469
+ }
470
+
471
+ /* --- Accordion --- */
472
+ .tiptap [data-component="accordion"][open] {
473
+ border-color: color-mix(in oklab, var(--border-color), var(--primary-color) 20%);
474
+ }
475
+
476
+ /* --- Sidenote --- */
477
+ .tiptap [data-component="sidenote"] .component-content {
478
+ font-size: 0.9rem;
479
+ color: var(--muted-color);
480
+ }
481
+
482
+ /* --- Reference / figure --- */
483
+ .tiptap [data-component="reference"] {
484
+ margin-bottom: var(--spacing-4, 1rem);
485
+ }
486
+
487
+ .tiptap [data-component="reference"] .component-content {
488
+ margin-bottom: 0;
489
+ }
490
+
491
+ /* --- Wide container --- */
492
+ .tiptap [data-component="wide"] {
493
+ width: calc(100% + 160px);
494
+ margin-left: -80px;
495
+ margin-right: -80px;
496
+ }
497
+
498
+ /* --- Full width container --- */
499
+ .tiptap [data-component="fullWidth"] {
500
+ width: calc(100% + 300px);
501
+ margin-left: -150px;
502
+ margin-right: -150px;
503
+ }
504
+
505
+ @media (max-width: 1100px) {
506
+ .tiptap [data-component="wide"],
507
+ .tiptap [data-component="fullWidth"] {
508
+ width: 100%;
509
+ margin-left: 0;
510
+ margin-right: 0;
511
+ }
512
+ }
513
+
514
  /* Glossary inline node */
515
  .glossary-node {
516
  position: relative;