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 primary booking action now 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(48px, 8vw, 92px)Display: the homepage hero headline only.
--type-headlineclamp(38px, 9vw, 58px)Headline: section titles.
--type-subheadclamp(30px, 7vw, 44px)Subhead: a second heading inside a section.
--type-title-lg34pxTitle L: closing index headings and the homepage's wide plate names.
--type-title28pxTitle: every card and entry heading.
--type-title-sm24pxTitle S: small titles, and every tile title on a phone.
--type-lead19pxLead: the one paragraph that introduces a section, set in the body face.
--type-serif20pxSerif text: serif running text inside a component.
--type-body17pxBody: Barlow 400 paragraphs.
--type-body-navy18pxBody on ink: one step up, because light text on a dark field reads smaller. The closing paragraph; the homepage's destination blurbs.
--type-small14pxSmall: secondary facts and credits.
--type-label12pxLabel: 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 the 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,

Font fallbacks

assets/home.css:29

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:39

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

rule*Border-box sizing and no default margins.
rulebodyInk 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 on Deep Water (falls back to Moonstruck Navy); Instrument White on the footer, the menu, #destinations, #sailing-school, #book and the scrolled nav.
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-outline]:hoverThe outlined buttons on ink 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:67

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:114

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, not by home.js, so a photograph that is 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. Said outright by the same onload attribute, from the paint timeline: a transition starts from the last computed style, not the last painted one, so being early was not enough to skip it. The timeline trails the paint itself by up to a tenth of a second on a slow phone; a photograph inside that gap appears at once as well. 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 and the section shows its background color, so the five of them, a megabyte together, are not fetched with the page. Only where script runs, as on the inner pages: with script off, or in a browser that does not know (scripting: enabled), they load with the page. Screen only: 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:163

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 ink bar with Instrument White links, the form the inner pages always use.

rulenavThe bare state, over the hero: no background and no blur.
hooknav.scrolledThe ink 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. Nav is not a place for the display face: seven Playfair italic lines shouted louder than anything they led to. Its styles are inline; only its phone padding is here.
breakpoint1180pxLinks 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:245

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:288

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.

rule.revealThe resting state.
hook.reveal[data-revealed="yes"]Entered. Written once by the reveal observer in home.js, never removed.

Hero

assets/home.css:304

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 photograph is inside the stage, not the header, so it stops at the stats strip's top edge rather than running on beneath it. The headline reads on the left, over open water, so an ink haze runs from the left edge to the end of the headline's first line (--hero-copy-right) and is gone by 62vw, which leaves the boat and the water around it untouched. Under 600px the copy leaves the photograph altogether, so the haze is dropped there, and the boat is centred in the band instead of three quarters across it: the band's cut holds the boat at its middle and is pinned at 50%.

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). The copy and the badge are its two flow items, apart by at least the headline's trailing margin (--head-trail), and the badge's auto top margin pushes it to the foot. A window about 900px to 1300px wide and under 720px tall has too little room for the headline's three lines and the badge's two, so the stage grows to hold them, by up to 78px; a fixed height put the badge over the headline's last line. The stage is never shorter than the floor, so the cuts' guarantee holds. A grown stage draws the plate wider than --hero-plate-w allows for, and the bow lands 0.156px further left for each pixel of growth: 12px at the worst, which leaves 12px of the copy's 24px margin.

The copy stops short of the boat rather than trusting the eye: --hero-boat-left works out where its bow sits in the box. cover draws the plate --hero-plate-w wide (the sizes attribute in index.html says the same thing), object-position pins the plate's 75% to the box's 75%, and the boat reaches 5.2% of the plate's width to the left of that. The narrower the window the wider cover draws the plate and the further left the boat lands, so the headline wraps sooner. 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: ink 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 ink.
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:359

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:389

The cards carry a Deep Water panel: the tint at 0.66 is a Deep Water-surface color and 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, 16px 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: Steel Ink on Instrument White is 7.3:1, so the label is legible.
breakpoint920pxOne column, capped at 440px.

See DESIGN.md, The Photographic Field Rule

The sailing school

assets/home.css:414

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, Sky Tint.
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:451

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:472

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:533

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.
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.
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:584

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 amber 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 6% ink wash 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 3% ink wash. 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 open window being pointed at, focused or chosen: ink, Instrument White numeral.
hook[data-cal-grid][data-slow="yes"]A chosen window's ten seconds are up, and its days fade back over 1.2s.
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.

See DESIGN.md, The Calendar Legibility Rule

Tokens

