{{!
Computes content height based on line count in interactions.
Expected hash inputs: `interactions`, `const`, `line_height`.
}}
{{~#*inline "compute_content_height"}}
{{#scope lines=0 margins=0 displayed_interactions=0}}
{{#each interactions}}
{{#if (not input.hidden)}}
{{set @../lines (add @../lines (count_lines input.text))}}
{{set @../margins (add @../margins 1)}}
{{set @../displayed_interactions (add @../displayed_interactions 1)}}
{{/if}}
{{set @../lines (add @../lines (len output))}}
{{#if (ne 0 (len output))}}
{{set @../margins (add @../margins 1)}}
{{/if}}
{{/each}}
{{#if (gt @margins 0)}}
{{! The last margin is not displayed. }}
{{set @margins (sub @margins 1)}}
{{/if}}
{{#debug lines=@lines margins=@margins displayed_interactions=@displayed_interactions~}}
computed params for content height
{{~/debug}}
{{add (mul @lines line_height)
(mul @margins const.BLOCK_MARGIN)
(mul @displayed_interactions (mul 2 const.USER_INPUT_PADDING)) }}
{{/scope}}
{{/inline~}}
{{! Common styles header, e.g. `@font-face`s }}
{{~#*inline "common_styles_header"}}
{{~#if additional_styles}}
{{{additional_styles}}}
{{~/if~}}
{{~#with embedded_font~}}
{{~#each faces}}
@font-face {
font-family: "{{../family_name}}";
src: url("data:{{mime_type}};base64,{{{base64_data}}}");
{{#if (ne is_bold null)}}
font-weight: {{#if is_bold}}bold{{else}}normal{{/if}};
{{/if}}
{{#if (ne is_italic null)}}
font-style: {{#if is_italic}}italic{{else}}normal{{/if}};
{{/if}}
}
{{/each~}}
{{~else~}}
{{~/with~}}
{{/inline~}}
{{! Common font styling }}
{{~#*inline "common_font_styles"}}
.bold,.prompt { font-weight: bold;{{#with embedded_font.metrics.bold_spacing as |sp|}} letter-spacing: {{round sp digits=4}}em;{{else}}{{/with}} }
.italic { font-style: italic;{{#with embedded_font.metrics.italic_spacing as |sp|}} letter-spacing: {{round sp digits=4}}em;{{else}}{{/with}} }
{{/inline~}}
{{! Terminal background }}
{{~#*inline "background"}}
<rect width="100%" height="100%" y="{{#if window}}-{{const.WINDOW_FRAME_HEIGHT}}{{else}}0{{/if}}" rx="4.5" style="fill: {{ palette.colors.black }};"/>
{{~#if window}}
<rect width="100%" height="26" y="-22" clip-path="inset(0 0 -10 0 round 4.5)" style="fill: #fff; fill-opacity: 0.1;"/>
<circle cx="17" cy="-9" r="7" style="fill: {{ palette.colors.red }};"/>
<circle cx="37" cy="-9" r="7" style="fill: {{ palette.colors.yellow }};"/>
<circle cx="57" cy="-9" r="7" style="fill: {{ palette.colors.green }};"/>
{{#if window.title~}}
<text x="50%" y="-3.5" style="font: bold 14px system-ui, sans-serif; text-anchor: middle; fill: {{ palette.colors.white }};">{{window.title}}</text>
{{~/if~}}
{{~/if}}
{{/inline~}}
{{!
Computes scroll animation parameters.
Expected hash inputs: `content_height`, `const`, `scroll`, `width`
}}
{{~#*inline "compute_scroll_animation"}}
{{#if (gte scroll.max_height content_height)}}
{{! No need for scroll animation }}
null
{{else}}
{{#scope
steps=(round (div (sub content_height scroll.max_height) scroll.pixels_per_scroll) mode="up")
scroll_height=(sub content_height scroll.max_height)
scrollbar_height=(max
(min scroll.min_scrollbar_height scroll.max_height)
(round (mul scroll.max_height (div scroll.max_height content_height)))
)
pos=0
view_box=""
scrollbar_y=""
sep=""
}}
{{#debug steps=@steps scrollbar_height=@scrollbar_height~}}computed scroll animation params{{/debug}}
{{#each (range 0 (add @steps 1))}}
{{set @../pos (min @../scroll_height (mul ../scroll.pixels_per_scroll @index))}}
{{! Skip the penultimate step if its position is too close to the last one }}
{{#if (or @last (gt (sub @../scroll_height @../pos) (mul ../scroll.pixels_per_scroll ../scroll.elision_threshold)))}}
{{#set @../sep}}{{#if @first}}""{{else}}";"{{/if}}{{/set}}
{{#set @../view_box append=true}}{{@../sep}}0 {{round @../pos digits=1}} {{../width}} {{../scroll.max_height}}{{/set}}
{{#set @../scrollbar_y append=true}}{{@../sep}}{{round
(add
(mul (div @../pos @../scroll_height) (sub ../scroll.max_height @../scrollbar_height))
../const.WINDOW_PADDING
)
digits=1
}}{{/set}}
{{else}}
{{#debug}}eliding last scroll keyframe{{/debug}}
{{set @../steps (sub @../steps 1)}}
{{/if}}
{{/each}}
{
"duration": {{mul scroll.interval @steps}},
"view_box": "{{@view_box}}",
"scrollbar_height": {{@scrollbar_height}},
"scrollbar_x": {{sub width (add const.SCROLLBAR_RIGHT_OFFSET 5)}}, {{! 5 is scrollbar width }}
"scrollbar_y": "{{@scrollbar_y}}"
}
{{/scope}}
{{/if}}
{{/inline~}}
{{! Scrollbar }}
{{~#*inline "scrollbar"}}
{{#with scroll_animation}}
<rect id="scrollbar" class="scrollbar" x="{{scrollbar_x}}" y="10" width="5" height="{{scrollbar_height}}" />
{{/with}}
{{/inline~}}
{{! Scrolling animations }}
{{~#*inline "scroll_animation"}}
{{#with scroll_animation}}
<animate id="scroll" href="#scroll-container" attributeName="viewBox" values="{{view_box}}" begin="0s" dur="{{duration}}s" repeatCount="indefinite" calcMode="discrete" />
<animate href="#scrollbar" attributeName="y" values="{{scrollbar_y}}" begin="scroll.begin" dur="{{duration}}s" repeatCount="indefinite" calcMode="discrete" />
{{/with}}
{{/inline~}}
{{! Renders an HTML span }}
{{~#*inline "html_span"}}
{{~#if (gt (len this) 1)~}}{{! Are there any style properties in `span`? }}
{{~#scope fg=fg bg=bg classes="" styles=""~}}
{{~#if inverted~}}
{{~set @fg bg~}}
{{~set @bg fg~}}
{{~/if~}}
{{~#set @classes append=true}}
{{~>common_span_classes this fg=@fg bg=@bg~}}
{{~#if (and dimmed (ne (typeof @bg) "null"))}} dimmed-bg{{/if~}}
{{~#if (eq (typeof @fg) "number")}} fg{{@fg}}{{/if~}}
{{~#if (eq (typeof @bg) "number")}} bg{{@bg}}{{/if~}}
{{/set~}}
{{~set @classes (trim @classes)~}}
{{~#set @styles append=true}}
{{~#if (eq (typeof @fg) "string")}} color: {{@fg}};{{/if~}}
{{~#if (eq (typeof @bg) "string")}} background: {{@bg}};{{/if~}}
{{/set~}}
{{~set @styles (trim @styles)~}}
<span{{#if @classes}} class="{{@classes}}"{{/if}}{{#if @styles}} style="{{@styles}}"{{/if}}>
{{~/scope~}}
{{~/if~}}
{{!
If dimmed or blinking, enclose text in an additional span to apply pseudo-opacity effects based on `currentColor` and/or
by having a semi-transparent background; see .dimmed / .blink CSS. Using `opacity` or filters doesn't work in Safari.
}}
{{#if (or dimmed blink)}}<span>{{/if}}{{text}}{{#if (or dimmed blink)}}</span>{{/if}}
{{~#if (gt (len this) 1)~}}
</span>
{{~/if~}}
{{~/inline~}}
{{! Swaps background and foreground colors. If any are not specified, use the default palette colors. }}
{{~#*inline "common_span_classes"}}
{{~#if bold}} bold{{/if~}}
{{~#if italic}} italic{{/if~}}
{{~#if underline}} underline{{/if~}}
{{~#if strikethrough}} strike{{/if~}}
{{~#if dimmed}} dimmed{{/if~}}
{{~#if blink}} blink{{/if~}}
{{~#if concealed}} concealed{{/if~}}
{{~#if inverted}} inv{{/if~}}
{{~#if (and inverted (eq (typeof fg) "null"))}} fg-none{{/if~}}
{{~#if (and inverted (eq (typeof bg) "null"))}} bg-none{{/if~}}
{{~/inline~}}
{{! Renders an SVG foreground <tspan> styling attributes (class and/or style) }}
{{~#*inline "svg_tspan_attrs"}}
{{~#scope fg=fg bg=bg classes="" styles=""}}
{{~#if (gt (len this) 1)~}}{{! Are there any style properties in `span`? }}
{{~#if inverted~}}
{{~set @fg bg~}}
{{~set @bg fg~}}
{{~/if~}}
{{~#set @classes append=true}}
{{~>common_span_classes this fg=@fg bg=@bg~}}
{{~#if (eq (typeof @fg) "number")}} fg{{@fg}}{{/if~}}
{{! Unlike with HTML <span>s, we add the bg class in any case to assist with parsing }}
{{~#if (ne (typeof @bg) "null")}} bg{{@bg}}{{/if~}}
{{/set~}}
{{~set @classes (trim @classes)~}}
{{~#set @styles append=true}}
{{~#if (eq (typeof @fg) "string")}}fill: {{@fg}};{{/if~}}
{{/set~}}
{{~/if~}}
{{#if @classes}} class="{{@classes}}"{{/if}}{{#if @styles}} style="{{@styles}}"{{/if}}
{{~/scope~}}
{{~/inline~}}
{{!
Scales font metrics from design units to ems to pixels.
Expected hash inputs: `metrics`, `font_size`
}}
{{~#*inline "scale_font_metrics"}}
{
"advance_width": {{mul font_size (div metrics.advance_width metrics.units_per_em)}},
"ascent": {{mul font_size (div metrics.ascent metrics.units_per_em)}},
"descent": {{mul font_size (div metrics.descent metrics.units_per_em)}}
}
{{/inline~}}
{{! Sets `line_height` based on font metrics (if supplied) or the user-provided value }}
{{~#*inline "compute_line_height"}}
{{~#if (eq line_height null) ~}}
{{#if embedded_font}}
{{~#scope metrics=null ~}}
{{~#set @metrics~}}
{{~>scale_font_metrics metrics=embedded_font.metrics font_size=const.FONT_SIZE ~}}
{{~/set~}}
{{~#debug metrics=@metrics}}using line height from font metrics{{/debug~}}
{{~round (sub (lookup @metrics "ascent") (lookup @metrics "descent")) digits=1~}}
{{~/scope~}}
{{~else~}}
{{~#debug}}using default line height{{/debug~}}
16.8 {{! Set to a somewhat reasonable default (1.2 * font_size) }}
{{~/if~}}
{{~else~}}
{{~#debug line_height=line_height}}using user-provided line height{{/debug~}}
{{! Scale line height according to the font size }}
{{~round (mul line_height const.FONT_SIZE) digits=1~}}
{{~/if~}}
{{/inline~}}
{{!
Include main rendering logic. We need this roundabout construction in order for common inlines to be available
to the format-specific rendering logic.
}}
{{~>main~}}