S/V MoonstruckCode reference

JavaScript reference

Generated from the JSDoc comments in the site's two scripts: assets/home.js for the homepage and assets/pages.js for the three inner pages. assets/availability.js is generated data, not documented here; ARCHITECTURE.md explains what it holds.

assets/home.js

The homepage is plain HTML and reads without this file, its only script. It has no dependencies and runs deferred, inside one IIFE so that nothing leaks onto window. The inner pages have their own, assets/pages.js, which mirrors several of the behaviors here (menu, reveals, floating bar, weather, theme). What the page loads is assets/home.min.js, which tools/minify.py builds from this file with the comments and spacing taken out; this is the one to read and edit.

What script adds: the entrances, the nav's two states, the menu, the gallery and its two dialogs, the availability calendar, the footer weather, the floating action bar and the theme switch. The stylesheet's "Without script" section undoes the few states that would otherwise wait for it.

Two parts of the page are written here, because they are made of data that is not in the markup:

the availability calendar  from window.__availability (assets/availability.js, written by
                           tools/sync-availability.py) and today's date
the gallery's thumbnails   from GALLERY_COUNT

Each is copied from a template element in index.html, so the markup and its inline styles stay in the page. The footer weather comes from Open-Meteo.

State lives in the DOM so that the stylesheet can do the drawing; this file sets no color or transition. The hooks it writes:

nav.scrolled, nav.menu-open, .nav-burger.active     the nav's state, and the menu's
aria-current="location"                              the nav link for the section being read
hidden                                               the menu and its backdrop, the two dialogs, the calendar's how-to line and readout
data-revealed="yes"                                  an element that has entered
data-bg-on="yes"                                     a section whose background image may load
data-entrances="live" on the root element            entrances are being watched, so the head script's four-second fail-open stands down
data-mist="on" / "off"                               the rate table is on screen, so its mist drifts, or is not, so it holds
data-cal-day, data-lit="yes" / "maybe"               a calendar day's status, and whether it is in the chosen stay or a previewed one
aria-pressed, tabindex on a start day                the chosen boarding day, and the calendar's one tab stop
data-chosen on the readout, data-more on the grid    whether dates are chosen, and whether a phone's folded months are shown
data-zoomed, --zoom-natural                          the plate zoom's size
data-wx-faded, data-cta-shown                        the weather's fade, the floating bar
--cta-vv                                             the phone toolbar's height
data-settled="yes" on the root element               the page has loaded: in-page links scroll smoothly from here on
data-theme on the root element, aria-pressed         the theme that is on, and the footer switch's button for it

Doc comments follow JSDoc, plus one house tag, @section, which opens a named group; the stylesheets use it too. tools/build-docs.py turns them into docs/javascript.html.

Types

AvailabilityDatatypedef

Object · assets/home.js:48

window.__availability, as written to assets/availability.js by tools/sync-availability.py.

PropertyTypeDescription
sourcestringThe listing the blocks were read from.
asOfstringMonth the dates were last confirmed, as YYYY-MM.
first?stringFirst month worth drawing, as YYYY-MM, or null when there is nothing to show.
last?stringLast month worth drawing.
blocksArray<Array<string>>Runs of taken days, each [first day, last day, state], the days as YYYY-MM-DD and inclusive, the state one of "booked", "hold" or "unavailable". A day in no block is open.

Readingtypedef

Object · assets/home.js:59

One harbor's current conditions.

PropertyTypeDescription
namestringPlace label, as shown.
tempnumberTemperature in whole degrees Fahrenheit.
windnumberWind speed in whole knots.
codenumberWMO weather interpretation code, as Open-Meteo reports it.

Head and structured data

assets/home.js:73

The head in index.html is static. What it cannot know is the address the page is served from, so the absolute parts are written here.

injectSeo()function

assets/home.js:79

Writes what the static head cannot: a canonical link, absolute URLs for the social card images (crawlers want them absolute, and the source can only hold relative paths), and a JSON-LD graph of three nodes: the charter as a TouristTrip with its offers, the operator as an Organization, and the WebSite.

The offers are written by hand here, as the rate table's rows are in index.html, so a change of rates has to be made in both places.

Availability calendar

assets/home.js:152

From the blocks in assets/availability.js and today's date: each day's status, the days a charter can start on with the stay each would begin, and the months to draw. Dates are compared as YYYY-MM-DD strings, which sort as the days do. The sync script says only which days are taken; the rest is worked out here. If the file does not load there are no months to draw, and the section says the calendar is being brought up to date rather than showing every week as open.

