/* ==========================================================================
   vivaocean-theme / shell
   --------------------------------------------------------------------------
   The machine: one window, two rails, one menu, and a desk to put the window
   down on. The water is next door in scene/scene.css; this file is everything
   the reader can touch.

   THE SHELL HAS TWO STATES AND THEY ARE BOTH REAL. Window-open is the site.
   Desk-clear is the window closed: the scene with nothing in front of it, the
   rails still in the water at 1024 and up, and one launcher at the top left.
   Neither is a mode and neither is a dialog; they are the two things a window
   on a desk can be doing. Every rule below that is scoped under `.vo-desk`
   belongs to the second one.

   HOW THE PROGRESSIVE ENHANCEMENT WORKS. Every page in this theme is a real
   static HTML document. There is no app, no router, and no client-rendered
   anything, and the enhancement is not a fallback story bolted onto one:

     1. The base styles below assume no JavaScript. A page is a masthead,
        two navigations as ordinary link lists, and the window as a card in
        the document flow, on the ocean. It scrolls like a page. Every link
        is a link and every URL is a URL.

     2. shell.js puts `vo-live` on the <html> element as its first act.
        Every rule scoped under .vo-live upgrades that same markup: the page
        stops scrolling, the window pins to the middle of the screen, the
        two navigations become rails down the left and right edges, and
        clicking one swaps the window's contents instead of reloading the
        document. No markup is duplicated and nothing is re-rendered.

   THE SWAP IS AN OPTIMISATION, NOT AN ARCHITECTURE. shell.js fetches the
   target page, lifts its window body out, and puts it in this one. If the
   fetch cannot run, and over file:// it cannot, the click falls through to
   an ordinary navigation and the reader gets the same page by the slow
   road. That is why every page is a real document: the fallback is not a
   degraded mode, it is the site.

   There is not one color literal in this file. Every color, every duration
   and every piece of shell geometry is a var() reference to tokens.css.

   What is a bare length here, and why. Three kinds, and nothing else:

     1. Control and target minimums that exist to satisfy a standard rather
        than a design: min-width and min-height 44px, WCAG 2.5.8. These stay
        literal deliberately. A token invites a re-skin to lower an
        accessibility floor, which is the one move this theme should make
        awkward rather than easy.
     2. Injected-glyph boxes: the 18px, 20px and 26px that size the SVGs
        shell.js draws. Those are intrinsic to the drawings, not design
        values.
     3. The 1023px breakpoint in the media queries, which cannot be a custom
        property because media query conditions do not read them.

   Requires tokens.css, and scene.css if you want the water. Load them, then
   this, then the script:

     <link rel="stylesheet" href="tokens/tokens.css" />
     <link rel="stylesheet" href="scene/scene.css" />
     <link rel="stylesheet" href="components/components.css" />
     <link rel="stylesheet" href="shell/shell.css" />
     <script src="shell/shell.js" defer></script>
   ========================================================================== */

/* --------------------------------------------------------------------------
   THE DOCUMENT
   Live, the page does not scroll; the window does. Without the script the
   page is a page, and pages scroll.
   -------------------------------------------------------------------------- */
.vo-live,
.vo-live body {
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
}

/* --------------------------------------------------------------------------
   SKIP LINK
   Above everything, including the menu panel.
   -------------------------------------------------------------------------- */
.vo-skip {
  position: absolute;
  left: -9999px;
}

.vo-skip:focus {
  left: var(--vo-space-4);
  top: var(--vo-space-4);
  z-index: var(--vo-z-skip);
  padding: var(--vo-space-3) var(--vo-space-4);
  background: var(--vo-surface);
  color: var(--vo-ink);
  border: var(--vo-border-width) solid var(--vo-border-strong);
  border-radius: var(--vo-radius);
  box-shadow: var(--vo-shadow-raised);
  font-weight: var(--vo-weight-bold);
}

/* --------------------------------------------------------------------------
   THE PAGE MASTHEAD
   Only ever seen without the script. Live, the brand moves into the title
   bar, where an application keeps its name, and this element goes away
   rather than being duplicated up there.
   -------------------------------------------------------------------------- */
.vo-masthead {
  padding: var(--vo-space-8) var(--vo-space-4) 0;
  text-align: center;
  color: var(--vo-ground-ink);
}

.vo-masthead-name {
  margin: 0;
  font-size: var(--vo-text-h1);
  font-weight: var(--vo-weight-display);
  letter-spacing: var(--vo-tracking-tight);
}

