@markuxt/markuxt 0.1.4

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 (94) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +168 -0
  3. package/app.config.d.ts +33 -0
  4. package/nuxt.config.ts +170 -0
  5. package/package.json +43 -0
  6. package/src/components/AppFooter.vue +225 -0
  7. package/src/components/AppHeader.vue +342 -0
  8. package/src/components/Hero.vue +438 -0
  9. package/src/components/Icon.vue +131 -0
  10. package/src/components/LanguageSwitcher.vue +71 -0
  11. package/src/components/MemberCard.vue +198 -0
  12. package/src/components/MembersGrid.vue +129 -0
  13. package/src/components/MermaidDiagram.vue +99 -0
  14. package/src/components/NewsCard.vue +119 -0
  15. package/src/components/PublicationCard.vue +245 -0
  16. package/src/components/SectionTitle.vue +75 -0
  17. package/src/components/content/ProseImg.vue +29 -0
  18. package/src/components/content/ProsePre.vue +58 -0
  19. package/src/components/content/ProseVideo.vue +45 -0
  20. package/src/composables/resolveContentImage.ts +35 -0
  21. package/src/content-transformers/binary-assets.ts +20 -0
  22. package/src/error.vue +58 -0
  23. package/src/layouts/default.vue +37 -0
  24. package/src/middleware/navigation-guard.ts +22 -0
  25. package/src/pages/index.vue +232 -0
  26. package/src/pages/members/[...slug].vue +542 -0
  27. package/src/pages/members/index.vue +147 -0
  28. package/src/pages/news/[...slug].vue +280 -0
  29. package/src/pages/news/index.vue +102 -0
  30. package/src/pages/positions/[...slug].vue +425 -0
  31. package/src/pages/positions/index.vue +266 -0
  32. package/src/pages/projects/[...slug].vue +441 -0
  33. package/src/pages/projects/index.vue +499 -0
  34. package/src/pages/publications/[...slug].vue +435 -0
  35. package/src/pages/publications/index.vue +145 -0
  36. package/src/plugins/mathml-components.ts +33 -0
  37. package/src/plugins/suppress-warnings.ts +40 -0
  38. package/src/public/_markuxt/components/AppFooter.vue +225 -0
  39. package/src/public/_markuxt/components/AppHeader.vue +342 -0
  40. package/src/public/_markuxt/components/Hero.vue +430 -0
  41. package/src/public/_markuxt/components/Icon.vue +131 -0
  42. package/src/public/_markuxt/components/LanguageSwitcher.vue +71 -0
  43. package/src/public/_markuxt/components/MemberCard.vue +198 -0
  44. package/src/public/_markuxt/components/MembersGrid.vue +129 -0
  45. package/src/public/_markuxt/components/MermaidDiagram.vue +99 -0
  46. package/src/public/_markuxt/components/NewsCard.vue +119 -0
  47. package/src/public/_markuxt/components/PublicationCard.vue +245 -0
  48. package/src/public/_markuxt/components/SectionTitle.vue +75 -0
  49. package/src/public/_markuxt/components/content/ProseImg.vue +29 -0
  50. package/src/public/_markuxt/components/content/ProsePre.vue +58 -0
  51. package/src/public/_markuxt/components/content/ProseVideo.vue +45 -0
  52. package/src/public/_markuxt/composables/resolveContentImage.ts +35 -0
  53. package/src/public/_markuxt/content-transformers/binary-assets.ts +20 -0
  54. package/src/public/_markuxt/error.vue +58 -0
  55. package/src/public/_markuxt/layouts/default.vue +37 -0
  56. package/src/public/_markuxt/middleware/navigation-guard.ts +22 -0
  57. package/src/public/_markuxt/pages/index.vue +232 -0
  58. package/src/public/_markuxt/pages/members/[...slug].vue +542 -0
  59. package/src/public/_markuxt/pages/members/index.vue +147 -0
  60. package/src/public/_markuxt/pages/news/[...slug].vue +280 -0
  61. package/src/public/_markuxt/pages/news/index.vue +102 -0
  62. package/src/public/_markuxt/pages/positions/[...slug].vue +425 -0
  63. package/src/public/_markuxt/pages/positions/index.vue +266 -0
  64. package/src/public/_markuxt/pages/projects/[...slug].vue +441 -0
  65. package/src/public/_markuxt/pages/projects/index.vue +499 -0
  66. package/src/public/_markuxt/pages/publications/[...slug].vue +435 -0
  67. package/src/public/_markuxt/pages/publications/index.vue +145 -0
  68. package/src/public/_markuxt/plugins/mathml-components.ts +33 -0
  69. package/src/public/_markuxt/plugins/suppress-warnings.ts +40 -0
  70. package/src/public/_markuxt/server/plugins/content-locale.ts +47 -0
  71. package/src/public/_markuxt/server/plugins/fix-content-anchors.ts +63 -0
  72. package/src/public/_markuxt/styles/_animations.css +99 -0
  73. package/src/public/_markuxt/styles/_base.css +31 -0
  74. package/src/public/_markuxt/styles/_code.css +109 -0
  75. package/src/public/_markuxt/styles/_components.css +109 -0
  76. package/src/public/_markuxt/styles/_layout.css +220 -0
  77. package/src/public/_markuxt/styles/_markdown.css +52 -0
  78. package/src/public/_markuxt/styles/_tokens.css +62 -0
  79. package/src/public/_markuxt/styles/_typography.css +144 -0
  80. package/src/public/_markuxt/styles/_utilities.css +110 -0
  81. package/src/public/_markuxt/styles/main.css +784 -0
  82. package/src/public/images/logo.png +0 -0
  83. package/src/server/plugins/content-locale.ts +47 -0
  84. package/src/server/plugins/fix-content-anchors.ts +63 -0
  85. package/src/styles/_animations.css +99 -0
  86. package/src/styles/_base.css +31 -0
  87. package/src/styles/_code.css +109 -0
  88. package/src/styles/_components.css +109 -0
  89. package/src/styles/_layout.css +220 -0
  90. package/src/styles/_markdown.css +52 -0
  91. package/src/styles/_tokens.css +62 -0
  92. package/src/styles/_typography.css +144 -0
  93. package/src/styles/_utilities.css +110 -0
  94. package/src/styles/main.css +784 -0
