@acms-subir/core 0.3.16 → 0.3.18

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 (48) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/cli/common.mjs +2 -2
  3. package/cli/main.mjs +10 -3
  4. package/cli/migrations.generated.mjs +71 -1
  5. package/cli/remote.mjs +1 -1
  6. package/cli/sync.mjs +876 -0
  7. package/docs/environment-sync.md +302 -0
  8. package/integration.mjs +2 -0
  9. package/package.json +1 -1
  10. package/section-library/upcoming-events/UpcomingEvents.astro +26 -17
  11. package/section-library/upcoming-events/section.json +41 -0
  12. package/src/actions/index.ts +5 -1
  13. package/src/components/CalendarFallbackImage.tsx +18 -0
  14. package/src/components/DesignAccess.tsx +3 -0
  15. package/src/components/EventPage.astro +4 -2
  16. package/src/components/LinksManager.tsx +86 -55
  17. package/src/components/ThemeSectionsEditor.tsx +13 -1
  18. package/src/layouts/Site.astro +1 -0
  19. package/src/lib/calendar/cards.ts +4 -2
  20. package/src/lib/calendar/images.ts +17 -0
  21. package/src/lib/editor/schemas.ts +2 -2
  22. package/src/lib/editor/site-data.ts +5 -3
  23. package/src/lib/links-avatar.ts +10 -0
  24. package/src/lib/mobile/links.ts +4 -0
  25. package/src/lib/mobile/schemas.ts +2 -2
  26. package/src/lib/repo/migrations.ts +26 -0
  27. package/src/lib/repo/sql.ts +8 -3
  28. package/src/lib/repo/types.ts +3 -1
  29. package/src/lib/schema.ts +14 -0
  30. package/src/lib/sync/auth.ts +24 -0
  31. package/src/lib/sync/catalog.ts +250 -0
  32. package/src/lib/sync/migration.ts +51 -0
  33. package/src/lib/sync/protocol.mjs +125 -0
  34. package/src/lib/sync/service.ts +994 -0
  35. package/src/lib/sync/store.ts +349 -0
  36. package/src/pages/admin/calendar/settings.astro +21 -2
  37. package/src/pages/admin/integrations.astro +2 -0
  38. package/src/pages/admin/links.astro +1 -1
  39. package/src/pages/api/agent/docs.ts +2 -0
  40. package/src/pages/api/design/keys.ts +7 -3
  41. package/src/pages/api/sync/index.ts +114 -0
  42. package/src/pages/api/sync/media.ts +90 -0
  43. package/src/pages/links.astro +13 -2
  44. package/src/styles/admin.css +27 -36
  45. package/src/styles/page-transitions.css +27 -0
  46. package/src/styles/site.css +3 -1
  47. package/src/styles/theme-editor.css +47 -12
  48. package/src/theme-sdk/index.ts +1 -0
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import LiveCard from '../components/LiveCard.astro';
3
+ import { linksAvatarStyles } from '../lib/links-avatar';
3
4
  import Site from '../layouts/Site.astro';
4
5
  import { getRepo } from '../lib/repo';
5
6
  import { cleanEventName } from '../lib/analytics';
@@ -17,6 +18,7 @@ const socials = navItems
17
18
  .map((n) => ({ ...n, path: socialIconPath(n.url) }));
18
19
 
19
20
  // links page has its own identity; falls back to the site's until it's set
21
+ const avatarStyles = linksAvatarStyles(settings);
20
22
  const profileName = settings.linksName || settings.siteTitle;
21
23
  const profileBio = settings.linksBio || settings.tagline;
22
24
 
