@jjlmoya/utils-shared 1.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.
@@ -0,0 +1,166 @@
1
+ ---
2
+ import { Icon } from "astro-icon/components";
3
+
4
+ interface Props {
5
+ title: string;
6
+ items: string[];
7
+ }
8
+
9
+ const { title, items } = Astro.props;
10
+ ---
11
+
12
+ <section class="summary-panel">
13
+ <div class="panel-header">
14
+ <div class="header-content">
15
+ <div class="header-icon-box">
16
+ <Icon name="mdi:lightbulb-on-outline" class="header-icon" />
17
+ </div>
18
+ <div class="header-text">
19
+ <span class="header-subtitle">Resumen </span>
20
+ <h4 class="header-title">{title}</h4>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ <div class="panel-body">
25
+ <div class="points-grid">
26
+ {
27
+ items.map((item) => (
28
+ <div class="point-item">
29
+ <div class="point-marker">
30
+ <Icon
31
+ name="mdi:chevron-right"
32
+ class="marker-icon"
33
+ />
34
+ </div>
35
+ <div class="point-content">
36
+ <Fragment set:html={item} />
37
+ </div>
38
+ </div>
39
+ ))
40
+ }
41
+ </div>
42
+ </div>
43
+ </section>
44
+
45
+ <style>
46
+ .summary-panel {
47
+ margin: 0 0 4rem;
48
+ background: var(--bg-surface);
49
+ border: 1px solid var(--border-base);
50
+ border-radius: 16px;
51
+ box-shadow: 0 1px 2px var(--shadow-base), 0 10px 15px -3px var(--shadow-base);
52
+ overflow: hidden;
53
+ }
54
+
55
+ .panel-header {
56
+ padding: 1.5rem 2rem;
57
+ background: var(--bg-page);
58
+ border-bottom: 1px solid var(--border-base);
59
+ }
60
+
61
+ .header-content {
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 1.25rem;
65
+ }
66
+
67
+ .header-icon-box {
68
+ width: 44px;
69
+ height: 44px;
70
+ background: var(--bg-surface);
71
+ border: 1px solid var(--border-base);
72
+ border-radius: 12px;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ box-shadow: 0 2px 4px var(--shadow-base);
77
+ }
78
+
79
+ .header-icon {
80
+ width: 24px;
81
+ height: 24px;
82
+ color: var(--accent);
83
+ }
84
+
85
+ .header-text {
86
+ display: flex;
87
+ flex-direction: column;
88
+ }
89
+
90
+ .header-subtitle {
91
+ font-size: 0.65rem;
92
+ font-weight: 800;
93
+ color: var(--text-dimmed);
94
+ text-transform: uppercase;
95
+ letter-spacing: 0.1em;
96
+ }
97
+
98
+ .header-title {
99
+ font-size: 1.25rem;
100
+ font-weight: 800;
101
+ color: var(--text-base);
102
+ margin: 0;
103
+ letter-spacing: -0.01em;
104
+ }
105
+
106
+ .panel-body {
107
+ padding: 2rem;
108
+ }
109
+
110
+ .points-grid {
111
+ display: grid;
112
+ grid-template-columns: repeat(2, 1fr);
113
+ gap: 1.5rem 2.5rem;
114
+ }
115
+
116
+ .point-item {
117
+ display: flex;
118
+ gap: 1rem;
119
+ align-items: flex-start;
120
+ }
121
+
122
+ .point-marker {
123
+ width: 22px;
124
+ height: 22px;
125
+ background: var(--accent-bg);
126
+ border-radius: 6px;
127
+ display: flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ flex-shrink: 0;
131
+ margin-top: 2px;
132
+ }
133
+
134
+ .marker-icon {
135
+ width: 14px;
136
+ height: 14px;
137
+ color: var(--accent);
138
+ }
139
+
140
+ .point-content {
141
+ font-size: 0.95rem;
142
+ color: var(--text-muted);
143
+ line-height: 1.6;
144
+ font-weight: 500;
145
+ }
146
+
147
+ .point-content :global(strong) {
148
+ color: var(--text-base);
149
+ font-weight: 700;
150
+ }
151
+
152
+ @media (max-width: 768px) {
153
+ .points-grid {
154
+ grid-template-columns: 1fr;
155
+ gap: 1.25rem;
156
+ }
157
+
158
+ .panel-body {
159
+ padding: 1.5rem;
160
+ }
161
+
162
+ .panel-header {
163
+ padding: 1.25rem 1.5rem;
164
+ }
165
+ }
166
+ </style>
@@ -0,0 +1,154 @@
1
+ ---
2
+ interface Props {
3
+ headers: string[];
4
+ striped?: boolean;
5
+ responsive?: boolean;
6
+ }
7
+
8
+ const { headers, responsive = true } = Astro.props;
9
+ ---
10
+
11
+ <div class={`seo-table-wrapper ${responsive ? "responsive" : ""}`}>
12
+ <table class="seo-table" role="table">
13
+ <thead>
14
+ <tr class="header-row">
15
+ {
16
+ headers.map((header, index) => (
17
+ <th
18
+ scope="col"
19
+ class={`header-cell ${index === 0 ? "first" : ""} ${index === headers.length - 1 ? "last" : ""}`}
20
+ >
21
+ {header}
22
+ </th>
23
+ ))
24
+ }
25
+ </tr>
26
+ </thead>
27
+ <tbody>
28
+ <slot />
29
+ </tbody>
30
+ </table>
31
+ </div>
32
+
33
+ <style>
34
+ .seo-table-wrapper {
35
+ width: 100%;
36
+ margin: 2.5rem 0;
37
+ border-radius: 1rem;
38
+ overflow: hidden;
39
+ box-shadow: 0 1px 3px 0 var(--shadow-base), 0 4px 6px -2px var(--shadow-base);
40
+ background: var(--bg-surface);
41
+ border: 1px solid var(--border-base);
42
+ }
43
+
44
+ .seo-table-wrapper.responsive {
45
+ overflow-x: auto;
46
+ }
47
+
48
+ .seo-table {
49
+ width: 100%;
50
+ border-collapse: collapse;
51
+ text-align: left;
52
+ font-size: 0.95rem;
53
+ line-height: 1.5;
54
+ }
55
+
56
+ .header-row {
57
+ background: linear-gradient(
58
+ 135deg,
59
+ var(--bg-page) 0%,
60
+ var(--bg-muted) 100%
61
+ );
62
+ border-bottom: 2px solid var(--border-base);
63
+ }
64
+
65
+ .header-cell {
66
+ padding: 1.25rem 1.5rem;
67
+ font-weight: 700;
68
+ color: var(--text-base);
69
+ white-space: nowrap;
70
+ text-transform: uppercase;
71
+ font-size: 0.8rem;
72
+ letter-spacing: 0.5px;
73
+ }
74
+
75
+ .header-cell.first {
76
+ border-radius: 1rem 0 0;
77
+ }
78
+
79
+ .header-cell.last {
80
+ border-radius: 0 1rem 0 0;
81
+ }
82
+
83
+ .seo-table :global(tbody tr) {
84
+ border-bottom: 1px solid var(--border-base);
85
+ transition: all 0.2s ease-in-out;
86
+ }
87
+
88
+ .seo-table :global(tbody tr:last-child) {
89
+ border-bottom: none;
90
+ }
91
+
92
+ .seo-table :global(tbody tr:nth-child(odd)) {
93
+ background-color: var(--bg-surface);
94
+ }
95
+
96
+ .seo-table :global(tbody tr:nth-child(even)) {
97
+ background-color: var(--bg-page);
98
+ }
99
+
100
+ .seo-table :global(tbody tr:hover) {
101
+ background-color: var(--bg-muted);
102
+ box-shadow: inset 0 0 0 1px var(--border-base);
103
+ }
104
+
105
+ .seo-table :global(td) {
106
+ padding: 1.25rem 1.5rem;
107
+ color: var(--text-muted);
108
+ vertical-align: middle;
109
+ font-weight: 400;
110
+ }
111
+
112
+ .seo-table :global(td strong) {
113
+ color: var(--text-base);
114
+ font-weight: 600;
115
+ }
116
+
117
+ @media (max-width: 768px) {
118
+ .header-cell {
119
+ padding: 1rem 1.25rem;
120
+ font-size: 0.75rem;
121
+ }
122
+
123
+ .seo-table :global(td) {
124
+ padding: 1rem 1.25rem;
125
+ font-size: 0.9rem;
126
+ }
127
+
128
+ .seo-table-wrapper {
129
+ margin: 2rem 0;
130
+ border-radius: 0.75rem;
131
+ }
132
+
133
+ .header-cell.first {
134
+ border-radius: 0.75rem 0 0;
135
+ }
136
+
137
+ .header-cell.last {
138
+ border-radius: 0 0.75rem 0 0;
139
+ }
140
+ }
141
+
142
+ @media (max-width: 480px) {
143
+ .header-cell {
144
+ padding: 0.875rem 1rem;
145
+ font-size: 0.7rem;
146
+ letter-spacing: 0.25px;
147
+ }
148
+
149
+ .seo-table :global(td) {
150
+ padding: 0.875rem 1rem;
151
+ font-size: 0.875rem;
152
+ }
153
+ }
154
+ </style>
@@ -0,0 +1,36 @@
1
+ ---
2
+ interface Props {
3
+ title?: string | undefined;
4
+ }
5
+
6
+ const { title } = Astro.props;
7
+ ---
8
+
9
+ <div class="seo-tip">
10
+ {title && <h5>{title}</h5>}
11
+ <slot />
12
+ </div>
13
+
14
+ <style>
15
+ .seo-tip {
16
+ background: var(--bg-muted);
17
+ border-left: 4px solid var(--primary);
18
+ padding: 1.5rem;
19
+ border-radius: 1rem;
20
+ margin: 2.5rem 0;
21
+ }
22
+
23
+ .seo-tip h5 {
24
+ margin: 0 0 0.75rem;
25
+ color: var(--text-base);
26
+ font-size: 1.1rem;
27
+ font-weight: 700;
28
+ }
29
+
30
+ .seo-tip :global(p) {
31
+ margin: 0;
32
+ color: var(--text-muted);
33
+ font-size: 0.95rem;
34
+ line-height: 1.6;
35
+ }
36
+ </style>
@@ -0,0 +1,59 @@
1
+ ---
2
+ interface Props {
3
+ title: string;
4
+ level?: 2 | 3 | 4;
5
+ }
6
+
7
+ const { title, level = 2 } = Astro.props;
8
+ type HeadingTag = "h2" | "h3" | "h4";
9
+ const Tag = `h${level}` as HeadingTag;
10
+ ---
11
+
12
+ <Tag class="seo-title">
13
+ <span class="prefix">#</span>
14
+ {title}
15
+ </Tag>
16
+
17
+ <style>
18
+ .seo-title {
19
+ color: var(--text-base);
20
+ line-height: 1.3;
21
+ margin-bottom: 1.5rem;
22
+ display: flex;
23
+ align-items: center;
24
+ gap: 0.75rem;
25
+ }
26
+
27
+ .prefix {
28
+ font-weight: 700;
29
+ color: var(--primary);
30
+ }
31
+
32
+ h2.seo-title {
33
+ font-size: 2rem;
34
+ font-weight: 800;
35
+ margin-top: 4rem;
36
+ }
37
+
38
+ h3.seo-title {
39
+ font-size: 1.5rem;
40
+ font-weight: 700;
41
+ margin-top: 3rem;
42
+ }
43
+
44
+ h4.seo-title {
45
+ font-size: 1.25rem;
46
+ font-weight: 700;
47
+ margin-top: 2rem;
48
+ }
49
+
50
+ @media (max-width: 768px) {
51
+ h2.seo-title {
52
+ font-size: 1.75rem;
53
+ }
54
+
55
+ h3.seo-title {
56
+ font-size: 1.35rem;
57
+ }
58
+ }
59
+ </style>
@@ -0,0 +1,48 @@
1
+ :root,
2
+ .theme-light {
3
+ --bg-page: #f8fafc;
4
+ --bg-surface: #fff;
5
+ --bg-muted: #f1f5f9;
6
+ --bg-code: #0f172a;
7
+ --text-base: #0f172a;
8
+ --text-muted: #475569;
9
+ --text-dimmed: #64748b;
10
+ --text-code: #f8fafc;
11
+ --border-base: #e2e8f0;
12
+ --primary: #3b82f6;
13
+ --primary-bg: #eff6ff;
14
+ --primary-strong: #1e40af;
15
+ --accent: #6366f1;
16
+ --accent-bg: rgba(99, 102, 241, 0.1);
17
+ --text-on-accent: #fff;
18
+ --text-on-primary: #fff;
19
+ --color-warning: #f59e0b;
20
+ --color-error: #f43f5e;
21
+ --color-info: #3b82f6;
22
+ --color-success: #10b981;
23
+ --shadow-base: rgba(0, 0, 0, 0.05);
24
+ --shadow-hover: rgba(0, 0, 0, 0.1);
25
+ }
26
+
27
+ .theme-dark {
28
+ --bg-page: #020617;
29
+ --bg-surface: #0f172a;
30
+ --bg-muted: #1e293b;
31
+ --bg-code: #020617;
32
+ --text-base: #f8fafc;
33
+ --text-muted: #94a3b8;
34
+ --text-dimmed: #64748b;
35
+ --text-code: #f8fafc;
36
+ --border-base: #1e293b;
37
+ --primary: #60a5fa;
38
+ --primary-bg: rgba(59, 130, 246, 0.1);
39
+ --primary-strong: #93c5fd;
40
+ --accent: #818cf8;
41
+ --accent-bg: rgba(129, 140, 248, 0.15);
42
+ --color-warning: #fbbf24;
43
+ --color-error: #fb7185;
44
+ --color-info: #60a5fa;
45
+ --color-success: #34d399;
46
+ --shadow-base: rgba(0, 0, 0, 0.3);
47
+ --shadow-hover: rgba(0, 0, 0, 0.4);
48
+ }
@@ -0,0 +1,64 @@
1
+ export interface UtilityItem {
2
+ title: string;
3
+ description: string;
4
+ href: string;
5
+ iconBg: string;
6
+ iconFg: string;
7
+ color: string;
8
+ appSlug?: string;
9
+ }
10
+
11
+ export interface SectionData {
12
+ title: string;
13
+ slug: string;
14
+ icon: string;
15
+ theme:
16
+ | "emerald"
17
+ | "orange"
18
+ | "purple"
19
+ | "blue"
20
+ | "pink"
21
+ | "cyan"
22
+ | "indigo"
23
+ | "slate"
24
+ | "rose"
25
+ | "amber"
26
+ | "nature";
27
+ utilities: UtilityItem[];
28
+ }
29
+
30
+ export type SEOSection =
31
+ | { type: "title"; level?: 2 | 3 | 4; text: string }
32
+ | { type: "paragraph"; html: string }
33
+ | { type: "list"; items: string[]; icon?: string }
34
+ | { type: "table"; headers: string[]; rows: string[][] }
35
+ | { type: "tip"; title?: string; html: string }
36
+ | { type: "card"; icon?: string; title?: string; html: string }
37
+ | { type: "stats"; items: { value: string; label: string; icon?: string }[]; columns?: 2 | 3 | 4 }
38
+ | { type: "glossary"; items: { term: string; definition: string }[] }
39
+ | { type: "code"; code: string; ariaLabel?: string }
40
+ | {
41
+ type: "comparative";
42
+ items: {
43
+ title: string;
44
+ description: string;
45
+ icon?: string;
46
+ highlight?: boolean;
47
+ points?: string[];
48
+ pointIcon?: string;
49
+ }[];
50
+ columns?: 2 | 3;
51
+ }
52
+ | { type: "diagnostic"; variant: "warning" | "error" | "info" | "success"; title: string; icon?: string; badge?: string; html: string }
53
+ | { type: "proscons"; title?: string; items: { pro: string; con: string }[]; proTitle?: string; conTitle?: string }
54
+ | { type: "summary"; title: string; items: string[] }
55
+ | { type: "grid"; columns?: 1 | 2 | 3 | 4 }
56
+ | { type: "message"; title?: string; ariaLabel?: string; html: string };
57
+
58
+ export interface UtilitySEOContent {
59
+ locale: string;
60
+ sections: SEOSection[];
61
+ faqItems?: { question: string; answer: string }[];
62
+ howToSteps?: { name: string; text: string }[];
63
+ bibliography?: { name: string; url: string }[];
64
+ }
@@ -0,0 +1,158 @@
1
+ ---
2
+ import { Icon } from "astro-icon/components";
3
+
4
+ interface Link {
5
+ name: string;
6
+ url: string;
7
+ }
8
+
9
+ interface Props {
10
+ links: Link[];
11
+ title?: string;
12
+ }
13
+
14
+ const { links = [], title = "Referencias Bibliográficas" } = Astro.props;
15
+ ---
16
+
17
+ <div class="bibliography">
18
+ <div class="bibliography-card">
19
+ <div class="bibliography-header">
20
+ <Icon
21
+ name="mdi:format-quote-open"
22
+ class="bibliography-quote-icon"
23
+ />
24
+ <h3 class="bibliography-title">{title}</h3>
25
+ </div>
26
+ <ul class="bibliography-list">
27
+ {
28
+ links.map((link: Link, index: number) => (
29
+ <li class="bibliography-item">
30
+ <span class="bibliography-index">[{index + 1}]</span>
31
+ <div class="bibliography-content">
32
+ <a
33
+ href={link.url}
34
+ target="_blank"
35
+ rel="noopener noreferrer"
36
+ class="bibliography-link"
37
+ >
38
+ {link.name}
39
+ <Icon
40
+ name="mdi:open-in-new"
41
+ class="bibliography-external-icon"
42
+ />
43
+ </a>
44
+ <p class="bibliography-url">{link.url}</p>
45
+ </div>
46
+ </li>
47
+ ))
48
+ }
49
+ </ul>
50
+ </div>
51
+ </div>
52
+
53
+ <style>
54
+ .bibliography {
55
+ margin-top: 4rem;
56
+ margin-bottom: 3rem;
57
+ }
58
+
59
+ .bibliography-card {
60
+ position: relative;
61
+ border-radius: 0 0.5rem 0.5rem 0;
62
+ border-left: 4px solid var(--primary-strong);
63
+ background-color: var(--bg-page);
64
+ padding: 1.5rem 2rem;
65
+ }
66
+
67
+ .bibliography-header {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 0.75rem;
71
+ margin-bottom: 1.5rem;
72
+ padding-bottom: 1rem;
73
+ border-bottom: 1px solid var(--border-base);
74
+ }
75
+
76
+ .bibliography-quote-icon {
77
+ width: 2rem;
78
+ height: 2rem;
79
+ color: var(--accent);
80
+ opacity: 0.2;
81
+ flex-shrink: 0;
82
+ }
83
+
84
+ .bibliography-title {
85
+ font-size: 1.5rem;
86
+ font-weight: 700;
87
+ color: var(--text-base);
88
+ letter-spacing: -0.02em;
89
+ margin: 0;
90
+ }
91
+
92
+ .bibliography-list {
93
+ list-style: none;
94
+ padding: 0;
95
+ margin: 0;
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: 1.25rem;
99
+ }
100
+
101
+ .bibliography-item {
102
+ display: flex;
103
+ gap: 1rem;
104
+ align-items: flex-start;
105
+ }
106
+
107
+ .bibliography-index {
108
+ font-family: ui-monospace, SFMono-Regular, monospace;
109
+ font-size: 0.75rem;
110
+ color: var(--text-dimmed);
111
+ user-select: none;
112
+ flex-shrink: 0;
113
+ padding-top: 0.2rem;
114
+ }
115
+
116
+ .bibliography-content {
117
+ flex: 1;
118
+ }
119
+
120
+ .bibliography-link {
121
+ font-size: 1rem;
122
+ font-weight: 500;
123
+ color: var(--text-base);
124
+ text-decoration: none;
125
+ display: inline-flex;
126
+ align-items: center;
127
+ gap: 0.5rem;
128
+ transition: color 0.2s ease;
129
+ }
130
+
131
+ .bibliography-link:hover {
132
+ color: var(--primary);
133
+ text-decoration: underline;
134
+ text-decoration-thickness: 1px;
135
+ text-underline-offset: 4px;
136
+ }
137
+
138
+ .bibliography-external-icon {
139
+ width: 0.75rem;
140
+ height: 0.75rem;
141
+ opacity: 0.3;
142
+ transition: opacity 0.2s ease;
143
+ flex-shrink: 0;
144
+ }
145
+
146
+ .bibliography-item:hover .bibliography-external-icon {
147
+ opacity: 1;
148
+ }
149
+
150
+ .bibliography-url {
151
+ font-family: ui-monospace, SFMono-Regular, monospace;
152
+ font-size: 0.7rem;
153
+ color: var(--text-dimmed);
154
+ margin: 0.25rem 0 0;
155
+ word-break: break-all;
156
+ line-height: 1.5;
157
+ }
158
+ </style>