S/V MoonstruckStyle guide

Style guide

Every color, size, component and rule of the S/V Moonstruck site, shown as it ships. The specimens are the real components, and the numbers are read from the source.

65°06′W65°00′W64°54′W64°48′W64°42′W64°36′W64°30′W64°24′W64°18′W18°42′N18°36′N18°30′N18°24′N18°18′NN5 NAUTICAL MILESVirgin IslandsA SEVEN-NIGHT PASSAGE

Drag the chart to pan

The itineraries hero chartNot for navigation

The system is called The Chartroom Logbook: a navigator's chart table made elegant. Deep ink fields printed with charts, and navy fields that are the sea itself, alternate with pale panel pages; a classical serif speaks, and small tracked capitals annotate.

This page documents what index.html, assets/pages.css and tools/chart-pages.py actually ship. DESIGN.md holds the reasoning behind every rule named here, and where the two disagree the source is the truth. Token values and contrast ratios are written into this page by tools/build-docs.py; several specimens are lifted by it straight out of the generated pages.

Palette

Three pinned colors and one amber. Every other color is a lightness step inside one of the three families, computed to hit a contrast target, not picked by eye.

The palette is entirely cool on purpose. Anchor Light is the only warm thing on the page, which is what gives it its force. A new color means a new step in an existing family, or a new pin, and a new pin is a decision for the client.

Field

Moonstruck Navy#061e45
Night Watch#07101d
Midnight Hull#010509

Structure

Instrument White#eef3ff
the tint at 0.78rgb(200 225 255 / 0.78)
the tint at 0.66rgb(200 225 255 / 0.66)
Depth Cyan#00c8e8
the tint at 0.66rgb(200 225 255 / 0.66)

Water

Depth Cyan Muted#00b2d8
Depth Cyan#00c8e8
the rule tintrgb(200 225 255 / 0.12)
Night Watch#07101d

Light

Instrument White#eef3ff
Midnight Hull#010509
Deep Water#040c16
Night Watch#07101d

The one amber

Anchor Light#faa80f

What each color is for

ColorTokenRole
Moonstruck Navy Pinned--chart-navyThe anchor. Every ink field and every chart field, photo wells, the browser's theme color.
Night Watch--ink-panelThe darker ink step: the field behind the destination card photos.
Midnight Hull--ink-wellText on panel, dark buttons, badges, overlays and the footer.
Instrument White Pinned--cream-brightThe charcoal. Body and secondary text on panel, the nav links over the hero, fine linework.
the tint at 0.78--text-softSupporting text and the base link color. Deep Water surfaces only.
the tint at 0.66--text-muteMuted small print. Deep Water surfaces only.
Depth Cyan--cyanThe focus ring, chosen to clear 3:1 against ink, Deep Water and Instrument White at once. Small accents.
the tint at 0.66--text-muteTracked uppercase annotations on Deep Water: places, days, captions. Deep Water surfaces only.
Depth Cyan Muted--cyan-mutedSecondary action fill with Midnight Hull text; gauge fills; the on-hold stripe; text selection.
Depth Cyan--cyanHighlights on ink: chart pins, key numerals, the weather reading. Open days in the calendar.
the rule tint--line-ruleHairlines. Quiet on purpose: it marks an edge, not a boundary anyone has to read.
Night Watch--ink-panelQuiet panels, and the base of the calendar's on-hold stripe.
Instrument White Pinned--cream-brightThe pale. The text color on every ink field, small chrome on ink, the hover fill of dark buttons.
Midnight Hull--ink-wellWhat shows behind a photograph while it loads.
Deep Water--ink-pageThe one light section surface. A background only, never a text color.
Night Watch--ink-panelThe polaroid mat around plates and chart insets, and caption panels.
Anchor Light--amberHeld in reserve; the primary booking action now takes Depth Cyan.

Translucent steps are written from the channel tokens, for example rgb(var(--tint-rgb) / 0.72). Instrument White runs from 0.12 (borders) to 0.95 (halos); Midnight Hull from 0.72 to 0.96 for overlays. Functional labels never drop below 0.72.

  • The Three Pins RuleThe palette derives from #061e45, #eef3ff and #eef3ff, plus Anchor Light. Reference colors through the tokens, never as raw hex.
  • The One Amber RuleAmber marks the single most important action in a view, the Charter Smarter booking link. Never decoration, text or tags, and never two competing in one viewport: the floating bar hides itself while the closing section's amber button is on screen.

Two worlds and contrast

Every section is either ink with Instrument White ink, or Deep Water with ink and charcoal ink. There is no white section and no gray one.

The site has one color scheme and does not change with the device's light or dark setting (color-scheme: light). The two worlds do the work a theme would: ink for atmosphere (stats, destinations, the sailing school, booking), panel for detail and decisions (gallery, layout, CAPTAIN360, rates, availability).

On ink

