@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,84 @@
1
+ ---
2
+ interface Props {
3
+ title?: string | undefined;
4
+ icon?: string | undefined;
5
+ }
6
+
7
+ import { Icon } from "astro-icon/components";
8
+
9
+ const { title, icon } = Astro.props;
10
+ ---
11
+
12
+ <article class="seo-card">
13
+ {
14
+ icon && (
15
+ <div class="icon-wrapper">
16
+ <Icon name={icon} class="card-icon" />
17
+ </div>
18
+ )
19
+ }
20
+ {title && <h3 class="card-title">{title}</h3>}
21
+ <div class="card-content">
22
+ <slot />
23
+ </div>
24
+ </article>
25
+
26
+ <style>
27
+ .seo-card {
28
+ background: var(--bg-surface);
29
+ padding: 2.5rem;
30
+ border-radius: 1.5rem;
31
+ border: 1px solid var(--border-base);
32
+ box-shadow: 0 4px 6px -1px var(--shadow-base);
33
+ transition: all 0.3s ease;
34
+ display: flex;
35
+ flex-direction: column;
36
+ margin-bottom: 3rem;
37
+ }
38
+
39
+ .seo-card:hover {
40
+ transform: translateY(-4px);
41
+ box-shadow: 0 20px 25px -5px var(--shadow-hover);
42
+ border-color: var(--primary);
43
+ }
44
+
45
+ .icon-wrapper {
46
+ width: 3rem;
47
+ height: 3rem;
48
+ background: var(--primary-bg);
49
+ border-radius: 0.75rem;
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+ margin-bottom: 1.5rem;
54
+ color: var(--primary);
55
+ }
56
+
57
+ .card-icon {
58
+ width: 1.5rem;
59
+ height: 1.5rem;
60
+ margin: auto;
61
+ }
62
+
63
+ .card-title {
64
+ margin: 0 0 1rem;
65
+ font-size: 1.25rem;
66
+ font-weight: 800;
67
+ color: var(--text-base);
68
+ line-height: 1.4;
69
+ }
70
+
71
+ .card-content {
72
+ font-size: 1rem;
73
+ color: var(--text-muted);
74
+ line-height: 1.6;
75
+ }
76
+
77
+ .card-content :global(p) {
78
+ margin: 0;
79
+ }
80
+
81
+ .card-content :global(strong) {
82
+ color: var(--primary-strong);
83
+ }
84
+ </style>
@@ -0,0 +1,60 @@
1
+ ---
2
+ interface Props {
3
+ code?: string | undefined;
4
+ ariaLabel?: string | undefined;
5
+ }
6
+
7
+ const { code, ariaLabel } = Astro.props;
8
+ ---
9
+
10
+ <pre
11
+ class="seo-code"
12
+ {...ariaLabel && { role: "region", "aria-label": ariaLabel }}
13
+ >
14
+ {code ? (
15
+ <div class="code-content">
16
+ {code.split('\n').map(line => (
17
+ <div class="code-line">{line || '\u00A0'}</div>
18
+ ))}
19
+ </div>
20
+ ) : (
21
+ <slot />
22
+ )}
23
+ </pre>
24
+
25
+ <style>
26
+ .seo-code {
27
+ background: var(--bg-code);
28
+ padding: 1.5rem;
29
+ border-radius: 1rem;
30
+ margin: 2rem 0;
31
+ font-size: 0.95rem;
32
+ color: var(--text-code);
33
+ box-shadow:
34
+ 0 10px 15px -3px var(--shadow-hover),
35
+ 0 4px 6px -2px var(--shadow-base);
36
+ border: 1px solid var(--border-base);
37
+ font-family:
38
+ ui-monospace,
39
+ SFMono-Regular,
40
+ Menlo,
41
+ Monaco,
42
+ Consolas,
43
+ "Liberation Mono",
44
+ "Courier New",
45
+ monospace;
46
+ }
47
+
48
+ .code-content {
49
+ margin: 0;
50
+ padding: 0;
51
+ }
52
+
53
+ .code-line {
54
+ line-height: 1.6;
55
+ margin: 0;
56
+ padding: 0;
57
+ white-space: pre-wrap;
58
+ word-break: break-all;
59
+ }
60
+ </style>
@@ -0,0 +1,163 @@
1
+ ---
2
+ interface ComparativeItem {
3
+ title: string;
4
+ description: string;
5
+ icon?: string;
6
+ highlight?: boolean;
7
+ points?: string[];
8
+ pointIcon?: string;
9
+ }
10
+
11
+ interface Props {
12
+ items: ComparativeItem[];
13
+ columns?: 2 | 3 | undefined;
14
+ }
15
+
16
+ const { items, columns = 2 } = Astro.props;
17
+ import { Icon } from "astro-icon/components";
18
+ ---
19
+
20
+ <div class={`seo-comparative grid-cols-${columns}`}>
21
+ {items.map((item) => (
22
+ <div class={`comparative-card ${item.highlight ? "is-highlighted" : ""}`}>
23
+ {item.highlight && <div class="highlight-badge">Recomendado</div>}
24
+ <div class="card-header">
25
+ {item.icon && <Icon name={item.icon} class="header-icon" />}
26
+ <h4 class="card-title">{item.title}</h4>
27
+ </div>
28
+ <p class="card-desc">{item.description}</p>
29
+ {item.points && (
30
+ <ul class="points-list">
31
+ {item.points.map((point) => (
32
+ <li class="point-item">
33
+ <Icon
34
+ name={item.pointIcon || "mdi:check-circle-outline"}
35
+ class={`point-icon ${item.pointIcon?.includes("alert") || item.pointIcon?.includes("minus") || item.pointIcon?.includes("close") ? "is-negative" : ""}`}
36
+ />
37
+ <span>{point}</span>
38
+ </li>
39
+ ))}
40
+ </ul>
41
+ )}
42
+ </div>
43
+ ))}
44
+ </div>
45
+
46
+ <style>
47
+ .seo-comparative {
48
+ display: grid;
49
+ gap: 1.5rem;
50
+ margin: 2.5rem 0;
51
+ }
52
+
53
+ .grid-cols-2 {
54
+ grid-template-columns: repeat(2, 1fr);
55
+ }
56
+
57
+ .grid-cols-3 {
58
+ grid-template-columns: repeat(3, 1fr);
59
+ }
60
+
61
+ .comparative-card {
62
+ position: relative;
63
+ background: var(--bg-surface);
64
+ border: 1px solid var(--border-base);
65
+ border-radius: 20px;
66
+ padding: 2rem;
67
+ display: flex;
68
+ flex-direction: column;
69
+ transition:
70
+ transform 0.3s ease,
71
+ box-shadow 0.3s ease;
72
+ }
73
+
74
+ .comparative-card.is-highlighted {
75
+ background: var(--accent-bg);
76
+ border-color: var(--accent);
77
+ box-shadow: 0 10px 25px -5px var(--shadow-base);
78
+ transform: scale(1.02);
79
+ }
80
+
81
+ .highlight-badge {
82
+ position: absolute;
83
+ top: -12px;
84
+ right: 24px;
85
+ background: var(--accent);
86
+ color: var(--text-on-accent);
87
+ font-size: 0.7rem;
88
+ font-weight: 800;
89
+ padding: 4px 12px;
90
+ border-radius: 100px;
91
+ text-transform: uppercase;
92
+ letter-spacing: 0.05em;
93
+ }
94
+
95
+ .card-header {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 12px;
99
+ margin-bottom: 1rem;
100
+ }
101
+
102
+ .header-icon {
103
+ width: 24px;
104
+ height: 24px;
105
+ color: var(--accent);
106
+ }
107
+
108
+ .card-title {
109
+ font-size: 1.2rem;
110
+ font-weight: 800;
111
+ color: var(--text-base);
112
+ margin: 0;
113
+ }
114
+
115
+ .card-desc {
116
+ font-size: 0.95rem;
117
+ color: var(--text-muted);
118
+ line-height: 1.6;
119
+ margin-bottom: 1.5rem;
120
+ flex-grow: 1;
121
+ }
122
+
123
+ .points-list {
124
+ list-style: none;
125
+ padding: 0;
126
+ margin: 0;
127
+ display: flex;
128
+ flex-direction: column;
129
+ gap: 0.75rem;
130
+ }
131
+
132
+ .point-item {
133
+ display: flex;
134
+ align-items: flex-start;
135
+ gap: 10px;
136
+ font-size: 0.9rem;
137
+ color: var(--text-muted);
138
+ font-weight: 500;
139
+ }
140
+
141
+ .point-icon {
142
+ width: 18px;
143
+ height: 18px;
144
+ color: var(--color-success);
145
+ flex-shrink: 0;
146
+ margin-top: 2px;
147
+ }
148
+
149
+ .point-icon.is-negative {
150
+ color: var(--color-error);
151
+ }
152
+
153
+ @media (max-width: 768px) {
154
+ .grid-cols-2,
155
+ .grid-cols-3 {
156
+ grid-template-columns: 1fr;
157
+ }
158
+
159
+ .comparative-card.is-highlighted {
160
+ transform: none;
161
+ }
162
+ }
163
+ </style>
@@ -0,0 +1,167 @@
1
+ ---
2
+ import { Icon } from "astro-icon/components";
3
+
4
+ interface Props {
5
+ title: string;
6
+ icon?: string | undefined;
7
+ type?: "warning" | "error" | "info" | "success" | undefined;
8
+ badge?: string | undefined;
9
+ }
10
+
11
+ const { title, icon, type = "info", badge } = Astro.props;
12
+
13
+ const typeConfig = {
14
+ warning: { color: "var(--color-warning)", badgeText: badge ?? "Advertencia" },
15
+ error: { color: "var(--color-error)", badgeText: badge ?? "Problema crítico" },
16
+ info: { color: "var(--color-info)", badgeText: badge ?? "A tener en cuenta" },
17
+ success: { color: "var(--color-success)", badgeText: badge ?? "Buena práctica" },
18
+ } as const;
19
+
20
+ const cfg = typeConfig[type as keyof typeof typeConfig];
21
+ ---
22
+
23
+ <div class={`seo-diagnostic is-${type}`}>
24
+ <div class="diag-header">
25
+ <div class="header-left">
26
+ {icon && (
27
+ <div class="icon-bubble">
28
+ <Icon name={icon} class="bubble-icon" />
29
+ </div>
30
+ )}
31
+ <h3 class="header-title">{title}</h3>
32
+ </div>
33
+ <span class="type-badge">{cfg.badgeText}</span>
34
+ </div>
35
+ <div class="diag-body">
36
+ <slot />
37
+ </div>
38
+ </div>
39
+
40
+ <style define:vars={{
41
+ diagColor: cfg.color
42
+ }}>
43
+ .seo-diagnostic {
44
+ border: 1px solid color-mix(in srgb, var(--diagColor) 30%, transparent);
45
+ border-radius: 1.25rem;
46
+ overflow: hidden;
47
+ margin-bottom: 2rem;
48
+ transition: box-shadow 0.3s ease;
49
+ }
50
+
51
+ .seo-diagnostic:hover {
52
+ box-shadow: 0 8px 24px -4px var(--shadow-hover);
53
+ }
54
+
55
+ .diag-header {
56
+ background: color-mix(in srgb, var(--diagColor) 8%, transparent);
57
+ padding: 1.125rem 1.75rem;
58
+ display: flex;
59
+ align-items: center;
60
+ justify-content: space-between;
61
+ gap: 1rem;
62
+ border-bottom: 1px solid color-mix(in srgb, var(--diagColor) 30%, transparent);
63
+ flex-wrap: wrap;
64
+ }
65
+
66
+ .header-left {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 0.875rem;
70
+ flex: 1;
71
+ min-width: 0;
72
+ }
73
+
74
+ .icon-bubble {
75
+ width: 38px;
76
+ height: 38px;
77
+ border-radius: 50%;
78
+ background: var(--diagColor);
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ flex-shrink: 0;
83
+ box-shadow: 0 2px 8px var(--shadow-base);
84
+ }
85
+
86
+ .bubble-icon {
87
+ width: 20px;
88
+ height: 20px;
89
+ color: var(--text-on-primary);
90
+ }
91
+
92
+ .header-title {
93
+ margin: 0;
94
+ font-size: 1.1rem;
95
+ font-weight: 800;
96
+ color: var(--text-base);
97
+ line-height: 1.3;
98
+ letter-spacing: -0.01em;
99
+ }
100
+
101
+ .type-badge {
102
+ font-size: 0.7rem;
103
+ font-weight: 800;
104
+ text-transform: uppercase;
105
+ letter-spacing: 0.08em;
106
+ color: var(--diagColor);
107
+ background: color-mix(in srgb, var(--diagColor) 12%, transparent);
108
+ padding: 0.3rem 0.75rem;
109
+ border-radius: 100px;
110
+ white-space: nowrap;
111
+ border: 1px solid color-mix(in srgb, var(--diagColor) 25%, transparent);
112
+ flex-shrink: 0;
113
+ }
114
+
115
+ .diag-body {
116
+ background: var(--bg-surface);
117
+ padding: 1.75rem;
118
+ font-size: 1rem;
119
+ color: var(--text-muted);
120
+ line-height: 1.7;
121
+ }
122
+
123
+ .diag-body :global(p) {
124
+ margin-bottom: 1rem;
125
+ }
126
+
127
+ .diag-body :global(p:last-child) {
128
+ margin-bottom: 0;
129
+ }
130
+
131
+ .diag-body :global(strong) {
132
+ color: var(--text-base);
133
+ font-weight: 700;
134
+ }
135
+
136
+ .diag-body :global(ol),
137
+ .diag-body :global(ul) {
138
+ margin: 0.75rem 0 1rem;
139
+ padding-left: 1.5rem;
140
+ display: flex;
141
+ flex-direction: column;
142
+ gap: 0.5rem;
143
+ }
144
+
145
+ .diag-body :global(li) {
146
+ line-height: 1.65;
147
+ }
148
+
149
+ @media (max-width: 640px) {
150
+ .diag-header {
151
+ padding: 1rem 1.25rem;
152
+ gap: 0.75rem;
153
+ }
154
+
155
+ .diag-body {
156
+ padding: 1.25rem;
157
+ }
158
+
159
+ .header-title {
160
+ font-size: 1rem;
161
+ }
162
+
163
+ .type-badge {
164
+ font-size: 0.65rem;
165
+ }
166
+ }
167
+ </style>
@@ -0,0 +1,92 @@
1
+ ---
2
+ interface Props {
3
+ items: { term: string; definition: string }[];
4
+ }
5
+
6
+ const { items } = Astro.props;
7
+ ---
8
+
9
+ <div class="seo-glossary-wrapper">
10
+ <dl class="seo-glossary">
11
+ {
12
+ items.map((item) => (
13
+ <div class="glossary-item">
14
+ <dt class="glossary-term">
15
+ <span class="term-text">{item.term}</span>
16
+ <div class="term-line" />
17
+ </dt>
18
+ <dd class="glossary-definition">{item.definition}</dd>
19
+ </div>
20
+ ))
21
+ }
22
+ </dl>
23
+ </div>
24
+
25
+ <style>
26
+ .seo-glossary-wrapper {
27
+ margin: 3rem 0;
28
+ background: var(--bg-page);
29
+ border: 1px solid var(--border-base);
30
+ border-radius: 1.5rem;
31
+ padding: 2.5rem;
32
+ }
33
+
34
+ .seo-glossary {
35
+ display: grid;
36
+ grid-template-columns: 1fr;
37
+ gap: 2.5rem;
38
+ }
39
+
40
+ .glossary-item {
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: 0.75rem;
44
+ }
45
+
46
+ .glossary-term {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 1.5rem;
50
+ }
51
+
52
+ .term-text {
53
+ font-size: 1.125rem;
54
+ font-weight: 800;
55
+ color: var(--text-base);
56
+ white-space: nowrap;
57
+ letter-spacing: -0.01em;
58
+ }
59
+
60
+ .term-line {
61
+ height: 1px;
62
+ background: var(--border-base);
63
+ flex: 1;
64
+ }
65
+
66
+ .glossary-definition {
67
+ margin: 0;
68
+ font-size: 1rem;
69
+ line-height: 1.7;
70
+ color: var(--text-muted);
71
+ font-weight: 450;
72
+ max-width: 80ch;
73
+ }
74
+
75
+ @media (max-width: 768px) {
76
+ .seo-glossary-wrapper {
77
+ padding: 1.5rem;
78
+ }
79
+
80
+ .glossary-term {
81
+ gap: 1rem;
82
+ }
83
+
84
+ .term-text {
85
+ font-size: 1rem;
86
+ }
87
+
88
+ .seo-glossary {
89
+ gap: 2rem;
90
+ }
91
+ }
92
+ </style>
@@ -0,0 +1,49 @@
1
+ ---
2
+ interface Props {
3
+ columns?: 1 | 2 | 3 | 4 | undefined;
4
+ }
5
+
6
+ const { columns = 3 } = Astro.props;
7
+ ---
8
+
9
+ <div class={`seo-grid grid-cols-${columns}`}>
10
+ <slot />
11
+ </div>
12
+
13
+ <style>
14
+ .seo-grid {
15
+ display: grid;
16
+ gap: 2rem;
17
+ margin: 3rem 0;
18
+ }
19
+
20
+ .grid-cols-1 {
21
+ grid-template-columns: 1fr;
22
+ }
23
+
24
+ .grid-cols-2 {
25
+ grid-template-columns: repeat(2, 1fr);
26
+ }
27
+
28
+ .grid-cols-3 {
29
+ grid-template-columns: repeat(3, 1fr);
30
+ }
31
+
32
+ .grid-cols-4 {
33
+ grid-template-columns: repeat(4, 1fr);
34
+ }
35
+
36
+ @media (max-width: 1024px) {
37
+ .seo-grid {
38
+ grid-template-columns: repeat(2, 1fr);
39
+ gap: 1.5rem;
40
+ }
41
+ }
42
+
43
+ @media (max-width: 640px) {
44
+ .seo-grid {
45
+ grid-template-columns: 1fr;
46
+ gap: 1.25rem;
47
+ }
48
+ }
49
+ </style>