@bendyline/squisq 2.4.3 → 2.5.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.
@@ -456,6 +456,25 @@ interface SectionHeaderInput extends BaseTemplateBlock {
456
456
  /** Ambient motion for background image */
457
457
  ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
458
458
  }
459
+ /**
460
+ * Big text - thumbnail-style display card with the title in gigantic
461
+ * uppercase type, sized to fill the frame minus a small padding margin.
462
+ * With an image, the title sits over a full-bleed photo with a theme-tinted
463
+ * contrast bloom behind the text; without one, it sits on a clean theme
464
+ * surface. Shows nothing but the title — body content feeds narration only.
465
+ * Built for YouTube covers and social thumbnails.
466
+ */
467
+ interface BigTextInput extends BaseTemplateBlock {
468
+ template: 'bigText';
469
+ /** The display text (rendered uppercase). */
470
+ title: string;
471
+ /** Optional full-bleed background image path. */
472
+ imageSrc?: string;
473
+ /** Alt text for the background image. */
474
+ imageAlt?: string;
475
+ /** Ambient motion for the background image. */
476
+ ambientMotion?: 'zoomIn' | 'zoomOut' | 'panLeft' | 'panRight';
477
+ }
459
478
  /**
460
479
  * Content block - loss-averse heading and body layout.
461
480
  *
@@ -1066,7 +1085,7 @@ type ChartTemplateInput = BarChartInput | ColumnChartInput | PieChartInput | Don
1066
1085
  /**
1067
1086
  * Union of all template block types.
1068
1087
  */
1069
- type TemplateBlock = TitleBlockInput | SectionHeaderInput | ContentBlockInput | StatHighlightInput | QuoteBlockInput | FactCardInput | TwoColumnInput | DateEventInput | ImageWithCaptionInput | LeftFeatureInput | RightFeatureInput | MapBlockInput | FullBleedQuoteInput | ListBlockInput | PhotoGridInput | DefinitionCardInput | ComparisonBarInput | PullQuoteInput | VideoWithCaptionInput | VideoPullQuoteInput | DataTableInput | BarChartInput | ColumnChartInput | PieChartInput | DonutChartInput | LineChartInput | AreaChartInput | ScatterChartInput | DiagramBlockInput | TreeBlockInput | TimelineBlockInput | DrawingBlockInput | RawLayersInput;
1088
+ type TemplateBlock = TitleBlockInput | SectionHeaderInput | BigTextInput | ContentBlockInput | StatHighlightInput | QuoteBlockInput | FactCardInput | TwoColumnInput | DateEventInput | ImageWithCaptionInput | LeftFeatureInput | RightFeatureInput | MapBlockInput | FullBleedQuoteInput | ListBlockInput | PhotoGridInput | DefinitionCardInput | ComparisonBarInput | PullQuoteInput | VideoWithCaptionInput | VideoPullQuoteInput | DataTableInput | BarChartInput | ColumnChartInput | PieChartInput | DonutChartInput | LineChartInput | AreaChartInput | ScatterChartInput | DiagramBlockInput | TreeBlockInput | TimelineBlockInput | DrawingBlockInput | RawLayersInput;
1070
1089
  /**
1071
1090
  * A block can be either a raw Block or a TemplateBlock.
1072
1091
  */
@@ -2638,6 +2657,15 @@ interface TextStyle {
2638
2657
  padding?: number;
2639
2658
  /** Maximum number of lines before truncation (adds "..." to last line) */
2640
2659
  maxLines?: number;
2660
+ /**
2661
+ * Fit the rendered text to the layer's box: the renderer measures the
2662
+ * real glyphs and scales the text down (never up) to stay inside the box,
2663
+ * and — when vertically centered — centers the whole wrapped block on the
2664
+ * anchor instead of hanging extra lines below it. For display type sized
2665
+ * from estimates (e.g. the bigText template), where the actual font can
2666
+ * run wider than any pure width model.
2667
+ */
2668
+ shrinkToFit?: boolean;
2641
2669
  }
2642
2670
  /**
2643
2671
  * Animation to apply to a layer.
@@ -2770,4 +2798,4 @@ declare function getBlockAtTime(blocks: Block[], time: number): Block | null;
2770
2798
  */
2771
2799
  declare function getCaptionAtTime(captions: CaptionTrack | undefined, time: number): CaptionPhrase | null;
2772
2800
 