A week has shape and generosity.

Body copy takes Instrument White at 0.86. Prose links inherit the field's ink.

Annotation at 0.72

On Deep Water

A week has shape and generosity.

Body copy takes Instrument White. Prose links take the tint at 0.78, and Moonstruck Navy on hover.

Annotation in the tint at 0.66

Measured contrast

Computed from the shipped token values by tools/build-docs.py, with a translucent ink composited over its field first, and rounded down to one decimal so that no pair is ever rounded up over a threshold. Text needs 4.5:1; large text and non-text marks such as the focus ring need 3:1.

SamplePairRatioUsed for
Aa Instrument WhiteInstrument White on Deep Water17.6:1Body copy, lists and UI text on the page field.
Aa Lamp CreamLamp Cream on Deep Water17.0:1Headlines and every large figure.
Aa Instrument WhiteInstrument White on Night Watch17.1:1Text on panels, cards and the navigation bar.
Aa Instrument WhiteInstrument White on Midnight Hull18.4:1Text in wells, the footer and photo mats.
Aa Instrument WhiteInstrument White on Moonstruck Navy14.7:1Type over a chart field.
Aa at 0.78Tint at 0.78 on Deep Water9.0:1Supporting text and section leads.
Aa at 0.66Tint at 0.66 on Deep Water6.6:1Muted small print and the tracked label voice.
12 at 0.55Tint at 0.55 on Deep Water4.9:1The text floor: a booked or turnaround day.
Aa Anchor LightAnchor Light on Deep Water9.9:1The conversion accent set as text.
Aa Depth CyanDepth Cyan on Deep Water9.7:1Data, chart furniture and the focus ring.
Aa BookDeep Water on Anchor Light9.9:1The amber pairing, held in reserve.
7 pinDeep Water on Depth Cyan9.7:1Chart pins, key numerals and a lit day.
Aa chosenDeep Water on Lamp Cream17.0:1Inverted controls and a hovered action.

Pairs that are never used

Recorded so that nobody reaches for them. Each has cost a bug before.

SamplePairRatioInstead
Instrument White on Anchor Light1.7:1Anchor Light always takes Deep Water.
Instrument White on Depth Cyan1.8:1Depth Cyan always takes Deep Water.
Rust on Deep Water4.3:1Under the floor. Rust is a wash and a border, never type.
Rule tint on Deep Water1.2:1A rule colour. Text takes the 0.55 step or better.
  • The Two Worlds RuleEvery section is ink or light. Deep Water is the one approved light surface; Instrument White stays available as a panel fill that reads as a panel.
  • The Photographic Field RuleA light section that carries a photograph (CAPTAIN360, rates) is still a Deep Water section. It is washed with rgb(var(--ink-page-rgb) / α), and α is solved so Instrument White clears 4.5:1 over the photograph's darkest pixel: 0.74 on CAPTAIN360, 0.64 on rates. the tint at 0.78, the tint at 0.66 and the tint at 0.66 never sit on the wash; they get a Deep Water panel. Re-measure when a photograph changes.
  • The Calendar Legibility RuleEvery calendar day state clears 4.5:1, and state is never carried by color alone: each day has hidden text, and on-hold days keep their stripe.

Themes

Everything else in this guide is Night Watch, the theme a visitor arrives in. Ensign is the second: the same tokens, pinned again on red, white and two blues over navy.

A visitor chooses a theme with the switch in the footer of every page, and the choice stays in their browser. A color theme is one block of token values, [data-theme="ensign"], and nothing else: no rule asks which theme is on. On the root element the attribute themes the page. On any other element it themes what is inside it, which is how the plate below is drawn in Ensign on a Night Watch page. The values and ratios in it are filled from Ensign's block by tools/build-docs.py, the way the rest of the guide is filled from :root.

Ensign is a second theme of the same world, not a light build. The darkest of its five colors is the page field, so every section is still one of three dark steps, and every wash that was solved for light type on a dark field keeps its shape. A third theme, Reader, has no colors to show here: it takes the design off and hands the page to the browser's own stylesheet, on the system canvas.

Field

Ensign Navy#1d3557
the panel step#234062
the well#132339
Moonstruck Navy, kept#061e45

Type

Sailcloth#f1faee
the tint at 0.82rgb(241 250 238 / 0.82)
the tint at 0.7rgb(241 250 238 / 0.7)
the tint at 0.6rgb(241 250 238 / 0.6)
the rule tintrgb(241 250 238 / 0.16)

Data and action

Shoal Blue#a8dadc
Shoal Blue Muted#8abec9
Signal Red#e63946
its label ink#09101b
Steel Blue, as the mistrgb(69 123 157 / 0.25)

The three steps

On the panel step

A week has shape and generosity.

Body copy and headlines both take Sailcloth, because the scheme has one white. Prose links take the tint at 0.82.

Annotation in the tint at 0.7