.vo-masthead-tagline {
  max-width: var(--vo-measure);
  margin: var(--vo-space-2) auto 0;
}

.vo-live .vo-masthead {
  display: none;
}

/* --------------------------------------------------------------------------
   THE RAILS
   Two navigations, split by what they are for rather than by where they
   fit: the left one is the product, the right one is the company. That is
   also why they survive the collapse to one menu without needing new
   labels, and why a screen reader hears two named navigations rather than
   one navigation cut in half.

   As a page: two ordinary link lists under the masthead. Live on a wide
   screen: two vertical rails pinned to the left and right edges. Live on a
   narrow screen: both of them inside one menu panel, further down.

   THE RAILS ARE DRAWN STRAIGHT ONTO THE WATER, with no plate behind them,
   and that is only allowable because the scene has a measured ceiling.
   Ground ink on the lightest paint the scene can produce is 4.94:1, so a
   rail label clears AA over any frame of the animation, including the one
   where a sunbeam is crossing it. tokens.css carries the arithmetic.
   -------------------------------------------------------------------------- */
.vo-rails {
  display: block;
}

.vo-rail {
  padding: var(--vo-space-6) var(--vo-space-4) 0;
}

.vo-rail-title {
  margin: 0 0 var(--vo-space-2);
  font-size: var(--vo-text-xs);
  font-weight: var(--vo-weight-bold);
  letter-spacing: var(--vo-tracking-wide);
  text-transform: uppercase;
  color: var(--vo-ground-ink);
  text-align: center;
}

.vo-rail-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--vo-rail-gap);
}

.vo-rail-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--vo-space-1);
  width: var(--vo-rail-cell);
  padding: var(--vo-space-2) var(--vo-space-1);
  pointer-events: auto;

  color: var(--vo-ground-ink);
  text-decoration: none;
  text-align: center;
  border-radius: var(--vo-radius);
}

.vo-rail-item:hover {
  background: var(--vo-rail-hover);
}

.vo-rail-item:focus-visible {
  outline: var(--vo-ring-width) solid var(--vo-ring);
  outline-offset: var(--vo-ring-offset);
  box-shadow: 0 0 0 calc(var(--vo-ring-width) + var(--vo-ring-offset) + 1px)
    var(--vo-ring-halo);
}

.vo-rail-glyph {
  display: grid;
  place-items: center;
  width: var(--vo-rail-icon);
  height: var(--vo-rail-icon);
}

.vo-rail-glyph > svg {
  width: 100%;
  height: 100%;
}

.vo-rail-label {
  font-size: var(--vo-text-xs);
  font-weight: var(--vo-weight-medium);
  line-height: var(--vo-leading-snug);
}

/* Where you are. A warm bar under the glyph in the same light as the focus
   ring and the sunbeams, because on this theme warmth is what marks a
   thing. It measures 4.17:1 against the lightest water, past the 3:1 floor
   for a non-text indicator, and the weight change carries it a second time
   for anyone who cannot see the bar at all. */
.vo-rail-item[aria-current="page"] {
  font-weight: var(--vo-weight-bold);
}

.vo-rail-item[aria-current="page"] .vo-rail-glyph {
  border-bottom: 2px solid var(--vo-ring);
}

/* Live, on a wide screen. Each rail is a full-height column with its items
   centred in it, so the two rails balance whatever their item counts are.

   The rail zone is 116px and a rail cell is 88px, which leaves 14px of
   clearance at the screen edge. The focus ring needs 5px of that
   (--vo-ring-width plus --vo-ring-offset), so the ring on the outermost
   item is never shaved by the viewport. That is class decision 36's third
   mechanic, applied at the only edge on this shell that has chrome on it. */
/* A RAIL IS 116px WIDE AND 100% TALL AND DRAWS NOTHING, which makes it an
   invisible click target down the whole edge of the screen. That was harmless
   for as long as the only thing in that column was water, and it stopped being
   harmless the moment a second control stood at the top of it: the launcher
   was painted under the rail and, because hit-testing follows the same order,
   was dead across its first ninety-two pixels.

   Giving the launcher its own layer fixes the launcher. This fixes the class,
   which is the difference class decision 36 is about: the column takes pointer
   events only where it actually draws something, so the next element to share
   this space does not have to know the rails are there. The items opt back in
   on their own rule; the narrow block opts the whole rail back in, because
   inside the menu panel it is an ordinary list on a surface and the surface
   should stop clicks rather than pass them to the window behind it. */