2773
- export { type ImageTreatment as $, type AccentImage as A, type BarChartInput as B, type CaptionPhrase as C, DARK_SURFACE as D, DEFAULT_TITLE_FONT as E, type DataTableInput as F, type DateEventInput as G, type DeepPartial as H, type DefinitionCardInput as I, type DiagramBlockInput as J, type DiagramEdgeAnchor as K, type DiagramTemplateEdge as L, type DiagramTemplateNode as M, type Doc as N, type DocBlock as O, type DocDiagnostic as P, type DonutChartInput as Q, type DrawingBlockInput as R, FRONTMATTER_CUSTOM_TEMPLATES_KEY as S, FRONTMATTER_CUSTOM_THEMES_KEY as T, type FactCardInput as U, type FontFamily as V, type FontFamilyKind as W, type FullBleedQuoteInput as X, type GradientBackgroundConfig as Y, type ImageBackgroundConfig as Z, type ImageLayer as _, type AccentPosition as a, type SectionHeaderInput as a$, type ImageWithCaptionInput as a0, LIGHT_SURFACE as a1, type Layer as a2, type LayerRepeat as a3, type LayoutHints as a4, type LeftFeatureInput as a5, type LineChartInput as a6, type LinearGradient as a7, type ListBlockInput as a8, type MapBlockInput as a9, type PageBackground as aA, type PageDesignFamily as aB, type PageEmphasis as aC, type PageHeadingTreatment as aD, type PageSectionKind as aE, type PageSectionOverride as aF, type PageTokens as aG, type PathLayer as aH, type PatternBackgroundConfig as aI, type PersistentLayer as aJ, type PersistentLayerConfig as aK, type PersistentLayerTemplate as aL, type PersistentLayerTemplateConfig as aM, type PersistentLayerTemplateType as aN, type PhotoGridInput as aO, type PieChartInput as aP, type PipStyle as aQ, type Position as aR, type ProgressIndicatorConfig as aS, type PromotedBodyAnnotation as aT, type PullQuoteInput as aU, type QuoteBlockInput as aV, type RawLayersInput as aW, type RenderStyle as aX, type RightFeatureInput as aY, type ScatterChartInput as aZ, type ScheduledClip as a_, type MapLayer as aa, type MapMarker as ab, type MapTileStyle as ac, type MarkerStyle as ad, type MediaClip as ae, type MediaScheduleOptions as af, type MermaidLayer as ag, PAGE_ACCENT_STRATEGIES as ah, PAGE_BACKGROUNDS as ai, PAGE_BACKGROUND_RHYTHMS as aj, PAGE_DESIGN_FAMILIES as ak, PAGE_DIVIDERS as al, PAGE_EMPHASES as am, PAGE_EYEBROWS as an, PAGE_HEADING_CASES as ao, PAGE_HEADING_SCALES as ap, PAGE_HEADING_UNDERLINES as aq, PAGE_HERO_STYLES as ar, PAGE_IMAGE_FRAMINGS as as, PAGE_NUMERAL_STYLES as at, PAGE_PATTERNS as au, PAGE_QUOTE_MARKS as av, PAGE_SECTION_KINDS as aw, PAGE_SECTION_SPACINGS as ax, PAGE_SHADOWS as ay, type PageAccentRotation as az, type AmbientGradientConfig as b, getTwoColumnPositions as b$, type ShapeFilter as b0, type ShapeLayer as b1, type ShapePattern as b2, type SolidBackgroundConfig as b3, type StartBlockConfig as b4, type StatHighlightInput as b5, type SurfaceScheme as b6, THEME_SCHEMA_VERSION as b7, type TableLayer as b8, type TableLayerStyle as b9, type TwoColumnInput as bA, VIEWPORT_PRESETS as bB, type VideoLayer as bC, type VideoPipPosition as bD, type VideoPipShape as bE, type VideoPipSize as bF, type VideoPlacement as bG, type VideoPresentation as bH, type VideoPullQuoteInput as bI, type VideoWithCaptionInput as bJ, type ViewportConfig as bK, type ViewportOrientation as bL, type ViewportPreset as bM, type VignetteConfig as bN, applySurface as bO, calculateDuration as bP, calculateFontScale as bQ, createTemplateContext as bR, createTheme as bS, createThemeRegistry as bT, getAspectRatioString as bU, getBlockAtTime as bV, getCaptionAtTime as bW, getDocPlaybackDuration as bX, getLayoutHints as bY, getSafeTextBounds as bZ, getSegmentAtTime as b_, type TemplateBlock as ba, type TemplateContext as bb, type TemplateFunction as bc, type TemplateRegistry as bd, type TextLayer as be, type TextStyle as bf, type Theme as bg, type ThemeColorPalette as bh, type ThemeColorScheme as bi, type ThemePageStyle as bj, type ThemeRegistry as bk, type ThemeSchemaVersion as bl, type ThemeSeedColors as bm, type ThemeStyle as bn, type ThemeTypography as bo, type TimelineBlockInput as bp, type TimelineTemplateEvent as bq, type TimelineTemplateLink as br, type TimelineTemplateTrack as bs, type TitleBlockInput as bt, type TitleCaptionConfig as bu, type TreeBlockInput as bv, type TreeLayer as bw, type TreeLayerItem as bx, type TreeLayerStyle as by, type TreeTemplateItem as bz, type Animation as c, getViewport as c0, getViewportOrientation as c1, isCustomTemplateDefinition as c2, isPersistentLayerTemplate as c3, isTemplateBlock as c4, scaledFontSize$1 as c5, resolveMediaSchedule as c6, scaledFontSize as c7, validateCustomTemplateDefinition as c8, type AnimationType as d, type AreaChartInput as e, type AudioBookmark as f, type AudioSegment as g, type AudioTimingData as h, type AudioTrack as i, type Block as j, type BlockConnection as k, type BorderStyle as l, type CaptionTrack as m, type CaptionWord as n, type ChartBaseFields as o, type ChartTemplateInput as p, type ColorScheme as q, type ColumnChartInput as r, type ComparisonBarInput as s, type ContentBlockInput as t, type CornerBrandingConfig as u, type CustomTemplateDefinition as v, type CustomTemplateLayer as w, type CustomTemplateValidationError as x, type CustomTemplateValidationResult as y, DEFAULT_DOC_FONT as z };
2801
+ export { type ImageLayer as $, type AccentImage as A, type BarChartInput as B, type CaptionPhrase as C, DARK_SURFACE as D, DEFAULT_DOC_FONT as E, DEFAULT_TITLE_FONT as F, type DataTableInput as G, type DateEventInput as H, type DeepPartial as I, type DefinitionCardInput as J, type DiagramBlockInput as K, type DiagramEdgeAnchor as L, type DiagramTemplateEdge as M, type DiagramTemplateNode as N, type Doc as O, type DocBlock as P, type DocDiagnostic as Q, type DonutChartInput as R, type DrawingBlockInput as S, FRONTMATTER_CUSTOM_TEMPLATES_KEY as T, FRONTMATTER_CUSTOM_THEMES_KEY as U, type FactCardInput as V, type FontFamily as W, type FontFamilyKind as X, type FullBleedQuoteInput as Y, type GradientBackgroundConfig as Z, type ImageBackgroundConfig as _, type AccentPosition as a, type ScheduledClip as a$, type ImageTreatment as a0, type ImageWithCaptionInput as a1, LIGHT_SURFACE as a2, type Layer as a3, type LayerRepeat as a4, type LayoutHints as a5, type LeftFeatureInput as a6, type LineChartInput as a7, type LinearGradient as a8, type ListBlockInput as a9, type PageAccentRotation as aA, type PageBackground as aB, type PageDesignFamily as aC, type PageEmphasis as aD, type PageHeadingTreatment as aE, type PageSectionKind as aF, type PageSectionOverride as aG, type PageTokens as aH, type PathLayer as aI, type PatternBackgroundConfig as aJ, type PersistentLayer as aK, type PersistentLayerConfig as aL, type PersistentLayerTemplate as aM, type PersistentLayerTemplateConfig as aN, type PersistentLayerTemplateType as aO, type PhotoGridInput as aP, type PieChartInput as aQ, type PipStyle as aR, type Position as aS, type ProgressIndicatorConfig as aT, type PromotedBodyAnnotation as aU, type PullQuoteInput as aV, type QuoteBlockInput as aW, type RawLayersInput as aX, type RenderStyle as aY, type RightFeatureInput as aZ, type ScatterChartInput as a_, type MapBlockInput as aa, type MapLayer as ab, type MapMarker as ac, type MapTileStyle as ad, type MarkerStyle as ae, type MediaClip as af, type MediaScheduleOptions as ag, type MermaidLayer as ah, PAGE_ACCENT_STRATEGIES as ai, PAGE_BACKGROUNDS as aj, PAGE_BACKGROUND_RHYTHMS as ak, PAGE_DESIGN_FAMILIES as al, PAGE_DIVIDERS as am, PAGE_EMPHASES as an, PAGE_EYEBROWS as ao, PAGE_HEADING_CASES as ap, PAGE_HEADING_SCALES as aq, PAGE_HEADING_UNDERLINES as ar, PAGE_HERO_STYLES as as, PAGE_IMAGE_FRAMINGS as at, PAGE_NUMERAL_STYLES as au, PAGE_PATTERNS as av, PAGE_QUOTE_MARKS as aw, PAGE_SECTION_KINDS as ax, PAGE_SECTION_SPACINGS as ay, PAGE_SHADOWS as az, type AmbientGradientConfig as b, getSegmentAtTime as b$, type SectionHeaderInput as b0, type ShapeFilter as b1, type ShapeLayer as b2, type ShapePattern as b3, type SolidBackgroundConfig as b4, type StartBlockConfig as b5, type StatHighlightInput as b6, type SurfaceScheme as b7, THEME_SCHEMA_VERSION as b8, type TableLayer as b9, type TreeTemplateItem as bA, type TwoColumnInput as bB, VIEWPORT_PRESETS as bC, type VideoLayer as bD, type VideoPipPosition as bE, type VideoPipShape as bF, type VideoPipSize as bG, type VideoPlacement as bH, type VideoPresentation as bI, type VideoPullQuoteInput as bJ, type VideoWithCaptionInput as bK, type ViewportConfig as bL, type ViewportOrientation as bM, type ViewportPreset as bN, type VignetteConfig as bO, applySurface as bP, calculateDuration as bQ, calculateFontScale as bR, createTemplateContext as bS, createTheme as bT, createThemeRegistry as bU, getAspectRatioString as bV, getBlockAtTime as bW, getCaptionAtTime as bX, getDocPlaybackDuration as bY, getLayoutHints as bZ, getSafeTextBounds as b_, type TableLayerStyle as ba, type TemplateBlock as bb, type TemplateContext as bc, type TemplateFunction as bd, type TemplateRegistry as be, type TextLayer as bf, type TextStyle as bg, type Theme as bh, type ThemeColorPalette as bi, type ThemeColorScheme as bj, type ThemePageStyle as bk, type ThemeRegistry as bl, type ThemeSchemaVersion as bm, type ThemeSeedColors as bn, type ThemeStyle as bo, type ThemeTypography as bp, type TimelineBlockInput as bq, type TimelineTemplateEvent as br, type TimelineTemplateLink as bs, type TimelineTemplateTrack as bt, type TitleBlockInput as bu, type TitleCaptionConfig as bv, type TreeBlockInput as bw, type TreeLayer as bx, type TreeLayerItem as by, type TreeLayerStyle as bz, type Animation as c, getTwoColumnPositions as c0, getViewport as c1, getViewportOrientation as c2, isCustomTemplateDefinition as c3, isPersistentLayerTemplate as c4, isTemplateBlock as c5, scaledFontSize$1 as c6, resolveMediaSchedule as c7, scaledFontSize as c8, validateCustomTemplateDefinition as c9, type AnimationType as d, type AreaChartInput as e, type AudioBookmark as f, type AudioSegment as g, type AudioTimingData as h, type AudioTrack as i, type BigTextInput as j, type Block as k, type BlockConnection as l, type BorderStyle as m, type CaptionTrack as n, type CaptionWord as o, type ChartBaseFields as p, type ChartTemplateInput as q, type ColorScheme as r, type ColumnChartInput as s, type ComparisonBarInput as t, type ContentBlockInput as u, type CornerBrandingConfig as v, type CustomTemplateDefinition as w, type CustomTemplateLayer as x, type CustomTemplateValidationError as y, type CustomTemplateValidationResult as z };
@@ -1,4 +1,4 @@
1
- import { _ as ImageLayer, be as TextLayer, b1 as ShapeLayer, aH as PathLayer } from './Doc-BKKcPjfe.js';
1
+ import { $ as ImageLayer, bf as TextLayer, b2 as ShapeLayer, aI as PathLayer } from './Doc-DBadkoP4.js';
2
2
 