In the well

A week has shape and generosity.

The recessed step: the footer, badges, and what shows behind a photograph while it loads.

The text floor, the tint at 0.6

On Ensign Navy: the one booking action in Signal Red with its own label ink, beside .btn-dark and .btn-ghost. The same markup as under Buttons and controls; only the tokens differ.

Measured contrast

Measured on Ensign's own values, by the same arithmetic as the table above. The tint steps sit higher than Night Watch's (0.82, 0.7 and 0.6 against 0.78, 0.66 and 0.55) because the field is lighter, and they are measured on the panel step, which is the worst of the three fields.

SamplePairRatioUsed for
Aa SailclothSailcloth on Ensign Navy11.5:1Headlines, body copy and UI text on the page field.
Aa SailclothSailcloth on the panel step9.9:1Text on panels, cards and the navigation bar.
Aa SailclothSailcloth in the well14.8:1Text in wells and the footer.
Aa SailclothSailcloth on Moonstruck Navy15.3:1Type over a chart field, which no theme re-tints.
Aa at 0.82Tint at 0.82 on the panel step7.2:1Supporting text and section leads.
Aa at 0.7Tint at 0.7 on the panel step5.7:1Muted small print and the tracked label voice.
12 at 0.6Tint at 0.6 on the panel step4.7:1The text floor: a booked or turnaround day. No rule sets type lower.
Aa Shoal BlueShoal Blue on Ensign Navy8.0:1Data, chart furniture and the focus ring.
7 pinThe well on Shoal Blue10.3:1Chart pins and key numerals.
Aa BookThe label ink on Signal Red4.5:1The one booking action. The ink is the navy taken down to 7% lightness, because nothing in the scheme clears 4.5:1 on the red.

Pairs that are never used

SamplePairRatioInstead
Sailcloth on Signal Red3.8:1The red always takes its own label ink.
Signal Red on Ensign Navy2.9:1Under the floor for type and for a thin mark. Signal Red is a fill: the booking action, and a stale reading's wash and border.

What a theme leaves alone, and what it solves again

Whatever is bound to an asset stays. Moonstruck Navy and the sea plate are matched to 26 satellite plates, so in Ensign a chart sits a step below the page where in Night Watch it sits above it. The layout drawing keeps the ink it was repainted in, and Anchor Light stays in reserve.

Whatever was solved against the field is solved again. In Ensign the wash over a photograph, --ink-page-rgb, is 9 16 27 and not the page field as channels: with Ensign Navy in the wash, the US Virgin Islands headline fell to 2.76:1 on a phone, where the type crosses the brightest sky. The layout drawing's wash, --wash-drawing, comes down from 0.45 to 0.32, because its lines composite brighter on a lighter field. The token table lists every value Ensign pins beside Night Watch's.

  • The Theme Is Tokens RuleA color theme is one block of token values, mirrored in both stylesheets, and nothing else. Whatever has to differ between themes becomes a token first: --action and --action-ink exist so that the booking action can take a color the data does not share. A new theme is measured the way a new photograph is, every line of type against the worst pixel behind it, before it ships.
  • The One Action Rule, in any themeThe rule is about there being one filled action in a view, not about its hue. It is Depth Cyan in Night Watch and Signal Red in Ensign, and in both it is the only thing that takes the fill.

Type system

A classical, high-contrast serif for voice, and one geometric sans in two voices: used light for everything you read or count, and tracked out in capitals like the lettering on an instrument panel.

Aa Aa

Playfair Display

400 and 400 italic. Headlines, ledes, names, the serif running text inside components.

A week aboard Moonstruck 0123456789

Aa

Barlow, read

300 for body copy and readings, 400 for prices and the odd stressed fact. Its italic is not loaded, so a ship's name in body copy is slanted by the browser.

Short sails, long afternoons 0123456789

Aa

Barlow, annotated

The same face as the label voice: always uppercase and tracked, 400 for labels, 500 for short standout labels and pin numbers, 300 for the faint print on the charts. Nothing heavier exists here.

Under sail · Book · 1 h 30 min

That is everything the font request carries, and CSS may ask for nothing else: a weight the request does not carry is silently drawn at the nearest one, so the source would describe a page that does not exist. The families are reached through --font-display, --font-body and --font-label (both sans tokens are Barlow; they stay separate so either voice can change face alone), whose stacks lead with a metric-matched stand-in (Georgia at 88.4% for Playfair, Arial at 92.8% for Barlow) so the swap to the webfont moves no line break.

  • The Logbook Voice RuleSerif for things you would say; tracked uppercase Barlow for things you would annotate or press; light Barlow, in sentence case, for everything you would read or count. Never a label in Playfair, never a paragraph in tracked capitals, never a headline in Barlow. Facts follow the reading voice: an inclusion, a discount, “per person, for the week” are light Barlow, even inside a card whose name is serif.
  • The One Weight Per Voice RuleThe serif is 400, the label voice is Barlow 400 and body is Barlow 300, on every page. The inner pages set body to Barlow 300, so their labels and serif fragments are named in two grouped rules beside .lede in pages.css; the label group is also where a label takes --font-label. A new class joins the matching group.
  • The Light Hand RuleBold is not part of the system. 500 is the ceiling, and only for short uppercase labels that must hold up over photography.
  • The Heading Carries the Section RuleNo uppercase eyebrow sits above a heading. Uppercase strings are written in sentence case in the source and transformed by CSS, so assistive technology reads words, not letters.