availmember

AvailabilityData · assets/home.js:162

MIN_WINDOW_DAYSmember

assets/home.js:173

The owner's floor for a stay worth offering: five nights, which span six dates as drawn in the grid.

blockOf(key)function

assets/home.js:176

The state of the block a day falls in, or "open".

ParameterTypeDescription
keystringThe day, as YYYY-MM-DD.

Returns string

statusOf(y, m, d)function

assets/home.js:186

A day's status as the grid draws it: "past", a block's state, "turnaround" or "open". The crew needs a clear day between charters, so an open day that touches a booking or a hold is a turnaround day: it reads as unavailable and is never offered. Neighbors are read from the blocks, so a charter that ended yesterday still claims today.

ParameterTypeDescription
ynumberYear.
mnumberMonth, from zero.
dnumberDay of the month.

Returns string

STATUS_WORDSmember

assets/home.js:206

Each status in words, for the hidden text in every day: state is never carried by color alone.

keyboardFocus(el)function

assets/home.js:209

A mouse click's focus does not count as keyboard focus. Older browsers cannot tell, so they say yes.

ParameterTypeDescription
elElement

Returns boolean

stayFrom(day)function

assets/home.js:212

The stay a guest would get by boarding on a day: that day and the seven after it (seven nights span eight dates), or as far as the open days run when a charter, a hold, a turnaround day or the end of the calendar comes first. A day can start a charter only when at least MIN_WINDOW_DAYS open dates run from it, which is the five-night minimum; any other day returns null and stays a plain cell in the grid. Turnaround days are never open, so a stay always leaves the clear day either side of a charter that the boat needs.

ParameterTypeDescription
dayDateThe boarding day.

Returns ?{a: Date, b: Date, nights: number} The first and last date, and the nights between them.

calDaysmember

HTMLElement[] · assets/home.js:235

Every day drawn, so that lighting a stay is one pass over them.

calStartsmember

Array<{cell: HTMLButtonElement, key: string, stay: {a: Date, b: Date, nights: number}}> · assets/home.js:237

The days a charter can start on, in date order, each with its button and the stay it would begin.

paintStay()function

assets/home.js:245

Inks the chosen stay in full and a previewed one more quietly, and no other day.

stayDates(stay, names)function

assets/home.js:257

A stay's dates in words. Long for the page ("December 28 to January 4, 2027"), short for a mail subject ("Dec 28 to Jan 4, 2027"), which stays ASCII: some mail clients hand back a mailto whose subject carries an en dash or a colon, and nothing opens.

ParameterTypeDescription
stay{a: Date, b: Date}
namesstring[]MONTHS or SHORT_MONTHS.

Returns string

chooseDay(i)function

assets/home.js:270

Chooses a start day, or clears the choice. The grid inks the stay, the button says it is pressed, and the readout under the grid gives the dates, the nights and one action: an email with the dates in its subject. No price is shown or worked out here, on purpose: what a week costs is the rate table's to say.

ParameterTypeDescription
i?numberIndex into calStarts, or null to clear.

showAllMonths()function

assets/home.js:292

Shows the months a phone keeps folded away (the stylesheet folds all but three at 430px and below).

focusDay(i)function

assets/home.js:297

Moves the keyboard's place in the grid. The start days share one tab stop (a roving tabindex), so Tab passes the calendar in one step and the arrow keys move inside it.

ParameterTypeDescription
inumberIndex into calStarts.

renderCalendar()function

assets/home.js:309

Draws the section: the "current as of" line and one grid per month from its template, in which every day that can start a charter is a button, then wires the readout under the grid, the phone's "show more months" button and the arrow keys.

Reveals

assets/home.js:402

Elements with the class .reveal enter once, when 15% of them is on screen and they are 60px clear of the viewport's bottom edge. Entering means data-revealed="yes"; the stylesheet owns the motion, including the slide-ins, the polaroid plates and the gallery stage's blur.

Deferred loading

assets/home.js:420

What is kept off the first paint's back: the section backgrounds.

Navigation

assets/home.js:444

The bar's two states, the mark under the link for the section being read, and the mobile menu.

onMenuKeys(e)function

assets/home.js:472

Keys while the mobile menu is open: Escape closes it, and Tab wraps from the burger (which stays on screen as the close control) through the drawer's links.

