@guidelytech/react-native 0.5.0 → 0.6.2

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.
package/dist/index.cjs CHANGED
@@ -54,7 +54,7 @@ module.exports = __toCommonJS(index_exports);
54
54
  var import_react8 = require("react");
55
55
  var import_react_native11 = require("react-native");
56
56
 
57
- // ../shared/dist/chunk-5IQVBZO6.js
57
+ // ../shared/dist/chunk-OLWGMDRX.js
58
58
  function fnv1a(input) {
59
59
  let h = 2166136261;
60
60
  for (let i = 0; i < input.length; i++) {
@@ -360,6 +360,13 @@ function sanitizeProperties(props) {
360
360
  var SOURCEMAP_MAX_FILE_BYTES = 25 * 1024 * 1024;
361
361
  var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
362
362
  var MEDIA_VIDEO_MAX_BYTES = 200 * 1024 * 1024;
363
+ var MEDIA_FILE_MAX_BYTES = 50 * 1024 * 1024;
364
+ var MEDIA_MAX_BYTES = Math.max(MEDIA_IMAGE_MAX_BYTES, MEDIA_VIDEO_MAX_BYTES, MEDIA_FILE_MAX_BYTES);
365
+ var FILE_LINK_CLASS = "gd-file";
366
+ var PDF_BLOCK_CLASS = "gd-pdf";
367
+ function isFileLink(className, href) {
368
+ return !!className && className.split(/\s+/).includes(FILE_LINK_CLASS) && isSafeMediaUrl(href);
369
+ }
363
370
  var YOUTUBE_ID = /^[A-Za-z0-9_-]{11}$/;
364
371
  var VIMEO_ID = /^\d{1,12}$/;
365
372
  var VIMEO_HASH = /^[0-9a-f]{6,20}$/i;
@@ -500,8 +507,45 @@ function isArticleListed(collections, article, member) {
500
507
  }
501
508
  return true;
502
509
  }
510
+ var CAPTURE_SCREENSHOT_MAX_BYTES = 8 * 1024 * 1024;
503
511
 
504
- // ../sdk-core/dist/chunk-GJT7CFAC.js
512
+ // ../sdk-core/dist/chunk-CYAG6ZYX.js
513
+ function readJson(store, key, fallback) {
514
+ const raw = store.get(key);
515
+ if (raw == null) return fallback;
516
+ try {
517
+ return JSON.parse(raw);
518
+ } catch {
519
+ return fallback;
520
+ }
521
+ }
522
+ function writeJson(store, key, value) {
523
+ try {
524
+ store.set(key, JSON.stringify(value));
525
+ } catch {
526
+ }
527
+ }
528
+ var KEYS = {
529
+ anon: "guidely:anon",
530
+ seen: "guidely:seen",
531
+ config: (key) => `guidely:config:${key}`,
532
+ etag: (key) => `guidely:etag:${key}`,
533
+ state: (key) => `guidely:state:${key}`,
534
+ user: (key) => `guidely:user:${key}`,
535
+ queue: (key) => `guidely:queue:${key}`,
536
+ session: "guidely:session",
537
+ active: "guidely:active",
538
+ authoring: "guidely:authoring",
539
+ /** How-to capture session marker ({ token, … }) and its not-yet-uploaded steps. */
540
+ capture: "guidely:capture",
541
+ capturePending: "guidely:capture:pending",
542
+ /** Messenger conversations this browser can read: [{ id, token }]. */
543
+ conversations: (key) => `guidely:conversations:${key}`,
544
+ /** Announcement ids opened in the messenger's News tab. */
545
+ newsSeen: (key) => `guidely:news:${key}`
546
+ };
547
+
548
+ // ../sdk-core/dist/chunk-AKNE7D5M.js
505
549
  var __defProp2 = Object.defineProperty;
506
550
  var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
507
551
  var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -523,7 +567,8 @@ var Api = class {
523
567
  const f = this.fetcher;
524
568
  if (!f) throw new Error("fetch is not available");
525
569
  const headers = { ...options.headers, "X-Guidely-Key": this.key, Accept: "application/json" };
526
- if (options.body !== void 0) headers["Content-Type"] = "application/json";
570
+ const form = typeof FormData !== "undefined" && options.body instanceof FormData;
571
+ if (options.body !== void 0 && !form) headers["Content-Type"] = "application/json";
527
572
  if (options.etag) headers["If-None-Match"] = options.etag;
528
573
  const ac = typeof AbortController !== "undefined" ? new AbortController() : null;
529
574
  const timer = ac ? setTimeout(() => ac.abort(), options.timeoutMs ?? 1e4) : null;
@@ -531,7 +576,7 @@ var Api = class {
531
576
  const res = await f(this.url(path, options.params), {
532
577
  method,
533
578
  headers,
534
- body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
579
+ body: options.body === void 0 ? void 0 : form ? options.body : JSON.stringify(options.body),
535
580
  keepalive: options.keepalive,
536
581
  signal: ac?.signal,
537
582
  credentials: "omit",
@@ -556,6 +601,10 @@ var Api = class {
556
601
  post(path, body, options) {
557
602
  return this.request("POST", path, { body, ...options });
558
603
  }
604
+ /** Multipart POST (file uploads). No Content-Type header: the browser adds it with the boundary. */
605
+ postForm(path, form, options) {
606
+ return this.request("POST", path, { body: form, ...options });
607
+ }
559
608
  /**
560
609
  * POST that may answer with server-sent events. Each event is passed to `onEvent`; a JSON answer is
561
610
  * returned instead. No timeout: Scout replies can take a while and the server keeps the stream alive.
@@ -598,10 +647,10 @@ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
598
647
  function backoffMs(attempt, baseMs = 1e3, maxMs = 3e4) {
599
648
  return Math.min(maxMs, baseMs * 2 ** Math.max(0, attempt));
600
649
  }
601
- async function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
650
+ async function withRetry(send, attempts = 3, baseMs = 500) {
602
651
  for (let i = 0; i < attempts; i++) {
603
652
  try {
604
- const res = await api.post(path, body);
653
+ const res = await send();
605
654
  if (res.ok || res.status >= 400 && res.status < 500 && res.status !== 429) return res;
606
655
  } catch {
607
656
  }
@@ -609,6 +658,9 @@ async function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
609
658
  }
610
659
  return null;
611
660
  }
661
+ function postWithRetry(api, path, body, attempts = 3, baseMs = 500) {
662
+ return withRetry(() => api.post(path, body), attempts, baseMs);
663
+ }
612
664
  async function readEventStream(body, onEvent) {
613
665
  const reader = body.getReader();
614
666
  const decoder = new TextDecoder();
@@ -643,36 +695,7 @@ async function readEventStream(body, onEvent) {
643
695
  if (buffer.trim()) dispatch(buffer);
644
696
  }
645
697
 
646
- // ../sdk-core/dist/chunk-HLR3W63Z.js
647
- function readJson(store, key, fallback) {
648
- const raw = store.get(key);
649
- if (raw == null) return fallback;
650
- try {
651
- return JSON.parse(raw);
652
- } catch {
653
- return fallback;
654
- }
655
- }
656
- function writeJson(store, key, value) {
657
- try {
658
- store.set(key, JSON.stringify(value));
659
- } catch {
660
- }
661
- }
662
- var KEYS = {
663
- anon: "guidely:anon",
664
- seen: "guidely:seen",
665
- config: (key) => `guidely:config:${key}`,
666
- etag: (key) => `guidely:etag:${key}`,
667
- state: (key) => `guidely:state:${key}`,
668
- user: (key) => `guidely:user:${key}`,
669
- queue: (key) => `guidely:queue:${key}`,
670
- session: "guidely:session",
671
- active: "guidely:active",
672
- authoring: "guidely:authoring",
673
- /** Messenger conversations this browser can read: [{ id, token }]. */
674
- conversations: (key) => `guidely:conversations:${key}`
675
- };
698
+ // ../sdk-core/dist/chunk-GDF6RCHQ.js
676
699
  var EventQueue = class {
677
700
  constructor(opts) {
678
701
  __publicField2(this, "opts", opts);
@@ -1754,7 +1777,7 @@ var PersistentStore = class {
1754
1777
 
1755
1778
  // src/core/client.ts
1756
1779
  var SDK_NAME = "@guidelytech/react-native";
1757
- var SDK_VERSION = "0.5.0";
1780
+ var SDK_VERSION = "0.6.2";
1758
1781
  var DEFAULT_API_URL = "https://guidelytech.com";
1759
1782
  var PAGE_SCOPED = /* @__PURE__ */ new Set(["page_load", "url_match", "element_click", "element_visible", "delay"]);
1760
1783
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
@@ -2973,6 +2996,8 @@ function parseHtml(html) {
2973
2996
  const lists = [];
2974
2997
  let current = null;
2975
2998
  let skipping = null;
2999
+ let pdf = null;
3000
+ let fileLink = null;
2976
3001
  const media = (block) => {
2977
3002
  flush();
2978
3003
  blocks.push({ level: 1, runs: [], ...block });
@@ -3007,12 +3032,30 @@ function parseHtml(html) {
3007
3032
  if (last && last.bold === run.bold && last.italic === run.italic && last.underline === run.underline && last.code === run.code && last.href === run.href) last.text += text;
3008
3033
  else current.runs.push(run);
3009
3034
  };
3035
+ const size = (attrs, name) => {
3036
+ const n = Number(attr(attrs, name));
3037
+ return n > 0 ? n : void 0;
3038
+ };
3039
+ const endPdf = (p) => {
3040
+ const name = p.name.replace(/\s+/g, " ").trim();
3041
+ if (p.href && p.pages.length) media({ kind: "pdf", href: p.href, alt: name || "PDF", pages: p.pages });
3042
+ else if (p.href) media({ kind: "file", href: p.href, alt: name || "Open PDF" });
3043
+ else for (const page of p.pages) media({ kind: "image", src: page.src, alt: page.alt, ...page.width && page.height ? { width: page.width, height: page.height } : {} });
3044
+ };
3010
3045
  const re = /<!--[\s\S]*?-->|<\/?([a-zA-Z][a-zA-Z0-9]*)\b([^>]*)>|([^<]+|<)/g;
3011
3046
  let m;
3012
3047
  while (m = re.exec(html || "")) {
3013
3048
  const [token, rawTag, attrs = "", text] = m;
3014
3049
  if (text !== void 0) {
3015
3050
  if (skipping) continue;
3051
+ if (pdf) {
3052
+ if (pdf.inLink) pdf.name += decodeEntities(text);
3053
+ continue;
3054
+ }
3055
+ if (fileLink) {
3056
+ fileLink.name += decodeEntities(text);
3057
+ continue;
3058
+ }
3016
3059
  push(decodeEntities(text.replace(/\s+/g, " ")));
3017
3060
  continue;
3018
3061
  }
@@ -3036,6 +3079,44 @@ function parseHtml(html) {
3036
3079
  if (!token.endsWith("/>")) skipping = tag;
3037
3080
  continue;
3038
3081
  }
3082
+ if (pdf) {
3083
+ if (tag === "div") {
3084
+ pdf.depth += closing ? -1 : 1;
3085
+ if (pdf.depth === 0) {
3086
+ endPdf(pdf);
3087
+ pdf = null;
3088
+ }
3089
+ } else if (tag === "a") {
3090
+ if (closing) pdf.inLink = false;
3091
+ else if (!pdf.href && isFileLink(attr(attrs, "class"), attr(attrs, "href"))) {
3092
+ pdf.href = attr(attrs, "href");
3093
+ pdf.inLink = true;
3094
+ }
3095
+ } else if (tag === "img" && !closing) {
3096
+ const src = attr(attrs, "src");
3097
+ const w = size(attrs, "width");
3098
+ const h = size(attrs, "height");
3099
+ if (isSafeMediaUrl(src)) pdf.pages.push({ src, alt: attr(attrs, "alt") ?? "", ...w && h ? { width: w, height: h } : {} });
3100
+ }
3101
+ continue;
3102
+ }
3103
+ if (fileLink) {
3104
+ if (closing && tag === "a") {
3105
+ media({ kind: "file", href: fileLink.href, alt: fileLink.name.replace(/\s+/g, " ").trim() || "Open PDF" });
3106
+ fileLink = null;
3107
+ }
3108
+ continue;
3109
+ }
3110
+ if (!closing && tag === "div" && (attr(attrs, "class") ?? "").split(/\s+/).includes(PDF_BLOCK_CLASS)) {
3111
+ flush();
3112
+ pdf = { depth: 1, name: "", inLink: false, pages: [] };
3113
+ continue;
3114
+ }
3115
+ if (!closing && tag === "a" && isFileLink(attr(attrs, "class"), attr(attrs, "href"))) {
3116
+ flush();
3117
+ fileLink = { href: attr(attrs, "href"), name: "" };
3118
+ continue;
3119
+ }
3039
3120
  if (closing) {
3040
3121
  if (tag === "b" || tag === "strong") style.bold = Math.max(0, style.bold - 1);
3041
3122
  else if (tag === "i" || tag === "em") style.italic = Math.max(0, style.italic - 1);
@@ -3104,11 +3185,13 @@ function parseHtml(html) {
3104
3185
  }
3105
3186
  }
3106
3187
  }
3188
+ if (pdf) endPdf(pdf);
3189
+ if (fileLink) media({ kind: "file", href: fileLink.href, alt: fileLink.name.replace(/\s+/g, " ").trim() || "Open PDF" });
3107
3190
  flush();
3108
3191
  return blocks;
3109
3192
  }
3110
3193
  function htmlToText(html) {
3111
- return parseHtml(html).map((b) => b.kind === "image" || b.kind === "video" ? b.alt ?? "" : (b.marker ? `${b.marker} ` : "") + b.runs.map((r) => r.text).join("")).filter(Boolean).join("\n");
3194
+ return parseHtml(html).map((b) => b.kind === "image" || b.kind === "video" || b.kind === "pdf" || b.kind === "file" ? b.alt ?? "" : (b.marker ? `${b.marker} ` : "") + b.runs.map((r) => r.text).join("")).filter(Boolean).join("\n");
3112
3195
  }
3113
3196
 
3114
3197
  // src/ui/RichText.tsx
@@ -3144,6 +3227,48 @@ function BodyVideo({ block, palette: p, onLink }) {
3144
3227
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: { color: p.primary, fontFamily: p.fontFamily, fontWeight: "600" }, children: block.alt })
3145
3228
  ] });
3146
3229
  }
3230
+ var PAGE_RATIO = 1 / 1.414;
3231
+ var pageRatio = (p) => p.width && p.height ? p.width / p.height : PAGE_RATIO;
3232
+ function PdfBadge() {
3233
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.badge, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.badgeText, children: "PDF" }) });
3234
+ }
3235
+ function BodyFile({ block, palette: p, onLink }) {
3236
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Pressable, { accessibilityRole: "link", accessibilityLabel: `${block.alt}, PDF`, onPress: () => onLink(block.href), style: ({ pressed }) => [styles2.video, { borderColor: p.border, backgroundColor: p.surface, opacity: pressed ? 0.7 : 1 }], children: [
3237
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PdfBadge, {}),
3238
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { numberOfLines: 2, ellipsizeMode: "middle", style: [styles2.flex, { color: p.text, fontFamily: p.fontFamily, fontWeight: "600" }], children: block.alt })
3239
+ ] });
3240
+ }
3241
+ function BodyPdf({ block, palette: p, onLink }) {
3242
+ const pages = block.pages ?? [];
3243
+ const [width, setWidth] = (0, import_react.useState)(0);
3244
+ const [index, setIndex] = (0, import_react.useState)(0);
3245
+ const [reading, setReading] = (0, import_react.useState)(false);
3246
+ const height = width ? Math.max(...pages.map((pg) => width / pageRatio(pg))) : 0;
3247
+ const onScroll = (e) => width && setIndex(Math.round(e.nativeEvent.contentOffset.x / width));
3248
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.pdf, { borderColor: p.border, backgroundColor: p.surface }], children: [
3249
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Pressable, { accessibilityRole: "button", accessibilityLabel: `Read ${block.alt} full screen`, onPress: () => setReading(true), style: ({ pressed }) => [styles2.pdfHeader, { opacity: pressed ? 0.7 : 1 }], children: [
3250
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PdfBadge, {}),
3251
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { numberOfLines: 1, ellipsizeMode: "middle", style: [styles2.flex, { color: p.text, fontFamily: p.fontFamily, fontWeight: "600" }], children: block.alt }),
3252
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: { color: p.muted, fontSize: 16 }, children: "\u2922" })
3253
+ ] }),
3254
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.pdfPages, onLayout: (e) => setWidth(e.nativeEvent.layout.width), children: [
3255
+ width > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ScrollView, { horizontal: true, pagingEnabled: true, showsHorizontalScrollIndicator: false, onScroll, scrollEventThrottle: 32, accessibilityLabel: `${block.alt}, page ${index + 1} of ${pages.length}`, children: pages.map((pg, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Pressable, { onPress: () => setReading(true), accessibilityRole: "imagebutton", accessibilityLabel: pg.alt || `Page ${i + 1}`, accessibilityHint: "Opens the document full screen", style: { width, height, alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: pg.src }, resizeMode: "contain", style: [styles2.page, { width: Math.min(width, height * pageRatio(pg)), aspectRatio: pageRatio(pg) }] }) }, i)) }),
3256
+ pages.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { pointerEvents: "none", style: styles2.count, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Text, { style: styles2.countText, children: [
3257
+ index + 1,
3258
+ " / ",
3259
+ pages.length
3260
+ ] }) })
3261
+ ] }),
3262
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Modal, { visible: reading, animationType: "slide", presentationStyle: "fullScreen", onRequestClose: () => setReading(false), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.reader, children: [
3263
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.readerBar, children: [
3264
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { numberOfLines: 1, ellipsizeMode: "middle", style: [styles2.flex, styles2.readerTitle], accessibilityRole: "header", children: block.alt }),
3265
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Pressable, { accessibilityRole: "link", onPress: () => onLink(block.href), style: styles2.readerBtn, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.readerBtnText, children: "Open PDF \u2197" }) }),
3266
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Pressable, { accessibilityRole: "button", accessibilityLabel: "Close", onPress: () => setReading(false), style: styles2.readerBtn, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.readerBtnText, children: "Done" }) })
3267
+ ] }),
3268
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ScrollView, { contentContainerStyle: styles2.readerPages, children: pages.map((pg, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: pg.src }, accessible: true, accessibilityLabel: pg.alt || `Page ${i + 1}`, resizeMode: "contain", style: [styles2.readerPage, { aspectRatio: pageRatio(pg) }] }, i)) })
3269
+ ] }) })
3270
+ ] });
3271
+ }
3147
3272
  function RichText({ html, palette: p, onLink, size = 15 }) {
3148
3273
  const blocks = (0, import_react.useMemo)(() => parseHtml(html), [html]);
3149
3274
  if (!blocks.length) return null;
@@ -3151,6 +3276,8 @@ function RichText({ html, palette: p, onLink, size = 15 }) {
3151
3276
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.wrap, children: blocks.map((b, i) => {
3152
3277
  if (b.kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyImage, { block: b, palette: p }, i);
3153
3278
  if (b.kind === "video") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyVideo, { block: b, palette: p, onLink }, i);
3279
+ if (b.kind === "pdf") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyPdf, { block: b, palette: p, onLink }, i);
3280
+ if (b.kind === "file") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyFile, { block: b, palette: p, onLink }, i);
3154
3281
  const runs = b.runs.map((r, j) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: runStyle(r, p), onPress: r.href ? () => onLink(r.href) : void 0, accessibilityRole: r.href ? "link" : void 0, children: r.text }, j));