Type scale

Roles are tokens, named for the job and not the value. Every size below is set live from its token.

Display--type-display
clamp(48px, 8vw, 92px)
leading 0.98 · hero only

Set sail.

Headline--type-headline
clamp(38px, 9vw, 58px)
leading 1 to 1.02

A captain, and a good deal more

Subhead--type-subhead
clamp(30px, 7vw, 44px)
leading 1.1

Open weeks at a glance

Title L--type-title-lg
34px · leading 1.15

Also on the chart

Title--type-title
28px · every card and entry heading

Norman Island to the Baths

Title S--type-title-sm
24px · every tile title on a phone

Clearing in and out

Lead--type-lead
19px · leading 1.5
on five columns

Every charter aboard Moonstruck is planned around the people on board, so no two weeks are alike.

Serif text--type-serif
20px · the floor for serif running text

The balance this charter was planned around, from its planning sheet. Yours is drawn to your own taste.

Body--type-body
17px · Barlow 300 · leading 1.65
45 to 75 characters

We head for the Baths for light swimming and a hike through the granite boulders, then on to Spanish Town for dinner ashore.

Body on ink--type-body-navy
18px · Barlow 300 · leading 1.65
Instrument White on a ink field

Connect with a knowledgeable broker who can get you on board Moonstruck right away.

Small--type-small
14px · Barlow 300

per person, for the week

Label--type-label
12px to 13px · Barlow 400 · 2 to 4px tracking

1 h 30 min under sail

Standout label16px · Barlow 500 · 1px tracking
badges only

Most popular

Stat46px · Barlow 300 · leading 1
instrument readings, so not the serif
50 ftVessel length
  • The Optical Size RulePlayfair's x-height is 0.386em to Barlow's 0.460em, so the serif reads about a sixth smaller at the same pixel size. Serif running text is never set below Serif text (20px), which is what sits level with 17px Barlow: Side by side: Barlow 17 · Playfair 20 · and Playfair 17, which is lost
  • The Heading Leads RuleA heading takes its space from above, not below, so it binds to the block it introduces instead of floating between two. --head-lead (1.25em) sits over a heading that follows content in the same flow; --head-trail (0.4em) sits under every heading. Both are em, so a subhead leads by more than a title without a second pair of values. The lead is written with :where() and only matches a heading that follows a sibling, because a heading that opens a container already has the container's padding over it. A definition list is a heading and its paragraph too, and so cannot be laid out on a grid gap: a gap falls between every child, which would put a term as far from its own explanation as from the next entry. The rule Clearing in The term hugs its own explanation and keeps its distance from the entry above. On a grid gap Clearing in One gap between every child, so the term floats and reads as the tail of what came before.
  • The Dark Field RuleLight text on ink reads smaller and thinner than the same text on Deep Water. Body copy there steps up to Body on ink (18px), and running text of 14px or smaller is never lighter than 400, which is why the footer sets 400 where the page sets 300. The 46px stats numerals keep 300: at that size the hairline is the point. Every body paragraph takes --leading-body; a list of one-line items may open to 1.8, where the leading is the space between items.
  • The Plain Figures RuleFigures here are data. Playfair's default old-style 1 and 0 read as letters, so body sets lining-nums site-wide and anything columnar adds tabular-nums beside it. The property is not additive: a rule that sets tabular-nums alone turns the old-style figures back on. $4,100 lining$4,100 old-style, never

The mark

One mark, in two inks: its own colors over light grounds, and Instrument White on ink, with its water kept in two bands, a light blue grey over the sea, which is Depth Cyan Muted in Night Watch and the hull's Moonstruck Navy in Ensign.

The S/V Moonstruck mark in its own colors
The S/V Moonstruck mark recolored to Instrument White, with a blue grey band over its sea

