S/V MoonstruckCode reference

CSS reference

Generated from the doc comments in the site's two stylesheets. The homepage's is assets/home.css, inlined into index.html by tools/minify.py and holds only what an inline style cannot say; the inner pages share assets/pages.css. Each entry is tagged with what it is: a rule, a modifier or state of one, a hook that script sets, a component-scoped custom property (var), or a breakpoint.

Tokens

The custom properties on :root. assets/home.css and assets/pages.css each carry the set and must agree; the values here are read from the source, and the descriptions from the @token tags in pages.css.

TokenValueIn EnsignAt 768px and belowWhat it is for
Color
--ink-page#040c16#1d3557Deep Water. The page field: the reading sections, and what shows behind an overscroll.
--ink-panel#07101d#234062Night Watch. The lifted step: panels, cards, the navigation bar, photo mats.
--ink-well#010509#132339Midnight Hull. The recessed step: wells, the footer, badges, and what shows behind a photograph while it loads.
--ink-page-rgb4 12 229 16 27Deep Water as channels: the wash over a photographic section, and the hero haze.
--ink-well-rgb1 5 919 35 57Midnight Hull as channels, for overlays (0.72 to 0.96).
--chart-navy#061e45Moonstruck Navy, kept for the chart field alone. Asset-bound: every satellite plate has its open water toned to it.
--chart-navy-rgb6 30 69Moonstruck Navy as channels, for the legibility halo on type set over a chart.
--cream#f5efd9#f1faeeLamp Cream. The warm cream: headlines and every large figure.
--cream-bright#eef3ff#f1faeeInstrument White. The cool cream: body copy, lists and UI text.
--tint-rgb200 225 255241 250 238The tint as channels. One hue carries every rule and every secondary voice (0.06 to 0.78).
--text-softrgb(200 225 255 / 0.78)rgb(241 250 238 / 0.82)The tint at 0.78. Supporting text and section leads.
--text-mutergb(200 225 255 / 0.66)rgb(241 250 238 / 0.7)The tint at 0.66. Muted small print and the tracked label voice.
--text-faintrgb(200 225 255 / 0.55)rgb(241 250 238 / 0.6)The tint at 0.55. The text floor: a booked or turnaround day in the calendar.
--line-rulergb(200 225 255 / 0.12)rgb(241 250 238 / 0.16)The tint at 0.12. Hairlines between rows, facts and notes.
--line-softrgb(200 225 255 / 0.06)rgb(241 250 238 / 0.08)The tint at 0.06. The quietest edge, where a rule would otherwise read as a boundary.
--amber#faa80fAnchor Light. Held in reserve; the booking action takes Depth Cyan.
--amber-rgb250 168 15Anchor Light as channels, for its washes and borders.
--cyan#00c8e8#a8dadcDepth Cyan. Data and nothing else: chart pins, key numerals, open days, the focus ring.
--cyan-rgb0 200 232168 218 220Depth Cyan as channels: an open day's wash, the on-hold stripe, the rate table's glow.
--cyan-muted#00b2d8#8abec9The darker cyan step. Text selection and gauge fills.
--action#00c8e8#e63946The fill of the one booking action. Depth Cyan here; a theme may give it a color of its own (Ensign: Signal Red).
--action-ink#040c16#09101bThe label on that fill, solved to clear 4.5:1 on it. Deep Water here.
--mark-sea#00b2d8#061e45The sea at the foot of the mark, under its blue grey band. Depth Cyan Muted here; Ensign takes the hull's own Moonstruck Navy.
--rust#c0572a#e63946Rust. A stale reading. It measures 4.34:1 on the page field, so it is a wash and a border, never type.
--rust-rgb192 87 42230 57 70Rust as channels, for that wash.
--mistrgb(200 225 255 / 0.06)rgb(69 123 157 / 0.25)The cyan haze inside the rate table. Homepage only.
--mist-clearrgb(200 225 255 / 0)rgb(69 123 157 / 0)The same color at zero alpha, so the haze's gradients fade without graying. Homepage only.
--nav-glassrgb(4 12 22 / 0.82)rgb(29 53 87 / 0.82)The plate behind the hero headline on a phone.
--nav-glass-rgb4 12 2229 53 87The same as channels.
Type
--font-display'Playfair Display', 'Playfair Fallback', Georgia, serifPlayfair Display, then its metric-matched fallback. Set italic: headings, and every large figure.
--font-body'Barlow', 'Barlow Fallback', system-ui, sans-serifBarlow, then its metric-matched fallback. Everything you read: paragraphs, leads, links in prose.
--font-label'IBM Plex Mono', 'Plex Mono Fallback', ui-monospace, monospaceIBM Plex Mono, then its metric-matched fallback. The tracked uppercase label voice: navigation, buttons, captions, chart furniture, calendar days.
--type-displayclamp(3rem, 8vw, 5.75rem)Display: the homepage hero headline only.
--type-headlineclamp(2.375rem, 9vw, 3.625rem)Headline: section titles.
--type-subheadclamp(1.875rem, 7vw, 2.75rem)Subhead: a second heading inside a section.
--type-title-lg2.125remTitle L: closing index headings and the homepage's wide plate names.
--type-title1.75remTitle: every card and entry heading.
--type-title-sm1.5remTitle S: small titles, and every tile title on a phone.
--type-lead1.1875remLead: the one paragraph that introduces a section, set in the body face.
--type-serif1.25remSerif text: serif running text inside a component.
--type-body1.0625remBody: Barlow 400 paragraphs.
--type-body-navy1.125remBody on navy: one step up, because light text on a dark field reads smaller. The closing paragraph; the homepage's destination blurbs.
--type-small0.875remSmall: secondary facts and credits.
--type-label0.75remLabel: the floor for tracked uppercase annotations.
--leading-serif1.5Line height for serif running text.
--leading-body1.65Line height for body copy.
--leading-title1.15Line height for titles.
--head-lead1.25emSpace over a heading that follows content in the same flow (The Heading Leads Rule).
--head-trail0.4emSpace under a heading, binding it to what it introduces.
Motion
--ease-revealcubic-bezier(0.22, 0.61, 0.36, 1)The one easing curve for entrances: soft, decelerating.
Washes
--wash-drawing0.450.32The opacity of the layout drawing behind the homepage's layout section, solved so the lead clears 4.5:1 over its brightest line. Homepage only.
Grid and measure
--grid-cols9Columns in the page grid. Informational; the spans below are what rules use. Homepage only. home.css only
--grid-max1040pxFull width of the nine-column page grid.
--gutter32px24pxGutter between columns.
--colcalc((var(--grid-max) - 8 * var(--gutter)) / 9)One column, derived from the two above.
--span-3calc(3 * var(--col) + 2 * var(--gutter))Three columns and their two gutters. Spans 4 to 8 follow the same pattern.
--span-4calc(4 * var(--col) + 3 * var(--gutter))Four columns.
--span-5calc(5 * var(--col) + 4 * var(--gutter))Five columns: the measure for a lede.
--span-6calc(6 * var(--col) + 5 * var(--gutter))Six columns.
--span-7calc(7 * var(--col) + 6 * var(--gutter))Seven columns.
--span-8calc(8 * var(--col) + 7 * var(--gutter))Eight columns.
--measure-readvar(--span-6)Reading container (span 6).
--measure-midvar(--span-7)Mid container (span 7).
--measure-widevar(--span-8)Wide container (span 8).
--measure-fullvar(--grid-max)The whole grid (span 9).
Spacing
--section-x56px24pxHorizontal page margin. The only source of it: anything that breaks the margin computes from this.
--section-y56px64pxDefault vertical section padding.
--section-y-lg96px64pxEmphasis step: the homepage's availability section, and the main sections of these pages.
--section-y-xl128px80pxEmphasis step for the closing booking section.
--nav-h89px70pxHeight of the fixed nav bar: hero top padding, sticky offsets and scroll margins clear it. These pages only. pages.css only