3155
3282
  if (b.kind === "list-item") {
3156
3283
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [styles2.li, { paddingLeft: (b.level - 1) * 14 }], children: [
@@ -3172,7 +3299,22 @@ var styles2 = import_react_native2.StyleSheet.create({
3172
3299
  flex: { flex: 1 },
3173
3300
  image: { width: "100%", borderRadius: 6 },
3174
3301
  video: { flexDirection: "row", alignItems: "center", gap: 8, paddingVertical: 12, paddingHorizontal: 14, borderWidth: 1, borderRadius: 8 },
3175
- play: { fontSize: 14 }
3302
+ play: { fontSize: 14 },
3303
+ badge: { backgroundColor: "#e5484d", borderRadius: 4, paddingHorizontal: 5, paddingVertical: 3 },
3304
+ badgeText: { color: "#fff", fontSize: 9, fontWeight: "700", letterSpacing: 0.4 },
3305
+ pdf: { borderWidth: 1, borderRadius: 10, overflow: "hidden" },
3306
+ pdfHeader: { flexDirection: "row", alignItems: "center", gap: 8, minHeight: 44, paddingHorizontal: 12 },
3307
+ pdfPages: { marginHorizontal: 10, marginBottom: 10 },
3308
+ page: { backgroundColor: "#fff", borderRadius: 3 },
3309
+ count: { position: "absolute", right: 8, bottom: 8, backgroundColor: "rgba(17,17,17,0.72)", borderRadius: 999, paddingHorizontal: 8, paddingVertical: 2 },
3310
+ countText: { color: "#fff", fontSize: 11, fontWeight: "500", fontVariant: ["tabular-nums"] },
3311
+ reader: { flex: 1, backgroundColor: "#0b0b0d" },
3312
+ readerBar: { flexDirection: "row", alignItems: "center", gap: 8, paddingTop: import_react_native2.Platform.OS === "ios" ? 52 : 12, paddingBottom: 10, paddingHorizontal: 16 },
3313
+ readerTitle: { color: "#fff", fontSize: 16, fontWeight: "600" },
3314
+ readerBtn: { minHeight: 36, justifyContent: "center", paddingHorizontal: 12, borderRadius: 8, backgroundColor: "rgba(255,255,255,0.12)" },
3315
+ readerBtnText: { color: "#fff", fontWeight: "600" },
3316
+ readerPages: { gap: 12, paddingHorizontal: 12, paddingBottom: 32 },
3317
+ readerPage: { width: "100%", backgroundColor: "#fff", borderRadius: 2 }
3176
3318
  });
3177
3319
 
3178
3320
  // src/ui/Checklist.tsx