The file is assets/Moonstruck-Logo.svg. It follows the name as it's lettered on the hulls: MOON cut out of a moon disc, then STRUCK beside it in light Jost capitals. The two O's are a pair of wayfarer sunglasses seen straight on: a brow that rises to the end piece at each side, two lenses that narrow toward the bottom, and a keyhole bridge between them. The cut-out letters and the sunglasses are real holes, which is what lets the file work as a mask. The water under the disc is two bands, as on the hull: a light blue grey, then Moonstruck Navy. The disc's navy (#1f2b5b) and the band's blue grey (#9db2cc) belong to the asset and sit outside the token set; the sea is Moonstruck Navy itself. On ink the same file becomes a mask over a fill that is Instrument White down to the gap above the water, the tint at its text-soft step from there, and --mark-sea for the sea, so there is one file to maintain and every ink is a token. The sea is a token of its own because the field decides it: Night Watch is too dark to show the hull's navy (1.20:1), so there it is Depth Cyan Muted, the sea on the app icon, and Ensign takes Moonstruck Navy. The first stop is at 75% of the height and holds while the img keeps the file's proportions (a set height, width auto); the second, at 86.2%, falls inside the water, which the mask draws solid. It is not tinted with a CSS filter chain: a chain lands on its color only in browsers that clamp after every step, and iOS Safari does not (it drew the mark cyan).

background: linear-gradient(var(--cream-bright) 75%, var(--text-soft) 75% 86.2%, var(--mark-sea) 86.2%);   /* the water is below 75%, the sea below 86.2% */
mask: url("Moonstruck-Logo.svg") center / contain no-repeat;   /* with the -webkit- twin */
object-position: -999px 0;   /* the img's own pixels, out of the box */
WhereHeightInk
Homepage nav, over the hero60pxInstrument White and cyan water, by mask, on the photograph
Homepage nav once scrolled; every inner page's nav46pxInstrument White and cyan water, by mask
Footer46pxInstrument White and cyan water, by mask, at 90%
This guide's bar40pxInstrument White and cyan water, by mask

Beside it

The favicon: the mark on a rounded ink field The touch icon: the mark square to the edge
Paradise Yacht Management
The Charter Smarter mark

The favicon (assets/icon.svg) sets the mark on a rounded field of Moonstruck Navy, the same --chart-navy that the pages' theme-color and the web manifest carry. Every other icon is that same drawing, rasterized by tools/build-icons.py: a 32px PNG for browsers that will not read an SVG, a 180px touch icon and the manifest's 192 and 512, all square to the edge because the platform cuts its own corners and composites what is outside them onto black, a maskable 512 drawn smaller to sit inside Android's safe circle, and a flat black mask-icon.svg for a pinned Safari tab. Paradise Yacht Management's mark sits beside Moonstruck's in the footer at the same 46px. The Charter Smarter mark appears once, on the homepage's booking card, where it says where the amber button leads.

Grid and measure

A nine-column page grid: 1040px at most, a 32px gutter, and everything else derived from those two numbers.

Nine is chosen for the editorial 3 + 6 and 4 + 5 splits it allows; it is deliberately not a twelve-column utility grid. The grid governs page-level placement only. Components subdivide freely inside their span, because the page's natural groupings (four stats, two destinations, seven days) do not divide by nine.

One column is 87px. The widths above are worked out from the tokens by tools/build-docs.py.

Margins and rhythm

TokenDesktop768px and belowUse
--section-x56px24pxThe horizontal page margin, everywhere: sections, nav, hero and footer.
--section-y56px64pxDefault vertical section padding.
--section-y-lg96px64pxEmphasis: availability, and the inner pages' main sections.
--section-y-xl128px80pxThe closing booking section.
--gutter32px24pxBetween columns, and between most paired blocks.

Vertical rhythm goes lower on a phone, not higher.

Breakpoints

WidthWhat changes
1152px and upThe nine-column line exists. Derived, not chosen: --grid-max plus twice --section-x. Compositions that need a full-width column line engage here and no earlier, or their bases overflow and wrap.
1151px and belowTwo-column compositions go fluid: 5fr to 6fr with an 8% alley.
1100pxThe itineraries hero copy leaves the chart and sits above it.
920pxNav links give way to the burger and its drawer.
900px, 800pxPassage plans and notes, then pilot entries, drop to one column.
768pxThe phone layout: margins and rhythm step down, grids go to one column, charts pan sideways, the floating bar becomes a row along the bottom.
600pxThe rate table turns each row into a party-size header over two season cells.
500px, 430px, 380px, 360pxSmall repairs: the hero headline's glass plate, the calendar to one month across, the footer address, the pictogram legend.
  • The Column Span RuleA new container takes a span token, never a hand-picked pixel width. If a block needs a width the scale does not offer, the scale is wrong: fix the tokens, not the block.
  • The One Margin RuleThe horizontal page margin comes from --section-x and nothing else. A full-bleed escape is written calc(-1 * var(--section-x)), so it tracks the margin.
  • The Shrinkable Track RuleA single-column grid is written minmax(0, 1fr), never a bare 1fr, which cannot shrink below its content. With a pannable chart inside, a bare track once ran a 390px phone page to 734px. When checking a phone layout, confirm that innerWidth equals the width you asked for.

Layouts

The compositions the pages are built from, drawn on the nine columns. Every split sums to nine, so edges land on the same column lines from section to section.

Blue blocks are photographs, ink blocks are charts, gray blocks are type, outlined blocks are Deep Water panels. Below 1152px these go fluid, and at 768px every one of them is a single column in source order, which is also the order a screen reader gets.

The boat: header

Six columns of headline and lede against a three-column round portrait.

The boat: feature field

Two four-column tiles with a derived one-column alley (dashed), opened and closed by full-measure plates.

CAPTAIN360

Three equal cards across the wide measure: the captain's card, then what the program puts in every rate and what the captain does.

Passage plan

Four columns of chart, sticky below the nav, against five of log.

Chart index

On ink: six columns of chart against a three-column key. A chart too wide to share a row takes all nine, with the key in three columns beneath.

Pilot entries

Four columns of chart inset against five of text, mirrored on every second entry.

Notes

Reading the plan, and Between the anchorages: a heading and lede on four columns against a definition list on five.

Also on the chart

A three-column heading against the two cross links.

Buttons and controls

Square, tracked and quiet. Small, widely tracked uppercase labels with generous padding and a color swap on hover.

Every button is a link. The site has no form: each action leaves for a phone call, an email, or the Charter Smarter booking form, and an external one says so in its accessible name (“Book with Charter Smarter (opens in a new tab)”). These are the real .btn classes from pages.css; the homepage draws the same buttons with inline styles.

On Deep Water: .btn-primary, .btn-dark, and the focus ring in Depth Cyan

On ink: the closing section's actions. .btn-sky, .btn-ghost, and the ring in Instrument White

The floating action bar, over photography. Dark is 92% so it holds on any frame

ButtonFill and inkType and paddingWhere
PrimaryDepth Cyan, Midnight Hull12px, 4px tracking; 18 by 26px; 132px minimumBooking, once per view.
DarkMidnight Hull at 92%, Instrument WhiteAs primaryCall and Email in the floating bar and under the calendar.
SkyDepth Cyan Muted, Midnight Hull14px, 2px tracking; 18 by 40pxSecondary actions on ink: Broker partners, Learn about layaway.
Ghost1px Instrument White at 50%, Instrument WhiteAs skyInk fields only: the phone number, hero jump links.

On hover every filled button swaps to a Instrument White fill with Midnight Hull ink over 0.25s; the ghost firms its outline and takes a 10% fill. In the closing section the three actions share a 236px minimum width, so none reads as the runt.

Icon controls

Round is reserved for controls that are only an icon. Anything on imagery carries the Control shadow.

7 / 17 3

Carousel arrow 52px · lightbox arrow 56px · close 48px · counter chip · chart pin 24px · a 24px mark inside its 44px hit area

The divider knob (40px) and the map opener (34px) are in the same vocabulary; both are live on the inset under Components. The first three specimens above are restated from the homepage's inline styles in assets/style-guide.css.

  • The Hit Area RuleA small target keeps its drawn size and grows an invisible 44px hit area from an ::after with a negative inset: the destination cards' text links, the footer's 24px social marks and its email address. Chart pins stay at their drawn size, because each has a full-height row in the key that does the same thing, and padded pins on a crowded chart would steal each other's taps.
  • Promise only what worksNo role="button" with a guarded handler, and no “Tap” hint under a mouse. The plate zoom is available at every width, never enlarges past the source's own pixels, and words its hint for the pointer in use.

Components

Sections sit flat. Only what floats over content gets a shadow, and only contained cards get a radius.

DepthShadowOn
Float0 8px 24px rgba(0,0,0,0.15)Polaroid tiles, chart insets, the plan chart's mat.
Lift0 8px 32px rgba(0,0,0,0.2)Wide plates, the menu drawer.
Control0 4px 18px rgba(0,0,0,0.35)Any control on imagery: close buttons, the divider knob, the map opener.
Stage0 30px 80px rgba(0,0,0,0.5)The photograph in the lightbox.

Corners: none on buttons and section edges; 2px on mats and frames; 14 to 16px on contained panels such as the CAPTAIN360 cards; fully round for icon-only controls and portraits.

The polaroid mat, carrying a chart inset

The boat section's panel mat: Night Watch wrapping the plate on three sides and deepening into a lip that carries the caption, so the caption belongs to the picture. Photographs sit square, never tilted. On the destination pages the same mat carries a chart inset, which can be swapped for a photograph without changing the page. This one is lifted from usvi.html and is fully live: drag the divider, or focus it and use the arrow keys.

Satellite view of Trunk and Cinnamon Bays
5
Chart inset18°21.2′N 64°46.2′W

Badge

A short Standout label on a square Midnight Hull plate. One vocabulary for the hero's line, the “Most popular” tag and the photo band's caption. Never amber, never a pill.

Captain-only catamaran chartersMost popular

Navigation

Uppercase Barlow at 13px with 2px tracking. The link for where you are carries a 1px hairline 8px below the label, drawn as a pseudo-element so that it stops at the last letter; text-decoration would run on under the trailing tracking. On the homepage it follows a reading line two fifths of the way down the viewport and draws in over 0.35s; inner pages mark Destinations, their parent.

Planning pictograms

The thirteen pictograms that printed charter itineraries use, lifted as vector paths into assets/icons/tags.svg. They fill with currentColor: the tint at 0.66 on panel, Depth Cyan on ink. In a log they stand alone at 24px, each named by its title and an accessible label; only the legend gives them visible names. They are the only icon set on the site besides the stroke glyphs in the buttons.

  • Beach chairs
  • Beach time
  • Dive / SCUBA
  • Dock available
  • Hiking
  • Island tour
  • Jumping
  • Lobster specialty
  • Notable bar
  • Paddleboarding
  • Secluded
  • Snorkeling
  • Swimming

Gauges

“How the week leans”: relative emphasis from a charter's planning sheet, on a scale ruled in quarters. These are the seven-night passage's. Each fill runs once, from the left, when the block enters view.

  • Snorkeling
  • Beach time
  • Sailing time

Calendar days

The availability grid's six states, with the fills and inks the homepage's stylesheet gives data-cal-day. An open day that touches a booking or a hold is a turnaround day: it is computed, never entered, and draws exactly as unavailable. Pointing at an open window above the grid inks its days.

12Open
13On hold
14Booked
15Unavailable, or a turnaround day
16Past
17Lit: its window is pointed at

The months drawn run only as far as the blocks are maintained. A date with no block reads as open, so an unmaintained month would advertise weeks nobody has checked.

Charts

The inner pages draw real nautical charts; they do not decorate with them. One asset carries the geography, and every chart on the site is a viewBox crop of that one drawing.

This is the seven-night plan chart and the first two entries of its log, lifted from itineraries.html. Point at an entry, or tab to a pin, and its leg lights while the others fall to 28%. The satellite view is the same window, in register; the passage stays plotted on both.

64°54′W64°48′W64°42′W64°36′W64°30′W64°24′W64°18′W18°42′N18°36′N18°30′N18°24′N18°18′N
Satellite view of the seven nights passage
12345678
Numbered by legNot for navigation

Drag the chart to pan

  1. Day 1
    Saturday

    Charlotte Amalie to Norman Island

    1 h under sail

    Welcome to the Virgin Islands. After everyone is aboard, we hold a short safety briefing and set sail for Norman Island: snorkeling at the Caves, then an evening at the Willy T, the floating bar in the Bight.

  2. Day 2
    Sunday

    Norman Island to The Baths, Virgin Gorda

    3 h under sail

    We head for the Baths for light swimming and a hike through the granite boulders, then on to Spanish Town for dinner ashore at CocoMaya.

Anatomy

PartHow it is drawn
FieldAlways Moonstruck Navy.
Shoal halo, landassets/chart/vi-land.svg referenced twice through <use>: once as a wide faint line (Instrument White at 7%, 11px), once as the land (Instrument White fill at 16% under a 1px line). Coastline from OpenStreetMap.
FurnitureIn the label voice at chart scale: a graticule every tenth of a degree with edge labels, a four-point compass rose, a nautical-mile scale bar, a serif title block, and the line every chart carries: Not for navigation.
RoutesDashed Instrument White, one passage to a chart, drawn on once through a mask. A leg is the track a boat sails: a smooth spline through waypoints in open water, turning a corner only where the boat stops.
Pins, marksHTML, so they hold their size at any chart scale and take focus: a 24px Depth Cyan disc with a Midnight Hull numeral for a stop, a small ring for a place a leg passes through.
Satellite plateEvery chart but the itineraries hero carries one under a draggable divider. Open water in every plate is toned to Moonstruck Navy, so the sea is continuous across the divider and only land, reef and shallows change.
x = (lon + 65.12) × 1000        y = (18.80 − lat) × 1054.35        1 nautical mile = 17.57 units

The divider

A role="slider" whose value text names the share of satellite. Only the knob and the line take a drag, never the whole plate, because on a phone the chart already pans sideways and the page has to scroll past it.

InputEffect
Arrow keys2%; 10% with Shift
Page Up, Page Down10%
Home, EndSatellite only, chart only
Double-click the knobBack to rest

Where the divider rests and how high the knob rides are chosen by hand per chart to clear its pins and labels, the same license a label's side has.

  • The Chart Truth RuleChart geometry is data, never drawn by eye: coastlines come from the asset, positions from coordinates, the scale bar from the projection. A pin is where the boat lies (the mooring field, not the beach or the town), and the generator's check_afloat() names any pin or track that touches land. A satellite plate is emitted only at the chart's exact viewBox, so imagery is never shown out of register.

Overlays and dialogs

Four overlays, one behavior. Each is a real dialog, and each gives the keyboard back exactly where it took it.

DialogFieldOpens with focus onAlso
Photo viewer (homepage)Midnight Hull at 94%CloseArrow keys and a horizontal swipe turn the photo; a counter chip; a polite “Photo 7 of 17”.
Plate zoom (homepage)Midnight Hull at 96%CloseClick toggles fit and enlarged; never past the source's own pixels; its hint sits on a Midnight Hull plate so it survives white cabinetry.
Menu drawer (every page)Moonstruck Navy, from the right, up to 20rem, over a Midnight Hull backdrop at 60%. The same nine links and Book on every pageIts first linkThe burger stays on screen as the close control and is part of the focus trap. A tap on the backdrop closes it too.
Anchorage map (inner pages)Midnight Hull at 96%CloseDormant until a MapKit token is set. Until then the opener is a link to Apple Maps.

All four share this contract: role="dialog" and aria-modal; the element that had focus is remembered; the page stops scrolling behind; Tab wraps inside; Escape closes; focus returns to where it came from. A closed overlay is visibility: hidden as well as transparent, because pointer-events: none alone leaves its links in the tab order.

The implementations are openOverlay(), closeOverlay() and onOverlayKeys() on the homepage, and toggleMenu(), onMenuKeys(), openMap() and mapKeys() in pages.js; see the JavaScript reference.

Motion

Slow and calm. Things arrive once, over most of a second, on one soft curve, and then stay put.

--ease-reveal: cubic-bezier(0.22, 0.61, 0.36, 1)
What movesHowUnder reduced motion
Hero photographFades in once it has loaded, 0.6s, the shortest reveal, because its length counts toward load time. None if it is ready for the first frame0.2s fade
Reveal (.reveal)36px rise and fade, 0.8s0.4s fade, no movement
Polaroid plates, slide-insFrom 70px or 90px to the side, 0.9 to 1s0.4s fade
Gallery stageSharpens from gray and blurred, 1.2s; it does not moveNone
A chart's routeDraws itself on through a mask, 2.6s; pins follow 0.14s apartAlready drawn
Satellite plateWipes in from the east, 1.5s, after the route has drawnAlready at rest
GaugesFill from the left, 1.2s, 0.1s apartAlready full
Nav hairlineDraws in from the left, 0.35sAppears at once
Buttons, calendar daysColor swaps, 0.2 to 0.25s. A chosen window's ink fades out over 1.2s after ten secondsUnchanged
Rate table mistA 34s driftStill
Footer weatherChanges harbor every ten seconds behind a 0.5s fade, on the homepage only while the footer is on screenHomepage: holds on Tortola

Every animation has its counterpart in the prefers-reduced-motion block at the foot of each stylesheet, and a new one needs a line there. Adding ?static to an inner page's URL resolves every entrance at once, for screenshots and print.

CSS tokens

Every custom property on :root, with its value read from the source.

The set is written twice: in the <style> block of index.html and at the top of assets/pages.css. The two must agree, and tools/build-docs.py reports any that do not. Descriptions come 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

Component properties

A few custom properties are scoped to a component. They carry a measurement from the generator or from script, never a design decision.

PropertySet byMeaning
--splitpages.jsThe chart / satellite divider's position, in percent from the left.
--knob-y, --split-delaythe generator, inlineHow high the knob rides, and how long the satellite's entrance waits for the route.
--chart-navythe generator, inlineThe width a chart keeps on a phone, where it pans.
--ithe generator, inlineA pin's place in the entrance order.
--v, --nmthe generator, inlineA gauge's reading; one nautical mile as a share of an inset's width.
--mat, --plate-arthe stylesheets; inline per tileThe polaroid mat's width, and a plate's proportion.
--cta-vvboth scriptsHow far a phone's toolbar or keyboard has pushed the visible bottom edge up.
--link-hover, --hero-copy-rightindex.htmlThe link hover ink for a surface; where the hero headline's first line ends, for the haze.

Code reference

The source comments are the documentation. The reference pages are generated from them.

PageGenerated from
JavaScript referenceThe JSDoc in assets/home.js and assets/pages.js: every function, its parameters, the events it listens for, and the shapes of the data it handles.
CSS referenceThe doc comments in assets/pages.css and in the <style> block of index.html: every section, selector, modifier, state, script hook, custom property and breakpoint.
ARCHITECTURE.mdWritten by hand: how the site is put together, where its data comes from, and what to touch when something has to change.
DESIGN.mdWritten by hand: the reasoning behind every rule on this page.

After changing a doc comment, a token or this page, run the builder and commit what it writes:

python3 tools/build-docs.py            # writes docs/ and updates this page
python3 tools/build-docs.py --check    # writes nothing; exits 1 if anything is out of date

An internal page, marked noindex and linked only from About this site, which credits the work this guide describes. Values here are written by tools/build-docs.py.