3
3
  /**
4
4
  * ImageEditDoc — JSON document describing a layered, editable image.
@@ -6,7 +6,7 @@ import {
6
6
  quoteAttrValue,
7
7
  splitKeyValueToken,
8
8
  tokenizeAttrTokens
9
- } from "./chunk-D6YTLQCL.js";
9
+ } from "./chunk-NBKAXPSX.js";
10
10
  import {
11
11
  resolveIcon
12
12
  } from "./chunk-7N4G32LG.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  expectedSyllablesAt,
3
3
  wordPosAtExpectedSyllables
4
- } from "./chunk-2HY2ZA7U.js";
4
+ } from "./chunk-GSJEGMKF.js";
5
5
 
6
6
  // src/narration/types.ts
7
7
  var DEFAULT_FEATURE_CONFIG = Object.freeze({
@@ -56,7 +56,7 @@ import {
56
56
  sanitizeUrl,
57
57
  splitKeyValueToken,
58
58
  tokenizeAttrTokens
59
- } from "./chunk-D6YTLQCL.js";
59
+ } from "./chunk-NBKAXPSX.js";
60
60
  import {
61
61
  extractPlainText,
62
62
  readFrontmatterThemeId,
@@ -934,6 +934,138 @@ function sectionHeader(input, context) {
934
934
  return layers;
935
935
  }
936
936
 
937
+ // src/doc/templates/bigText.ts
938
+ var BIG_TEXT_LINE_HEIGHT = 1.05;
939
+ var BIG_TEXT_WIDTH_FRACTION = 0.92;
940
+ var BIG_TEXT_HEIGHT_FRACTION = 0.86;
941
+ var BIG_TEXT_CHAR_WIDTH = 0.62;
942
+ var BIG_TEXT_MIN_PX = 48;
943
+ function wrappedLineCount(wordLengths, charsPerLine) {
944
+ let lines = 1;
945
+ let current = 0;
946
+ for (const length of wordLengths) {
947
+ const withWord = current === 0 ? length : current + 1 + length;
948
+ if (withWord <= charsPerLine) {
949
+ current = withWord;
950
+ } else {
951
+ lines += 1;
952
+ current = length;
953
+ }
954
+ }
955
+ return lines;
956
+ }
957
+ function fitBigTextSize(title, viewport) {
958
+ const availWidth = viewport.width * BIG_TEXT_WIDTH_FRACTION;
959
+ const availHeight = viewport.height * BIG_TEXT_HEIGHT_FRACTION;
960
+ const wordLengths = title.trim().split(/\s+/).filter(Boolean).map((word) => word.length);
961
+ if (wordLengths.length === 0) return Math.floor(availHeight / BIG_TEXT_LINE_HEIGHT);
962
+ const longestWord = Math.max(...wordLengths);
963
+ const widthCap = availWidth / (BIG_TEXT_CHAR_WIDTH * longestWord);
964
+ const singleLineCap = availHeight / BIG_TEXT_LINE_HEIGHT;
965
+ const fits = (size) => {
966
+ const charsPerLine = Math.max(1, Math.floor(availWidth / (size * BIG_TEXT_CHAR_WIDTH)));
967
+ const lines = wrappedLineCount(wordLengths, charsPerLine);
968
+ return lines * size * BIG_TEXT_LINE_HEIGHT <= availHeight;
969
+ };
970
+ let low = BIG_TEXT_MIN_PX;
971
+ let high = Math.min(widthCap, singleLineCap);
972
+ if (high <= low) return Math.max(BIG_TEXT_MIN_PX, Math.floor(high));
973
+ if (!fits(low)) return BIG_TEXT_MIN_PX;
974
+ for (let i = 0; i < 24; i += 1) {
975
+ const mid = (low + high) / 2;
976
+ if (fits(mid)) low = mid;
977
+ else high = mid;
978
+ }
979
+ return Math.floor(low);
980
+ }
981
+ function bigTextBloom(background) {
982
+ return `radial-gradient(ellipse at 50% 50%, ${withAlpha(background, 0.92)} 0%, ${withAlpha(background, 0.78)} 38%, ${withAlpha(background, 0.32)} 66%, ${withAlpha(background, 0)} 88%)`;
983
+ }
984
+ function bigText(input, context) {
985
+ const { title = "", imageSrc, imageAlt, ambientMotion } = input;
986
+ const { theme, viewport } = context;
987
+ const treatment = themedImageTreatment(context, input.imageTreatment);
988
+ const displayTitle = title.toUpperCase();
989
+ const titleFontSize = fitBigTextSize(displayTitle, viewport);
990
+ const layers = [];
991
+ if (imageSrc) {
992
+ layers.push(
993
+ {
994
+ type: "image",
995
+ id: "bigtext-image",
996
+ content: {
997
+ src: imageSrc,
998
+ alt: imageAlt || title,
999
+ fit: "cover",
1000
+ ...treatment ? { treatment } : {}
1001
+ },
1002
+ position: { x: 0, y: 0, width: "100%", height: "100%" },
1003
+ animation: mapAmbientMotion(ambientMotion, 8) ?? {
1004
+ type: "slowZoom",
1005
+ duration: 8,
1006
+ direction: "in"
1007
+ }
1008
+ },
1009
+ {
1010
+ type: "shape",
1011
+ id: "bigtext-bloom",
1012
+ content: {
1013
+ shape: "rect",
1014
+ fill: bigTextBloom(theme.colors.background)
1015
+ },
1016
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
1017
+ }
1018
+ );
1019
+ } else {
1020
+ layers.push(
1021
+ {
1022
+ type: "shape",
1023
+ id: "bigtext-bg",
1024
+ content: {
1025
+ shape: "rect",
1026
+ fill: theme.colors.background
1027
+ },
1028
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
1029
+ },
1030
+ {
1031
+ type: "shape",
1032
+ id: "bigtext-tint",
1033
+ content: {
1034
+ shape: "rect",
1035
+ fill: `radial-gradient(ellipse at 50% 50%, ${withAlpha(theme.colors.primary, 0.38)} 0%, ${withAlpha(theme.colors.primary, 0.14)} 48%, ${withAlpha(theme.colors.primary, 0)} 78%)`
1036
+ },
1037
+ position: { x: 0, y: 0, width: "100%", height: "100%" }
1038
+ }
1039
+ );
1040
+ }
1041
+ layers.push({
1042
+ type: "text",
1043
+ id: "bigtext-title",
1044
+ content: {
1045
+ text: displayTitle,
1046
+ style: {
1047
+ fontSize: titleFontSize,
1048
+ fontFamily: getThemeFont(context, "title"),
1049
+ fontWeight: "bold",
1050
+ color: theme.colors.text,
1051
+ textAlign: "center",
1052
+ lineHeight: BIG_TEXT_LINE_HEIGHT,
1053
+ shadow: !!imageSrc,
1054
+ shrinkToFit: true
1055
+ }
1056
+ },
1057
+ position: {
1058
+ x: "50%",
1059
+ y: "50%",
1060
+ anchor: "center",
1061
+ width: `${BIG_TEXT_WIDTH_FRACTION * 100}%`,
1062
+ height: `${BIG_TEXT_HEIGHT_FRACTION * 100}%`
1063
+ },
1064
+ animation: themedEntrance(context, "text", { type: "fadeIn", duration: 1 })
1065
+ });
1066
+ return layers;
1067
+ }
1068
+
937
1069
  // src/doc/templates/contentBlock.ts
938
1070
  function completeBodyText(context) {
939
1071
  return (context.block?.contents ?? []).filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((node) => extractPlainText(node)).join("\n").trim();
@@ -2414,7 +2546,7 @@ function renderInlineHtml(nodes) {
2414
2546
  case "mention":
2415
2547
  return escapeInlineHtml(`@${node.displayName}`);
2416
2548
  case "inlineIcon":
2417
- return escapeInlineHtml(`{[${node.token}]}`);
2549
+ return `<i class="fa-${node.family} fa-${node.name}" aria-hidden="true"></i>`;
2418
2550
  case "htmlInline":
2419
2551
  return escapeInlineHtml(node.rawHtml);
2420
2552
  }
@@ -2525,10 +2657,16 @@ function extractEmbeddedVideos(contents, limit = Infinity) {
2525
2657
  const attrs = n.attributes;
2526
2658
  const src = attrs?.src || nestedSource(n.children);
2527
2659
  if (src) {
2660
+ const startAt = attrs?.["data-squisq-video-start-at"] ? parseTimeSeconds(attrs["data-squisq-video-start-at"]) : null;
2661
+ const clipStart = attrs?.["data-squisq-video-clip-start"] ? parseTimeSeconds(attrs["data-squisq-video-clip-start"]) : null;
2662
+ const clipEnd = attrs?.["data-squisq-video-clip-end"] ? parseTimeSeconds(attrs["data-squisq-video-clip-end"]) : null;
2528
2663
  add({
2529
2664
  src,
2530
2665
  ...attrs?.poster ? { posterSrc: attrs.poster } : {},
2531
- alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || ""
2666
+ alt: attrs?.["aria-label"] || attrs?.title || attrs?.alt || "",
2667
+ ...startAt != null ? { startAt } : {},
2668
+ ...clipStart != null ? { clipStart } : {},
2669
+ ...clipEnd != null ? { clipEnd } : {}
2532
2670
  });
2533
2671
  }
2534
2672
  } else if ((n.type === "link" || n.type === "image") && typeof n.url === "string" && VIDEO_FILE_RE.test(n.url)) {
@@ -2757,6 +2895,13 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
2757
2895
  if (!img) return placeholders ? { caption: headingText } : null;
2758
2896
  return { imageSrc: img.src, imageAlt: img.alt || headingText, caption: headingText };
2759
2897
  }
2898
+ case "bigText": {
2899
+ const img = extractFirstImage(contents);
2900
+ return {
2901
+ title: headingText,
2902
+ ...img ? { imageSrc: img.src, imageAlt: img.alt || headingText } : {}
2903
+ };
2904
+ }
2760
2905
  case "photoGrid": {
2761
2906
  const images = extractImages(contents, 4);
2762
2907
  if (images.length < 2) return placeholders ? {} : null;
@@ -3767,9 +3912,9 @@ function fitDiagramLabel(text, boxWidth, boxHeight, preferredFontSize) {
3767
3912
  };
3768
3913
  }
3769
3914
  function countWrappedLines(text, fontSize, maxWidth) {
3770
- return text.split("\n").reduce((count, line) => count + wrappedLineCount(line, fontSize, maxWidth), 0);
3915
+ return text.split("\n").reduce((count, line) => count + wrappedLineCount2(line, fontSize, maxWidth), 0);
3771
3916
  }
3772
- function wrappedLineCount(text, fontSize, maxWidth) {
3917
+ function wrappedLineCount2(text, fontSize, maxWidth) {
3773
3918
  if (!text.trim()) return 1;
3774
3919
  const charsPerLine = Math.floor(maxWidth / (fontSize * 0.5));
3775
3920
  if (charsPerLine <= 0) return 1;
@@ -6615,6 +6760,7 @@ function scatterChart(input, context) {
6615
6760
  var templateRegistry = {
6616
6761
  title: titleBlock,
6617
6762
  sectionHeader,
6763
+ bigText,
6618
6764
  content: contentBlock,
6619
6765
  statHighlight,
6620
6766
  quote: quoteBlock,
@@ -6829,6 +6975,12 @@ var TEMPLATE_INPUT_DESCRIPTORS = {
6829
6975
  { key: "imageAlt", description: "Alt text for the background image" },
6830
6976
  { key: "ambientMotion", description: "Slow zoom/pan effect", values: AMBIENT_MOTION_VALUES }
6831
6977
  ],
6978
+ bigText: [
6979
+ { key: "title", description: "The display text (rendered uppercase)" },
6980
+ { key: "imageSrc", description: "Optional full-bleed background image path" },
6981
+ { key: "imageAlt", description: "Alt text for the background image" },
6982
+ { key: "ambientMotion", description: "Slow zoom/pan effect", values: AMBIENT_MOTION_VALUES }
6983
+ ],
6832
6984
  content: [{ key: "title", description: "Heading shown above the complete body" }],
6833
6985
  statHighlight: [
6834
6986
  { key: "stat", description: 'The statistic (e.g. "89%", "2x")' },
@@ -7245,6 +7397,15 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
7245
7397
  unconsumedMedia: "reserve-when-no-native-media",
7246
7398
  variants: TITLE_VARIANTS
7247
7399
  },
7400
+ bigText: {
7401
+ summary: "Uses its authored background image (behind a contrast bloom) when present; otherwise renders gigantic text on the theme surface.",
7402
+ noMedia: "template-without-optional-media",
7403
+ ownership: "optional-native",
7404
+ nativeLayout: "background-image",
7405
+ additionalMediaLayout: "overlay-inset",
7406
+ unconsumedMedia: "reserve-when-no-native-media",
7407
+ variants: TITLE_VARIANTS
7408
+ },
7248
7409
  content: {
7249
7410
  summary: "Preserves the complete heading and body; supplemental media receives a companion field.",
7250
7411
  noMedia: "template-default",
@@ -8125,7 +8286,10 @@ function collectRichMediaItems(layers, block) {
8125
8286
  src: video.src,
8126
8287
  ...video.posterSrc ? { posterSrc: video.posterSrc } : {},
8127
8288
  alt: video.alt,
8128
- aspectRatio: 16 / 9
8289
+ aspectRatio: 16 / 9,
8290
+ ...video.startAt != null ? { startAt: video.startAt } : {},
8291
+ ...video.clipStart != null ? { clipStart: video.clipStart } : {},
8292
+ ...video.clipEnd != null ? { clipEnd: video.clipEnd } : {}
8129
8293
  })
8130
8294
  ),
8131
8295
  ...sources.map((source) => ({ kind: "mermaid", source, aspectRatio: 16 / 9 })),
@@ -8181,6 +8345,7 @@ function richMediaLayers(item, position, block, theme, kindIndex) {
8181
8345
  ];
8182
8346
  }
8183
8347
  if (item.kind === "video") {
8348
+ const clipStart = Math.max(0, item.clipStart ?? 0);
8184
8349
  return [
8185
8350
  {
8186
8351
  id: `${block.id}-embedded-video-${kindIndex}`,
@@ -8191,8 +8356,9 @@ function richMediaLayers(item, position, block, theme, kindIndex) {
8191
8356
  ...item.posterSrc ? { posterSrc: item.posterSrc } : {},
8192
8357
  alt: item.alt || block.title || "Embedded video",
8193
8358
  fit: "contain",
8194
- clipStart: 0,
8195
- clipEnd: Math.max(0, block.duration)
8359
+ clipStart,
8360
+ clipEnd: Math.max(clipStart, item.clipEnd ?? clipStart + Math.max(0, block.duration)),
8361
+ ...item.startAt != null ? { startAt: Math.max(0, item.startAt) } : {}
8196
8362
  }
8197
8363
  }
8198
8364
  ];
@@ -8233,11 +8399,36 @@ function richMediaLayers(item, position, block, theme, kindIndex) {
8233
8399
  `${block.id}-embedded-${item.template}-${kindIndex}`
8234
8400
  );
8235
8401
  }
8402
+ function applyEmbeddedVideoTiming(layers, block) {
8403
+ const timedBySrc = new Map(
8404
+ extractEmbeddedVideos(block.contents).filter((video) => video.startAt != null || video.clipStart != null || video.clipEnd != null).map((video) => [video.src, video])
8405
+ );
8406
+ if (timedBySrc.size === 0) return layers;
8407
+ return layers.map((layer) => {
8408
+ if (layer.type !== "video") return layer;
8409
+ const video = timedBySrc.get(layer.content.src);
8410
+ if (!video) return layer;
8411
+ const clipStart = Math.max(0, video.clipStart ?? layer.content.clipStart);
8412
+ return {
8413
+ ...layer,
8414
+ content: {
8415
+ ...layer.content,
8416
+ clipStart,
8417
+ clipEnd: Math.max(
8418
+ clipStart,
8419
+ video.clipEnd ?? (video.clipStart != null ? clipStart + Math.max(0, block.duration) : layer.content.clipEnd)
8420
+ ),
8421
+ ...video.startAt != null ? { startAt: Math.max(0, video.startAt) } : {}
8422
+ }
8423
+ };
8424
+ });
8425
+ }
8236
8426
  function appendRichContentLayers(layers, block, theme, viewport) {
8237
- const items = collectRichMediaItems(layers, block);
8238
- if (items.length === 0) return layers;
8427
+ const timedLayers = applyEmbeddedVideoTiming(layers, block);
8428
+ const items = collectRichMediaItems(timedLayers, block);
8429
+ if (items.length === 0) return timedLayers;
8239
8430
  const layout = resolveSupplementalMediaLayout(
8240
- layers,
8431
+ timedLayers,
8241
8432
  block.template ? resolveTemplateName(block.template) : void 0,
8242
8433
  viewport,
8243
8434
  items.length,
@@ -8356,6 +8547,10 @@ var TEMPLATE_METADATA = {
8356
8547
  label: "Section Header",
8357
8548
  description: "A clean section break with a prominent title and optional background image."
8358
8549
  },
8550
+ bigText: {
8551
+ label: "Big Text",
8552
+ description: "Gigantic uppercase display text \u2014 thumbnail-style \u2014 on a theme surface or over an image with a contrast bloom."
8553
+ },
8359
8554
  content: {
8360
8555
  label: "Content",
8361
8556
  description: "Shows a heading and the complete body in a loss-averse content-first layout."
@@ -8496,6 +8691,12 @@ var TEMPLATE_AUTHORING_METADATA = {
8496
8691
  safeForContentFirst: false,
8497
8692
  placement: "heading"
8498
8693
  },
8694
+ bigText: {
8695
+ role: "opener",
8696
+ bodyPolicy: "ignored",
8697
+ safeForContentFirst: false,
8698
+ placement: "heading"
8699
+ },
8499
8700
  content: {
8500
8701
  role: "content",
8501
8702
  bodyPolicy: "complete",
@@ -8847,8 +9048,81 @@ function startBlockToCoverInput(config) {
8847
9048
  heroLicense: config.heroLicense
8848
9049
  };
8849
9050
  }
8850
- function expandCoverBlock(config, context) {
9051
+ function expandCoverBlock(config, context, template = "cover") {
8851
9052
  const input = startBlockToCoverInput(config);
9053
+ const base = {
9054
+ id: "cover-block",
9055
+ duration: 0,
9056
+ audioSegment: -1
9057
+ };
9058
+ if (template === "title") {
9059
+ return titleBlock(
9060
+ {
9061
+ ...base,
9062
+ template: "title",
9063
+ title: input.title,
9064
+ subtitle: input.subtitle
9065
+ },
9066
+ context
9067
+ );
9068
+ }
9069
+ if (template === "sectionHeader") {
9070
+ return sectionHeader(
9071
+ {
9072
+ ...base,
9073
+ template: "sectionHeader",
9074
+ title: input.title,
9075
+ imageSrc: input.heroSrc,
9076
+ imageAlt: input.heroAlt,
9077
+ ambientMotion: input.ambientMotion
9078
+ },
9079
+ context
9080
+ );
9081
+ }
9082
+ if (template === "bigText" || template === "bigTextImage") {
9083
+ const useHero = template === "bigTextImage" && !!input.heroSrc;
9084
+ return bigText(
9085
+ {
9086
+ ...base,
9087
+ template: "bigText",
9088
+ title: input.title,
9089
+ ...useHero ? {
9090
+ imageSrc: input.heroSrc,
9091
+ imageAlt: input.heroAlt ?? input.title,
9092
+ ambientMotion: input.ambientMotion
9093
+ } : {}
9094
+ },
9095
+ context
9096
+ );
9097
+ }
9098
+ if (template === "imageWithCaption") {
9099
+ if (!input.heroSrc) {
9100
+ return titleBlock(
9101
+ {
9102
+ ...base,
9103
+ template: "title",
9104
+ title: input.title,
9105
+ subtitle: input.subtitle
9106
+ },
9107
+ context
9108
+ );
9109
+ }
9110
+ return imageWithCaption(
9111
+ {
9112
+ ...base,
9113
+ template: "imageWithCaption",
9114
+ imageSrc: input.heroSrc,
9115
+ imageAlt: input.heroAlt ?? input.title,
9116
+ caption: input.title,
9117
+ subtitle: input.subtitle,
9118
+ ambientMotion: input.ambientMotion,
9119
+ imageCredit: input.heroCredit,
9120
+ imageLicense: input.heroLicense,
9121
+ isTitle: true
9122
+ },
9123
+ context
9124
+ );
9125
+ }
8852
9126
  return coverBlock(input, context);
8853
9127
  }
8854
9128
 
@@ -10105,7 +10379,7 @@ function markdownToDoc(markdownDoc, options) {
10105
10379
  ...documentMedia.length > 0 ? { documentMedia } : {}
10106
10380
  };
10107
10381
  if (options?.generateCoverBlock ?? true) {
10108
- const coverConfig = buildStartBlock(markdownDoc, rootBlocks);
10382
+ const coverConfig = buildStartBlock(markdownDoc, rootBlocks, options?.fileName);
10109
10383
  if (coverConfig) {
10110
10384
  doc.startBlock = coverConfig;
10111
10385
  }
@@ -10355,12 +10629,35 @@ function findFirstImage(node) {
10355
10629
  }
10356
10630
  return void 0;
10357
10631
  }
10358
- function buildStartBlock(markdownDoc, rootBlocks) {
10359
- const firstH1 = rootBlocks.find((b) => b.sourceHeading?.depth === 1);
10360
- if (!firstH1) return void 0;
10361
- const title = firstH1.title ?? extractPlainText(firstH1.sourceHeading);
10632
+ function shallowestHeadingBlock(rootBlocks) {
10633
+ let best;
10634
+ for (const block of rootBlocks) {
10635
+ const depth = block.sourceHeading?.depth;
10636
+ if (depth === void 0) continue;
10637
+ const title = block.title ?? extractPlainText(block.sourceHeading);
10638
+ if (!title) continue;
10639
+ if (!best || depth < best.sourceHeading.depth) {
10640
+ best = block;
10641
+ if (depth === 1) break;
10642
+ }
10643
+ }
10644
+ return best;
10645
+ }
10646
+ function fileNameCoverTitle(fileName) {
10647
+ if (!fileName) return void 0;
10648
+ const base = fileName.split(/[\\/]/).pop() ?? "";
10649
+ const stem = base.replace(/\.[^.]+$/, "").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
10650
+ if (!stem) return void 0;
10651
+ return stem.charAt(0).toUpperCase() + stem.slice(1);
10652
+ }
10653
+ function buildStartBlock(markdownDoc, rootBlocks, fileName) {
10654
+ const frontmatterTitle = typeof markdownDoc.frontmatter?.title === "string" ? markdownDoc.frontmatter.title.trim() : void 0;
10655
+ const headingBlock = shallowestHeadingBlock(rootBlocks);
10656
+ const headingTitle = headingBlock ? headingBlock.title ?? extractPlainText(headingBlock.sourceHeading) : void 0;
10657
+ const title = frontmatterTitle || headingTitle || fileNameCoverTitle(fileName);
10362
10658
  if (!title) return void 0;
10363
- const subtitle = firstH1.contents?.[0]?.type === "paragraph" ? extractPlainText(firstH1.contents[0]) : void 0;
10659
+ const subtitleBlock = headingBlock ?? rootBlocks[0];
10660
+ const subtitle = subtitleBlock?.contents?.[0]?.type === "paragraph" ? extractPlainText(subtitleBlock.contents[0]) : void 0;
10364
10661
  const firstImage = findFirstImage(markdownDoc);
10365
10662
  const config = {
10366
10663
  title,
@@ -10708,6 +11005,8 @@ export {
10708
11005
  createAccentLayers,
10709
11006
  adjustY,
10710
11007
  sectionHeader,
11008
+ fitBigTextSize,
11009
+ bigText,
10711
11010
  contentBlock,
10712
11011
  statHighlight,
10713
11012
  quoteBlock,
@@ -2,7 +2,7 @@ import {
2
2
  assertMarkdownDocumentWithinLimits,
3
3
  parseMarkdown,
4
4
  toMdast
5
- } from "./chunk-7TJJA2RI.js";
5
+ } from "./chunk-6MVWWZHL.js";
6
6
  import {
7
7
  formatFrontmatterYaml,
8
8
  getChildren,