@@ -94,8 +96,17 @@ const visible = links.filter((l) => {
94
96
 
95
97
  <Site title="Links" bare>
96
98
  <div class="links-page">
97
- {settings.linksAvatar && <img class="links-avatar" src={settings.linksAvatar} alt={profileName} />}
98
- <h1>{profileName}</h1>
99
+ {settings.linksAvatar && (
100
+ <div class="links-avatar" style={avatarStyles.frame}>
101
+ <img src={settings.linksAvatar} alt={profileName} style={avatarStyles.image} />
102
+ </div>
103
+ )}
104
+ <h1>{profileName}{settings.linksVerified && (
105
+ <svg class="links-verified" viewBox="0 0 24 24" role="img" aria-label="Verified">
106
+ <path fill="currentColor" d="m12 1 3 2.2 3.7.1 1.2 3.5L22 10l-1 3.6.3 3.7-3.4 1.5-2.4 2.8L12 20.5l-3.5 1.1-2.4-2.8-3.4-1.5.3-3.7L2 10l2.1-3.2 1.2-3.5 3.7-.1Z" />
107
+ <path d="m7.5 11.8 3 3 6-6" fill="none" stroke="var(--bg)" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" />
108
+ </svg>
109
+ )}</h1>
99
110
  {profileBio && <p class="tagline">{profileBio}</p>}
100
111
  {socials.length > 0 && (
101
112
  <nav class="social-nav" aria-label="Social">
@@ -504,6 +504,7 @@ input.bare.mono { font-family: ui-monospace, 'SF Mono', monospace; font-size: 12
504
504
  display: flex; align-items: center; gap: 8px; padding: 7px 8px 7px 16px;
505
505
  background: var(--text); color: var(--bg); border-radius: var(--r-lg); box-shadow: var(--shadow-pop); white-space: nowrap;
506
506
  }
507
+ @media (max-width: 420px) { .lm-savebar { width: calc(100% - 24px); flex-wrap: wrap; justify-content: flex-end; } .lm-savebar > span { flex: 1 0 100%; } }
507
508
  .lm-savebar span { margin-right: 8px; font-weight: 600; font-size: 13px; }
508
509
  .lm-savebar .btn:not(.primary) { background: transparent; color: var(--bg); border-color: color-mix(in srgb, var(--bg) 30%, transparent); box-shadow: none; }
509
510
  .lm-url { flex: 1; min-width: 0; background: var(--panel); border: 1px solid var(--line-strong); border-radius: var(--r-sm); padding: 4px 10px; font-size: 12px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -518,44 +519,34 @@ input.bare.mono { font-family: ui-monospace, 'SF Mono', monospace; font-size: 12
518
519
  color: var(--text-2); background: var(--sel); border-radius: 5px; padding: 2px 7px;
519
520
  }
520
521
 
521
- /* profile card — reads like the links page header, edits in place */
522
- .lm-profile { display: flex; gap: 14px; align-items: center; margin-bottom: 16px; }
523
- .lm-avatarwrap { position: relative; flex: 0 0 64px; }
524
- .lm-avatar {
525
- width: 64px; height: 64px; border-radius: 50%; border: none; padding: 0;
526
- display: grid; place-items: center; font-size: 20px; font-weight: 700; color: var(--text-2);
527
- background: var(--sel);
528
- user-select: none; cursor: pointer; position: relative; overflow: hidden; font-family: inherit;
529
- }
522
+ /* Explicit profile controls stay visible on touch screens, too. */
523
+ .lm-profile-card { padding: 20px; margin-bottom: 20px; border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--panel); }
524
+ .lm-profile-card h2 { margin: 0 0 18px; font-size: 16px; }
525
+ .lm-profile { display: flex; gap: 20px; align-items: flex-start; }
526
+ .lm-avatarwrap { display: flex; flex-direction: column; align-items: center; gap: 8px; flex: 0 0 110px; }
527
+ .lm-avatar { width: 80px; height: 80px; border-radius: 50%; border: 1px solid var(--line); padding: 0; display: grid; place-items: center; color: var(--text-2); background: var(--sel); cursor: pointer; position: relative; overflow: hidden; }
530
528
  .lm-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
531
- .lm-avatar-hint {
532
- position: absolute; inset: auto 0 0 0; padding: 2px 0 3px;
533
- font-size: 9px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
534
- background: rgb(0 0 0 / 55%); color: #fff; opacity: 0; transition: opacity 0.12s;
535
- }
536
- .lm-avatar:hover .lm-avatar-hint { opacity: 1; }
537
- .lm-avatar-remove {
538
- position: absolute; top: -4px; right: -4px; width: 18px; height: 18px;
539
- border-radius: 50%; border: 1px solid var(--line-strong); background: var(--panel); color: var(--muted);
540
- font-size: 9px; cursor: pointer; display: grid; place-items: center; padding: 0;
541
- opacity: 0; transition: opacity 0.12s;
542
- }
543
- .lm-avatarwrap:hover .lm-avatar-remove { opacity: 1; }
544
- .lm-avatar-remove:hover { color: var(--red); border-color: var(--red); }
529
+ .lm-photo-controls { min-width: 0; margin: 0 0 20px; padding: 0 0 16px; border: 0; border-bottom: 1px solid var(--line); }
530
+ .lm-photo-controls legend { margin-bottom: 10px; padding: 0; font-size: 12px; font-weight: 600; }
531
+ .lm-photo-options { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
532
+ .lm-photo-options label { flex: 1 1 130px; min-width: 0; }
533
+ .lm-photo-controls label { font-size: 12px; }
534
+ .lm-photo-controls select { display: block; width: 100%; margin-top: 5px; }
535
+ .lm-photo-controls output { margin-left: 6px; font-variant-numeric: tabular-nums; font-weight: 600; }
536
+ .lm-photo-controls input[type='range'] { display: block; width: 100%; padding: 0; margin: 8px 0; accent-color: var(--accent); }
537
+ .lm-photo-controls p { margin: 0 0 10px; color: var(--muted); font-size: 12px; line-height: 1.5; }
545
538
  .lm-profile-fields { flex: 1; min-width: 0; }
546
- input.lm-profile-name {
547
- display: block; width: 100%; background: none; border: 1px solid transparent; border-radius: var(--r-md);
548
- color: var(--text); font-family: inherit; font-size: 17px; font-weight: 700; letter-spacing: -0.01em;
549
- padding: 3px 8px; margin-left: -8px; outline: none;
550
- }
551
- .lm-profile-bio {
552
- display: block; width: 100%; background: none; border: 1px solid transparent; border-radius: var(--r-md);
553
- color: var(--muted); font-family: inherit; font-size: 13px; line-height: 1.55;
554
- padding: 3px 8px; margin: 1px 0 0 -8px; outline: none; resize: none; overflow: hidden;
555
- }
556
- .lm-profile-name::placeholder, .lm-profile-bio::placeholder { color: var(--faint); }
557
- input.lm-profile-name:hover, .lm-profile-bio:hover { border-color: var(--line); }
558
- input.lm-profile-name:focus, .lm-profile-bio:focus { border-color: var(--accent); background: var(--panel); }
539
+ .lm-profile-fields > label { display: block; margin-bottom: 6px; font-size: 12px; font-weight: 600; }
540
+ input.lm-profile-name, .lm-profile-bio { display: block; width: 100%; margin: 0 0 14px; padding: 9px 11px; border: 1px solid var(--line); border-radius: var(--r-md); background: var(--bg); color: var(--text); font: inherit; }
541
+ .lm-profile-bio { resize: vertical; min-height: 70px; }
542
+ .lm-profile-fields > .lm-verification { display: flex; align-items: center; gap: 10px; margin: 4px 0 0; cursor: pointer; }
543
+ .lm-verification input { appearance: none; width: 34px; height: 20px; padding: 2px; margin: 0; flex: 0 0 auto; border: 1px solid var(--line-strong); border-radius: 99px; background: var(--line-strong); cursor: pointer; }
544
+ .lm-verification input::before { content: ''; display: block; width: 14px; height: 14px; border-radius: 50%; background: var(--panel); transition: transform 0.15s; }
545
+ .lm-verification input:checked { background: var(--accent); border-color: var(--accent); }
546
+ .lm-verification input:checked::before { transform: translateX(14px); }
547
+ .lm-verification input:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
548
+ .lm-verification small { display: block; margin-top: 3px; color: var(--muted); font-weight: 400; }
549
+ @media (max-width: 540px) { .lm-profile { flex-direction: column; } .lm-avatarwrap { flex-basis: auto; } .lm-profile-fields { width: 100%; } }
559
550
 
560
551
  /* add-block picker */
561
552
  .lm-picker-head { display: flex; align-items: center; gap: 8px; }
@@ -0,0 +1,27 @@
1
+ /* Native same-origin document transitions. Both pages opt in through Site.astro;
2
+ the browser keeps normal navigation, history, forms and script lifecycles. */
3
+ @view-transition {
4
+ navigation: auto;
5
+ }
6
+
7
+ /* Keep the browser's built-in crossfade; themes can adjust its pace. */
8
+ @layer base {
9
+ ::view-transition-group(root),
10
+ ::view-transition-old(root),
11
+ ::view-transition-new(root) {
12
+ animation-duration: var(--page-transition-duration, 180ms);
13
+ animation-timing-function: var(--page-transition-easing, ease-out);
14
+ }
15
+ }
16
+
17
+ @media (prefers-reduced-motion: reduce) {
18
+ @view-transition {
19
+ navigation: none;
20
+ }
21
+
22
+ ::view-transition-group(*),
23
+ ::view-transition-old(*),
24
+ ::view-transition-new(*) {
25
+ animation: none !important;
26
+ }
27
+ }
@@ -488,10 +488,12 @@ li::marker { color: var(--muted); }
488
488
  /* ---- links page (/links) ------------------------------------------------------------ */
489
489
  .links-page { max-width: 480px; margin: 0 auto; text-align: center; padding: 48px 0 24px; }
490
490
  .links-page h1 { font-size: 26px; letter-spacing: -0.03em; margin: 0 0 6px; }
491
+ .links-verified { display: inline-block; width: 0.8em; height: 0.8em; margin-left: 0.25em; vertical-align: -0.02em; color: var(--muted); }
491
492
  .links-avatar {
492
- width: 96px; height: 96px; border-radius: 50%; object-fit: cover; display: block; margin: 0 auto 16px;
493
+ width: 96px; height: 96px; border-radius: 50%; overflow: hidden; background: var(--panel); margin: 0 auto 16px;
493
494
  box-shadow: 0 0 0 4px var(--panel), var(--sh-2);
494
495
  }
496
+ .links-avatar img { width: 100%; height: 100%; display: block; object-fit: cover; transform-origin: center; }
495
497
  .links-page .tagline { color: var(--muted); margin: 0 0 18px; white-space: pre-wrap; font-size: 15.5px; }
496
498
  .links-page .social-nav { justify-content: center; margin: 0 0 26px; }
497
499
  .link-btn {
@@ -36,12 +36,25 @@
36
36
  .te-mode .seg > [role='radio'] { min-height: 28px; }
37
37
  .te-mode .seg > [role='radio']:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
38
38
 
39
- /* section list */
40
- .te-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--panel); overflow: hidden; }
39
+ /* section list: each section is its own card, with a gap, alternating surface and
40
+ a stronger frame when open, so it is clear where one section ends */
41
+ .te-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
41
42
  .te-list.is-inactive { opacity: 0.72; }
42
- .te-none { padding: 14px 16px; margin: 0; }
43
- .te-sec + .te-sec { border-top: 1px solid var(--line); }
44
- .te-sec-head { display: flex; align-items: center; gap: 4px; padding: 4px 6px 4px 0; }
43
+ .te-none { padding: 14px 16px; margin: 0; border: 1px dashed var(--line-strong); border-radius: var(--r-lg); }
44
+ .te-sec {
45
+ border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--panel);
46
+ transition: border-color 150ms ease-out, box-shadow 150ms ease-out;
47
+ }
48
+ .te-sec:nth-child(even) { background: var(--panel-2); }
49
+ .te-sec:hover { border-color: var(--line-strong); }
50
+ .te-sec.is-hidden { border-style: dashed; }
51
+ .te-sec.is-open {
52
+ margin: 8px 0; background: var(--panel); border-color: var(--line-strong);
53
+ box-shadow: var(--shadow-card);
54
+ }
55
+ .te-sec.is-open:first-child { margin-top: 0; }
56
+ .te-sec.is-open:last-child { margin-bottom: 0; }
57
+ .te-sec-head { display: flex; align-items: center; gap: 4px; padding: 4px 6px 4px 0; border-radius: calc(var(--r-lg) - 1px); }
45
58
  .te-sec-toggle {
46
59
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; min-height: 40px;
47
60
  padding: 6px 8px 6px 12px; border: 0; background: none; font: inherit; color: var(--text); text-align: left;
@@ -51,7 +64,9 @@
51
64
  .te-sec-toggle:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
52
65
  .te-sec-toggle svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--muted); transition: transform 150ms ease-out; }
53
66
  .te-sec.is-open .te-sec-toggle svg { transform: rotate(90deg); }
54
- .te-sec.is-open .te-sec-head { background: var(--sel); }
67
+ .te-sec.is-open .te-sec-head {
68
+ background: var(--sel); border-radius: calc(var(--r-lg) - 1px) calc(var(--r-lg) - 1px) 0 0;
69
+ }
55
70
  .te-sec.is-open .te-sec-name { font-weight: 600; }
56
71
  .te-sec-name { font-size: 13.5px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
57
72
  .te-sec-meta { font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -61,10 +76,28 @@
61
76
  .te-sec-actions .icon-btn:disabled { opacity: 0.35; cursor: default; background: none; }
62
77
  .te-sec-actions .icon-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
63
78
  .te svg { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
64
- .te-sec-body { padding: 14px 16px 12px; border-top: 1px solid var(--line); }
79
+ .te-sec-body { padding: 16px 16px 14px; border-top: 1px solid var(--line-strong); }
65
80
  .te-sec-body textarea { resize: vertical; min-height: 84px; }
66
- .te-sec-foot { display: flex; flex-wrap: wrap; gap: 6px; padding-top: 12px; border-top: 1px solid var(--line); }
67
- .te-sec-foot .te-delete { margin-left: auto; }
81
+ .te-sec-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding-top: 14px; margin-top: 4px; border-top: 1px solid var(--line); }
82
+ .te-sec-foot:empty { display: none; }
83
+ .te-sec-foot .te-publish-one { margin-left: auto; }
84
+ .te-sec-foot .btn:focus-visible, .te-more > summary:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-ring); }
85
+ /* destructive action lives in its own tinted strip, apart from everyday tools */
86
+ .te-sec-danger {
87
+ display: flex; align-items: center; gap: 12px; margin-top: 14px; padding: 10px 12px;
88
+ border: 1px solid color-mix(in srgb, var(--red) 28%, transparent); border-radius: var(--r-md);
89
+ background: var(--red-soft);
90
+ }
91
+ .te-sec-danger-text { flex: 1; min-width: 0; margin: 0; display: flex; flex-direction: column; gap: 1px; font-size: 12px; color: var(--text-2); }
92
+ .te-sec-danger-text b { font-size: 12.5px; font-weight: 600; color: var(--red); }
93
+ .te-sec-delete.btn {
94
+ flex-shrink: 0; color: var(--red); background: var(--panel);
95
+ border-color: color-mix(in srgb, var(--red) 45%, transparent);
96
+ }
97
+ .te-sec-delete.btn:hover { background: var(--red-soft); border-color: var(--red); color: var(--red); }
98
+ .te-sec-delete.btn:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 35%, transparent); }
99
+ .te-sec-delete svg { width: 14px; height: 14px; }
100
+ @media (prefers-reduced-motion: reduce) { .te-sec, .te-sec-toggle svg { transition: none; } }
68
101
 
