Views and panels
A view is a file in views/. It has an id, and it contains panels.
id: holopanels:warps
panels:
title:
type: text
offset: { right: -2.4, up: 1.3, forward: 0.0 }
style:
background-opacity: 0.0
lines:
- "<gradient:#5ee7ff:#7a5cff><bold>WARPS</bold></gradient>"
The id is namespaced and global. Any board can point at it, and any open-view action can navigate to it.
Panel types
text
Fixed lines. Every line is MiniMessage and supports PlaceholderAPI, plus the <entry:…>, <state:…> and <player:…> tokens.
detail:
type: text
offset: { right: 2.4, up: 0.0, forward: 0.0 }
selection: list
visible-if:
selected: list
lines:
- "<white><bold><entry:label>"
- "<gray><entry:blurb>"
Lines can also come from a content-provider registered by another plugin, in which case lines is the fallback.
list
Rows with pagination and a per-viewer selection.
list:
type: list
offset: { right: -2.4, up: 0.0, forward: 0.0 }
page-size: 5
header:
- "<gray>Left click to preview"
- ""
row: "<gray> <entry:label>"
selected-row: "<white>▸ <entry:label>"
empty: "<gray>No warps are configured."
entries:
spawn:
label: "Spawn"
fields:
blurb: "Where everyone starts."
attributes:
donor: "false"
clicks:
left:
- type: select
An entry has a label, any fields you like (resolved by <entry:name>), and attributes, which are plain strings that entry-attribute conditions compare against. Entries can instead come from source.provider:
list:
type: list
source:
provider: yourplugin:entries
page-size: 10
row: "<entry:label>"
empty: "<gray>Loading..."
A list cannot define both static entries and a provider.
buttons
A strip of individually clickable, individually conditional controls.
pager:
type: buttons
offset: { right: -2.4, up: -1.0, forward: 0.0 }
style:
background-opacity: 0.0
buttons:
- id: previous
text: "<dark_gray>[ <white>◀ Previous</white> <dark_gray>]"
clicks:
left:
- type: previous-page
panel: list
Each button has its own id, text, optional visible-if and its own clicks map.
Offsets and layout
offset is right, up and forward in the board's own axes. Negative right is to the viewer's left.
A panel's up offset is its bottom edge, and it grows upward from there. A panel is lines × line-height tall, so with the default line-height: 0.25 a five-row panel at up: 0.0 occupies 0.0 to 1.25, and anything above it needs up: 1.25 or more.
Two more things that bite:
interaction-width(default4.0) is how wide the clickable region is, not how wide the text is. Side-by-side panels want theirrightoffsets at leastinteraction-widthapart, or their click regions overlap and the nearer one wins.- A panel with
background-opacity: 0.0still occupies space and still takes clicks. That is exactly what button strips use, so they read as floating text under a solid panel.
Style
line-widthpixelsWhere text wraps.
line-heightnumberdefault0.25Vertical spacing per line, and therefore how tall the panel is.
alignmentleft | center | rightHorizontal alignment of the text within the panel.
background-colorcolourThe panel's backing plate.
background-opacity0.0-1.00.0removes the plate visually while keeping the panel's footprint and clicks.text-opacity0-255Opacity of the text itself.
text-shadowbooleanVanilla text shadow.
see-throughbooleanWhether the panel draws through blocks.
billboardfixed | vertical | horizontal | centerHow the panel turns to face the viewer.
fixedkeeps the board's own facing.interaction-widthnumberdefault4.0Width of the click region.
Selection links panels together
A list panel owns a selection. Another panel points at it:
detail:
type: text
selection: list # resolve <entry:...> from that panel's selection
visible-if:
selected: list # and disappear when nothing is picked
visible-if: { selected: <panel> } is the usual companion, so the detail panel vanishes when nothing is chosen. The mirror image, not: { selected: list }, gives you the "pick something" hint panel.
Selection is per player. Two people at one board highlight different rows.
What is templated and what is not
Only strings shown to the player are templated: lines, row, selected-row, header, empty, button text, and an action's message, title, denied-message and confirm-message. Those get MiniMessage, PlaceholderAPI and the <entry:…> / <state:…> / <player:…> tokens.
Action arguments are not. A command action gets exactly three substitutions, <player>, <uuid> and <entry_id>, which is why per-entry travel runs warp <entry_id> rather than templating coordinates into a teleport action.
Validate the tallest state
Test the first, middle and last list page; a selected and unselected row; every conditional button;
and the longest provider result. /holopanels refresh <board> clears provider caches so the test
does not accidentally reuse an earlier response. Then verify clicks at the left and right edges of
side-by-side panels to catch overlapping interaction widths.