assets/home.css:641

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:740

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, which is what keeping raw colors out of the rules pays for. Ensign is still one dark field in three steps (The One Field Rule), so every wash and scrim that was 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/pages.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 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. Reader, the third, is not a palette and is not here: it is the last section of the file.

See DESIGN.md, Themes

Reduced motion

assets/home.css:797

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:814

The page is ordinary HTML, so it reads without home.js; these rules undo the few states that wait for it. Entrances are resolved and 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.

Reader

assets/home.css:828

The third theme, and not a palette: the site with its design rolled back to the browser's own stylesheet. One column on the system canvas, in the browser's default font, colors and links, so it follows the visitor's light or dark setting and their own font size. No background images, no panels, nothing fixed or sticky, and no entrances. It is the one exception to The Theme Is Tokens Rule, and it has to come last in the file.

What is left alone is whatever is a plate of its own: the two dialogs (the plate zoom and the photo viewer), inline icons, and visually hidden text. A plate keeps Night Watch's tokens, because Reader pins none. Everything after the roll-back gives a component the rendering its native element would have had. Every declaration is !important, because the roll-back is. assets/pages.css carries the same section for the inner pages, addressed by its classes.

hook:root[data-theme="reader"]Reader is on. Set the way Ensign is: by the line in the head, and by the footer's switch.

See DESIGN.md, Themes

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 exactly, and the two must be kept in step (tools/build-docs.py reports any token whose value differs between them).

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 two 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 Three Pins Rule, The Column Span Rule, The One Margin Rule).
  • A section is ink (text in Instrument White) or Deep Water (text in ink or Instrument White). A component used on both grounds follows its Deep Water rules and re-inks them in Instrument White steps (The One Field Rule). Nothing does today: .between and .close are ink throughout, and the plans are Deep Water.
  • 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:38

Every color, type size, measure and spacing step on the inner pages. The palette is three pinned colors (Moonstruck Navy, Deep Water, Lamp Cream and Instrument White) plus Anchor Light; every other color is a lightness step inside one of those families, computed to hit a contrast target. 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 Three Pins 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, which is what keeping raw colors out of the rules pays for. Ensign is still one dark field in three steps (The One Field Rule), so every wash and scrim that was 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. Reader, the third, is not a palette and is not here: it is the last section of the file.

See DESIGN.md, Themes

Font fallbacks

assets/pages.css:249

Metric-matched stand-ins, measured against the webfonts, so the swap does not re-wrap the page. The webfonts load asynchronously with display=swap; until they arrive Georgia and Arial stand in, scaled and with their 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:264

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, Moonstruck Navy on hover. Right for Deep Water only: a ink surface names its own link color.
rule:focus-visibleThe site-wide focus ring: 3px Depth Cyan, 3px offset. Instrument White on the ink surfaces, where Depth Cyan is too quiet.
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:306

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 ink inherit the field's Instrument White.
rulesection[id]Anchored sections stop clear of the fixed nav.

See DESIGN.md, The Logbook Voice Rule; The One Weight Per Voice Rule

Buttons

assets/pages.css:332

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

rule.btnThe base: label voice at 12px with 4px 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.
modifier.btn-darkMidnight Hull at 92% with Instrument White text: Call and Email in the floating bar.
modifier.btn-skyDepth Cyan Muted with Midnight Hull text, in the larger 14px size: secondary actions on ink.
modifier.btn-ghostA 1px Instrument White outline at 50% with Instrument White text, 14px: ink fields only.
state:hoverEvery filled button swaps to a Instrument White fill with Midnight Hull text; the ghost firms its outline and takes a 10% fill.

See DESIGN.md, Buttons; The One Amber Rule

Navigation

assets/pages.css:356