69
102
  /* image field */
70
103
  .te-image { margin-bottom: 12px; }
@@ -152,8 +185,6 @@
152
185
  .te-preset-more summary { cursor: pointer; color: var(--muted); }
153
186
  .te-orphans, .te-sec-body .notice.warn { display: flex; align-items: flex-start; gap: 10px; justify-content: space-between; }
154
187
  .te-orphans .btn, .te-sec-body .notice.warn .btn { flex-shrink: 0; }
155
- .te-publish-one { order: -1; }
156
- .te-sec-foot .te-publish-one + * { margin-left: 0; }
157
188
 
158
189
  /* ---- field types ---- */
159
190
  .te fieldset.field { border: 0; margin: 0 0 14px; padding: 0; min-width: 0; }
@@ -224,7 +255,11 @@
224
255
  .te-pagepick { flex-basis: 100%; }
225
256
  .te-mode { flex-wrap: wrap; }
226
257
  .te-sec-actions .icon-btn { width: 36px; height: 36px; }
227
- .te-sec-foot .btn, .te-savebar .btn, .te-add .btn, .da-keys .btn, .da-token-row .btn { min-height: 36px; }
258
+ .te-sec-body { padding: 14px 12px 12px; }
259
+ .te-sec-foot .te-publish-one { margin-left: 0; flex-basis: 100%; justify-content: center; }
260
+ .te-sec-danger { flex-direction: column; align-items: stretch; gap: 10px; }
261
+ .te-sec-delete.btn { justify-content: center; }
262
+ .te-sec-foot .btn, .te-sec-delete.btn, .te-savebar .btn, .te-add .btn, .da-keys .btn, .da-token-row .btn { min-height: 36px; }
228
263
  .te-sec-body input, .te-sec-body select, .te-sec-body textarea,
229
264
  .te-pagebar select, .te-add select, .da input[type='text'], .da-token-row input { font-size: 16px; }
230
265
  .te-review li { grid-template-columns: minmax(0, 1fr) auto; }
@@ -1,6 +1,7 @@
1
1
  export { ThemeTokens, ThemeManifest, PostOptions, SectionDefinition, SectionInstance, SectionLayout, SectionPages, PagePreset, BUILTIN_SECTIONS, sectionDefinitions, sectionIssues, THEME_API_VERSION } from './contract.mjs';
2
2
  export { formatTime, formatDay } from './fields.mjs';
3
3
  export { upcomingEvents, calendarConfig, eventUrl } from './calendar';
4
+ export { eventImage } from '../lib/calendar/images';
4
5
  export type { UpcomingEventsOptions, UpcomingEvent, ThemeCalendarConfig } from './calendar';
5
6
  export { CONTENT, postsIndexUrl, postUrl, tagUrl, collectionUrl } from '../lib/content';
6
7
  export { formatDate } from '../lib/dates';