.vo-live .vo-rail {
  position: fixed;
  top: 0;
  bottom: 0;
  width: var(--vo-rail-zone);
  z-index: var(--vo-z-rail);
  pointer-events: none;

  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0;
}

.vo-live .vo-rail-start {
  left: 0;
}

.vo-live .vo-rail-end {
  right: 0;
}

.vo-live .vo-rail-list {
  flex-direction: column;
  align-items: center;
  gap: var(--vo-rail-gap);
}

/* The group headings are page furniture. Live, the rails are two short
   columns of named icons at opposite edges of a screen and a heading over
   each one is a label on a label. */
.vo-live .vo-rail-title {
  display: none;
}

/* --------------------------------------------------------------------------
   THE WINDOW
   One window. There is no pile, no cascade, no z-order, no minimize and no
   maximize, and the class-decision log in the pull request argues each of
   those deletions rather than assuming them. There IS a close, and it is
   argued in the same place: a window you cannot put down is a picture of a
   window, and this shell's whole subject is the thing behind it.

   As a page it is a card in the flow. Live it is pinned to the middle of
   the screen with the water showing around it, which is the composition
   this whole repository is built around. Closed, it is not there at all, by
   `display: none` rather than by `visibility: hidden`: closing is not
   covering, and the difference the reader can actually observe is that a
   reopened window is at the top of its page rather than wherever it was
   scrolled to. That is the honest reading of a close control, and it is the
   second half of why there is no minimize.

   overflow: hidden is on the base rule and the live rules never take it
   away. That is class decision 36 honoured at the only rounded container on
   this shell: one clip, not a list of elements each remembering to round
   their own corner.
   -------------------------------------------------------------------------- */
.vo-window {
  max-width: var(--vo-window-width);
  margin: var(--vo-space-8) auto;

  display: flex;
  flex-direction: column;

  background: var(--vo-surface);
  color: var(--vo-ink);
  border-radius: var(--vo-radius-window);
  box-shadow: var(--vo-shadow-window);
  overflow: hidden;
}

.vo-titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--vo-space-4);
  min-height: var(--vo-titlebar-height);
  padding: 0 var(--vo-space-2) 0 var(--vo-space-4);

  background: var(--vo-chrome);
  border-bottom: var(--vo-border-width) solid var(--vo-border);
  box-shadow: inset 0 2px 0 0 var(--vo-accent);
}

/* The window's name is the brand and the page, in that order, the way an
   application window is titled. The brand half is a link home; the page
   half is the only text in the chrome that a swap rewrites. */
.vo-window-name {
  display: flex;
  align-items: center;
  gap: var(--vo-space-2);
  margin: 0;
  min-width: 0;
  font-size: var(--vo-text-sm);
  overflow: hidden;
}

.vo-brand {
  font-weight: var(--vo-weight-display);
  letter-spacing: var(--vo-tracking-tight);
  color: var(--vo-ink);
  text-decoration: none;
  white-space: nowrap;
}

.vo-brand:focus-visible {
  outline: var(--vo-ring-width) solid var(--vo-accent);
  outline-offset: var(--vo-ring-offset);
  border-radius: var(--vo-radius-tight);
}

.vo-window-sep {
  color: var(--vo-ink-muted);
}