ParameterTypeDescription
eKeyboardEvent

Listens document#keydown

toggleMenu()function

assets/home.js:488

Opens the drawer if it is closed and closes it if it is open. Open or closed is the drawer's hidden attribute; nothing else remembers it. Kept in step with it: the backdrop, the classes the stylesheet keys on (nav.menu-open, .nav-burger.active), aria-expanded, the page's scroll lock, the focus trap, and focus itself, which goes to the first link on opening and back to the burger on closing.

Scroll state

assets/home.js:517

handleScroll()function

assets/home.js:528

Reads the scroll position and settles two things, each written only when it changes, and once at the start for a page opened part-way down:

the nav          past 100px it takes .scrolled: the glass bar over the hero becomes the Night Watch bar
the floating bar shows once the middle of the boat section has passed the middle of the viewport, and
                 hides while the closing section is in view (it has its own Book button), while the
                 availability calendar fills the middle of the viewport (the bar would cover its
                 third column, and the same three actions sit under the grid), and while it would
                 lie over one of the gallery's arrows

Floating action bar

assets/home.js:566

ctaShiftmember

assets/home.js:570

The last offset written to --cta-vv; zero is also what the stylesheet assumes before any write.

syncViewportOffset()function

assets/home.js:572

Keeps the bar on the visible bottom edge of a phone. A fixed element is laid out against the layout viewport, but a mobile browser's toolbar or keyboard shrinks the visual viewport inside it. The difference is written to --cta-vv on the root element, and the bar's bottom offset adds it.

Listens window#resize, VisualViewport#resize, VisualViewport#scroll

Overlays and focus

assets/home.js:598

The photo viewer and the plate zoom are real dialogs (role="dialog", aria-modal) that share one mechanism. Both are in the page, hidden, and carry data-overlay. Opening one remembers the element that had focus, stops the page scrolling behind it and lands focus on its Close button; closing returns focus to where it came from. Escape closes, Tab wraps inside. The mobile menu has the same trap, with the burger standing in as its close control.

overlaymember

?HTMLElement · assets/home.js:607

The overlay that is open, or null.

overlayReturnmember

?Element · assets/home.js:609

The element that had focus when it opened.

openOverlay(node)function

assets/home.js:614

Opens an overlay.

ParameterTypeDescription
nodeHTMLElementThe viewer or the zoom dialog.

closeOverlay()function

assets/home.js:628

Closes the open overlay and hands focus back. Its image is let go of, so the next opening does not begin by showing the last one.

onOverlayKeys(e)function

assets/home.js:646

Keys while an overlay is open: Escape closes it, the left and right arrows turn the photo in the viewer, and Tab wraps among the overlay's buttons.

ParameterTypeDescription
eKeyboardEvent

Listens document#keydown

Plate zoom

assets/home.js:674

Any element carrying data-zoom opens the zoom dialog on the file it names, under its own alt text. The image opens fitted to the window; a click enlarges it to 260% and the dialog scrolls to pan, and another click fits it again. The enlarged files carry their width in their names (-2052.webp), which is how the zoom knows never to enlarge past the source's own pixels.

setZoomed(zoomed)function

assets/home.js:684

Sets the zoom's size and words its hint for the pointer in use.

ParameterTypeDescription
zoomedbooleanEnlarged, not fitted.

Theme

assets/home.js:925

The footer's switch between the site's two themes: Night Watch, the stylesheet's own tokens, and Ensign, the [data-theme="ensign"] block beside them. The theme is the root element's data-theme attribute and nothing else, so the stylesheet does all the drawing.

The choice is kept in localStorage under "moonstruck-theme" and never leaves the browser. A one-line script in the head puts it on before first paint, because this file is deferred and the page would otherwise be drawn in Night Watch first. What is here shows the switch (it ships hidden, since without script it would do nothing), keeps its pressed state in step, and remembers a change. assets/pages.js carries the same for the inner pages.

themeSwitchmember

?HTMLElement · assets/home.js:938

The switch in the footer.

themeMetamember

?HTMLMetaElement · assets/home.js:940

The meta tag that colors the browser's own chrome.

syncTheme()function

assets/home.js:942

Bring everything that follows the theme into line with the root element's attribute: each button's pressed state, and the browser's theme color, which is read back from the stylesheet's --ink-page so that no color is written here.

Arriving with a fragment

assets/home.js:972