Inner pages open in the scrolled ink state; there is no light hero to sit over. A fixed ink 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, value for value, 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. Its file is used as a mask over a fill that turns from --cream-bright to --text-soft at 75% of the height and to --mark-sea at 86.2%, not tinted by filter.
ruleimg.mark-seaglassThe same recolor for the mark anywhere else on ink (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 drawn 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 a 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. A z-index on the burger cannot do this: the bar is its own stacking context, so the drawer covered the burger whatever it said.
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 (a phone on its side).
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: centered overflow cannot be scrolled back to, and auto margins fall to zero instead.
rule#mobile-menu .menu-groupA group of links, stacked with no gap: each link carries 9px of its own padding, so a 26px line makes a 44px target and the column reads as one list rather than seven separate headings. The second group sits under a hairline and a .label heading. Nav is the label's job, not the display face's: seven Playfair italic lines at 30px shouted louder than anything they led to, and the long ones wrapped. Barlow roman is the quiet voice that still fits "British Virgin Islands" on one line at 20rem. Only the current page and the hovered link take full Instrument White, so the panel has a hierarchy instead of seven equal shouts.
rule#mobile-menu a.btnThe drawer's Book button. It restates its ink because the id rule above outranks .btn-primary, and its leading (1.5, so 54px tall as on the homepage) because the links' 1.1 would reach it too.
rule#mobile-menu .btn svgIts calendar, 15px like the floating bar's. Unsized, it grew to fill the line.
rule.menu-backdropMidnight Hull at 60% over the page while the drawer is open. A click on it closes the drawer.
breakpoint1180pxLinks hide, the burger shows.
breakpoint768pxThe bar and the drawer tighten their padding.

See DESIGN.md, Navigation; Inner Page Chrome

Reveal

assets/pages.css:451

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. Without script it stays hidden.

rule.revealThe resting state.
hook.reveal.reveal-visibleEntered. Added once by pages.js, never removed.

Charts

assets/pages.css:462

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 whole 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 without re-tinting anything: the sea still runs continuous across the satellite divider. It sits on .chart rather than on the surround, so below 900px the swell pans with the land it surrounds instead of sliding under it. Its 784px repeat is a fixed size, never scaled to the chart, because it is the water the chart is drawn on and not part of the drawing.
var--seaWhere this chart's window falls on that one sea: its viewBox origin, a chart unit to a pixel, set inline by the generator. The plate tiles, so the offset changes nothing about how it lies; it is what makes the site's charts windows onto one body of water rather than thirty copies of the same corner of it. Charts over the same water show the same water, and the twelve insets on a page each show their 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:517

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 ink 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 ink halo so it holds over coastline and satellite 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 fall 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 a size, the size their places were chosen at on a chart held to its drawn width.
breakpoint768pxGraticule labels and the title block are dropped.

Chart / satellite split

assets/pages.css:551

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 (still read aloud) 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 all round. 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:597

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. A chart's 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, at the label size the placement was made for.

rule.chart-scrollThe scroller. Clipped on desktop, horizontally scrollable at 900px and below.
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:632

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 ink 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 ink 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 the chart keeps for it and a screen of empty water was all that stood between the buttons and the islands. The cut takes Anegada, the compass rose and the passage north, which runs off the top edge; 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:671

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 are Deep Water today; the ink variant is kept working beside every rule.

rule.planThe section.
modifier.plan.panelDeep Water ground.
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 the rule tint 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 ink plate inside a Deep Water plan: it names its own ink, as .hero-chart and .index do, so nothing in it inherits the plan's Midnight Hull.
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:711

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 the rule tint hairlines.
hook.leg.activeIts leg is lit on the chart (pages.js): a Instrument White wash from the left, and the numeral grows.
rule.leg::beforeThe lit wash. It is the row's own layer, not its background, so it can lead the day column by --leg-lead without taking 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 itself once the log spans the measure, so the lit row breaks the margin (--section-x).
rule.leg-numThe leg's number in a 30px ink 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 Deep Water, Depth Cyan on ink). 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:755

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:800

The notes block under the two plans. Both plans are Deep Water now, so a the rule tint 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:832

The two destination pages open on a photograph under a ink 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: ink 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:852

A ink 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 ink 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:889

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:915

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 ink 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:940

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:956

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:992

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:1003

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:1028

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 the rule tint hairline.
rule.more aOne link. .t is the title and arrow, .d the description.

Closing

assets/pages.css:1047

Same field as the homepage's booking section: the sea plate, centered type, and the page's one amber 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:1064

404.html: one sea section the height of the window, with a lifebuoy riding three lines of swell above the headline. The host serves this page 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. Each is drawn two wavelengths wider than the window and slides 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 rather than on it.
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:1176

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, and out of the pointer's way.
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:1193

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:1212

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.

Reader

assets/pages.css:1227

The third theme, and not a palette: the site with its design rolled back to the browser's own stylesheet. One column on the system canvas, in the browser's default font, colors and links, so it follows the visitor's light or dark setting and their own font size. No background images, no panels, nothing fixed or sticky, and no entrances. It is the one exception to The Theme Is Tokens Rule, and it has to come last in the file.

What is left alone is whatever is a plate of its own: the charts (their drawing, pins and satellite split), the dialogs, inline icons, and visually hidden text. A plate keeps Night Watch's tokens, because Reader pins none. Everything after the roll-back gives a component the rendering its native element would have had. Every declaration is !important, because the roll-back is. assets/home.css carries the same section for the homepage, addressed by its data attributes.

hook:root[data-theme="reader"]Reader is on. Set the way Ensign is: by the line in the head, and by the footer's switch.

See DESIGN.md, Themes

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