@mevdragon/vidfarm-devcli 0.20.6 → 0.20.9

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 (157) hide show
  1. package/.agents/skills/editor-capabilities/SKILL.md +15 -1
  2. package/.agents/skills/vidfarm-director/SKILL.md +106 -0
  3. package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
  4. package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
  5. package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
  6. package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
  7. package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
  8. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +245 -0
  9. package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
  10. package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
  11. package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
  12. package/.agents/skills/vidfarm-director/references/primitives.md +308 -0
  13. package/SKILL.director.md +517 -250
  14. package/dist/src/cli.js +563 -99
  15. package/dist/src/devcli/clips.js +29 -12
  16. package/dist/src/devcli/doctor.js +2 -2
  17. package/dist/src/devcli/local-backend.js +46 -2
  18. package/dist/src/services/clip-curation/cost.js +5 -1
  19. package/dist/src/services/clip-curation/gemini.js +14 -2
  20. package/dist/src/services/clip-curation/hunt.js +56 -0
  21. package/dist/src/services/clip-curation/index.js +1 -1
  22. package/dist/src/services/clip-curation/scan.js +5 -3
  23. package/package.json +30 -10
  24. package/SKILL.platform.md +0 -432
  25. package/demo/README.md +0 -28
  26. package/demo/dist/app.css +0 -1
  27. package/demo/dist/app.js +0 -1850
  28. package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
  29. package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
  30. package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
  31. package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
  32. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
  33. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
  34. package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
  35. package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
  36. package/demo/dist/favicon.ico +0 -0
  37. package/demo/dist/icons/timeline/audio.svg +0 -7
  38. package/demo/dist/icons/timeline/captions.svg +0 -5
  39. package/demo/dist/icons/timeline/composition.svg +0 -12
  40. package/demo/dist/icons/timeline/image.svg +0 -18
  41. package/demo/dist/icons/timeline/music.svg +0 -10
  42. package/demo/dist/icons/timeline/text.svg +0 -3
  43. package/demo/dist/index.html +0 -15
  44. package/dist/src/account-pages-legacy.js +0 -9396
  45. package/dist/src/account-pages.js +0 -61
  46. package/dist/src/app.js +0 -21603
  47. package/dist/src/composition-runtime.js +0 -1053
  48. package/dist/src/config.js +0 -217
  49. package/dist/src/context.js +0 -447
  50. package/dist/src/dev-app-legacy.js +0 -739
  51. package/dist/src/dev-app.js +0 -6
  52. package/dist/src/devcli/migrate-local.js +0 -140
  53. package/dist/src/devcli/sync.js +0 -368
  54. package/dist/src/domain.js +0 -5
  55. package/dist/src/editor-chat-history.js +0 -82
  56. package/dist/src/editor-chat.js +0 -828
  57. package/dist/src/editor-dark-theme.js +0 -1128
  58. package/dist/src/frontend/debug.js +0 -71
  59. package/dist/src/frontend/discover-client.js +0 -130
  60. package/dist/src/frontend/discover-store.js +0 -23
  61. package/dist/src/frontend/file-directory.js +0 -1018
  62. package/dist/src/frontend/flockposter-cache-store.js +0 -124
  63. package/dist/src/frontend/homepage-client.js +0 -446
  64. package/dist/src/frontend/homepage-shared.js +0 -201
  65. package/dist/src/frontend/homepage-store.js +0 -66
  66. package/dist/src/frontend/homepage-view.js +0 -705
  67. package/dist/src/frontend/page-runtime-client.js +0 -132
  68. package/dist/src/frontend/page-runtime-store.js +0 -9
  69. package/dist/src/frontend/sentry.js +0 -42
  70. package/dist/src/frontend/template-editor-chat.js +0 -4181
  71. package/dist/src/help-page.js +0 -346
  72. package/dist/src/homepage.js +0 -1458
  73. package/dist/src/index.js +0 -16
  74. package/dist/src/instrument.js +0 -30
  75. package/dist/src/landing-page.js +0 -384
  76. package/dist/src/page-runtime.js +0 -2
  77. package/dist/src/page-shell.js +0 -1452
  78. package/dist/src/primitive-context.js +0 -416
  79. package/dist/src/primitive-registry.js +0 -3940
  80. package/dist/src/primitive-sdk.js +0 -4
  81. package/dist/src/primitives/hyperframes-media.js +0 -108
  82. package/dist/src/react-page-shell.js +0 -35
  83. package/dist/src/ready-post-schedule-component.js +0 -1540
  84. package/dist/src/registry.js +0 -296
  85. package/dist/src/reskin/agency-page.js +0 -299
  86. package/dist/src/reskin/calendar-page.js +0 -568
  87. package/dist/src/reskin/chat-page.js +0 -942
  88. package/dist/src/reskin/discover-page.js +0 -1788
  89. package/dist/src/reskin/document.js +0 -1587
  90. package/dist/src/reskin/help-page.js +0 -357
  91. package/dist/src/reskin/index-page.js +0 -62
  92. package/dist/src/reskin/inpaint-clipper-page.js +0 -890
  93. package/dist/src/reskin/inpaint-page.js +0 -2554
  94. package/dist/src/reskin/inpaint-video-page.js +0 -1339
  95. package/dist/src/reskin/job-runs-page.js +0 -477
  96. package/dist/src/reskin/library-page.js +0 -1634
  97. package/dist/src/reskin/login-page.js +0 -262
  98. package/dist/src/reskin/portfolio-page.js +0 -687
  99. package/dist/src/reskin/pricing-page.js +0 -390
  100. package/dist/src/reskin/settings-page.js +0 -732
  101. package/dist/src/reskin/theme.js +0 -711
  102. package/dist/src/runtime.js +0 -35
  103. package/dist/src/services/api-call-history.js +0 -249
  104. package/dist/src/services/auth.js +0 -152
  105. package/dist/src/services/billing-pricing.js +0 -39
  106. package/dist/src/services/billing.js +0 -241
  107. package/dist/src/services/cast.js +0 -127
  108. package/dist/src/services/chat-threads.js +0 -92
  109. package/dist/src/services/clip-records.js +0 -250
  110. package/dist/src/services/clip-search.js +0 -77
  111. package/dist/src/services/clip-vectors.js +0 -125
  112. package/dist/src/services/composition-sanitize.js +0 -124
  113. package/dist/src/services/composition-watch.js +0 -79
  114. package/dist/src/services/elevenlabs.js +0 -222
  115. package/dist/src/services/file-directory.js +0 -117
  116. package/dist/src/services/fork-access.js +0 -93
  117. package/dist/src/services/fork-manifest.js +0 -42
  118. package/dist/src/services/ghostcut.js +0 -179
  119. package/dist/src/services/hyperframes.js +0 -3654
  120. package/dist/src/services/job-capacity.js +0 -14
  121. package/dist/src/services/job-logs.js +0 -197
  122. package/dist/src/services/jobs.js +0 -136
  123. package/dist/src/services/local-dynamo.js +0 -0
  124. package/dist/src/services/media-processing.js +0 -766
  125. package/dist/src/services/primitive-media-lambda.js +0 -280
  126. package/dist/src/services/providers.js +0 -2748
  127. package/dist/src/services/rate-limits.js +0 -262
  128. package/dist/src/services/scene-annotations.js +0 -32
  129. package/dist/src/services/serverless-auth.js +0 -382
  130. package/dist/src/services/serverless-jobs.js +0 -1084
  131. package/dist/src/services/serverless-provider-keys.js +0 -409
  132. package/dist/src/services/serverless-records.js +0 -1515
  133. package/dist/src/services/serverless-template-configs.js +0 -75
  134. package/dist/src/services/storage.js +0 -461
  135. package/dist/src/services/swipe-customize.js +0 -437
  136. package/dist/src/services/template-certification.js +0 -413
  137. package/dist/src/services/template-loader.js +0 -99
  138. package/dist/src/services/template-runtime-bundles.js +0 -217
  139. package/dist/src/services/template-sources.js +0 -1017
  140. package/dist/src/services/upstream.js +0 -248
  141. package/dist/src/services/video-normalization.js +0 -2
  142. package/dist/src/services/webhooks.js +0 -62
  143. package/dist/src/template-editor-pages.js +0 -2576
  144. package/dist/src/template-editor-shell.js +0 -2893
  145. package/dist/src/template-sdk.js +0 -4
  146. package/dist/src/worker.js +0 -17
  147. package/public/assets/discover-client-app.js +0 -1
  148. package/public/assets/file-directory-app.js +0 -3
  149. package/public/assets/homepage-app.js +0 -54
  150. package/public/assets/homepage-client-app.js +0 -80
  151. package/public/assets/page-runtime-client-app.js +0 -94
  152. package/public/assets/placeholders/scene-placeholder.png +0 -0
  153. package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
  154. package/src/assets/SELLING_WITH_HOOKS.md +0 -377
  155. package/src/assets/SELLING_WITH_VSLS.md +0 -606
  156. package/src/assets/favicon.ico +0 -0
  157. package/src/assets/logo-vidfarm.png +0 -0