index.html#rates from an inner page's nav, or a shared link. The browser makes the jump itself as soon as it finds the target, but the calendar and the thumbnails are written after that and push the target down, as do images arriving above it, and Safari does not anchor the scroll. So the jump is made again here, last of all, and once more at window load.

Smooth scrolling is switched on only after that (data-settled on the root element, which the stylesheet reads): a smooth scroll begun by the browser on arrival would still be traveling to where the target used to be, and would carry the page past every correction made here.

scrollToFragment()function

assets/home.js:985

Instant, not smooth: this is arriving, not traveling. scrollIntoView() honors the sections' scroll-margin-top, so the landing clears the bar exactly as a nav link's does.

settle()function

assets/home.js:996

The page has loaded: the last correction, then smooth scrolling from here on.

Listens window#load

assets/pages.js

The inner pages are plain HTML written by tools/chart-pages.py and styled by assets/pages.css; this is their only script. It has no dependencies and is written in ES5 inside one IIFE, so nothing leaks onto window. The homepage does not load it: index.html has its own script, assets/home.js, which mirrors several of the behaviors here (menu, reveals, floating bar, weather, theme). What the pages load is assets/pages.min.js, which tools/minify.py builds from this file with the comments and spacing taken out; this is the one to read and edit.

Everything here is an enhancement. Without script a chart shows no divider, each anchorage's map control is a plain link to Apple Maps, and entrances fail open: .reveal blocks, pins and routes are hidden only while the root element carries data-entrances, which a one-line script in the head takes off again if this file has not started within four seconds. Each block below looks its elements up first and does nothing when they are missing, so one file serves every page.

The contract with the markup is a small set of hooks, all emitted by tools/chart-pages.py:

.chart[data-split]        a chart that carries a satellite plate; the value is where the divider rests
.split-knob, .split-line  the divider's two draggable parts
[data-pin], [data-seg]    a numbered pin and a route segment inside a chart, keyed by leg or entry
[data-for][data-key]      a log entry or key row: names the chart id and the key it lights
.inset, .inset-open       an anchorage inset and the link that opens its satellite map
#mapbox                   the map dialog, present only when a MapKit token is set
.reveal, .gauges          elements that enter once when scrolled into view
.chart-scroll             the horizontal scroller around a chart, live at 900px and below;
                          while it overflows it is given tabindex, role="group" and a name, as is the style guide's .scroll
data-entrances            on the root element: set "armed" by the head script, marked "live" here once entrances are watched
.float-cta, .close        the floating action bar, and the closing section it hides over
footer .weather           the footer conditions widget
[data-theme-switch], [data-theme-pick]   the footer's theme switch and its buttons; the value is the theme's name, "" for Night Watch

State lives in the DOM (classes, data attributes, custom properties) so that pages.css can do the drawing. The exceptions are --split and --cta-vv, which are measurements only script can take.

Doc comments follow JSDoc, plus one house tag, @section, which opens a named group; the stylesheets use it too. tools/build-docs.py turns them into docs/javascript.html and docs/css.html.

Static capture switch

assets/pages.js:42

Adding ?static to any inner page's URL resolves every entrance at once: reveals shown, routes drawn, pins placed, the chart / satellite divider already at rest. It exists for screenshots and print. The class is set here, synchronously and before anything else runs, because headless browsers capture at the load event, before a requestAnimationFrame chain or a timer can settle. pages.css carries the matching html.static rules.

Mobile menu

assets/pages.js:52

The burger (shown at 1180px and below) opens #mobile-menu, a Night Watch drawer from the right, marked up as a modal dialog. It mirrors the homepage's menu: focus moves to the first link on opening and back to the burger on closing, Tab wraps inside the drawer, Escape or a click on the backdrop closes it, and the page behind does not scroll. Open or closed is the drawer's data-open attribute ("yes" / "no"), which pages.css reads.

burgermember

?HTMLButtonElement · assets/pages.js:61

The burger button in the nav bar.

menumember

?HTMLElement · assets/pages.js:63

The drawer itself.

siteNavmember

?HTMLElement · assets/pages.js:65

The bar the burger sits in. While the drawer is open it rises above it, so the burger stays in reach.

menuBackdropmember

?HTMLElement · assets/pages.js:67

The scrim over the page behind the drawer. pages.css shows it from the drawer's data-open.

menuOpen()function

assets/pages.js:69

