1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
|
# Callout Shortcode — Design Spec
**Date:** 2026-04-29
**Status:** Approved
---
## Context
Articles on danix.xyz need a way to call out notes, tips, warnings, and other semantic asides inline. No callout shortcode exists yet. SHORTCODES.md lists it as a planned future shortcode. This spec defines the design, CSS architecture, A11y requirements, and shortcode API.
---
## Approach
**Single shortcode + CSS-only variants** (Approach C from brainstorm).
One `callout.html` template handles all 6 types. Type-specific styling lives entirely in `main.css` via CSS custom properties and Tailwind utility classes. No inline style logic in the template. CSS rebuild required after adding new types.
Chosen over:
- Single shortcode with inline Hugo logic (more template complexity)
- Separate shortcode per type (6 files, duplicated logic)
---
## Shortcode API
```
{{< callout type="note" >}}
Content here. Supports **markdown**.
{{< /callout >}}
{{< callout type="danger" title="Custom heading" >}}
Overrides the default i18n title.
{{< /callout >}}
```
### Parameters
| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `type` | Yes | — | One of: `note`, `tip`, `info`, `warning`, `danger`, `success` |
| `title` | No | i18n key for type | Overrides the default translated title |
---
## Visual Design
**Layout:** Left border accent (4px solid, type color) + fading bottom border (gradient left→transparent, 50% width, 1px, type color). Subtle background tint. Rounded on right side only (`border-radius: 0 6px 6px 0`).
**Title row:** Feather icon (14×14, `aria-hidden="true"`) + uppercase label in `font-family: var(--font-mono)` (JetBrains Mono), 11px, 0.08em letter-spacing, type color.
**Body text:** `var(--text)`, 14px, 1.6 line-height. Supports markdown via `.Inner | markdownify`.
**Spacing:** `my-6` vertical margin, `py-3 px-4` inner padding.
**Background opacity:** dark 0.08, light 0.06 — consistent with existing toast/badge pattern.
---
## Color System
All colors via CSS custom properties. No hardcoded hex except the approved Danger exception.
### CSS Variables (added to `main.css`)
```css
/* Dark mode (:root) */
--callout-note: var(--accent); /* purple #a855f7 */
--callout-tip: var(--accent2); /* green #00ff88 */
--callout-info: var(--type-link); /* cyan #38bdf8 */
--callout-warning: var(--type-life); /* amber #f59e0b */
--callout-danger: #ef4444; /* red — approved exception, universally semantic */
--callout-success: var(--accent2); /* green #00ff88 */
/* Light mode (html.theme-light) */
--callout-note: var(--accent); /* purple #7c3aed */
--callout-tip: var(--accent2); /* green #008f5a */
--callout-info: var(--type-link); /* cyan #0284c7 */
--callout-warning: var(--type-life); /* amber #d97706 */
--callout-danger: #ef4444; /* red — same in both themes */
--callout-success: var(--accent2); /* green #008f5a */
```
### Tailwind Classes (added to `main.css` via `@apply`)
```css
.callout { @apply relative my-6 py-3 px-4 rounded-r-md; }
/* Each type sets --callout-color so ::after gradient picks it up automatically */
.callout-note { --callout-color: var(--callout-note); border-left: 4px solid var(--callout-note); background: rgba(168,85,247,0.08); }
.callout-tip { --callout-color: var(--callout-tip); border-left: 4px solid var(--callout-tip); background: rgba(0,255,136,0.08); }
.callout-info { --callout-color: var(--callout-info); border-left: 4px solid var(--callout-info); background: rgba(56,189,248,0.08); }
.callout-warning { --callout-color: var(--callout-warning); border-left: 4px solid var(--callout-warning); background: rgba(245,158,11,0.08); }
.callout-danger { --callout-color: var(--callout-danger); border-left: 4px solid var(--callout-danger); background: rgba(239,68,68,0.08); }
.callout-success { --callout-color: var(--callout-success); border-left: 4px solid var(--callout-success); background: rgba(0,255,136,0.08); }
```
The fading bottom border is a `::after` pseudo-element:
```css
.callout::after {
content: '';
position: absolute;
bottom: 0; left: 0;
width: 50%; height: 1px;
background: linear-gradient(to right, var(--callout-color), transparent);
}
```
**Light mode background opacity** — reduce to 0.06 per theming standard (light needs less emphasis).
rgba values use THEMING-STANDARD.md corrected light vars: `--accent #7c3aed`, `--accent2 #008f5a`, `--type-link #0284c7`, `--type-life #d97706`.
**Prerequisite:** `html.theme-light` in `main.css` must first be corrected to match THEMING-STANDARD.md (see implementation plan Task 0).
```css
html.theme-light .callout-note { background: rgba(124,58,237,0.06); } /* #7c3aed */
html.theme-light .callout-tip { background: rgba(0,143,90,0.06); } /* #008f5a */
html.theme-light .callout-info { background: rgba(2,132,199,0.06); } /* #0284c7 */
html.theme-light .callout-warning { background: rgba(217,119,6,0.06); } /* #d97706 */
html.theme-light .callout-danger { background: rgba(239,68,68,0.06); } /* #ef4444 */
html.theme-light .callout-success { background: rgba(0,143,90,0.06); } /* #008f5a */
```
---
## Feather Icons per Type
| Type | Feather icon name | Rationale |
|------|-------------------|-----------|
| note | `edit-2` | Writing/annotation |
| tip | `zap` | Quick insight |
| info | `info` | Standard info |
| warning | `alert-triangle` | Universal warning symbol |
| danger | `x-circle` | Stop/error |
| success | `check-circle` | Confirmed/done |
Icons rendered inline as `<i data-feather="icon-name" class="w-3.5 h-3.5" aria-hidden="true"></i>`. Feather is already loaded site-wide via `baseof.html`.
---
## i18n Keys
Added to all 4 i18n files: `themes/danix-xyz-hacker/i18n/en.yaml`, `it.yaml` and content repo `i18n/en.yaml`, `it.yaml`.
```yaml
# en.yaml
callout_note: "Note"
callout_tip: "Tip"
callout_info: "Info"
callout_warning: "Warning"
callout_danger: "Danger"
callout_success: "Success"
# it.yaml
callout_note: "Nota"
callout_tip: "Suggerimento"
callout_info: "Informazione"
callout_warning: "Attenzione"
callout_danger: "Pericolo"
callout_success: "Successo"
```
---
## Accessibility
- `role="note"` on all types — semantic, non-intrusive to screen readers
- `role="alert"` on `danger` type only — interrupts screen readers immediately (appropriate for critical warnings)
- `aria-hidden="true"` on all Feather icons — decorative
- Title text always present (i18n fallback) — color never used alone to convey meaning (WCAG 1.4.1)
- Body text uses `var(--text)` — verified ≥4.5:1 contrast ratio against `var(--bg)` in both themes per THEMING-STANDARD.md
- No animations — `prefers-reduced-motion` not required, but `::after` pseudo is static CSS, no transitions added
---
## Files to Create / Modify
| File | Action | Notes |
|------|--------|-------|
| `themes/danix-xyz-hacker/layouts/shortcodes/callout.html` | **Create** | Shortcode template |
| `themes/danix-xyz-hacker/assets/css/main.css` | **Modify** | Add `--callout-*` vars + `.callout-*` classes |
| `themes/danix-xyz-hacker/i18n/en.yaml` | **Modify** | Add 6 callout title keys |
| `themes/danix-xyz-hacker/i18n/it.yaml` | **Modify** | Add 6 callout title keys (Italian) |
| `i18n/en.yaml` | **Modify** | Mirror keys in content repo |
| `i18n/it.yaml` | **Modify** | Mirror keys in content repo |
| `SHORTCODES.md` | **Modify** | Document callout shortcode |
CSS rebuild required (`npm run build`) after CSS changes.
---
## Shortcode Template Sketch
```html
{{- $type := .Get "type" | default "note" -}}
{{- $title := .Get "title" | default (i18n (printf "callout_%s" $type)) -}}
{{- $role := cond (eq $type "danger") "alert" "note" -}}
<div class="callout callout-{{ $type }}" role="{{ $role }}">
<div class="callout-title">
<i data-feather="{{ /* icon per type */ }}" class="w-3.5 h-3.5" aria-hidden="true"></i>
<span>{{ $title }}</span>
</div>
<div class="callout-body">
{{ .Inner | markdownify }}
</div>
</div>
```
Icon selection via Hugo `dict` lookup — no if/else chain:
```
{{- $icons := dict "note" "edit-2" "tip" "zap" "info" "info" "warning" "alert-triangle" "danger" "x-circle" "success" "check-circle" -}}
{{- $icon := index $icons $type | default "info" -}}
```
---
## Verification
1. Run `hugo server` — render an article with all 6 callout types
2. Toggle dark/light theme — verify colors switch correctly via CSS vars
3. Override title with `title="Custom"` — verify it replaces i18n default
4. Screen reader test: `danger` type must announce immediately (`role="alert"`); others must not interrupt
5. Run `npm run build` — verify CSS compiles without errors and Tailwind includes all `.callout-*` classes
6. Check light theme backgrounds use lower opacity (0.06) vs dark (0.08)
|