@@ -1,568 +0,0 @@
1
- // /reskin/calendar — the scheduled-posts calendar, MIGRATED to real production data.
2
- //
3
- // Mirrors the live /calendar page's two-column layout: a LEFT rail listing the
4
- // connected channels (searchable, click to filter the board to one channel) and
5
- // a compact month board on the RIGHT sized to the viewport so the whole month
6
- // is visible without scrolling.
7
- //
8
- // Like the live page, this renders the shell server-side, then CLIENT-FETCHES
9
- // the real posts from `input.loadEndpoint` (`/calendar/feed`, same-origin,
10
- // credentials included) and drops them onto the correct day cells. It accepts
11
- // the SAME input object the live route already builds (via
12
- // `Parameters<typeof renderCalendarPage>[0]`).
13
- //
14
- // /calendar/feed response contract (see serializeScheduleCalendarPost in app.ts):
15
- // {
16
- // channels: [{ id, name, handle, platform, avatarUrl, platformIconUrl? }],
17
- // posts: [{ id, channelId, scheduledAt (ISO), status (label),
18
- // statusTone: "draft"|"review"|"ready"|"scheduled"|"sent"|"error",
19
- // title, summary, kind, url }],
20
- // startDate, endDate, syncStatus, error?
21
- // }
22
- // The client sends `?start_date=&end_date=` for the visible month range; posts map
23
- // onto days by the local date of `scheduledAt`. Email channel ids are `email:<id>`
24
- // and match `post.channelId`, so platform is resolved via the channels lookup.
25
- //
26
- // Design stays farmville (shared rk-* primitives + a page-scoped rk-calendar- block);
27
- // NO decorative emoji — platform swatches use letter initials (T/I/Y/E/…).
28
- import { reskinDocument, rkEscape } from "./document.js";
29
- // A safe default so calling with no/partial args never throws. The live route
30
- // always passes a fully-populated object (posts seeded empty — they arrive from
31
- // the feed), so this default only matters for previews / direct calls.
32
- const DEFAULT_INPUT = {
33
- userId: "",
34
- currentAccountId: "",
35
- name: null,
36
- email: "",
37
- loadEndpoint: "/calendar/feed",
38
- hasFlockPosterKey: false,
39
- connectChannelsHref: "/settings?tab=channels&connect=flockposter",
40
- channels: [],
41
- posts: []
42
- };
43
- const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
44
- const SEARCH_ICON = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>`;
45
- // statusTone → { label, pill } for the chip tooltip + the legend dots.
46
- const STATUS = {
47
- draft: { label: "Draft", pill: "rk-pill" },
48
- review: { label: "Review", pill: "rk-pill-gold" },
49
- ready: { label: "Ready", pill: "rk-pill-green" },
50
- scheduled: { label: "Scheduled", pill: "rk-pill-sky" },
51
- sent: { label: "Sent", pill: "rk-pill-violet" },
52
- error: { label: "Failed", pill: "rk-pill-red" }
53
- };
54
- // Render a bare month-grid skeleton server-side (based on `today`) so the board
55
- // paints immediately; the client rebuilds it and fills in the real posts.
56
- function skeletonCells() {
57
- const now = new Date();
58
- const year = now.getUTCFullYear();
59
- const month = now.getUTCMonth();
60
- const firstWeekday = new Date(Date.UTC(year, month, 1)).getUTCDay();
61
- const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
62
- const rows = Math.ceil((firstWeekday + daysInMonth) / 7);
63
- const total = rows * 7;
64
- const todayDom = now.getUTCDate();
65
- const cells = [];
66
- for (let i = 0; i < total; i++) {
67
- const offset = i - firstWeekday;
68
- const d = new Date(Date.UTC(year, month, 1 + offset));
69
- const inMonth = d.getUTCMonth() === month;
70
- const dom = d.getUTCDate();
71
- const isToday = inMonth && dom === todayDom;
72
- const cls = ["rk-calendar-cell"];
73
- if (!inMonth)
74
- cls.push("is-out");
75
- if (isToday)
76
- cls.push("is-today");
77
- cells.push(`<div class="${cls.join(" ")}">
78
- <div class="rk-calendar-cell-head">
79
- <span class="rk-calendar-daynum">${dom}</span>
80
- </div>
81
- <div class="rk-calendar-cell-posts"></div>
82
- </div>`);
83
- }
84
- return cells.join("");
85
- }
86
- export function renderReskinCalendar(input = DEFAULT_INPUT) {
87
- const cfg = { ...DEFAULT_INPUT, ...input };
88
- // "Schedule post" → the real destination the live app uses. Scheduling a post
89
- // happens from the Library's schedule modal (POST /approved/posts/:id/schedules);
90
- // if no channels are connected yet, the prerequisite is to connect them first.
91
- const acctPrefix = /^\/u\/[^/]+/.exec(cfg.connectChannelsHref)?.[0] ?? "";
92
- const scheduleHref = cfg.hasFlockPosterKey ? `${acctPrefix}/library` : cfg.connectChannelsHref;
93
- const scheduleLabel = cfg.hasFlockPosterKey ? "Schedule post" : "Connect a channel";
94
- const weekdayHead = WEEKDAYS.map((w) => `<div class="rk-calendar-weekday">${w}</div>`).join("");
95
- const legendOrder = ["draft", "review", "ready", "scheduled", "sent", "error"];
96
- const legend = legendOrder.map((s) => `<span class="rk-calendar-legend-item"><span class="rk-calendar-dot" data-status="${s}"></span>${STATUS[s].label}</span>`).join("");
97
- // seed + config handed to the client (posts arrive from the feed).
98
- const boot = {
99
- loadEndpoint: cfg.loadEndpoint,
100
- hasChannels: cfg.hasFlockPosterKey,
101
- connectChannelsHref: cfg.connectChannelsHref,
102
- seedChannels: cfg.channels,
103
- seedPosts: cfg.posts
104
- };
105
- const bootJson = JSON.stringify(boot).replace(/</g, "\\u003c");
106
- const body = `
107
- <main class="rk-container-wide rk-page rk-calendar-page">
108
- <div class="rk-spread rk-page-head rk-calendar-head">
109
- <div class="rk-stack rk-calendar-head-copy">
110
- <h1 class="rk-h1">Calendar</h1>
111
- <p class="rk-sub" data-rk-cal-sub>Your scheduled short-form posts across every connected channel.</p>
112
- </div>
113
- <a class="rk-btn rk-btn-gold rk-btn-sm rk-calendar-cta" href="${rkEscape(scheduleHref)}">${rkEscape(scheduleLabel)} <span class="rk-arrow">&rarr;</span></a>
114
- </div>
115
-
116
- <div class="rk-calendar-notice" data-rk-cal-notice hidden></div>
117
-
118
- <div class="rk-calendar-shell">
119
- <aside class="rk-calendar-rail" aria-label="Connected channels">
120
- <label class="rk-calendar-chsearch">
121
- <span class="rk-calendar-chsearch-ic" aria-hidden="true">${SEARCH_ICON}</span>
122
- <input class="rk-input rk-calendar-chsearch-input" data-rk-cal-chsearch type="search"
123
- placeholder="Search channels" autocomplete="off" aria-label="Search channels" />
124
- </label>
125
- <div class="rk-calendar-channels" data-rk-cal-channels></div>
126
- <a class="rk-calendar-connect" href="${rkEscape(cfg.connectChannelsHref)}">+ Connect a channel</a>
127
- </aside>
128
-
129
- <section class="rk-calendar-stage">
130
- <div class="rk-calendar-toolbar">
131
- <div class="rk-calendar-monthnav">
132
- <button type="button" class="rk-btn rk-btn-ghost rk-calendar-navbtn" data-rk-cal-prev aria-label="Previous month">&larr;</button>
133
- <span class="rk-calendar-month" data-rk-cal-month>&nbsp;</span>
134
- <button type="button" class="rk-btn rk-btn-ghost rk-calendar-navbtn" data-rk-cal-next aria-label="Next month">&rarr;</button>
135
- <button type="button" class="rk-btn rk-btn-ghost rk-btn-sm rk-calendar-today-btn" data-rk-cal-today>Today</button>
136
- <span class="rk-calendar-sync" data-rk-cal-sync aria-live="polite"></span>
137
- </div>
138
- <div class="rk-calendar-legend">${legend}</div>
139
- </div>
140
-
141
- <div class="rk-card rk-calendar-grid-card">
142
- <div class="rk-calendar-weekdays">${weekdayHead}</div>
143
- <div class="rk-calendar-grid" data-rk-cal-grid>${skeletonCells()}</div>
144
- </div>
145
- </section>
146
- </div>
147
- </main>
148
- <script type="application/json" data-rk-cal-boot>${bootJson}</script>`;
149
- const pageCss = `
150
- /* .rk-page zeroes horizontal padding; the wide container == viewport at 1280 so
151
- margin:auto adds no gutter. Restore side gutters (longhand beats the shorthand),
152
- and trim the bottom so the whole month fits in one viewport. */
153
- .rk-calendar-page{padding-left:var(--rk-gutter);padding-right:var(--rk-gutter);padding-bottom:28px}
154
- .rk-calendar-head{align-items:flex-start;margin-bottom:14px}
155
- .rk-calendar-head-copy{gap:4px;margin:0;flex:1 1 auto;min-width:0}
156
- .rk-calendar-cta{flex:none;margin-top:2px}
157
-
158
- /* notice banner (errors / empty warnings) */
159
- .rk-calendar-notice{margin-bottom:12px}
160
- .rk-calendar-notice[hidden]{display:none}
161
-
162
- /* two-column shell: channel rail left, compact month board right */
163
- .rk-calendar-shell{display:grid;grid-template-columns:280px minmax(0,1fr);gap:16px;align-items:start}
164
- @media(max-width:1000px){.rk-calendar-shell{grid-template-columns:1fr}}
165
-
166
- /* ── channel rail ── */
167
- .rk-calendar-rail{display:grid;gap:10px;background:#fff;border:1px solid var(--rk-border);
168
- border-radius:var(--rk-r-2xl);padding:12px;box-shadow:var(--rk-shadow-card)}
169
- .rk-calendar-chsearch{position:relative;display:block}
170
- .rk-calendar-chsearch-ic{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--rk-n-400);pointer-events:none;display:flex}
171
- .rk-calendar-chsearch-input{padding:9px 12px 9px 37px;border-radius:var(--rk-r-full);font-size:13.5px}
172
- .rk-calendar-channels{display:grid;gap:6px;max-height:calc(100vh - 320px);min-height:120px;overflow:auto}
173
- .rk-calendar-channel{display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border:1px solid transparent;
174
- border-radius:var(--rk-r-xl);background:transparent;cursor:pointer;text-align:left;font-family:var(--rk-font-body);
175
- transition:background var(--rk-dur) var(--rk-ease),border-color var(--rk-dur) var(--rk-ease)}
176
- .rk-calendar-channel:hover{background:var(--rk-n-100)}
177
- .rk-calendar-channel.is-selected{background:var(--rk-gold-tint);border-color:var(--rk-gold-600)}
178
- .rk-calendar-channel-avatar{position:relative;width:34px;height:34px;border-radius:var(--rk-r-full);flex:none;overflow:visible}
179
- .rk-calendar-channel-avatar img{width:100%;height:100%;border-radius:var(--rk-r-full);object-fit:cover;display:block;
180
- border:1px solid var(--rk-border)}
181
- .rk-calendar-channel-initials{width:100%;height:100%;border-radius:var(--rk-r-full);display:grid;place-items:center;
182
- background:var(--rk-n-800);color:#fff;font-family:var(--rk-font-display);font-weight:800;font-size:12px;letter-spacing:.03em}
183
- .rk-calendar-channel-main{display:grid;gap:1px;min-width:0;flex:1}
184
- .rk-calendar-channel-name{font-size:13.5px;font-weight:700;color:var(--rk-ink);letter-spacing:-.01em;
185
- white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
186
- .rk-calendar-channel-sub{font-size:11.5px;font-weight:500;color:var(--rk-text-muted);
187
- white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
188
- .rk-calendar-channel-count{flex:none;font-size:11px;font-weight:700;color:var(--rk-text-muted);
189
- background:var(--rk-n-100);border-radius:var(--rk-r-full);padding:2px 8px}
190
- .rk-calendar-channel.is-selected .rk-calendar-channel-count{background:#fff}
191
- .rk-calendar-channels-empty{padding:14px 10px;font-size:12.5px;color:var(--rk-text-muted);line-height:1.5}
192
- .rk-calendar-connect{display:block;text-align:center;padding:9px 12px;border:1.5px dashed var(--rk-border-strong);
193
- border-radius:var(--rk-r-xl);font-size:13px;font-weight:600;color:var(--rk-text-muted);
194
- transition:border-color var(--rk-dur) var(--rk-ease),background var(--rk-dur) var(--rk-ease),color var(--rk-dur) var(--rk-ease)}
195
- .rk-calendar-connect:hover{border-color:var(--rk-gold-600);background:var(--rk-gold-tint);color:var(--rk-ink)}
196
-
197
- /* platform swatch badge on the avatar — letter initial, no emoji */
198
- .rk-calendar-swatch{position:absolute;right:-3px;bottom:-3px;width:16px;height:16px;border-radius:6px;
199
- display:grid;place-items:center;font-family:var(--rk-font-display);font-size:9px;font-weight:800;line-height:1;
200
- box-shadow:var(--rk-shadow-xs);border:1.5px solid #fff}
201
- .rk-calendar-swatch-tiktok{background:var(--rk-ink);color:#fff}
202
- .rk-calendar-swatch-instagram{background:linear-gradient(135deg,#f9ce34,#ee2a7b 55%,#6228d7);color:#fff}
203
- .rk-calendar-swatch-youtube{background:#ff3b30;color:#fff}
204
- .rk-calendar-swatch-email{background:var(--rk-sky-tint);color:#1d6fb8}
205
- .rk-calendar-swatch-generic{background:var(--rk-gold-tint2);color:var(--rk-gold-700)}
206
-
207
- /* ── stage: toolbar + board ── */
208
- .rk-calendar-stage{display:grid;gap:10px;min-width:0}
209
- .rk-calendar-toolbar{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}
210
- .rk-calendar-monthnav{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
211
- .rk-calendar-month{font-family:var(--rk-font-display);font-weight:800;font-size:var(--rk-text-lg);letter-spacing:-.02em;color:var(--rk-ink);min-width:7.4rem;text-align:center}
212
- .rk-calendar-navbtn{width:32px;height:32px;padding:0;font-size:14px;line-height:1}
213
- @media(hover:none){.rk-calendar-navbtn{width:40px;height:40px}}
214
- .rk-calendar-today-btn{margin-left:2px;padding:5px 11px;font-size:12.5px}
215
- .rk-calendar-sync{font-size:12px;font-weight:600;color:var(--rk-text-muted);margin-left:4px}
216
- .rk-calendar-sync.is-error{color:var(--rk-red)}
217
-
218
- /* compact one-line legend, lives in the toolbar */
219
- .rk-calendar-legend{display:flex;flex-wrap:wrap;gap:12px}
220
- .rk-calendar-legend-item{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;color:var(--rk-text-muted)}
221
-
222
- /* month board — sized to the viewport so the whole month is visible */
223
- .rk-calendar-grid-card{padding:0;overflow:hidden;position:relative;display:flex;flex-direction:column;
224
- height:calc(100vh - 218px);min-height:430px}
225
- @media(max-width:960px){.rk-calendar-grid-card{height:auto;min-height:0}}
226
- .rk-calendar-weekdays{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));background:var(--rk-n-50);border-bottom:1px solid var(--rk-border);flex:none}
227
- .rk-calendar-weekday{padding:8px 8px;text-align:center;font-size:10px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--rk-text-muted)}
228
- .rk-calendar-grid{flex:1;min-height:0;display:grid;grid-template-columns:repeat(7,minmax(0,1fr));grid-auto-rows:1fr;
229
- gap:1px;background:var(--rk-border);transition:opacity var(--rk-dur) var(--rk-ease)}
230
- .rk-calendar-grid.is-loading{opacity:.55}
231
- .rk-calendar-cell{background:#fff;min-height:0;padding:6px 7px;display:flex;flex-direction:column;gap:4px;overflow:hidden;
232
- transition:background var(--rk-dur) var(--rk-ease)}
233
- @media(max-width:960px){.rk-calendar-cell{min-height:92px}}
234
- .rk-calendar-cell:hover{background:var(--rk-n-50)}
235
- .rk-calendar-cell.is-out{background:var(--rk-n-50)}
236
- .rk-calendar-cell.is-out:hover{background:var(--rk-n-100)}
237
- .rk-calendar-cell.is-today{background:var(--rk-gold-tint);box-shadow:inset 0 0 0 2px var(--rk-gold-500)}
238
- .rk-calendar-cell-head{display:flex;align-items:center;justify-content:space-between;gap:4px;flex:none}
239
- .rk-calendar-daynum{width:22px;height:22px;border-radius:var(--rk-r-full);display:grid;place-items:center;
240
- font-family:var(--rk-font-display);font-weight:700;font-size:12px;color:var(--rk-n-700)}
241
- .rk-calendar-cell.is-out .rk-calendar-daynum{color:var(--rk-n-400);font-weight:600}
242
- .rk-calendar-cell.is-today .rk-calendar-daynum{background:var(--rk-gold-500);color:var(--rk-ink);box-shadow:var(--rk-shadow-xs)}
243
- .rk-calendar-cell-posts{display:flex;flex-direction:column;gap:3px;min-height:0;overflow:hidden}
244
-
245
- /* post chip — one compact line: status dot · time · title */
246
- .rk-calendar-chip{display:flex;gap:6px;align-items:center;width:100%;text-align:left;padding:3px 7px;border-radius:8px;
247
- background:#fff;border:1px solid var(--rk-border);box-shadow:var(--rk-shadow-xs);cursor:pointer;min-width:0;flex:none;
248
- transition:transform var(--rk-dur) var(--rk-ease),box-shadow var(--rk-dur) var(--rk-ease);text-decoration:none;color:inherit}
249
- .rk-calendar-chip:hover{transform:translateY(-1px);box-shadow:var(--rk-shadow-sm)}
250
- .rk-calendar-chip-time{flex:none;font-size:10px;font-weight:700;color:var(--rk-text-muted);font-variant-numeric:tabular-nums}
251
- .rk-calendar-chip-title{flex:1;min-width:0;font-size:11px;font-weight:600;color:var(--rk-ink);letter-spacing:-.01em;
252
- white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
253
- .rk-calendar-more{font-size:10px;font-weight:700;color:var(--rk-text-muted);padding:0 2px;background:none;border:0;cursor:default}
254
-
255
- /* status dot — chips + legend */
256
- .rk-calendar-dot{width:7px;height:7px;border-radius:var(--rk-r-full);background:var(--rk-n-400);flex:none}
257
- .rk-calendar-dot[data-status="review"]{background:var(--rk-gold-700)}
258
- .rk-calendar-dot[data-status="ready"]{background:var(--rk-green)}
259
- .rk-calendar-dot[data-status="scheduled"]{background:var(--rk-sky)}
260
- .rk-calendar-dot[data-status="sent"]{background:var(--rk-violet)}
261
- .rk-calendar-dot[data-status="error"]{background:var(--rk-red)}
262
-
263
- @media(max-width:760px){
264
- .rk-calendar-weekdays,.rk-calendar-grid{min-width:660px}
265
- .rk-calendar-grid-card{overflow-x:auto}
266
- .rk-calendar-toolbar{gap:10px}
267
- }
268
- `;
269
- const script = `
270
- (function(){
271
- var root=document;
272
- var bootEl=root.querySelector('[data-rk-cal-boot]');
273
- var boot={};
274
- try{ boot=JSON.parse(bootEl&&bootEl.textContent||'{}'); }catch(e){ boot={}; }
275
- var loadEndpoint=boot.loadEndpoint||'/calendar/feed';
276
- var gridEl=root.querySelector('[data-rk-cal-grid]');
277
- var monthEl=root.querySelector('[data-rk-cal-month]');
278
- var subEl=root.querySelector('[data-rk-cal-sub]');
279
- var syncEl=root.querySelector('[data-rk-cal-sync]');
280
- var noticeEl=root.querySelector('[data-rk-cal-notice]');
281
- var channelsEl=root.querySelector('[data-rk-cal-channels]');
282
- var chSearchEl=root.querySelector('[data-rk-cal-chsearch]');
283
- if(!gridEl){return;}
284
-
285
- var STATUS=${JSON.stringify(STATUS)};
286
- var monthFmt=new Intl.DateTimeFormat(undefined,{month:'long',year:'numeric'});
287
- var timeFmt=new Intl.DateTimeFormat(undefined,{hour:'numeric',minute:'2-digit'});
288
-
289
- var today=startOfDay(new Date());
290
- var cursor=startOfMonth(today);
291
- // channelId -> channel record (resolved from the feed's channels list, seeded first)
292
- var channelsById={};
293
- var channelOrder=[];
294
- ingestChannels(boot.seedChannels||[]);
295
- // dateKey -> [posts]; seed with any server-provided posts
296
- var postsByDay={};
297
- ingestPosts(boot.seedPosts||[]);
298
- var loading=false;
299
- var selectedChannelId='';
300
- var channelQuery='';
301
-
302
- function esc(s){var d=root.createElement('div');d.textContent=s==null?'':String(s);return d.innerHTML;}
303
- function startOfDay(d){return new Date(d.getFullYear(),d.getMonth(),d.getDate());}
304
- function startOfMonth(d){return new Date(d.getFullYear(),d.getMonth(),1);}
305
- function startOfWeek(d){var n=startOfDay(d);n.setDate(n.getDate()-n.getDay());return n;}
306
- function addDays(d,n){var x=new Date(d);x.setDate(x.getDate()+n);return startOfDay(x);}
307
- function addMonths(d,n){var x=new Date(d.getFullYear(),d.getMonth()+n,1);return x;}
308
- function toKey(d){return [d.getFullYear(),String(d.getMonth()+1).padStart(2,'0'),String(d.getDate()).padStart(2,'0')].join('-');}
309
- function sameDay(a,b){return a.getFullYear()===b.getFullYear()&&a.getMonth()===b.getMonth()&&a.getDate()===b.getDate();}
310
- function sameMonth(a,b){return a.getFullYear()===b.getFullYear()&&a.getMonth()===b.getMonth();}
311
-
312
- function platformMeta(platform,channelId,kind){
313
- var p=String(platform||'').toLowerCase();
314
- if(p.indexOf('tiktok')>-1)return {initial:'T',swatch:'tiktok'};
315
- if(p.indexOf('instagram')>-1)return {initial:'I',swatch:'instagram'};
316
- if(p.indexOf('youtube')>-1)return {initial:'Y',swatch:'youtube'};
317
- if(p.indexOf('email')>-1||/^email:/.test(String(channelId||''))||/email/i.test(String(kind||'')))return {initial:'E',swatch:'email'};
318
- if(p.indexOf('facebook')>-1)return {initial:'F',swatch:'generic'};
319
- if(p.indexOf('linkedin')>-1)return {initial:'L',swatch:'generic'};
320
- if(p.indexOf('twitter')>-1||p==='x')return {initial:'X',swatch:'generic'};
321
- var src=String(platform||channelId||'').replace(/^email:/,'').trim();
322
- return {initial:(src.charAt(0)||'C').toUpperCase(),swatch:'generic'};
323
- }
324
-
325
- function ingestChannels(list){
326
- (Array.isArray(list)?list:[]).forEach(function(ch){
327
- if(!ch||ch.id==null)return;
328
- var id=String(ch.id);
329
- if(!channelsById[id])channelOrder.push(id);
330
- channelsById[id]={
331
- id:id,
332
- name:ch.name||ch.title||id,
333
- handle:ch.handle||null,
334
- platform:ch.platform||null,
335
- avatarUrl:ch.avatarUrl||null
336
- };
337
- });
338
- }
339
-
340
- function ingestPosts(list){
341
- (Array.isArray(list)?list:[]).forEach(function(post){
342
- if(!post)return;
343
- var t=Date.parse(post.scheduledAt);
344
- if(isNaN(t))return;
345
- var d=startOfDay(new Date(t));
346
- var key=toKey(d);
347
- if(!postsByDay[key])postsByDay[key]=[];
348
- // de-dupe by id within the day
349
- var id=String(post.id||post.scheduleId||'');
350
- if(id&&postsByDay[key].some(function(p){return String(p.id||p.scheduleId||'')===id;}))return;
351
- postsByDay[key].push(post);
352
- postsByDay[key].sort(function(a,b){return Date.parse(a.scheduledAt)-Date.parse(b.scheduledAt);});
353
- });
354
- }
355
-
356
- function postCountFor(channelId){
357
- var n=0;
358
- Object.keys(postsByDay).forEach(function(key){
359
- postsByDay[key].forEach(function(p){ if(String(p.channelId)===channelId)n++; });
360
- });
361
- return n;
362
- }
363
-
364
- function channelInitials(name){
365
- return String(name||'').split(/\\s+/).filter(Boolean).slice(0,2)
366
- .map(function(part){return (part[0]||'').toUpperCase();}).join('')||'CH';
367
- }
368
-
369
- function renderChannels(){
370
- if(!channelsEl)return;
371
- var q=channelQuery.trim().toLowerCase();
372
- var ids=channelOrder.filter(function(id){
373
- if(!q)return true;
374
- var ch=channelsById[id];
375
- return [ch.name,ch.handle,ch.platform,ch.id].filter(Boolean).join(' ').toLowerCase().indexOf(q)>-1;
376
- });
377
- if(!channelOrder.length){
378
- channelsEl.innerHTML='<div class="rk-calendar-channels-empty">No channels connected yet. Connect FlockPoster or add an email channel to start scheduling.</div>';
379
- return;
380
- }
381
- if(!ids.length){
382
- channelsEl.innerHTML='<div class="rk-calendar-channels-empty">No channels match \\u201c'+esc(channelQuery)+'\\u201d.</div>';
383
- return;
384
- }
385
- var allBtn='<button type="button" class="rk-calendar-channel'+(selectedChannelId===''?' is-selected':'')+'" data-rk-channel="">'
386
- +'<span class="rk-calendar-channel-avatar"><span class="rk-calendar-channel-initials">All</span></span>'
387
- +'<span class="rk-calendar-channel-main">'
388
- +'<span class="rk-calendar-channel-name">All channels</span>'
389
- +'<span class="rk-calendar-channel-sub">'+channelOrder.length+' connected</span>'
390
- +'</span>'
391
- +'</button>';
392
- var items=ids.map(function(id){
393
- var ch=channelsById[id];
394
- var pm=platformMeta(ch.platform,ch.id,null);
395
- var avatar=ch.avatarUrl
396
- ? '<img src="'+esc(ch.avatarUrl)+'" alt="" loading="lazy" />'
397
- : '<span class="rk-calendar-channel-initials">'+esc(channelInitials(ch.name))+'</span>';
398
- var count=postCountFor(id);
399
- return '<button type="button" class="rk-calendar-channel'+(selectedChannelId===id?' is-selected':'')+'" data-rk-channel="'+esc(id)+'">'
400
- +'<span class="rk-calendar-channel-avatar">'+avatar
401
- +'<span class="rk-calendar-swatch rk-calendar-swatch-'+pm.swatch+'" aria-hidden="true">'+esc(pm.initial)+'</span>'
402
- +'</span>'
403
- +'<span class="rk-calendar-channel-main">'
404
- +'<span class="rk-calendar-channel-name">'+esc(ch.name)+'</span>'
405
- +'<span class="rk-calendar-channel-sub">'+esc([ch.platform,ch.handle].filter(Boolean).join(' \\u00b7 ')||'Channel')+'</span>'
406
- +'</span>'
407
- +(count?'<span class="rk-calendar-channel-count">'+count+'</span>':'')
408
- +'</button>';
409
- }).join('');
410
- channelsEl.innerHTML=allBtn+items;
411
- }
412
-
413
- function chip(post){
414
- var tone=String(post.statusTone||'draft');
415
- var meta=STATUS[tone]||{label:tone,pill:'rk-pill'};
416
- var time='';
417
- var t=Date.parse(post.scheduledAt);
418
- if(!isNaN(t))time=timeFmt.format(new Date(t));
419
- var label=post.status?String(post.status):meta.label;
420
- var title=post.title||'Scheduled post';
421
- var ch=channelsById[String(post.channelId)];
422
- var chName=ch?ch.name:'';
423
- var tag='a';var attrs='';
424
- if(post.url){attrs=' href="'+esc(post.url)+'" target="_blank" rel="noreferrer"';}else{tag='button';attrs=' type="button"';}
425
- return '<'+tag+' class="rk-calendar-chip"'+attrs+' title="'+esc(title+' \\u00b7 '+label+(time?' \\u00b7 '+time:'')+(chName?' \\u00b7 '+chName:''))+'">'
426
- +'<span class="rk-calendar-dot" data-status="'+esc(tone)+'" aria-hidden="true"></span>'
427
- +(time?'<span class="rk-calendar-chip-time">'+esc(time)+'</span>':'')
428
- +'<span class="rk-calendar-chip-title">'+esc(title)+'</span>'
429
- +'</'+tag+'>';
430
- }
431
-
432
- function visiblePosts(key){
433
- var dayPosts=postsByDay[key]||[];
434
- if(!selectedChannelId)return dayPosts;
435
- return dayPosts.filter(function(p){return String(p.channelId)===selectedChannelId;});
436
- }
437
-
438
- function renderGrid(){
439
- monthEl.textContent=monthFmt.format(cursor);
440
- var firstCell=startOfWeek(startOfMonth(cursor));
441
- var lastOfMonth=new Date(cursor.getFullYear(),cursor.getMonth()+1,0);
442
- var rows=Math.ceil((addDays(lastOfMonth,0).getTime()-firstCell.getTime())/(7*86400000))||5;
443
- rows=Math.max(rows,Math.ceil(((startOfMonth(cursor).getDay())+lastOfMonth.getDate())/7));
444
- var total=rows*7;
445
- var cells=[];
446
- var monthPostCount=0;
447
- for(var i=0;i<total;i++){
448
- var d=addDays(firstCell,i);
449
- var key=toKey(d);
450
- var dayPosts=visiblePosts(key);
451
- var inMonth=sameMonth(d,cursor);
452
- if(inMonth)monthPostCount+=dayPosts.length;
453
- var isToday=sameDay(d,today);
454
- var cls='rk-calendar-cell';
455
- if(!inMonth)cls+=' is-out';
456
- if(isToday)cls+=' is-today';
457
- var shown=dayPosts.slice(0,2);
458
- var overflow=dayPosts.length-shown.length;
459
- var chips=shown.map(chip).join('');
460
- var more=overflow>0?'<span class="rk-calendar-more">+'+overflow+' more</span>':'';
461
- cells.push('<div class="'+cls+'">'
462
- +'<div class="rk-calendar-cell-head"><span class="rk-calendar-daynum">'+d.getDate()+'</span></div>'
463
- +'<div class="rk-calendar-cell-posts">'+chips+more+'</div>'
464
- +'</div>');
465
- }
466
- gridEl.innerHTML=cells.join('');
467
- if(subEl){
468
- var scope=selectedChannelId&&channelsById[selectedChannelId]?(' on '+channelsById[selectedChannelId].name):'';
469
- subEl.textContent=monthPostCount
470
- ?(monthPostCount+' scheduled post'+(monthPostCount===1?'':'s')+scope+' in '+monthFmt.format(cursor)+'.')
471
- :('Nothing scheduled'+scope+' in '+monthFmt.format(cursor)+' yet.');
472
- }
473
- }
474
-
475
- function setNotice(msg,isError){
476
- if(!noticeEl)return;
477
- if(!msg){noticeEl.hidden=true;noticeEl.textContent='';noticeEl.className='rk-calendar-notice';return;}
478
- noticeEl.hidden=false;
479
- noticeEl.className='rk-calendar-notice rk-notice '+(isError?'rk-notice-err':'rk-notice-ok');
480
- noticeEl.textContent=msg;
481
- }
482
-
483
- function setSync(msg,isError){
484
- if(!syncEl)return;
485
- syncEl.textContent=msg||'';
486
- syncEl.className='rk-calendar-sync'+(isError?' is-error':'');
487
- }
488
-
489
- function load(){
490
- if(loading)return;
491
- loading=true;
492
- gridEl.classList.add('is-loading');
493
- setSync('Loading\\u2026',false);
494
- var first=startOfWeek(startOfMonth(cursor));
495
- var last=addDays(first,41);
496
- var params=new URLSearchParams({start_date:toKey(first),end_date:toKey(last)});
497
- var url;
498
- try{
499
- var u=new URL(loadEndpoint,window.location.origin);
500
- params.forEach(function(v,k){u.searchParams.set(k,v);});
501
- url=u.pathname+u.search;
502
- }catch(e){ url=loadEndpoint+(loadEndpoint.indexOf('?')>-1?'&':'?')+params.toString(); }
503
- fetch(url,{credentials:'same-origin',headers:{Accept:'application/json'}})
504
- .then(function(r){return r.json().catch(function(){return {};}).then(function(j){return {ok:r.ok,json:j};});})
505
- .then(function(res){
506
- var payload=res.json||{};
507
- if(!res.ok){
508
- var em=(payload&&typeof payload.error==='string'&&payload.error)?payload.error:'Unable to load calendar.';
509
- throw new Error(em);
510
- }
511
- ingestChannels(payload.channels||[]);
512
- ingestPosts(payload.posts||[]);
513
- if(payload&&typeof payload.error==='string'&&payload.error){
514
- setNotice(payload.error,false);
515
- }else{
516
- setNotice('',false);
517
- }
518
- setSync(payload&&payload.syncStatus==='error'?'Synced with warnings':'Up to date',payload&&payload.syncStatus==='error');
519
- renderChannels();
520
- renderGrid();
521
- })
522
- .catch(function(err){
523
- var msg=(err&&err.message)?err.message:'Unable to load calendar.';
524
- setNotice(msg,true);
525
- setSync('Offline',true);
526
- renderChannels();
527
- renderGrid();
528
- })
529
- .finally(function(){
530
- loading=false;
531
- gridEl.classList.remove('is-loading');
532
- });
533
- }
534
-
535
- if(channelsEl){
536
- channelsEl.addEventListener('click',function(e){
537
- var btn=e.target.closest('[data-rk-channel]');
538
- if(!btn)return;
539
- selectedChannelId=btn.getAttribute('data-rk-channel')||'';
540
- renderChannels();
541
- renderGrid();
542
- });
543
- }
544
- if(chSearchEl){
545
- chSearchEl.addEventListener('input',function(){
546
- channelQuery=chSearchEl.value||'';
547
- renderChannels();
548
- });
549
- }
550
-
551
- root.querySelector('[data-rk-cal-prev]')&&root.querySelector('[data-rk-cal-prev]').addEventListener('click',function(){cursor=addMonths(cursor,-1);renderGrid();load();});
552
- root.querySelector('[data-rk-cal-next]')&&root.querySelector('[data-rk-cal-next]').addEventListener('click',function(){cursor=addMonths(cursor,1);renderGrid();load();});
553
- root.querySelector('[data-rk-cal-today]')&&root.querySelector('[data-rk-cal-today]').addEventListener('click',function(){cursor=startOfMonth(today);renderGrid();load();});
554
-
555
- renderChannels();
556
- renderGrid();
557
- load();
558
- })();`;
559
- return reskinDocument({
560
- title: "vidfarm reskin — Calendar",
561
- description: "Reskinned vidfarm calendar — connected channels on the left, a compact viewport-fit month of scheduled posts on the right, live from /calendar/feed.",
562
- activeSlug: "calendar",
563
- pageCss,
564
- body,
565
- script
566
- });
567
- }
568
- //# sourceMappingURL=calendar-page.js.map