Whether the drawer is open. The attribute is the state; nothing else remembers it.

Returns boolean

onMenuKeys(e)function

assets/pages.js:74

Keyboard handling while the drawer is open: Escape closes it, and Tab is wrapped so focus cannot leave the dialog. The trap runs from the burger (which stays on screen as the close control) through every link in the drawer. Focus that has strayed outside the set is pulled back to the burger. Attached to document only while the drawer is open.

ParameterTypeDescription
eKeyboardEvent

Listens document#keydown

toggleMenu()function

assets/pages.js:92

Opens the drawer if it is closed and closes it if it is open, keeping in step the drawer's data-open (which also shows the backdrop), the bar's .menu-open class (which lifts the burger above the drawer as its close control), the burger's .active class (its three waves into a cross), aria-expanded, the page's scroll lock and focus. The focus call is deferred a tick so the drawer is displayed before it receives focus; preventScroll keeps the page where it was.

Chart panning

assets/pages.js:123

At 900px and below a chart keeps a minimum width (--chart-min) and pans sideways inside its .chart-scroll box. It opens scrolled to its subject, not to the left edge: data-focus-x is the fraction of the chart's width to center. Applied at once and again at load, when the chart's final width is known. It runs before the split below, which rests its divider in the middle of whatever this leaves on screen.

scrollersmember

assets/pages.js:143

A box that scrolls sideways has to be reachable without a pointer, or a keyboard cannot see the far end of what is in it: a chart below 900px (which is also a desktop at 200% zoom) and the style guide's ledgers on a phone. While a box overflows it is a tab stop, so the arrow keys scroll it as they do any focused scroller, with a group role and a name that says why it took focus. While everything fits it is none of those, so a desktop at full size gains no empty stop.

Chart / satellite split

assets/pages.js:163

Every chart but the itineraries hero carries a satellite plate of exactly the same window, revealed by a draggable divider. Its position is the custom property --split on the chart, in percent from the left: chart to its left, satellite to its right. pages.css turns that one number into the plate's clip-path and the positions of the line and the knob.

Only the knob and the line take the drag, never the whole plate, because on a phone the plan charts already pan sideways and the page has to scroll past them (both set touch-action: pan-y).

Where it comes to rest is measured, not fixed. data-split is a percent of the whole chart, which means something only while the whole chart is on screen. Once the chart is wider than the screen and pans, the divider rests at the middle of what is on screen instead, and is measured again whenever the geometry moves, unless the visitor has taken it somewhere.

A chart moves through four classes:

.split-on     script is running: the plate, line and knob are displayed (they are display: none without it)
.split-intro  the entrance is in flight: --split changes are transitioned, after --split-delay
.split-live   the line and knob are visible
.is-dragging  a pointer holds the divider

The knob is a role="slider": aria-valuenow is the divider's position, and aria-valuetext names the share of satellite, which is what a listener wants to know.

stillmember

boolean · assets/pages.js:189

True when nothing should animate: the ?static switch, or the visitor prefers reduced motion. Still pages open with the divider already at rest instead of playing the entrance.

splitsmember

Object<string, function(): void> · assets/pages.js:195

Entrance functions for the charts that animate, keyed by chart id. enter() calls one when its chart first scrolls into view, so the satellite wipes in after the route has drawn.

relaxersmember

Array<function(): void> · assets/pages.js:201

One per chart: puts its divider back where it should rest, after the geometry has moved. Called for every chart on the page at once, by relax() below.

restAt()function

assets/pages.js:218

Where the divider should rest. The authored rest is a percent of the whole chart, so it only says what it means while the whole chart is on screen; while the chart pans inside .chart-scroll the divider rests at the middle of the part that is on screen, whatever the screen's size.

Returns number Percent from the chart's left edge.

set(v)function

assets/pages.js:231

Moves the divider. The one place --split is written, so the custom property and the slider's ARIA values cannot disagree.

ParameterTypeDescription
vnumberPosition in percent from the chart's left edge; clamped to 0 to 100.

settle()function

assets/pages.js:243

Ends the entrance, so that later moves follow the pointer at once instead of easing over 1.5s.

at(e)function

assets/pages.js:245

The divider position a pointer event asks for, allowing for where the knob was taken.

ParameterTypeDescription
ePointerEvent

Returns number Percent from the left; the current value if the chart has no width yet.

down(e)function

assets/pages.js:251