.vo-window-title {
  color: var(--vo-ink-muted);
  font-weight: var(--vo-weight-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The tray. Empty in the markup, filled by shell.js, because a control that
   does nothing must not exist. There are no decorative dots up here either:
   a row of coloured circles that imitates window controls without being any
   is the same lie as a dead button, one step further from the reader. */
.vo-tray {
  display: flex;
  align-items: center;
  gap: var(--vo-space-1);
  flex: none;
}

.vo-tray-btn {
  display: grid;
  place-items: center;
  width: var(--vo-control-size);
  height: var(--vo-control-size);
  padding: 0;

  color: var(--vo-ink);
  background: transparent;
  border: var(--vo-border-width) solid transparent;
  border-radius: var(--vo-radius-tight);
  cursor: pointer;
}

.vo-tray-btn:hover {
  background: var(--vo-surface-muted);
  border-color: var(--vo-border);
}

.vo-tray-btn:focus-visible {
  outline: var(--vo-ring-width) solid var(--vo-accent);
  outline-offset: 0;
}

.vo-tray-btn > svg {
  width: 18px;
  height: 18px;
}

/* The menu button only means something live and only fits on a narrow
   screen. shell.js injects it; the media query at the bottom shows it. */
.vo-menu-btn {
  display: none;
  min-width: 44px;
  min-height: 44px;
}

.vo-menu-btn > svg {
  width: 20px;
  height: 20px;
}

/* The close control. Last in the tray, where a window has kept it for as
   long as windows have had one, and 44px square at every width rather than
   only on a phone: it is the control with the largest consequence in the
   chrome and it should not be the smallest target in it.

   THERE IS STILL NO MINIMIZE AND NO MAXIMIZE, and the close button is the
   reason to say so again rather than the reason to stop. Minimize means "keep
   this window, put it somewhere I can get it back from, in the state I left
   it", and that sentence needs a restore surface to be true. This shell has
   no taskbar and no window list, because it has one window and a list of one
   is a label. Without that surface, minimize and close would leave the reader
   on the same screen and be undone by the same gesture: two controls, one
   behavior, which is the dead control of class decision 3 wearing a second
   icon. Inventing a dock to justify the button would be adding chrome to
   justify a control, which is the argument running backwards. */
.vo-close-btn {
  min-width: 44px;
  min-height: 44px;
}

/* --------------------------------------------------------------------------
   THE MENU BAR
   Every page in the site as a plain text link, in the window chrome directly
   under the title bar, which is where an application window has carried one
   for forty years. It is deliberately NOT a site navbar above the scene: that
   would be a second, non-diegetic layer floating over the water, and the
   whole composition is one window held in it.

   WHY IT EXISTS. The rails are the metaphor's navigation and they are lovely,
   and a reader who does not read them as navigation has, without this, no
   conventional path through the site. So the rails are the delight layer and
   this is the guarantee layer, which is WCAG 2.4.5 satisfied on purpose
   rather than by luck.

   Nine links, where the rails carry seven. The rails are curated into two
   named groups and reach the two documentation sub-pages from inside the docs
   page; a guarantee that skipped two of nine pages would not be one.

   THAT ASYMMETRY IS A CURATION CHOICE AT 1280 AND LOAD-BEARING AT 390. On a
   wide screen the rails are ambient and leaving the two documentation
   sub-pages to be found inside the docs page costs nothing. On a phone this
   strip is the ONLY surface in the repository that carries all nine: the rails
   carry seven at every width, in both modes, and docs-dives.html and
   docs-shoal.html are on no rail anywhere. This strip used to be hidden below
   1024, and that is what the hiding actually did.

   IT IS NOT SCOPED UNDER .vo-live, AND THAT IS THE POINT. These are plain
   anchors to real documents, so they behave identically with the script and
   without it, and there is nothing for an enhancement to add. The script's
   only involvement is moving aria-current after a swap, which is the same
   thing it already does for the rails.

   ONE HEIGHT, ONE LINE, SCROLLING SIDEWAYS, which is class decision 45's shape
   for a chrome strip carrying a list that grows with the site. Nine links are
   529px of content in a 390px window, so wrapping here is not a risk that
   might arrive with a tenth page, it is the default behaviour today, and it
   costs three rows of a phone's chrome. One scrolling row costs 35px and stays
   35px.

   TWO THINGS THIS REPOSITORY OWES THE LOG ABOUT THAT RULE.

   Part 3, the focus-ring reservation, is not owed here, for the same reason
   retro-desktop-theme is the log's stated exception: this ring is drawn INSIDE
   the control, at a negative outline-offset, so there is nothing outside the
   padding edge for an overflow container to clip. Retro arrived at that
   geometry because its era's ring is dotted and inset; this one arrived at it
   because an outline drawn outside a chrome item would collide with the
   chrome's own borders. Two routes, one exemption. The reservation is owed by
   ring geometry, not by the existence of a scroll box, and this is the second
   shell to demonstrate it.

   Part 2 says a strip may wrap as a page and should, because a plain document
   should not scroll sideways. THIS STRIP DEPARTS FROM THAT, and the departure
   is the design of the element rather than a shortcut: it is the one piece of
   chrome here that is identical in both modes, and splitting its layout
   between them to satisfy a rule about documents would trade the property the
   guarantee layer is built on for one that nothing here needs. The exchange
   part 2 is protecting does not arise: the strip scrolls inside itself, so the
   document does not scroll sideways in either mode.

   Contrast, measured against the chrome it sits on, and the same at 390
   because the strip takes no new tokens there:
     link ink on chrome 13.97:1, on the hover fill 13.32:1
     the current-page underline and the focus ring, accent on chrome, 5.01:1,
     past the 3:1 floor for a non-text indicator
   Muted ink was the first instinct for a chrome strip and measures 5.67:1,
   which clears AA and still reads as a row of disabled menus. Full ink.
   -------------------------------------------------------------------------- */
.vo-menubar {
  flex: none;
  background: var(--vo-chrome);
  border-bottom: var(--vo-border-width) solid var(--vo-border);
}

/* The scroll box. `scrollbar-width: none` hides the bar and does not disable
   the scrolling, which is the right trade in a 35px strip: a classic scrollbar
   is 15px of a 35px chrome row, it would be the tallest thing in the strip
   after the text, and a horizontal bar under nine words reads as a defect
   rather than as an affordance. What replaces it as the affordance is the
   content: the strip is deliberately cut mid-item at 390 rather than at a
   tidy boundary, so the row visibly continues. */
.vo-menubar-list {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  margin: 0;
  padding: 0 var(--vo-space-2);
  list-style: none;

  overflow-x: auto;
  scrollbar-width: none;
}

.vo-menubar-list::-webkit-scrollbar {
  display: none;
}

.vo-menu-item {
  display: inline-block;
  padding: var(--vo-space-2) var(--vo-space-3);

  font-size: var(--vo-text-sm);
  line-height: var(--vo-leading-snug);
  color: var(--vo-ink);
  text-decoration: none;
  white-space: nowrap;
}

.vo-menu-item:hover {
  background: var(--vo-surface-muted);
}

.vo-menu-item:focus-visible {
  outline: var(--vo-ring-width) solid var(--vo-accent);
  outline-offset: calc(-1 * var(--vo-ring-width));
}

/* Where you are, marked twice: an accent rule under the item and the weight
   change, so it does not depend on seeing a two-pixel line. */
.vo-menu-item[aria-current="page"] {
  font-weight: var(--vo-weight-bold);
  box-shadow: inset 0 -2px 0 0 var(--vo-accent);
}

.vo-window-body {
  padding: var(--vo-space-8);
  overflow-wrap: break-word;
}

.vo-window-body > :first-child {
  margin-top: 0;
}

/* Live: the window is the screen's centrepiece and its body is the only
   thing that scrolls.

   Centred with auto margins rather than a transform, deliberately. A
   transform on this element would make it the containing block for anything
   fixed inside it, which is the trap class decision 36 warns about, and the
   window is exactly the element a future addition would try to escape. */
.vo-live .vo-window {
  position: fixed;
  top: var(--vo-window-inset);
  bottom: var(--vo-window-inset);
  left: 0;
  right: 0;
  margin: 0 auto;
  max-width: min(
    var(--vo-window-width),
    calc(100vw - 2 * var(--vo-rail-zone) - 2 * var(--vo-space-6))
  );
  z-index: var(--vo-z-window);
}

/* The scroll region, and the element a swap moves focus to. shell.js gives
   it tabindex="0", so it is genuinely user-focusable and the ring is
   genuinely earned: class decision 29 exempts a programmatic-only target
   from drawing one, and this element is not that. :focus-visible is what
   keeps a mouse click on a rail item from outlining the whole page while a
   keyboard activation still shows where focus went. */
.vo-live .vo-window-body {
  flex: 1;
  overflow-y: auto;
}

.vo-live .vo-window-body:focus-visible {
  outline: var(--vo-ring-width) solid var(--vo-accent);
  outline-offset: calc(-1 * var(--vo-ring-width));
}

/* The swap's one animation. The outgoing content is never shown fading;
   only the arriving content fades up, over a distance small enough to read
   as settling rather than sliding. */
.vo-live .vo-window-body.vo-swapped {
  animation: vo-settle var(--vo-swap-duration) var(--vo-ease);
}

@keyframes vo-settle {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* --------------------------------------------------------------------------
   DESK-CLEAR
   The window closed, and the scene with nothing in front of it. At 1024 and
   up the rails stay exactly where they were, because they are drawn on the
   water rather than on the window and closing a window does not take the
   desk with it; below 1024 there are no rails to keep, and the launcher is
   the whole surface. That asymmetry is not a compromise, it is what the
   metaphor transform already decided at class decision 8, showing up in a
   second state.

   `.vo-desk` is written without `.vo-live` in front of it on purpose. The
   class is only ever set by shell.js, which sets `vo-live` as its first act,
   so the two are inseparable by construction rather than by selector, and
   the rules read as what they are.

   THE MENU PANEL CANNOT BE OPEN HERE, and it is worth saying how that is
   guaranteed rather than checked. The only control that opens it is the menu
   button, the menu button lives in the title bar, and the title bar is
   inside the element this rule hides. There is no path to the panel that does
   not go through a control that is not on the screen. shell.js also closes
   the menu on its way out, which is belt to that braces.

   The skip link goes too. It is an anchor to `#main`, `#main` is inside the
   closed window, and a control whose destination is not on the screen is a
   control that does nothing. Hiding it is also what puts the launcher first
   in the tab order, which the launcher owes to being the only way back.
   -------------------------------------------------------------------------- */
.vo-desk .vo-window {
  display: none;
}

.vo-desk .vo-skip {
  display: none;
}

/* The window's return settles rather than snapping. This is the same
   `vo-settle` keyframe the swap and the menu panel already use, at the
   interface duration, so it adds a moment rather than a vocabulary.

   Closing is deliberately NOT animated, and the asymmetry is the argument: a
   reader who closes the window is asking for the water, and the water should
   already be there. A reader who opens it is being handed something, and
   180ms is the difference between arriving and appearing. */
.vo-desk-open .vo-window {
  animation: vo-settle var(--vo-duration) var(--vo-ease);
}

/* --------------------------------------------------------------------------
   THE LAUNCHER
   The one control on a clear desk, and the only way back to the site at the
   widths where the rails are not on the screen. It is therefore a first-class
   control rather than an affordance: first in the tab order, 44px of target,
   a visible ring, and a label that says what will happen.

   IT IS A PIECE OF THE WINDOW, WHICH IS THE WHOLE IDEA. Not a button drawn on
   the water in the rails' language — ground ink over a glyph over a caption,
   which is what the seven navigation items already look like and would make
   the one control on the desk that is not navigation the eighth thing that
   looks like it. This is a plate in the window's own `--vo-surface`, carrying
   the title bar's own accent hairline, so the thing that brings the window
   back is visibly made of the window. A bubble mark rather than an arrow or a
   chevron: bubbles are what this ocean would use for something on its way up,
   and they appear nowhere else in the scene, so the mark collides with
   neither the fish nor the beams.

   CONTRAST, MEASURED THE WAY CLASS DECISION 38 REQUIRES: against the ceiling
   the scene can produce rather than against the paint that happens to be
   there. The plate is at the top of the frame, where the water is the surface
   stop, and it measures 7.45:1 against it; against #2e7095, the lightest
   paint the scene is capable of anywhere, it measures 5.22:1. Both clear the
   3:1 floor for a non-text boundary with room.

   The label is the number that matters and it is the number that cannot move:
   ink on surface, 15.63:1, both tokens belonging to the window rather than to
   the water. That is the payoff of the plate over the first concept. Under
   the lagoon re-skin this repository ships as a failure, ground ink on the
   lightest water falls to 1.76:1 and the rail labels are gone; this label is
   still at 15.63:1, because an opaque plate does not care what is behind it.
   The plate's own edge is the part the re-skin does take: 1.85:1 at that
   theme's ceiling, which is the same coverage story the focus ring tells, and
   the ring's halo is at 9.06:1 there, so a keyboard reader still finds it.
   -------------------------------------------------------------------------- */
.vo-launch {
  display: none;
  position: fixed;
  top: var(--vo-launch-inset);
  left: var(--vo-launch-inset);
  z-index: var(--vo-z-launch);

  align-items: center;
  gap: var(--vo-space-3);
  min-height: 44px;
  padding: var(--vo-space-2) var(--vo-space-4);

  font-family: inherit;
  font-size: var(--vo-text-sm);
  font-weight: var(--vo-weight-bold);
  line-height: var(--vo-leading-snug);
  text-align: left;

  color: var(--vo-ink);
  background: var(--vo-surface);
  border: var(--vo-border-width) solid var(--vo-border);
  border-radius: var(--vo-radius-window);
  box-shadow: inset 0 2px 0 0 var(--vo-accent), var(--vo-shadow-window);
  cursor: pointer;
}

.vo-desk .vo-launch {
  display: inline-flex;
}

.vo-launch:hover {
  background: var(--vo-chrome);
}

/* The rails' ring, not the window's. This control stands on the water, and
   the water is what class decision 37's two-colour ring exists for: the
   accent-on-chrome ring the tray buttons use is a promise about a background
   that is not behind this one. The window shadow is restated in the focus
   rule because a `box-shadow` list replaces rather than adds, and dropping it
   would make the plate lift off the water only while it was NOT focused. */
.vo-launch:focus-visible {
  outline: var(--vo-ring-width) solid var(--vo-ring);
  outline-offset: var(--vo-ring-offset);
  box-shadow: 0 0 0 calc(var(--vo-ring-width) + var(--vo-ring-offset) + 1px)
      var(--vo-ring-halo),
    inset 0 2px 0 0 var(--vo-accent), var(--vo-shadow-window);
}

/* An intrinsic glyph box, like the tray's 18px and 20px: the number belongs
   to the drawing rather than to the design. The accent is spent here because
   this is a light surface, which tokens.css says is the only side of the
   theme the accent lives on. */
.vo-launch-mark {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: none;
  color: var(--vo-accent);
}

.vo-launch-mark > svg {
  width: 100%;
  height: 100%;
}

/* --------------------------------------------------------------------------
   THE LIVE REGION
   Injected by shell.js. A swap changes the page without a page load, so
   nothing announces it; this is what does. Visually hidden by the standard
   clip rectangle rather than by display:none, which would take it out of
   the accessibility tree and defeat the point.
   -------------------------------------------------------------------------- */
.vo-announce {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   THE METAPHOR TRANSFORM
   Below 1024px there is not enough width for a window with water on both
   sides of it AND two rails in that water, and the honest response is to
   change the arrangement rather than shrink it until it is a diorama. The
   window goes full-bleed under a title bar that becomes the top of the
   screen, and both rails move inside one menu panel that drops from that
   title bar, above the window's content.

   The number is this repository's; the rule that a shell transforms rather
   than scales is class decision 8. The breakpoint is set by arithmetic
   rather than by convention: 2 rail zones (232px) plus two gutters (48px)
   plus a window narrow enough to still hold a four-column pricing table is
   the last width where the wide arrangement is telling the truth.

   The water does not transform. It is still the ocean, there is just less
   of it showing, and what shows is the lit part near the surface.
   -------------------------------------------------------------------------- */
@media (max-width: 1023px) {
  /* THE MENU BAR STAYS. It used to be hidden here, and the sentence that
     justified it said the menu button was "the same guarantee at that width".
     That sentence was wrong in two ways at once, and a per-state audit is what
     found it: the button opens a panel of seven icon-and-label links, which
     class decision 44 counts as an icon surface and which is missing two of
     the nine pages. So below 1024 this shell had no text-link navigation
     surface at all, in either mode, and its guarantee-layer compliance was
     assessed at 1280 and reported for the repository.

     The strip is unchanged here apart from tightening its own padding: same
     markup, same links, same nowrap-and-scroll it uses at every width. There
     is nothing to hide and nothing to substitute, which is what makes this
     block three lines instead of a second design. */
  .vo-menubar-list {
    padding: 0 var(--vo-space-1);
  }

  /* Full-bleed across, not full-screen. The window loses its side margins
     and its corners and takes the whole width, and it keeps a band of water
     above it, because the alternative was measured and it is worse: with the
     window at top: 0 the render matrix produced identical hashes for the
     ocean, calm and prefers-contrast cells at 390, which is what it looks
     like when the ground is not on screen at all. */
  .vo-live .vo-window {
    top: var(--vo-window-band);
    bottom: 0;
    max-width: none;
    border-radius: 0;
  }

  .vo-live .vo-menu-btn {
    display: grid;
  }

  /* THE LAUNCHER MOVES DOWN, and the number is not a nudge. A render of the
     390 desk found it parked on the one living thing a phone can see: the
     surface shoal draws at x 152-219, y 37-64, and a plate at the shipped
     inset occupies x 24-204, y 24-68, so it covered fifty-two of the shoal's
     sixty-seven pixels. Under motion that is a fish swimming behind a button;
     in the composed still life, which is the reduced-motion state and every
     capture in this repository, it is permanent, and on a theme whose whole
     subject is the ground it is the ground being sat on. Class decision 41
     was filed for the version of this where the ground goes off the screen
     entirely; this is the same mistake at a smaller radius, and the same
     thing caught it.

     The offset is --vo-window-band, which is the token that already means
     "how much ground a phone keeps", plus the inset the launcher already has.
     64 + 24 = 88, and the plate's 44px puts its bottom at 132, clear of the
     shoal by 24px and clear of the next shoal down by 34. Deriving it from
     that token rather than picking 88 means a re-skin that changes how much
     water a phone keeps takes the launcher with it. */
  .vo-launch {
    top: calc(var(--vo-window-band) + var(--vo-launch-inset));
  }

  /* Both rails, in one panel, under the title bar. The panel is a surface
     rather than a wash: it is holding the whole navigation and it has to be
     readable over whatever the water is doing behind it. */
  /* THE BORDER IS PART OF THE CHROME'S HEIGHT AND WAS MISSING FROM BOTH OF
     THESE SUMS. `--vo-titlebar-height` is a min-height on a border-box
     element, so the bar is that tall UNTIL something 44px tall goes in it,
     and then it is 44 of content plus its 1px bottom border. A 44px control
     is exactly what WCAG 2.5.8 puts in there. Measured on a long-titled page:
     the title bar is 45px at 390 and 320, the panel was landing at y=108
     against a bar whose bottom edge is at y=109, and the panel was covering
     the border it is supposed to hang from.

     One pixel, invisible, and the reason to fix it rather than round it off
     is that this is precisely the promise class decision 45 says to check —
     a calc() that subtracts a chrome token from a viewport unit is a claim
     that the chrome is that tall. It was not, on merged main, before this
     branch existed. The log records it as verified at 44px and that sentence
     is corrected in the same pull request as this line.

     NOTE WHAT IS DELIBERATELY NOT HERE: no menu-bar-height token. The panel
     hangs from the title bar and covers the strip, the way a dropdown covers
     what is under it, and the window body is `flex: 1` inside a flex column
     so layout carries the strip's height rather than arithmetic. Item 45's
     own preference is layout over the token where a shell can have it, and
     inventing a second chrome height to measure from is the trap rather than
     the fix. */
  .vo-live .vo-rails {
    display: none;
    position: fixed;
    top: calc(
      var(--vo-window-band) + var(--vo-titlebar-height) + var(--vo-border-width)
    );
    left: 0;
    right: 0;
    z-index: var(--vo-z-menu);
    max-height: calc(
      100dvh - var(--vo-window-band) - var(--vo-titlebar-height) -
        var(--vo-border-width)
    );
    overflow-y: auto;

    padding: var(--vo-space-4);
    background: var(--vo-surface);
    border-bottom: var(--vo-border-width) solid var(--vo-border-strong);
    box-shadow: var(--vo-shadow-window);
  }

  .vo-live .vo-rails.vo-open {
    display: block;
    animation: vo-settle var(--vo-duration) var(--vo-ease);
  }

  /* Inside the panel the rails are lists on a light surface, so they take
     page ink rather than ground ink. This is the second half of class
     decision 20: the two inks are not interchangeable, and the place that
     proves it is the one element that appears on both grounds. */
  .vo-live .vo-rail {
    position: static;
    width: auto;
    padding: 0;
    pointer-events: auto;
  }

  .vo-live .vo-rail + .vo-rail {
    margin-top: var(--vo-space-4);
    padding-top: var(--vo-space-4);
    border-top: var(--vo-border-width) solid var(--vo-border);
  }

  .vo-live .vo-rail-title {
    display: block;
    text-align: left;
    color: var(--vo-ink-muted);
  }

  .vo-live .vo-rail-list {
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-start;
  }

  .vo-live .vo-rail-item {
    color: var(--vo-ink);
  }

  .vo-live .vo-rail-item:hover {
    background: var(--vo-surface-muted);
  }

  .vo-live .vo-rail-item:focus-visible {
    outline-color: var(--vo-accent);
    box-shadow: none;
  }

  /* And the where-you-are mark swaps with them. The warm light that marks
     the current page over water measures 1.25:1 on the panel's surface,
     which a render caught as a barely-there tan smudge under one glyph. The
     mark is not the color, it is the role; on a light ground the accent is
     what carries the role. Third instance of class decision 20 in this
     repository, and the one that was nearly missed. */
  .vo-live .vo-rail-item[aria-current="page"] .vo-rail-glyph {
    border-bottom-color: var(--vo-accent);
  }

  .vo-live .vo-window-body {
    padding: var(--vo-space-6) var(--vo-space-4);
  }
}