Binary file
@@ -0,0 +1,47 @@
1
+ // Content locale detection from filename suffixes.
2
+ //
3
+ // Recognizes locale from Markdown filenames:
4
+ // - `page.md` → locale 'en' (default)
5
+ // - `page.en.md` → locale 'en'
6
+ // - `page.en-US.md` → locale 'en'
7
+ // - `page.zh.md` → locale 'zh-CN'
8
+ // - `page.zh-CN.md` → locale 'zh-CN'
9
+ //
10
+ // Sets `file.locale` and normalizes `_path` so locale variants
11
+ // share the same path (e.g. `salman-ijaz.zh-CN.md` → `/members/staff/salman-ijaz`).
12
+
13
+ const LOCALE_PATTERN = /\.(en|en-US|zh|zh-CN)\.md$/
14
+
15
+ // Normalize locale codes to the canonical form used by @nuxtjs/i18n
16
+ function normalizeLocale(raw: string): string {
17
+ if (raw === 'en-US' || raw === 'en') return 'en'
18
+ if (raw === 'zh' || raw === 'zh-CN') return 'zh-CN'
19
+ return raw
20
+ }
21
+
22
+ export default defineNitroPlugin((nitroApp) => {
23
+ nitroApp.hooks.hook('content:file:afterParse', (file: any) => {
24
+ if (!file?._id || typeof file._id !== 'string') return
25
+
26
+ // Only process Markdown files
27
+ if (!file._id.endsWith('.md')) return
28
+
29
+ const match = file._id.match(LOCALE_PATTERN)
30
+
31
+ if (match) {
32
+ file.locale = normalizeLocale(match[1])
33
+
34
+ // Strip locale suffix from _path so variants share the same route
35
+ // e.g. `/members/staff/salman-ijaz-zh-cn` → `/members/staff/salman-ijaz`
36
+ if (file._path) {
37
+ file._path = file._path.replace(
38
+ new RegExp(`-(?:en|en-US|zh|zh-CN)$`),
39
+ ''
40
+ )
41
+ }
42
+ } else {
43
+ // No locale suffix = default locale
44
+ file.locale = 'en'
45
+ }
46
+ })
47
+ })
@@ -0,0 +1,63 @@
1
+ // Fix in-page anchor links for content pasted from GitHub READMEs.
2
+ //
3
+ // Background: when a heading like `## 🧭 Start Here` is parsed, github-slugger
4
+ // produces `-start-here` (emoji dropped, leading dash kept). GitHub uses that
5
+ // exact string as the heading's anchor, so hand-written tables of contents link
6
+ // to `#-start-here`. Nuxt Content's MDC compiler, however, runs one extra step
7
+ // GitHub does not — it strips the leading/trailing dashes — so the heading's id
8
+ // becomes `start-here`. The result: every emoji-prefixed TOC link 404s in-page.
9
+ //
10
+ // Rather than rewrite the markdown (which is correct GitHub syntax), we
11
+ // normalize the anchor hrefs at parse time using the SAME rule MDC applies to
12
+ // heading ids, so the links converge onto the generated ids. Idempotent, and we
13
+ // only rewrite a link when doing so actually points it at a real heading.
14
+
15
+ // Mirror of MDC's heading-id tail normalization (compiler.js):
16
+ // slug(text).replace(/-+/g, '-').replace(/^-|-$/g, '').replace(/^(\d)/, '_$1')
17
+ function normalizeId(fragment: string): string {
18
+ return fragment
19
+ .replace(/-+/g, '-')
20
+ .replace(/^-|-$/g, '')
21
+ .replace(/^(\d)/, '_$1')
22
+ }
23
+
24
+ // Collect ids of all heading nodes (h1–h6) in the parsed body AST.
25
+ function collectHeadingIds(node: any, ids: Set<string>): void {
26
+ if (!node || typeof node !== 'object') return
27
+ if (Array.isArray(node)) {
28
+ for (const child of node) collectHeadingIds(child, ids)
29
+ return
30
+ }
31
+ if (typeof node.tag === 'string' && /^h[1-6]$/.test(node.tag) && node.props?.id) {
32
+ ids.add(node.props.id)
33
+ }
34
+ if (node.children) collectHeadingIds(node.children, ids)
35
+ }
36
+
37
+ // Rewrite in-page anchor hrefs that only match a heading after normalization.
38
+ function fixAnchors(node: any, ids: Set<string>): void {
39
+ if (!node || typeof node !== 'object') return
40
+ if (Array.isArray(node)) {
41
+ for (const child of node) fixAnchors(child, ids)
42
+ return
43
+ }
44
+ if (node.tag === 'a' && typeof node.props?.href === 'string' && node.props.href.startsWith('#')) {
45
+ const fragment = node.props.href.slice(1)
46
+ if (fragment && !ids.has(fragment)) {
47
+ const normalized = normalizeId(fragment)
48
+ if (ids.has(normalized)) {
49
+ node.props.href = '#' + normalized
50
+ }
51
+ }
52
+ }
53
+ if (node.children) fixAnchors(node.children, ids)
54
+ }
55
+
56
+ export default defineNitroPlugin((nitroApp) => {
57
+ nitroApp.hooks.hook('content:file:afterParse', (file: any) => {
58
+ if (!file?._id?.endsWith('.md') || !file.body?.children) return
59
+ const ids = new Set<string>()
60
+ collectHeadingIds(file.body, ids)
61
+ if (ids.size) fixAnchors(file.body, ids)
62
+ })
63
+ })
@@ -0,0 +1,99 @@
1
+ /* ============================================
2
+ Animation Classes
3
+ ============================================ */
4
+
5
+ @keyframes fadeInUp {
6
+ from {
7
+ opacity: 0;
8
+ transform: translateY(20px);
9
+ }
10
+ to {
11
+ opacity: 1;
12
+ transform: translateY(0);
13
+ }
14
+ }
15
+
16
+ @keyframes fadeIn {
17
+ from {
18
+ opacity: 0;
19
+ }
20
+ to {
21
+ opacity: 1;
22
+ }
23
+ }
24
+
25
+ @keyframes slideInLeft {
26
+ from {
27
+ opacity: 0;
28
+ transform: translateX(-30px);
29
+ }
30
+ to {
31
+ opacity: 1;
32
+ transform: translateX(0);
33
+ }
34
+ }
35
+
36
+ @keyframes slideInRight {
37
+ from {
38
+ opacity: 0;
39
+ transform: translateX(30px);
40
+ }
41
+ to {
42
+ opacity: 1;
43
+ transform: translateX(0);
44
+ }
45
+ }
46
+
47
+ .animate-fade-in-up {
48
+ animation: fadeInUp 0.6s ease forwards;
49
+ }
50
+
51
+ .animate-fade-in {
52
+ animation: fadeIn 0.4s ease forwards;
53
+ }
54
+
55
+ .animate-slide-left {
56
+ animation: slideInLeft 0.5s ease forwards;
57
+ }
58
+
59
+ .animate-slide-right {
60
+ animation: slideInRight 0.5s ease forwards;
61
+ }
62
+
63
+ /* Stagger delays */
64
+ .delay-100 {
65
+ animation-delay: 0.1s;
66
+ }
67
+ .delay-200 {
68
+ animation-delay: 0.2s;
69
+ }
70
+ .delay-300 {
71
+ animation-delay: 0.3s;
72
+ }
73
+ .delay-400 {
74
+ animation-delay: 0.4s;
75
+ }
76
+ .delay-500 {
77
+ animation-delay: 0.5s;
78
+ }
79
+
80
+ /* ============================================
81
+ Page Transitions
82
+ ============================================ */
83
+
84
+ .page-enter-active,
85
+ .page-leave-active {
86
+ transition:
87
+ opacity 0.3s ease,
88
+ transform 0.3s ease;
89
+ }
90
+
91
+ .page-enter-from {
92
+ opacity: 0;
93
+ transform: translateY(10px);
94
+ }
95
+
96
+ .page-leave-to {
97
+ opacity: 0;
98
+ transform: translateY(-10px);
99
+ }
@@ -0,0 +1,31 @@
1
+ /* ============================================
2
+ Base Styles
3
+ ============================================ */
4
+
5
+ *,
6
+ *::before,
7
+ *::after {
8
+ box-sizing: border-box;
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ html {
14
+ font-size: 16px;
15
+ scroll-behavior: smooth;
16
+ -webkit-font-smoothing: antialiased;
17
+ -moz-osx-font-smoothing: grayscale;
18
+ }
19
+
20
+ body {
21
+ font-family: var(--font-body);
22
+ font-size: 1rem;
23
+ line-height: 1.6;
24
+ color: var(--color-text);
25
+ background-color: var(--color-bg);
26
+ background-image:
27
+ linear-gradient(rgba(10, 37, 64, 0.02) 1px, transparent 1px),
28
+ linear-gradient(90deg, rgba(10, 37, 64, 0.02) 1px, transparent 1px);
29
+ background-size: 40px 40px;
30
+ min-height: 100vh;
31
+ }
@@ -0,0 +1,109 @@
1
+ /* ============================================
2
+ Markdown Code Blocks (ProsePre override)
3
+ Light theme: very light grey surface, subtle border
4
+ ============================================ */
5
+
6
+ /* Wrapper around a highlighted code block */
7
+ .code-block {
8
+ margin: var(--spacing-lg) 0;
9
+ border: 1px solid var(--color-border);
10
+ border-radius: var(--radius-lg);
11
+ overflow: hidden;
12
+ background: #f6f8fa;
13
+ box-shadow: var(--shadow-sm);
14
+ }
15
+
16
+ /* Header strip: language label + copy button */
17
+ .code-block__header {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: space-between;
21
+ gap: var(--spacing-md);
22
+ padding: var(--spacing-xs) var(--spacing-md);
23
+ background: #eaeef2;
24
+ border-bottom: 1px solid var(--color-border);
25
+ }
26
+
27
+ .code-block__lang {
28
+ font-family: var(--font-body);
29
+ font-size: 0.75rem;
30
+ font-weight: 600;
31
+ letter-spacing: 0.04em;
32
+ text-transform: uppercase;
33
+ color: var(--color-text-muted);
34
+ }
35
+
36
+ .code-block__copy {
37
+ display: inline-flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ padding: 0.3rem;
41
+ color: var(--color-text-muted);
42
+ background: transparent;
43
+ border: none;
44
+ border-radius: var(--radius-sm);
45
+ cursor: pointer;
46
+ transition: all var(--transition-fast);
47
+ }
48
+
49
+ .code-block__copy:hover {
50
+ background: rgba(0, 0, 0, 0.06);
51
+ color: var(--color-text);
52
+ }
53
+
54
+ .code-block__copy.is-copied {
55
+ color: #1a7f37;
56
+ }
57
+
58
+ /* The <pre> emitted inside our wrapper: reset chrome, keep scrolling */
59
+ .code-block pre {
60
+ margin: 0;
61
+ padding: var(--spacing-md) var(--spacing-lg);
62
+ overflow-x: auto;
63
+ background: transparent;
64
+ font-size: 0.875rem;
65
+ line-height: 1.6;
66
+ -webkit-overflow-scrolling: touch;
67
+ }
68
+
69
+ .code-block pre code {
70
+ font-family: ui-monospace, 'SF Mono', 'Cascadia Code', 'Fira Code', Menlo, Consolas, monospace;
71
+ font-size: inherit;
72
+ color: var(--color-text);
73
+ background: none;
74
+ padding: 0;
75
+ display: block;
76
+ counter-reset: line;
77
+ }
78
+
79
+ .code-block pre code .line {
80
+ display: block;
81
+ }
82
+
83
+ /* Line-number gutter: rendered from a CSS counter so the digits are never
84
+ part of the copied text (manual selection skips ::before; the copy button
85
+ copies the raw source). Sticky so numbers stay pinned while a long line
86
+ scrolls horizontally, GitHub-style. */
87
+ .code-block pre code .line::before {
88
+ counter-increment: line;
89
+ content: counter(line);
90
+ position: sticky;
91
+ left: 0;
92
+ display: inline-block;
93
+ width: 2.5ch;
94
+ margin-right: var(--spacing-md);
95
+ padding-right: var(--spacing-sm);
96
+ text-align: right;
97
+ color: var(--color-text-muted);
98
+ background: #f6f8fa;
99
+ border-right: 1px solid var(--color-border);
100
+ user-select: none;
101
+ -webkit-user-select: none;
102
+ }
103
+
104
+ /* Shiki dual-theme: the parser writes both colors as CSS vars
105
+ (--shiki-default = github-light, --shiki-dusk = github-dark) rather than a
106
+ plain `color`. We render on a light surface, so use the light-theme var. */
107
+ .code-block pre code span {
108
+ color: var(--shiki-default, inherit);
109
+ }
@@ -0,0 +1,109 @@
1
+ /* ============================================
2
+ Components
3
+ ============================================ */
4
+
5
+ /* IconPark / inline icon helper */
6
+ .icon-inline {
7
+ display: inline-flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ vertical-align: middle;
11
+ }
12
+ .icon-inline svg {
13
+ display: block;
14
+ }
15
+
16
+ /* Buttons */
17
+ .btn {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ gap: var(--spacing-sm);
22
+ padding: var(--spacing-sm) var(--spacing-lg);
23
+ font-family: var(--font-body);
24
+ font-size: 0.875rem;
25
+ font-weight: 600;
26
+ line-height: 1;
27
+ text-align: center;
28
+ text-decoration: none;
29
+ border: none;
30
+ border-radius: var(--radius-md);
31
+ cursor: pointer;
32
+ transition: all var(--transition-base);
33
+ }
34
+
35
+ .btn-primary {
36
+ background: linear-gradient(
37
+ 135deg,
38
+ var(--color-accent) 0%,
39
+ var(--color-secondary) 100%
40
+ );
41
+ color: white;
42
+ }
43
+
44
+ .btn-primary:hover {
45
+ transform: translateY(-2px);
46
+ box-shadow: 0 10px 20px -5px rgba(0, 217, 255, 0.4);
47
+ }
48
+
49
+ .btn-secondary {
50
+ background-color: transparent;
51
+ color: var(--color-primary);
52
+ border: 2px solid var(--color-primary);
53
+ }
54
+
55
+ .btn-secondary:hover {
56
+ background-color: var(--color-primary);
57
+ color: white;
58
+ }
59
+
60
+ /* Cards */
61
+ .card {
62
+ background: var(--color-bg-alt);
63
+ border-radius: var(--radius-xl);
64
+ padding: var(--spacing-xl);
65
+ box-shadow: var(--shadow-md);
66
+ border: 1px solid var(--color-border);
67
+ transition: all var(--transition-base);
68
+ }
69
+
70
+ .card:hover {
71
+ transform: translateY(-4px);
72
+ box-shadow: var(--shadow-xl);
73
+ border-color: var(--color-secondary);
74
+ }
75
+
76
+ /* Badge */
77
+ .badge {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ padding: var(--spacing-xs) var(--spacing-sm);
81
+ font-size: 0.75rem;
82
+ font-weight: 600;
83
+ line-height: 1;
84
+ border-radius: var(--radius-full);
85
+ background-color: var(--color-secondary);
86
+ color: white;
87
+ }
88
+
89
+ .badge-accent {
90
+ background-color: var(--color-accent);
91
+ }
92
+
93
+ .badge-muted {
94
+ background-color: var(--color-border);
95
+ color: var(--color-text-muted);
96
+ }
97
+
98
+ /* Divider */
99
+ .divider {
100
+ width: 100%;
101
+ height: 2px;
102
+ background: linear-gradient(
103
+ 90deg,
104
+ transparent,
105
+ var(--color-border),
106
+ transparent
107
+ );
108
+ margin: var(--spacing-2xl) 0;
109
+ }
@@ -0,0 +1,220 @@
1
+ /* ============================================
2
+ Base Styles
3
+ ============================================ */
4
+
5
+ *,
6
+ *::before,
7
+ *::after {
8
+ box-sizing: border-box;
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ html {
14
+ font-size: 16px;
15
+ scroll-behavior: smooth;
16
+ -webkit-font-smoothing: antialiased;
17
+ -moz-osx-font-smoothing: grayscale;
18
+ }
19
+
20
+ body {
21
+ font-family: var(--font-body);
22
+ font-size: 1rem;
23
+ line-height: 1.6;
24
+ color: var(--color-text);
25
+ background-color: var(--color-bg);
26
+ background-image:
27
+ linear-gradient(rgba(10, 37, 64, 0.02) 1px, transparent 1px),
28
+ linear-gradient(90deg, rgba(10, 37, 64, 0.02) 1px, transparent 1px);
29
+ background-size: 40px 40px;
30
+ min-height: 100vh;
31
+ }
32
+
33
+ /* ============================================
34
+ Typography
35
+ ============================================ */
36
+
37
+ h1,
38
+ h2,
39
+ h3,
40
+ h4,
41
+ h5,
42
+ h6 {
43
+ font-family: var(--font-display);
44
+ font-weight: 700;
45
+ line-height: 1.2;
46
+ color: var(--color-primary);
47
+ margin-bottom: var(--spacing-md);
48
+ scroll-margin-top: calc(var(--header-height) + var(--spacing-lg));
49
+ }
50
+
51
+ h1 {
52
+ font-size: clamp(2.5rem, 5vw, 4rem);
53
+ font-weight: 800;
54
+ letter-spacing: -0.02em;
55
+ }
56
+
57
+ h2 {
58
+ font-size: clamp(2rem, 4vw, 2.5rem);
59
+ font-weight: 700;
60
+ letter-spacing: -0.01em;
61
+ }
62
+
63
+ h3 {
64
+ font-size: clamp(1.5rem, 3vw, 1.875rem);
65
+ font-weight: 600;
66
+ }
67
+
68
+ h4 {
69
+ font-size: 1.25rem;
70
+ font-weight: 600;
71
+ }
72
+
73
+ p {
74
+ margin-bottom: var(--spacing-md);
75
+ }
76
+
77
+ /* Lists */
78
+ ul {
79
+ margin-bottom: var(--spacing-md);
80
+ padding-left: 0;
81
+ list-style: none !important;
82
+ }
83
+
84
+ ol {
85
+ margin-bottom: var(--spacing-md);
86
+ padding-left: 0;
87
+ list-style: none !important;
88
+ counter-reset: item;
89
+ }
90
+
91
+ li {
92
+ margin-bottom: var(--spacing-xs);
93
+ position: relative;
94
+ padding-left: 1.5rem;
95
+ }
96
+
97
+ ul > li::before {
98
+ content: '•' !important;
99
+ position: absolute !important;
100
+ left: 0 !important;
101
+ color: var(--color-secondary) !important;
102
+ }
103
+
104
+ ol > li::before {
105
+ content: counter(item) '.' !important;
106
+ counter-increment: item !important;
107
+ position: absolute !important;
108
+ left: 0 !important;
109
+ color: var(--color-secondary) !important;
110
+ font-weight: 600 !important;
111
+ }
112
+
113
+ /* Ensure ol li doesn't get ul's bullet */
114
+ ol li::before {
115
+ content: counter(item) '.' !important;
116
+ }
117
+
118
+ /* Task lists (GitHub-style checkboxes): the global `ul > li::before` bullet
119
+ and `li` indent don't apply here — the checkbox is the marker. Suppress the
120
+ blue dot and vertically center the box with its label line via flex. */
121
+ .task-list-item {
122
+ display: flex;
123
+ align-items: center;
124
+ padding-left: 0;
125
+ list-style: none;
126
+ }
127
+
128
+ .task-list-item::before {
129
+ content: none !important;
130
+ }
131
+
132
+ /* Custom checkbox: replace the native control (which is disabled/read-only for
133
+ markdown task lists) with a box that matches the design system. Flex-center
134
+ so the checkmark sits in the middle of the box on both axes. */
135
+ .task-list-item input[type='checkbox'] {
136
+ appearance: none;
137
+ -webkit-appearance: none;
138
+ display: inline-flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ flex-shrink: 0;
142
+ width: 1.05em;
143
+ height: 1.05em;
144
+ margin: 0 var(--spacing-sm) 0 0;
145
+ background: var(--color-bg-alt);
146
+ border: 2px solid var(--color-border);
147
+ border-radius: var(--radius-sm);
148
+ cursor: default;
149
+ transition: all var(--transition-fast);
150
+ }
151
+
152
+ /* Checked: accent fill + a CSS-drawn checkmark */
153
+ .task-list-item input[type='checkbox']:checked {
154
+ background: var(--color-secondary);
155
+ border-color: var(--color-secondary);
156
+ }
157
+
158
+ .task-list-item input[type='checkbox']:checked::after {
159
+ content: '';
160
+ width: 0.3em;
161
+ height: 0.55em;
162
+ margin-top: -0.1em;
163
+ border: solid #fff;
164
+ border-width: 0 0.16em 0.16em 0;
165
+ transform: rotate(45deg);
166
+ }
167
+
168
+ a {
169
+ color: var(--color-secondary);
170
+ text-decoration: none;
171
+ transition: color var(--transition-fast);
172
+ }
173
+
174
+ a:hover {
175
+ color: var(--color-accent);
176
+ }
177
+
178
+ /* ============================================
179
+ Layout
180
+ ============================================ */
181
+
182
+ .container {
183
+ width: 100%;
184
+ max-width: 1280px;
185
+ margin-left: auto;
186
+ margin-right: auto;
187
+ padding-left: var(--spacing-lg);
188
+ padding-right: var(--spacing-lg);
189
+ }
190
+
191
+ .container-narrow {
192
+ width: 100%;
193
+ max-width: 896px;
194
+ margin-left: auto;
195
+ margin-right: auto;
196
+ padding-left: var(--spacing-lg);
197
+ padding-right: var(--spacing-lg);
198
+ }
199
+
200
+ .section {
201
+ padding-top: var(--spacing-3xl);
202
+ padding-bottom: var(--spacing-3xl);
203
+ }
204
+
205
+ .grid {
206
+ display: grid;
207
+ gap: var(--spacing-xl);
208
+ }
209
+
210
+ .grid-2 {
211
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
212
+ }
213
+
214
+ .grid-3 {
215
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
216
+ }
217
+
218
+ .grid-4 {
219
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
220
+ }