Starts a drag from the knob or the line. Only the primary button counts. The pointer is captured so the drag carries on when it leaves the 40px knob; capture can throw for a synthetic event, which is harmless, hence the empty catch.

ParameterTypeDescription
ePointerEvent

Listens PointerEvent#pointerdown

move(e)function

assets/pages.js:267

Follows the pointer during a drag. Pointer events can arrive several times a frame, so only the newest position is kept and it is written once per animation frame.

ParameterTypeDescription
ePointerEvent

Listens PointerEvent#pointermove

up()function

assets/pages.js:278

Ends a pointer drag, however it ended: release, cancel, or losing the capture. It leaves the touch state below alone, because the cancel this fix is for arrives in the middle of a touch drag that is still going.

touchUp()function

assets/pages.js:284

Ends a touch drag: let the pointer drag go too, and forget the touch.

touchIdmember

assets/pages.js:286

A touch drag, held alongside the pointer one. Below 900px the chart sits in a .chart-scroll that pans sideways, and iOS decides whether that scroller has taken a sideways gesture before a pointer event can claim it, ignoring the touch-action: pan-y the knob and line declare: pointerdown fires, the scroller takes the gesture, and pointercancel ends the drag before it has moved. So the first move of a touch decides who owns the gesture: more along than up is the divider's, claimed with preventDefault(), which no scroller argues with; more up than along is left to the page, so the full-height line does not trap vertical scrolling. While it is ours these events drive the divider themselves, and the drag survives the pointer stream being cancelled. touchId: the touch being followed; owns: null until the axis is decided.

tracked(list)function

assets/pages.js:297

ParameterTypeDescription
listTouchList

Returns Touch|null The touch this drag follows, if it is still down.

touchDown(e)function

assets/pages.js:302

Takes note of a touch on the divider. Nothing is claimed yet: the first move decides.

ParameterTypeDescription
eTouchEvent

Listens TouchEvent#touchstart

touchMove(e)function

assets/pages.js:313

Follows a touch, deciding on the first move whose gesture it is.

ParameterTypeDescription
eTouchEvent

Listens TouchEvent#touchmove

note

assets/pages.js:362

Takes this chart's resting position again, after the geometry has moved under it: the chart's final width at load, a resize, a phone turned on its side. Nothing moves before the entrance has run or after the visitor has taken the divider, and a chart that does not pan measures the same authored rest and stays where it is.

splits[chart.id]()function

assets/pages.js:374

This chart's entrance: from closed (chart only), the satellite wipes in from the east and settles at rest.

relax()function

assets/pages.js:382

Takes every divider's resting position again, once per animation frame, when the geometry the measurement was made against has moved: the charts' final widths at load, a resize, a phone turned on its side. One listener for the page.

Listens Window#load, Window#resize

Anchorage maps

assets/pages.js:396

Every inset links to a satellite map at its pin, and the whole plate answers for it: Apple Maps on an Apple device, Google Maps anywhere else. With a MapKit token on the page the click opens a live map in a dialog instead; without one, or if anything about the map fails, the link still goes to Apple Maps. Nothing contacts Apple before a click: MapKit JS is fetched on the first.

The token is MAPKIT_TOKEN in tools/chart-pages.py, which writes it into a meta tag and emits the #mapbox dialog only when it is set. It is empty today, so the dialog code below is dormant and the insets behave as plain links.

MapRequesttypedef

Object · assets/pages.js:408

What the dialog needs to draw one anchorage, read from the opener link's data attributes.

PropertyTypeDescription
latnumberLatitude of the pin: where the boat lies.
lonnumberLongitude of the pin.
dlatnumberHeight of the region to show, in degrees; about the inset's own window.
dlonnumberWidth of the region to show, in degrees.
nstringThe anchorage's number on the index chart, shown as the marker's glyph.
namestringThe anchorage's name.

mapMetamember

?HTMLMetaElement · assets/pages.js:419

The meta tag carrying the MapKit JS token, if the generator wrote one.

mapTokenmember

string · assets/pages.js:421

The token, or "" when the in-page map is off.

mapboxmember

?HTMLElement · assets/pages.js:423

The map dialog.

openMapmember

?function(HTMLAnchorElement): void · assets/pages.js:425

Opens the map dialog for an inset's link. Stays null when there is no token or no dialog, which is how the inset wiring below knows to leave the links alone.

mapFailed()function

assets/pages.js:441