assets/home.css

This file is the source and is never served. tools/minify.py copies it, with the comments and spacing taken out, into the one <style> block in index.html, so the first paint does not wait for a second request (ARCHITECTURE.md's *Loading* section has the reasoning). Edit this file, run that script, and commit both. Never write a closing style tag in a comment here: the HTML parser would end the element there once this is inlined.

Most of the homepage's styling is not here. The page began as an export from a design tool, and each element carries its own style attribute. This block holds what an inline style cannot say: the tokens, media queries, pseudo-elements, hover and focus states, keyframes, the elements that assets/home.js writes (thumbnails, calendar days), and rules keyed on the state that home.js sets in classes and data attributes. Because these rules have to beat an inline style, many of them carry !important; that is the mechanism here, not a smell.

Elements are addressed by data attributes (data-tile, data-rate-row, data-hero-stage), not classes: the attribute names the element's job on the page. Classes are kept for the few things script toggles (.scrolled, .menu-open, .active).

The tokens in :root are the site's one set. assets/pages.css mirrors them for the inner pages and documents each one; the two blocks must be kept in step (tools/build-docs.py reports any difference).

Doc comments use the tags described at the top of assets/pages.css (@section, @rule, @hook, @var,

Typefaces

assets/home.css:28

The three families, served from this site (assets/fonts/, with each family's SIL Open Font License beside it), so a page load contacts nobody else for its type. They are the Latin subsets Google Fonts served, which is every file any page ever asked for; a character outside that range falls to the stand-ins below. Playfair Display is one variable file, italic only, because the serif is only ever italic (The Three Voices Rule). font-display is swap, and the stand-ins are metric-matched, so text paints at once and the swap does not re-wrap it. The files never change in place (a new cut gets a new name), which is what lets _headers cache them for a year.

rule@font-face 'Playfair Display'Italic, 400 to 900 in one variable file. Headlines and figures.
rule@font-face 'Barlow'300, 400 and 500. Everything read at length.
rule@font-face 'IBM Plex Mono'400 and 500. The label voice.

See DESIGN.md, Typography

Font fallbacks

assets/home.css:48

Metric-matched stand-ins, measured against the webfonts, so the swap does not re-wrap the page.

rule@font-face 'Playfair Fallback'Georgia at 85.2%, measured against Playfair italic 700, the headline weight.
rule@font-face 'Barlow Fallback'Arial at 94.4%. Body copy.
rule@font-face 'Plex Mono Fallback'Courier New at 100%. The label voice.

Base

assets/home.css:58

The reset, link colors per surface, focus, and the accessibility helpers.

rule*Border-box sizing and no default margins.
rulebodyDeep Water behind everything, so an overscroll never flashes white; lining figures site-wide (The Plain Figures Rule).
ruleh1 to h4; p, liBalanced headings, and paragraphs that do not end on a lone word.
ruleathe tint at 0.78, turning to --link-hover.
var--link-hoverThe hover color for links on a surface. Unset, it is Instrument White.
rule[data-dest-link], the footer's mail linkSmall targets keep their drawn size and grow an invisible 44px hit area from an ::after with a negative inset (The Hit Area Rule).
rule:focus-visibleThe site-wide ring: 3px Depth Cyan, 3px offset; Instrument White on the ink surfaces and over the hero.
rule[hidden]Hidden means not displayed, whatever display an inline style asks for: the menu, the two dialogs and the open windows all wait behind this attribute.
rule[data-action]:hoverCall, Email and Book (under the calendar, in the floating bar, in the menu) turn Instrument White.
rule[data-theme-pick]A button of the footer's theme switch, and its enlarged hit area. The switch ships hidden; home.js shows it.
state[data-theme-pick][aria-pressed="true"]The theme that is on: Instrument White over a hairline, so the state is not carried by color alone.
rule[data-theme-label]The switch's caption, "Theme", in the micro-label role: 10px, tracked wider, in the tint's mute step, with a hairline after it. In the buttons' own voice it read as a third choice.
rule[data-outline]:hoverThe outlined buttons on navy fields draw their border in full and take a 10% Instrument White wash.
rule.skip-link"Skip to content", off screen until it takes focus.
rule.vhVisually hidden, still read aloud: calendar day states, photo counts.

Heading rhythm

assets/home.css:87

The Heading Leads Rule: a heading takes its space from above, not below, so it binds to the block it introduces instead of floating between two. Written with :where(), so it carries no specificity and every authored heading margin on the page still wins; it is the floor, not an override.

The lead is only on a heading that follows a sibling in the same flow. A heading that opens a container already has the container's padding over it, and adding to that would double-count. In block flow the lead collapses with the previous element's own bottom margin and the larger wins, so it raises a short gap and leaves a long one alone.

rule:where(h1..h4)The trail: 0.4em under every heading.
rule:where(* + h2..h4)The lead: 1.25em over a heading that follows content.

State hooks

assets/home.css:144

Small rules keyed on what assets/home.js writes. Each value is the string "yes" or "no".

hook[data-weather][data-wx-faded="yes"]The footer weather is between two readings.
hook[data-hero-img][data-loaded="yes"]The hero photograph has loaded and fades in over 0.6s, the short end of the reveal range: Chrome times the largest paint at a fade's end, so each second here is a second of LCP. Set by the image's own onload attribute, so a photograph already there is marked before the first frame.
hook[data-hero-img][data-fade="no"]The photograph loaded before the browser had reported a first paint (cached, or preloaded in time), so it arrives with the page and does not fade. The same onload attribute says so, from the paint timeline, because a transition starts from the last computed style, not the last painted one. Important, to outrank reduced motion's 0.2s.
hook[data-bg-wait][data-bg-on="yes"]A section's background image is wanted: the section is within two screens or so. Until then the image is withheld, so the five of them, a megabyte together, are not fetched with the page. Only where script runs: without script, or where (scripting: enabled) is unknown, they load with the page, and a printed page gets them all.
hook[data-float-cta][data-cta-shown="yes"]The floating Call / Email / Book bar is shown. Hidden, it is visibility: hidden as well as transparent, because pointer-events alone leaves its links in the tab order.
rule::selection, htmlDepth Cyan Muted selection, and a scrollbar in the palette.
hookhtml[data-settled="yes"]The page has loaded, and in-page links scroll smoothly from here on. Not before: a page opened on a fragment is scrolled by the browser as soon as it finds the target, and a smooth scroll goes on chasing where the target was before the calendar was written above it.
rulesection[id], #book, #availabilityAnchored sections stop clear of the fixed nav: 89px, 58px on a phone.

Navigation

assets/home.css:190

A fixed bar with two states. Over the hero it has no plate: Instrument White links and the mark sit on the photograph itself, so their contrast depends on what the photograph puts behind them. Past 100px of scroll home.js adds .scrolled and it becomes the Night Watch bar, the form the inner pages always use.

rulenavThe bare state, over the hero: no background and no blur.
hooknav.scrolledThe Night Watch state. The logo turns Instrument White: its file becomes a mask over a --cream-bright fill.
rule[data-footer-logo]The footer's mark, in Instrument White with its water in --text-soft over --mark-sea by the same mask, at 90%.
rule[data-footer-asa]The American Sailing Association logo beside it, all Instrument White by a mask of its own file: the red A that #sailing-school keeps goes white with the letters. 32px, not 46: it is a wordmark four times as wide as tall.
hooknav.menu-openThe drawer is open: the bar goes transparent and hides its logo and links, leaving the burger as the close control.
rule.nav-burgerA 44px target holding three wave-shaped bars (an SVG mask over a fill).
hook.nav-burger.activeThe bars cross into an X.
rule.nav-links a::afterWhere you are: a 1px hairline under the link for the section being read, drawn in from the left over 0.35s. Drawn, not text-decoration, so it stops at the last letter instead of running under the tracking.
state.nav-links a[aria-current]The hairline is drawn. home.js sets aria-current="location" from the section on the reading line.
rule[data-menu] a[aria-current]The same mark in the drawer: an Instrument White underline at 45%, 5px under a sans line.
rule[data-menu]The drawer is the inner pages' one (#mobile-menu in assets/pages.css), value for value: two groups of 20px Barlow 500 links at the tint's 0.82 step, On the chart under a hairline, then Book. Its styles are inline; only its phone padding is here.
breakpoint73.75em1180px at the default text size. Links hide, the burger shows.
breakpoint768pxThe drawer's padding drops from 56px to 40px 32px, which is what lets nine links fit a short phone.

See DESIGN.md, Navigation

Phone layout

assets/home.css:273

One block of overrides per breakpoint, each rule beating an inline desktop style, which is why they are all !important. At 768px: every grid goes to one column, the gallery stage runs edge to edge, the floating bar becomes a full-width row along the bottom, and the footer stacks and centers. The hero has a phone shape of its own; it is in the Hero section below.

breakpoint768pxThe phone layout.
breakpoint430pxThe calendar drops from two months across to one.
var--cta-vvHow far a phone's toolbar or keyboard has pushed the visible bottom edge up. Written by home.js.

Reveal

assets/home.css:317

Slow, soft, once. A block starts low and transparent and rises over 0.8s when home.js marks it as entered. The variants are further down: the slide-ins, the polaroid plates, and the gallery stage, which sharpens from a blur instead of moving.

Entrances fail open. Nothing is hidden unless the root element carries data-entrances, which the one-line script in the head sets and takes off again if home.js has not started within four seconds or fails to load. So a visitor without script, or behind a blocker, reads the whole page without its entrances. The attribute is matched inside :where(), so these rules keep the specificity they had.

rule.revealThe resting state, while entrances are armed.
hook[data-entrances]On the root element: "armed" by the head script, "live" once home.js is watching. Absent, nothing waits.
hook.reveal[data-revealed="yes"]Entered. Written once by the reveal observer in home.js, never removed.

Hero

assets/home.css:341

Hero photo (Moonstruck under sail, from overhead), anchored 75% across and 54% down: object-position pins that point of the plate to the same point of the box, so the boat holds the right of the hero at every size while the sides and the empty water crop away. The headline reads on the left, over open water, under a haze in the page field that runs from the left edge to the end of the headline's first line (--hero-copy-right) and is gone by 62vw. Under 600px the copy leaves the photograph, so the haze is dropped and the band's cut is pinned at 50%, which puts the boat in its middle.

The stage has a floor, not a height: 80vh and never under 500px (67vh and 470px on a tablet, 62vh between 520px and 760px held upright). Where a short window leaves too little room for the headline and the badge, the stage grows to hold them, by up to 78px, and the plate is drawn wider than --hero-plate-w allows for, so the bow lands up to 12px further left, inside the copy's 24px margin.

The copy stops short of the boat rather than trusting the eye: --hero-boat-left works out where the bow sits in the box from --hero-plate-w (which the sizes attribute in index.html repeats) and the boat's reach of 5.2% of the plate's width. Change either number and remeasure the photograph.

var--hero-copy-rightWhere the headline's first line ends: the page margin plus 6.8 times the display size.
var--hero-plate-wHow wide cover draws the 3:1 plate over the stage.
var--hero-boat-leftWhere the boat's bow falls in the box; the copy ends 24px short of it.
var--hero-bandThe height of the photograph's band on a phone.
rule[data-hero-haze]The haze: Deep Water at 90%, 72% at --hero-copy-right, clear by 62vw.
rule[data-hero-img]The photograph, anchored on the boat: 75% across, 54% down; 50% across under 600px.
breakpoint600pxThe phone shape: the photograph becomes a band, centred on the boat, and the copy moves below it onto the page field.
breakpointportrait, 769px and upA tablet held upright gets a shorter stage floor: 62vh, between 520px and 760px.

See DESIGN.md, Hero Haze

The boat: header

assets/home.css:386

A three-column round portrait against six columns of headline and lede, each centered on the other. The portrait leads on the left, which turns the hero's arrangement around: there the type is on the left and the boat on the right, and the eye crosses the page the other way as it comes down. The order is set here rather than in the markup, because on a phone the portrait belongs under the headline it illustrates, which is the order the source is written in. The nine columns reach full size at 1152px; under that the same split goes fluid.

breakpoint769pxThe portrait moves to the left of the copy.
breakpoint769px to 1151pxThe fluid split. The portrait is three columns of the width there is, and gives way first when the headline needs the room.
breakpoint1152pxDerived, not chosen: --grid-max plus twice --section-x.

CAPTAIN360

assets/home.css:416

The cards carry a Deep Water panel: the tint at 0.66 cannot clear 4.5:1 on a photographic field at any wash alpha. Measured, not guessed. Three equal cards: the captain, who is still to be named, then what the program puts in every rate and what the captain does.

rule[data-crew-grid]The row. position and z-index lift it above the section's Deep Water wash.
rule[data-crew-grid] > divA flat Deep Water panel, 2px radius, no shadow.
rule[data-crew-photo]The round portrait, and the disc that stands in for one not taken yet.
rule[data-crew-soon]The waiting disc: Night Watch under an inset tint ring, its label in the tint at 0.66 (6.6:1).
breakpoint920pxOne column, capped at 440px.

See DESIGN.md, The Photographic Field Rule

The sailing school

assets/home.css:442

ASA instruction, on the sea field the destinations use. Two columns: what it is and how to ask, then the four ASA courses taught aboard as a ruled list, number first in the label voice.

rule[data-asa-grid]The two columns. One under 768px (set with the other phone rules above).
rule[data-asa-courses]The list: a hairline over each course, and one under the last.
rule[data-asa-courses] > divOne course: its number in a fixed column, its name beside it.
rule[data-asa-courses] dtThe number, in the label voice, in Depth Cyan.
rule[data-asa-courses] ddThe course's name, in the serif.
rule[data-asa-logo]The link over the list that carries the American Sailing Association's logo to its site. It is the size of the logo, so the focus ring hugs the mark and not the column.
rule[data-asa-logo] imgThe logo, 46px tall like the footer's marks. Its white-text variant in ASA's own colors: the red is theirs, so it is not recolored to Instrument White by mask.

Entrances

assets/home.css:479

The reveal's variants. All of them key on data-revealed="yes", and all of them are undone in the reduced-motion block at the foot of this stylesheet.

rule[data-gallery-stage].revealThe gallery stage does not move: it sharpens from gray and blurred over 1.2s.
rule[data-slide-in]A block that arrives from the left or the right, 90px, over 1s.
rule[data-polaroid]A boat plate arriving from 70px away.
rule[data-layout-zoom]The magnifier badge on the layout drawings, which fades in half a second after its drawing.

The boat: feature field

assets/home.css:502

A continuous two-up field of captioned plates between two full-measure plates. The alley is derived: --measure-full - 2 * --span-4 = 152px, so the tiles sit on column lines 1-4 and 6-9 and the wide plates run the full nine.

rule[data-boat-field]The grid: fluid halves with a 14% alley, exact spans from 1152px.
rule[data-tile]The polaroid: a Night Watch mat carrying the plate, with the caption printed on its lip.
modifier[data-tile="wide"]A full-measure plate: a wider mat and the Lift shadow.
modifier[data-tile="right"]A right-hand tile: it arrives from the right, a beat after its neighbor.
var--matThe mat's width: 16px on tiles, 22px on wide plates, 14px on a phone.
var--plate-arThe plate's proportion, set inline per tile: 16/9, 4/3 or 8/5.
rule[data-plate] > imgThe photograph. A role="button" that opens the plate zoom, hence the zoom-in cursor.
rule[data-zoom-hint]The round magnifier badge on a plate's corner, darkening on hover and focus.
rule[data-lip]The lip: title and description read as one unit, tight, with the air above them.
rule[data-lip] pThe description. It runs to a couple of sentences, which is a paragraph and so the sans, not the serif accent line the titles take (DESIGN.md, Serif text).
breakpoint1152pxExact spans; the wide lip splits three columns of title against the rest.
breakpoint768pxOne column; every plate arrives from the left; wide plates go 4:3.

See DESIGN.md, Layout; Shapes

Rate table

assets/home.css:563

A rate table pinned to the chartroom wall: party size down the side, season across the top. It is built from ARIA table roles on a CSS grid, so that at 600px a row can become a header over two cells. Another party size is another data-rate-row.

rule[data-rate-table]The Deep Water panel. isolation keeps its two pseudo-element layers behind its content.
rule[data-rate-table]::beforeThe mist: four soft radial gradients, blurred, drifting on a 34s loop (rateMist). Stilled under reduced motion, and held while the table is off screen.
hook[data-rate-table][data-mist="on"]The table is on screen, so the mist drifts. Written by home.js; "off", or absent, holds it where it is.
rule[data-rate-table]::afterA fine paper grain from an inline SVG turbulence filter, multiplied at 16%.
rule[data-rate-head], [data-rate-row]One grid for the header and every row: the week, the boat's rate, the rate per person.
rule[data-rate-label]Season labels in the label voice, in --accent-deep with the tint at 0.66 as the fallback.
rule[data-rate-pp]The per-person figure: Playfair 48px, lining tabular figures. Title large (34px) at 600px and below, capped by the column's width on the narrowest phones and under larger browser text.
rule[data-rate-total]The weekly total, in Barlow at Body size with tabular figures. Small (14px) at 600px and below.
rule[data-rate-inline]A season label repeated inside each cell. Hidden until the header row goes at 600px.
rule[data-rate-foot]The footnote under the table, in serif text.
rule[data-rate-terms]"Included in every rate" and "Not included", four columns against the rest.
breakpoint600pxEach row becomes its party-size header over two season cells that carry their own label. The header row is clipped, not removed, so the table keeps its column headers for a screen reader.

See DESIGN.md, Rate Table

Calendar and booking actions

assets/home.css:616

The floating bar hides over the calendar (it would cover the third column below ~1300px), so the same three actions sit under the grid. One Book button in view still holds.

home.js writes the days and gives each its status in data-cal-day; the colors are here. Every numeral clears 4.5:1 on Deep Water, and no status is carried by color alone: each day also says it in hidden text.

rule[data-cal-day]A day: a square, its numeral centered, answering a change of color in 0.2s. A day that has passed keeps this rule alone: a quiet numeral and no field.
modifier[data-cal-day="open"]Depth Cyan.
modifier[data-cal-day="booked"]A Night Watch panel and a struck-through numeral.
modifier[data-cal-day="hold"]A stripe, so that a hold does not rely on its color.
modifier[data-cal-day="unavailable"], [data-cal-day="turnaround"]A Midnight Hull well. A turnaround day is an open day beside a charter, kept clear for the crew.
hook[data-cal-day][data-lit="yes"]The day belongs to the stay that is chosen: Depth Cyan, Deep Water numeral.
hook[data-cal-day][data-lit="maybe"]The day belongs to a stay being pointed at or focused: Depth Cyan at 0.35 under a full hairline, Instrument White numeral: 8.3:1 in Night Watch and 5.0:1 in Ensign, whose lighter field is what sets the alpha.
rulebutton[data-cal-day]A day a charter can start on is a button; every other day is a plain cell. Its focus ring is Instrument White and sits inside the cell, because Depth Cyan cannot be seen on a lit day.
rule[data-cal-readout]What is chosen, and the one action: a Midnight Hull bar that stays at the foot of the screen while the calendar is in view, so it is never a scroll away from the day just chosen. It floats over the grid, so it takes the Float shadow. No price appears in it.
hook[data-cal-readout][data-chosen="yes"]Dates are chosen: the bar shows them, the nights, the email action and Clear, in place of the prompt. Written by home.js.
hook[data-cal-grid][data-more="no"]At 430px and below all but the first three months are folded away, and the button after the grid unfolds them. Written by home.js.
rule[data-cal-actions]Call, Email and Book under the grid; a full-bleed row on a phone.
rule[data-cal-grid]The months: three across, two at 768px, one at 430px (see Phone layout).
rule[data-book-grid]The closing section: copy and actions set left, the bow photograph in the right four columns; one centered column on a phone, photograph first.
rule[data-book-actions] > a, [data-book-primary] > aThe closing section's buttons share a 236px minimum width, so no action reads as the runt. The primary comes first, alone; Email and the phone number follow as ghosts; the broker list is a line of text under them ([data-book-brokers]), because it leaves the site and is not the page's action.

See DESIGN.md, The Calendar Legibility Rule

Tokens

assets/home.css:696

The site's one set of custom properties. Each is described in assets/pages.css, which mirrors this block for the inner pages; the comments below record why a value is what it is. Four exist only here: --ink-page-rgb, --mist, --mist-clear and --grid-cols.

var--accentSet on the page wrapper, not here (Depth Cyan): the foot of the gallery's current thumbnail.
var--accent-deepSet beside it (the tint at 0.78): crew roles and the rate table's season labels.
breakpoint768pxMargins, vertical rhythm and gutter step down together.

The tokens themselves are in the table above.

See DESIGN.md, Colors; Typography; Layout

Themes

assets/home.css:795

The tokens above are the site's first theme, Night Watch. Ensign is the second: the same tokens re-pinned on a five-color scheme, described in the same section of assets/pages.css, which this block mirrors by hand. A theme is token values and nothing else, and no rule here knows which one is on.

hook[data-theme="ensign"]Ensign, for the element that carries it and everything inside it. On the root element it themes the page: set before first paint by the inline script in the head, from the visitor's stored choice, and by home.js when the footer's switch is pressed. On any other element it themes that element alone, which is how the style guide draws Ensign on a Night Watch page. No attribute is Night Watch.

See DESIGN.md, Themes

Reduced motion

assets/home.css:846

Every animation's counterpart. Nothing slides, flies or sharpens: entrances become a 0.4s fade, the mist holds still, the nav's hairline appears at once. The weather widget's rotation is stopped in home.js. A new animation anywhere above needs a line here.

Without script

assets/home.css:863

The page is ordinary HTML, so it reads without home.js; these rules undo the few states that wait for it. The hero photograph is shown. The calendar says to call or email (its grid is computed in script), and the footer weather, which would never load, is removed. Entrances need no line here: they are armed by script, so without it they were never hidden (see Reveal).

assets/pages.css

The markup it styles is written by tools/chart-pages.py; the behavior it responds to comes from assets/pages.js. The homepage does not load this file: index.html carries its own styles inline, and the two share one set of tokens. The :root block below mirrors index.html's, and the two must be kept in step (tools/build-docs.py reports any token that differs).

How the file is organized: tokens, font fallbacks and base rules first, then the shared voice and components (buttons, nav, reveal, charts), then one block per page section in page order, then the footer and floating bar, and last the global switches (reduced motion, ?static).

Conventions:

  • Color, type size, measure and section spacing come from tokens. A raw hex or a hand-picked width in a rule is a bug (DESIGN.md: The Column Span Rule, The One Margin Rule).
  • Every section is one of the three ink steps; Moonstruck Navy is kept for the chart field and the sea plate (The One Field Rule, The Chart Field Rule).
  • State is carried by classes and data attributes that pages.js sets (.is-in, .active, .split-on, .bg-on, data-open, data-shown, data-active). Script never writes a color or a transition.
  • A single-column grid track is minmax(0, 1fr), never a bare 1fr (The Shrinkable Track Rule).
  • Every animation has a counterpart in the reduced-motion block at the foot of the file.

Doc comments: a block like this one documents what follows it. Tags, modeled on JSDoc:

@section Name         opens a section of the reference; prose after it describes the section
@token --name         a global custom property set on :root
@var --name           a custom property scoped to a component, set inline by the generator or by script
@rule selector        what a selector, or a group of them, is for
@modifier selector    a variant of the rule above it
@state selector       a state of the rule above it, and what puts it there
@hook selector        a class or attribute that script sets or reads
@breakpoint width     what changes at a width
@see                  where the reasoning lives, usually a named rule in DESIGN.md

tools/build-docs.py reads these into docs/css.html and into the style guide's token table. Plain comments beside a declaration are left as they are: they explain one value, in place.

Tokens

assets/pages.css:37

Every color, type size, measure and spacing step on the inner pages. The palette is three ink steps (Deep Water, Night Watch, Midnight Hull), two creams (Lamp Cream, Instrument White), one blue-white tint at documented alphas, and two accents: Depth Cyan for the action and the data, and Anchor Light in reserve. Moonstruck Navy is kept for the chart field. The -rgb tokens are channel triplets for translucent steps: rgb(var(--tint-rgb) / 0.72).

breakpoint768pxMargins, vertical rhythm, gutter and nav height step down together. Vertical rhythm goes lower on a phone, not higher.

The tokens themselves are in the table above.

See DESIGN.md, Colors; The One Field Rule

Themes

assets/pages.css:191

The tokens above are the site's first theme, Night Watch. Ensign is the second: the same tokens re-pinned on a five-color scheme, Ensign Navy #1d3557, Steel Blue #457b9d, Shoal Blue #a8dadc, Sailcloth #f1faee and Signal Red #e63946. A theme is token values and nothing else; no rule below knows which one is on. Ensign is still one dark field in three steps (The One Field Rule), so every wash and scrim solved against the field keeps its shape; what it leaves alone is asset-bound (--chart-navy) or held in reserve (--amber). This block mirrors the one in assets/home.css, and the two are kept in step by hand.

hook[data-theme="ensign"]Ensign, for the element that carries it and everything inside it. On the root element it themes the page: set before first paint by the inline script in the head, from the visitor's stored choice, and by pages.js when the footer's switch is pressed. On any other element it themes that element alone, which is how the style guide draws Ensign on a Night Watch page. No attribute is Night Watch.

See DESIGN.md, Themes

Typefaces

assets/pages.css:247

The same three families as assets/home.css, served from this site (assets/fonts/), so a page load contacts nobody else for its type. Playfair Display is one variable file, italic only, because the serif is only ever italic (The Three Voices Rule). font-display is swap and the stand-ins below are metric-matched, so the swap does not re-wrap the text.

rule@font-face 'Playfair Display'Italic, 400 to 900 in one variable file. Headlines and figures.
rule@font-face 'Barlow'300, 400 and 500. Everything read at length.
rule@font-face 'IBM Plex Mono'400 and 500. The label voice.

See DESIGN.md, Typography

Font fallbacks

assets/pages.css:265

Metric-matched stand-ins, measured against the webfonts, so the swap does not re-wrap the page: until a webfont arrives, Georgia, Arial or Courier New stands in, scaled and with its ascent and descent overridden so every line break stays where it was.

rule@font-face 'Playfair Fallback'Georgia at 85.2%, measured against Playfair italic 700, the headline weight.
rule@font-face 'Barlow Fallback'Arial at 94.4%. Body copy.
rule@font-face 'Plex Mono Fallback'Courier New at 100%. The label voice.

See DESIGN.md, Typography: Roles are tokens

Base

assets/pages.css:280

The reset, the page defaults and the accessibility helpers.

rule*Border-box sizing and no default margins: every gap on the page is set on purpose.
rulehtmlSmooth in-page scrolling (made instant under reduced motion) and a scrollbar in the palette.
rulebodyDeep Water ground, Instrument White text, Barlow 400 at Body size. lining-nums site-wide, because figures here are data and are pinned rather than left to the face (The Plain Figures Rule).
rule::selectionDepth Cyan Muted with Midnight Hull text.
ruleathe tint at 0.78, Instrument White on hover. The Moonstruck Navy sections name their own link color.
rule:focus-visibleThe site-wide focus ring: 3px Depth Cyan, 3px offset. Instrument White in the footer, the nav and the drawer.
rulep, li, ddtext-wrap: pretty, so no paragraph ends on a lone word.
rule:where(h1..h4)The Heading Leads Rule: 0.4em under every heading, 1.25em over one that follows content.
ruleh1, h2, h3Playfair italic 700 (The Three Voices Rule).
rule.vhVisually hidden, still read aloud.
rule.skip-link"Skip to content", off screen until it takes focus.

Voice

assets/pages.css:322

Italic serif for things you would say, tracked uppercase IBM Plex Mono for things you would annotate, Barlow for everything you read or count.

rule.labelThe annotation voice: IBM Plex Mono 400, 12px, uppercase, 0.16em tracking.
rule.ledeThe introducing paragraph: Barlow 400 at Lead size.
rule(the two weight groups)One weight per voice. body is Barlow 400, so every label and every serif fragment that is not a heading is named here and set to 400, and the label group is also where labels take --font-label. A new label or serif class joins the matching group.
rule.between a:not(.btn), .close a:not(.btn)Prose links on the navy sections inherit Instrument White.
rulesection[id]Anchored sections stop clear of the fixed nav.

See DESIGN.md, The Three Voices Rule

Buttons

assets/pages.css:348

Square, tracked, quiet color swaps. Corners have no radius; pills are for icon-only controls. Every button is a link: the site has no forms, and every action leaves for a call, an email or the Charter Smarter booking form.

rule.btnThe base: label voice at 12px with 0.18em tracking, 18px by 26px padding, 132px minimum width, 0.25s color swaps.
modifier.btn-primaryThe action pair, --action with --action-ink text: Depth Cyan, or Signal Red in Ensign. One per view, and only for booking. On hover it keeps its color and lights a 2px Lamp Cream bezel.
modifier.btn-darkMidnight Hull at 92% with Instrument White text: Call and Email in the floating bar.
modifier.btn-skyA 1px tint outline at 45% with Instrument White text, in the larger 14px size. No page uses it now.
modifier.btn-ghostA 1px tint outline at 50% with Instrument White text, 14px: navy fields.
state:hoverDark swaps to an Instrument White fill (Midnight Hull text), and Sky to one with Deep Water text; the ghost firms its outline and takes a 10% wash.

See DESIGN.md, Buttons; The One Action Rule

Navigation

assets/pages.css:372

A fixed Night Watch bar with the logo and seven uppercase links back to the homepage's sections. At 1180px and below the links give way to the burger, which opens #mobile-menu: a Night Watch drawer from the right, marked up as a modal dialog, with a second group linking the three inner pages. The homepage's drawer is the same one, written there as inline styles; a change to either is made to both.

rule.site-navThe fixed bar. Its height is --nav-h, which other rules clear.
rule.site-nav imgThe logo in Instrument White, its water in two bands (--text-soft over --mark-sea). The file is a mask over a fill, not tinted by filter.
ruleimg.mark-seaglassThe same recolor for the mark anywhere else on a dark field (the style guide uses it).
rule.nav-links a13px label voice at 85%, full strength on hover.
state.nav-links a[aria-current]Where you are: full strength, with a 1px hairline 8px below the label, drawn as a pseudo-element because text-decoration would run on under the trailing tracking.
rule.nav-burgerA 44px target holding three wave-shaped bars (an SVG mask over an Instrument White fill).
state.nav-burger.activeThe bars cross into an X. Set by pages.js while the drawer is open.
hook.site-nav.menu-openThe drawer is open: the bar rises above it, goes transparent and hides its logo and links, leaving the burger as the close control. Written by pages.js. The bar is its own stacking context, so a z-index on the burger cannot do this.
rule#mobile-menuThe drawer: full width up to 20rem, 20px Barlow 500 links at the tint's 0.82 step. It scrolls when a screen is too short for it.
hook#mobile-menu[data-open="yes"]Displays the drawer, and the backdrop after it. Written by pages.js.
rule#mobile-menu > :first-childWith the rule after it, centers the column by auto margins, not justify-content: overflow that is centered cannot be scrolled back to, and auto margins fall to zero instead.
rule#mobile-menu .menu-groupLinks stacked with no gap: each carries 9px of padding, so a 26px line makes a 44px target and the column reads as one list. The second group sits under a hairline and a .label heading. Barlow roman, not the display face; only the current page and the hovered link take full Instrument White.
rule#mobile-menu a.btnThe drawer's Book button. It restates its ink and its leading (1.5, so 54px tall as on the homepage), because the id rule and the links' 1.1 would otherwise reach it.
rule.menu-backdropMidnight Hull at 60% over the page while the drawer is open. A click on it closes the drawer.
breakpoint73.75em1180px at the default text size. Links hide, the burger shows.
breakpoint768pxThe bar and the drawer tighten their padding.

See DESIGN.md, Navigation; Inner Page Chrome

Reveal

assets/pages.css:464

Same grammar as the homepage: slow, soft, once. A block starts 36px low and transparent, and rises over 0.8s when pages.js marks it as entered.

Entrances fail open, here and for the chart pins, the route mask and the gauges further down. Nothing is hidden unless the root element carries data-entrances, which the one-line script in the head sets and takes off again if pages.js has not started within four seconds, or fails to load at all. Without script, behind a blocker or on a connection that drops the script, the page reads in full. The attribute is matched inside :where(), so these rules keep the specificity they had.

rule.revealThe resting state, while entrances are armed.
hook[data-entrances]On the root element: "armed" by the head script, "live" once pages.js is watching. Absent, nothing waits.
hook.reveal.reveal-visibleEntered. Added once by pages.js, never removed.

Charts

assets/pages.css:482

Every chart is a viewBox crop of one drawing, assets/chart/vi-land.svg, on a Moonstruck Navy field. The SVG carries the geography and the furniture (graticule, compass rose, scale bar, title block); pins and place labels are HTML laid over it, positioned in percent, so they stay legible at any chart scale and can take focus. Chart type is set in SVG user units, so it scales with the drawing.

rule.chartThe positioning context for everything laid over the drawing.
rule.chart .shoalThe coastline once as a wide, faint halo: Instrument White at 7%, 11px.
rule.chart .landThe coastline again as land: Instrument White fill at 16% under a 1px Instrument White line.
rule.chart .gratGraticule lines every tenth of a degree. Non-scaling, so always 1px.
rule.chart .grat-labelGraticule edge labels in the label voice at chart scale.
rule.chart .rose, .rose-fill, .rose-textThe four-point compass rose and its N.
rule.chart .scale, .scale-textThe nautical-mile scale bar.
rule.chart .chart-titleThe title block: a caps line, and with .serif the chart's name in Playfair.
rule.chart .routeOne leg of a passage: a dashed Instrument White line.
rule.chart .drawThe mask that draws the routes on: one unbroken path with pathLength 1, so a dash offset of 1 hides the passage and 0 shows it.
hook.chart.is-inEntered (pages.js): the mask's offset eases to 0 over 2.6s, then pins and labels fade in, staggered by --i. The field is the sea itself: assets/opt/chart-sea-784.webp, Moonstruck's own open water seen from above and carried onto Moonstruck Navy as one hue (tools/chart-sea.py). The plate averages to exactly #061e45 and tiles seamlessly, so it lies where flat navy lay and the sea runs continuous across the satellite divider. It sits on .chart rather than the surround, so below 900px the swell pans with the land, and its 784px repeat is never scaled to the chart.
var--seaWhere this chart's window falls on the one sea: its viewBox origin, and a chart unit to a pixel. Set inline by the generator. Charts over the same water show the same water, and each inset shows its own patch.
var--iA pin's place in the entrance order. Set inline by the generator.

See DESIGN.md, Charts; The Chart Truth Rule

Chart pins and linking

assets/pages.css:532

Pins are real HTML, so they stay legible at any chart scale and can be focused. Pointing at or focusing a log entry or key row lights its leg and pin, and the rest of the chart steps back.

rule.pinA 24px Depth Cyan disc with a Midnight Hull numeral and a Moonstruck Navy ring.
state.pin:hover, .pin:focus-visible, .pin.activeInstrument White, 1.25 times the size, with a wider ring and the Control shadow.
rule.pin-labelThe place name beside a pin, in the label voice, with a Moonstruck Navy halo so it holds over coastline and imagery. Hidden from assistive technology: the pin is named.
modifier.pin-label.left, .above, .below, .belowrWhich side of the pin the label sits on. The one thing on a chart chosen by hand.
rule.chart .markA small ring for a place a leg passes through without stopping.
hook.chart[data-active]A key is lit (pages.js): routes other than .active fall to 28%, and pins other than .active to 55%.
modifier.pin-holdA pin that keeps full strength while another is lit. Available to the generator (hold=True); unused today.
breakpoint900pxPins and labels step down to the size their places were chosen at.
breakpoint768pxGraticule labels and the title block are dropped.

Chart / satellite split

assets/pages.css:564

Every chart but the itineraries hero carries a satellite plate of the same window, in register with the drawing (tools/fetch-satellite.py): chart to the left of the divider, satellite to its right. The plate lies over the drawing and under the routes, pins and furniture (.chart-over), so the passage stays plotted on both. Nothing shows until pages.js marks the chart .split-on; without script it is simply the chart.

var--splitThe divider's position in percent from the left. Written by pages.js, which measures where it rests against what is on screen; defaults to 100% (chart only).
var--knob-yHow high the knob rides on the divider, chosen per chart to clear its pins. Set inline by the generator.
var--split-delayHow long the entrance waits, so the route has drawn first. Set inline by the generator.
rule.chart > .chart-overA second SVG above the plate for whatever must stay visible on both views: the routes, and on the index charts the rose, scale bar, title block and graticule labels.
rule.chart .satThe plate, clipped from the left by --split.
rule.chart .sat imgcolor: transparent keeps the alt text from being drawn across the chart while a plate loads.
rule.split-lineA 22px-wide grab strip drawing a 1px Instrument White line. touch-action: pan-y, so the page still scrolls past it.
rule.split-knobA 40px round control in the Button Dark vocabulary, with the Control shadow since it sits on imagery. Its ::after grows the hit area by 5px; it is clamped 26px inside either edge.
state.split-knob:hover, .chart.is-dragging .split-knobSolid Instrument White with Midnight Hull chevrons.
hook.chart.split-onScript is running: the plate, line and knob are displayed.
hook.chart.split-liveThe line and knob are visible.
hook.chart.split-introThe entrance is in flight: --split changes ease over 1.5s, after --split-delay.
hook.chart.is-draggingA pointer holds the divider: the resize cursor everywhere, and pins stop taking the pointer.

See DESIGN.md, The chart / satellite split

Chart panning and key

assets/pages.css:609

On a phone and a tablet a chart keeps a minimum width and pans sideways inside its scroller, opening on its subject (pages.js, data-focus-x) with its divider resting in the middle of what that leaves on screen. Its labels are placed by hand against the width it is drawn at, so squeezing it below that width runs them into each other and off its right edge: it pans instead.

rule.chart-scrollThe scroller. Clipped on desktop, horizontally scrollable at 900px and below, where pages.js makes it a tab stop so the arrow keys pan it.
var--chart-minThe width a chart keeps at 900px and below. Set inline by the generator, per chart.
rule.chart-hint"Drag the chart to pan": below 900px only.
rule.chart-keyThe key under the hero chart: a dashed sample line, and .fine for the "Not for navigation" line.
breakpoint900pxWhere the labels come clear on their own: the itineraries hero is drawn 870 units wide, and its two closest names ("Cane Garden Bay" and "The Baths") stop touching just above that. Measured, not chosen.
breakpoint768pxThe graticule labels and the title block are dropped. The pan itself starts at 900px.

Itineraries: hero

assets/pages.css:643

The itineraries page opens on a full-width chart of the seven-night passage, with the headline set over its open sea. This is the one chart with no satellite plate, because type sits on it.

rule.hero-chartThe Moonstruck Navy field. Its top padding clears the fixed nav.
rule.hero-chart .hero-copyThe headline block, laid over the chart's empty north-west with a navy halo on its type.
rule.hero-chart .hero-actionsTwo ghost buttons to the plans.
rule.hero-chart .chartOn a phone, cropped by the scroller to a band: 230 of the chart's 560 viewBox units come off the top, because the copy has left the open sea and a screen of empty water stood between the buttons and the islands. The cut takes Anegada, the compass rose and the passage north; nothing comes off the foot, where land reaches to within 5 units of the edge.
breakpoint768pxThe chart is 870px wide whatever the window is, so the cut is flat pixels.
breakpoint900pxThe chart holds its 870px and pans, so the hint under it shows on a tablet as well as a phone.
breakpoint1100pxThe copy leaves the chart and sits above it, without the halo.

Itineraries: passage plans

assets/pages.css:681

One section per sample passage: a head (title, lede, four facts), then a sticky chart beside the day-by-day log, then "how the week leans". Both plans sit on the page field.

rule.planThe section.
modifier.plan.frostThe page field as the ground, written out.
rule.plan-innerThe full nine-column measure, centered.
rule.plan-headFive columns of title and lede against the facts.
rule.plan-factsFour figures in Playfair, each over a label-voice caption, above a hairline.
rule.plan-bodyFour columns of chart against five of log, with a derived alley, so both sit on column lines.
rule.plan-chartThe chart column. Sticky below the nav while the log scrolls past.
rule.plan-chart .matThe chart's frame. A Moonstruck Navy plate on the page field, naming its own ink as .hero-chart and .index do.
rule.plan-chart .chart-capThe caption under the mat: "Numbered by leg" and "Not for navigation".
breakpoint1151pxBelow the full grid the two columns go fluid: 5fr to 6fr with an 8% alley.
breakpoint900pxOne column; the chart stops being sticky.
breakpoint768pxSection padding drops to --section-y; facts and titles step down a size.

Itineraries: the log

assets/pages.css:719

One entry per leg: a numbered day beside a title ("from, arrow, to"), the time under sail, a paragraph, and the day's planning tags.

rule.legAn entry: a 96px day column and the text, between hairlines.
hook.leg.activeIts leg is lit on the chart (pages.js): an Instrument White wash from the left, and the numeral grows.
rule.leg::beforeThe lit wash: the row's own layer, not its background, so it can lead the day column by --leg-lead without moving the hairline or the day column off the log's grid line.
var--leg-leadHow far the wash leads the day column: 20px into the alley beside the chart, and the page margin (--section-x) once the log spans the measure, so the lit row breaks the margin.
rule.leg-numThe leg's number in a 30px Moonstruck Navy disc, matching its pin on the chart.
rule.leg-when"Day 3, Monday" in the label voice.
rule.leg-titleTitle size. .to is the arrow between the two places; a visually hidden " to " is read in its place.
rule.leg-sailTime under sail, with the sail glyph.
rule.tagsPlanning tags: the crew's own pictograms from assets/icons/tags.svg at 24px, filled with currentColor (the tint at 0.66 on a panel, Depth Cyan on a chart field). Each is named by its title and an accessible label.
modifier.tags.legendThe key to the set, where each pictogram carries a visible label.

Itineraries: how the week leans

assets/pages.css:763

The mix: relative emphasis, from the charter's own planning sheet. Five gauges on a scale ruled in quarters.

rule.mixThe block: a title and lede against the gauges.
rule.gaugeOne row: a label and its track.
rule.gauge-trackThe ruled scale: a tick every 25%.
rule.gauge-fillThe reading. Scales in from the left over 1.2s, each row 0.1s after the one above.
var--vThe reading in percent. Set inline by the generator.
hook.gauges.is-inEntered (pages.js): the fills run.

Itineraries: reading the plan

assets/pages.css:809

The notes block under the two plans. A hairline marks the seam, as it does above .more.

rule.notesThe section: a heading and lede against a definition list.
rule.notes dt, ddA term in Playfair at Title S, and its explanation at 50ch. The term leads by --head-lead and trails by --head-trail (The Heading Leads Rule), not on a grid gap.
rule.notes .tagsThe full legend of pictograms in three columns, two at 480px, one at 360px.

Destinations: photo hero

assets/pages.css:840

The two destination pages open on a photograph under a scrim that deepens toward the foot, where the headline sits.

rule.hero-photoThe field: at least 72vh (capped at 820px), its copy at the bottom left.
rule.hero-photo::afterThe scrim: Deep Water at 94% at the foot, 70% two fifths of the way up and 36% at the top. The top is what holds the headline over a pale sky; solve it against the brightest pixels behind the copy in both photographs, and again whenever either changes.
rule.hero-photo .hero-copyThe headline, lede and two ghost buttons, on the mid measure.

Destinations: chart index

assets/pages.css:860

A Moonstruck Navy section holding the country's chart with every featured anchorage numbered, and a key beside it. Key rows and pins light each other (pages.js).

rule.indexThe Moonstruck Navy section.
rule.index-headFive columns of heading against the lede.
rule.index-bodySix columns of chart against three of key.
modifier.index-body.wideA chart too wide to share a row (the USVI): the chart takes the full measure and the key runs in three columns under it.
rule.index .matThe chart's frame: a 1px Instrument White line at 20%.
rule.keyThe numbered list of anchorages. Each row is one link to its entry.
rule.key .kThe numeral, in the pin's colors.
rule.key .name, .whereThe anchorage in Playfair, and its island in the label voice.
hook.key a.activeIts pin is lit on the chart.

Destinations: pilot entries

assets/pages.css:897

One entry per anchorage, written like a pilot book: a chart inset beside a number, a name, a place line, a paragraph or two, and what the anchorage is good for. Entries alternate sides.

rule.entriesThe Deep Water section.
rule.entryFour columns of inset against five of text, with a derived alley.
rule.entry:nth-child(even)The mirror image: text first, inset second.
rule.entry-numThe anchorage's number, matching its pin on the index chart.
rule.entry .whereThe place line, in the label voice.
rule.entries-creditThe imagery credit, composed per page from the sources its plates came from.
breakpoint1151pxThe columns go fluid.
breakpoint800pxOne column, inset first.

Chart insets

assets/pages.css:923

The inset: the boat section's panel mat, carrying a chart plate instead of a photograph. A 4:3 crop of the one drawing at anchorage scale, with the pin where the boat lies, a north arrow, a one-mile bar, and a caption lip giving the coordinates. It can be swapped for a photograph in the same mat without changing the page.

rule.insetThe Night Watch mat, with the Float shadow. It wraps the plate on three sides.
var--matThe mat's width: 16px, 14px at 800px and below.
rule.inset figcaptionThe lip: "Chart inset" and the coordinates, in the label voice.
rule.inset .northThe north arrow, with a soft Midnight Hull cast so it holds over reef and sand.
rule.inset .scalebarThe one-mile bar.
var--nmOne nautical mile as a fraction of the inset's width. Set inline by the generator.

Anchorage map opener

assets/pages.css:948

The inset opens Apple's satellite map at its pin: the whole plate is the target, and this round control is its keyboard and screen-reader handle, in the divider knob's vocabulary. A link until a MapKit token is set.

rule.inset .chartThe zoom-in cursor over the whole plate.
rule.inset-openA 34px round control at the plate's top left. Its ::after grows the hit area by 6px all round.

Map dialog

assets/pages.css:964

The homepage lightbox's field and close button, holding a live map instead of a photograph. Emitted only when a MapKit token is set, so dormant today.

rule.mapboxThe Midnight Hull field at 96%. Hidden with visibility as well as opacity, so nothing inside it can take focus while it is closed.
hook.mapbox[data-open="yes"]Open. Written by pages.js.
rule.mapbox-frameThree rows: heading, map, foot.
rule.mapbox-numThe anchorage's number, in the pin's colors.
rule.mapbox-mapThe box MapKit draws into.
rule.mapbox-failThe fallback line laid over the map when MapKit has not initialized.
rule.mapbox-foot"Open in Apple Maps", always present, beside "Not for navigation".
rule.mapbox-closeA 48px solid Instrument White close button with the Control shadow, fixed to the corner.

Photo band

assets/pages.css:1000

A quiet full-bleed plate between the entries and the notes.

rule.bandThe plate: between 320px and 640px tall, the photograph covering it.
rule.band figcaptionA square Midnight Hull caption plate at the bottom left: the badge vocabulary.

Between the anchorages

assets/pages.css:1011

Practical notes on the sea: the same plate that lies under every chart, at the same size, so the field here is the water the charts are drawn on rather than a second kind of texture. It averages to Moonstruck Navy exactly, so the type keeps the contrast it had on the flat colour and needs no wash over it.

rule.betweenThe field.
rule.between-innerA heading and lede against a definition list, as in .notes.

See DESIGN.md, The Sea Field Rule

Also on the chart

assets/pages.css:1037

Cross links between the three inner pages: the other two, each a serif title with an arrow over a one-line description.

rule.moreThe Deep Water section, under a hairline.
rule.more aOne link. .t is the title and arrow, .d the description.

Closing

assets/pages.css:1056

Same field as the homepage's booking section: the sea plate, centered type, and the page's one booking button above a row of secondary actions.

rule.closeThe field, on the emphasis step --section-y-xl.
rule.close h2The page's last word, and the one place the headline scale steps up (to 68px).
rule.close .ledeSet in the body sans here, not Playfair: a closing paragraph, not an introduction.
rule.close .rowA centered row of buttons.

Page not found

assets/pages.css:1076

404.html: one sea section the height of the window, with a lifebuoy riding three lines of swell above the headline. The host serves it at whatever address was asked for, at any depth, so tools/chart-pages.py writes every reference in it from the site root.

rule.lostThe field: the sea plate at its one size, clearing the fixed nav, its copy centered.
rule.lost-seaThe drawing. Its sides are masked away so the swell has no edge to run off.
rule.lost-buoyThe lifebuoy: it rides up and down and rolls a few degrees about its own center.
rule.lost-ringThe ring, in Instrument White.
rule.lost-bandIts four bands, in Signal Red (--band): a lifering is red and white in either theme.
rule.lost-ropeThe grab line around it, in the tint.
rule.lost-waveOne line of swell, drawn two wavelengths wider than the window and sliding one wavelength (--run) per cycle, so the loop has no seam.
rule.lost-waterThe water in front of the buoy: the chart field's navy, fading out below, so the ring sits in the sea.
rule.lost h1The headline, at the closing section's scale.
rule.lost .ledeThe message, in the body sans on the four-column measure.
rule.lost .rowThe one way home: a ghost button, because the filled action is kept for booking.
var--bandSignal Red, Ensign's action color, pinned here so the bands are as red in Night Watch, which has no red of its own.
var--runThe wavelength of a line of swell, which is how far it slides per cycle.

See DESIGN.md, Sea Sections

Floating action bar

assets/pages.css:1192

Call, Email and Book, mirroring index.html: a column on the right edge, a full-width row along the bottom on a phone.

rule.float-ctaHidden: transparent, 16px low, out of the pointer's way, and out of the tab order (visibility, not opacity alone).
hook.float-cta[data-shown="yes"]Shown. Written by pages.js once the hero has passed, and cleared over the closing section.
var--cta-vvHow far a phone's toolbar or keyboard has pushed the visible bottom edge up. Written by pages.js.

Reduced motion

assets/pages.css:1209

Every animation's counterpart. Nothing slides, draws or fills: reveals become a short fade, routes and pins are simply there, gauges are full, the split and the dialog change at once. A new animation anywhere in this file needs a line here.

Static capture

assets/pages.css:1229

Static capture switch (?static in the URL): every entrance resolved, for screenshots and print. pages.js sets the class before anything else runs.

hookhtml.staticEvery entrance is forced to its end state, with !important, and no transition.

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