Examples

Article Styling

Use style, hoverStyle and activeStyle to craft distinct visuals.

Style, Hover and Active Style Modes

Style any article via timeline-level defaults or per-article options. Hover and active styles inherit from the normal style, letting you override only what is needed. See the Article Style cascade for how layout, timeline, lane, and per-article values combine.

💡

Tip

Remove an override with Histropedia.UNSET to restore its applicable default or inherited value:

  • article.setActiveStyle('border.width', Histropedia.UNSET)
  • myTimeline.setOption('article.defaultHoverStyle.color', Histropedia.UNSET)

This restores inheritance; it does not turn off built-in hover or active effects. See Clearing Overrides.

/* Select an article in the timeline to see its style here */

For the six built-in presets, automatic sizing, layout inheritance, and connector controls, try Card Presets and Sizing.

Built-in and custom card layouts

Toggle between the built-in landscape and portrait layouts and a registered compact layout. The custom renderer keeps its measured size, clickable rectangle, and star interaction aligned through getWidth(), getHeight(), and getIconBox().

The same Article Style cascade applies to every layout. See the Custom Card Layouts guide for registration timing, drawing, responsive selection, aliases, caching, TypeScript, and troubleshooting.

function drawCustomLayoutStar(ctx, box, isStarred) {
  const centreX = box.left + box.width / 2;
  const centreY = box.top + box.height / 2;

  ctx.save();
  ctx.fillStyle = isStarred ? "#f59e0b" : "#2563eb";
  ctx.font = "18px sans-serif";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(isStarred ? "★" : "☆", centreX, centreY);
  ctx.restore();
}

Histropedia.Timeline.registerCardLayout({
  name: "compact",
  getWidth() {
    const style = this.getCurrentStyle();
    const key = `compact-width:${this.title}:${style.header.text.font}:${style.width}`;
    return this.memo(key, () => {
      const measureContext = this.owner.canvasContext;
      measureContext.save();
      measureContext.font = style.header.text.font;
      const measuredWidth = measureContext.measureText(this.title).width + 54;
      measureContext.restore();
      return Math.min(280, Math.max(style.width, measuredWidth));
    });
  },
  getHeight() {
    const style = this.getCurrentStyle();
    return this.subtitle ? Math.max(style.height, 60) : style.height;
  },
  getIconBox() {
    if (this.owner.options.article.star.visible === false) return false;
    const width = 18;
    const height = 18;
    const left = this.position.left + this.getWidth() - width - 8;
    const top = this.position.top + 8;
    return { left, top, width, height };
  },
  draw(ctx) {
    const style = this.getCurrentStyle();
    const left = this.position.left;
    const top = this.position.top;
    const width = this.getWidth();
    const height = this.getHeight();
    ctx.save();
    ctx.fillStyle = style.backgroundColor;
    ctx.fillRect(left, top, width, height);
    ctx.strokeStyle = style.border.color;
    const borderWidth = style.border.width;
    ctx.lineWidth = borderWidth;
    ctx.strokeRect(
      left + borderWidth / 2,
      top + borderWidth / 2,
      width - borderWidth,
      height - borderWidth
    );
    ctx.fillStyle = style.header.text.color;
    ctx.font = style.header.text.font;
    ctx.textAlign = "left";
    ctx.textBaseline = "top";
    ctx.fillText(this.title, left + 10, top + 9, width - 46);
    if (this.subtitle) {
      ctx.font = "12px sans-serif";
      ctx.fillText(this.subtitle, left + 10, top + 34, width - 20);
    }
    ctx.restore();

    const iconBox = this.getIconBox();
    if (iconBox && (this.isActive || this.isStarred)) {
      drawCustomLayoutStar(ctx, iconBox, this.isStarred);
    }
  },
  defaultStyle: {
    width: 190,
    height: 48,
    backgroundColor: "#eff6ff",
    border: { color: "#2563eb", width: 1 },
    header: { text: { color: "#1e3a8a", font: "600 14px sans-serif" } }
  },
  defaultHoverStyle: { backgroundColor: "#dbeafe" },
  defaultActiveStyle: { backgroundColor: "#bfdbfe", border: { width: 2 } }
});