Shows the fallback line over the map, unless MapKit is known to be working.

mapKeys(e)function

assets/pages.js:443

The dialog's keys: Escape closes, Tab wraps. The focusable set is rebuilt on every press because MapKit adds and removes its own controls, and an element counts only if it has a box on screen.

ParameterTypeDescription
eKeyboardEvent

Listens document#keydown

closeMap()function

assets/pages.js:459

Closes the dialog, lets the page scroll again, and returns focus to the link that opened it.

loadKit()function

assets/pages.js:467

Loads and initializes MapKit JS, once. Later calls return the same promise.

Returns Promise<Object> Resolves with window.mapkit; rejects if the script cannot load or init throws.

drawMap(mk)function

assets/pages.js:490

Aims the map at the wanted anchorage: satellite view, framed to about the inset's own window, with one marker in the pin's colors (Depth Cyan disc, Deep Water numeral).

ParameterTypeDescription
mkObjectThe mapkit namespace.

onApplemember

boolean · assets/pages.js:526

Whether this is an Apple device, where a maps.apple.com link opens the Maps app. Anywhere else (Android, Windows, Linux, ChromeOS) the insets go to Google Maps instead. An iPad asking for the desktop site calls itself a Mac, which is Apple either way.

Reveals, chart entrances and gauges

assets/pages.js:552

Three kinds of element enter once, when 15% of them is on screen and they are 60px clear of the viewport's bottom edge: .reveal blocks fade up, charts draw their route and place their pins, and the "how the week leans" gauges fill. Script only adds a class; pages.css owns the motion and its reduced-motion counterpart. Each element is unobserved after it has entered, so nothing replays.

enter(el)function

assets/pages.js:560

Marks one element as entered. Charts and gauges take .is-in; everything else takes .reveal-visible. A chart that registered a split entrance starts it here.

ParameterTypeDescription
elElement

iomember

?IntersectionObserver · assets/pages.js:569

The shared observer; null in a browser without IntersectionObserver, where everything enters at once.

Chart and log linking

assets/pages.js:584

Pointing at, or focusing, a log entry lights its leg and pin on the chart, and pointing at a pin lights its entry: the link runs both ways. A row names its chart with data-for and its key with data-key; pins carry data-pin and route segments data-seg with the same key (a leg number on the itineraries page, an anchorage number on the destination pages).

setActive(chartId, key)function

assets/pages.js:592

Lights one key on one chart, or clears the chart. Sets data-active on the chart, which is what dims the other routes and pins in pages.css, and toggles .active on the matching pin, route segment and rows.

ParameterTypeDescription
chartIdstringThe chart element's id.
key?stringThe key to light, or null to clear.

Floating action bar

assets/pages.js:625

Call, Email and Book, fixed to the viewport's right edge (its bottom edge on a phone). It shows once the hero has passed the middle of the viewport and hides over the closing section, which carries its own Book button, so two never share a view (The One Action Rule, DESIGN.md).

onScroll()function

assets/pages.js:635

Scroll handler, limited to one measurement per animation frame. Writes data-shown on the bar; pages.css fades it and takes it out of the pointer's way.

Listens window#scroll

ctaShiftmember

assets/pages.js:654

Keeps the bar on the visible bottom edge of a phone. A fixed element is laid out against the layout viewport, but a mobile browser's toolbar or keyboard shrinks the visual viewport inside it. The difference is written to --cta-vv on the root element, and the bar's bottom offset adds it.

Listens window#resize, VisualViewport#resize, VisualViewport#scroll

Theme

assets/pages.js:748

The footer's switch between the site's two themes, Night Watch and Ensign, as on the homepage (assets/home.js has the full account): the theme is the root element's data-theme attribute and nothing else, kept in localStorage under "moonstruck-theme" and put on before first paint by a one-line script in the head. What is here shows the switch (it ships hidden, since without script it would do nothing), keeps its pressed state in step, and remembers a change.

themeSwitchmember

?HTMLElement · assets/pages.js:757

The switch in the footer.

themeMetamember

?HTMLMetaElement · assets/pages.js:759

The meta tag that colors the browser's own chrome.

syncTheme()function

assets/pages.js:761

Bring everything that follows the theme into line with the root element's attribute: each button's pressed state, and the browser's theme color, which is read back from the stylesheet's --ink-page so that no color is written here.

Generated by tools/build-docs.py from the comments in the source. To change this page, change the comment and run it again.