/* ==========================================================================
   Ocean background patterns
   --------------------------------------------------------------------------
   Four pure-CSS patterns on a nautical theme, plus "none". The visitor picks
   one from the palette control in the topbar and the choice is remembered;
   see js/palette.js. No images, so the whole set costs nothing beyond this
   file.

   Patterns are named, not numbered — data-bg-pattern="crosshatch" rather than
   "3" — because the set has already been reordered once and a number in
   storage meant nothing to a reader. Anything unrecognised in storage falls
   back to the default, which also migrates visitors who still have a number
   saved from the earlier rotating version.

   Built on the same contract as the Lake Mead theme's bg-patterns.css: each
   recipe declares --pattern-* custom properties and a single layer consumes
   them. The difference is where the attribute lives. The theme puts it on
   <body> and moves it there from <html> after DOMContentLoaded; here the
   pre-paint script in each page's <head> writes it straight to <html>, and
   because custom properties inherit, body::before reads them anyway. That
   removes a step and, more importantly, removes the frame where the page
   renders bare and then gains a pattern.

   ==========================================================================
   STRENGTH: WHY DAY AND NIGHT ARE SET DIFFERENTLY
   ==========================================================================

   Almost all text here sits inside an opaque card, where this layer cannot
   reach it. Exactly two colours sit directly on the --paper background with
   the pattern behind them:

     --ink    #0a1b2b on #f3f5f0   15.88:1
     --muted  #54626f on #f3f5f0    5.70:1

   --muted was #617180 when this file was written, which measured 4.57:1 here
   and 4.23:1 on --paper-warm, i.e. it failed AA in the sidebar. Darkening the
   token (see tokens.css) fixed that and incidentally gave this layer more room
   than it had. The rule below is unchanged by it and deliberately so: it is
   expressed as "never worse than the bare paper", which holds whatever --muted
   is set to, rather than as a fixed ratio that would silently go stale the
   next time a text colour moves.

   Strokes always move the paper AWAY from the text colour:

     DAY    The paper is nearly white and the text dark, so strokes are built
            from --surface with a small share of accent mixed in. Every one is
            LIGHTER than the paper, so contrast goes UP where a stroke passes
            behind text, never down. The accent share is capped at 7% because
            the palette can set --aqua to any of five presets and they are not
            equally light. Measured against the darkest (Rose #f2546b), with
            --muted on bare --paper at 5.70:1 as the baseline:

              4% -> 5.98    6% -> 5.84    7% -> 5.77    lighter than paper
              8% -> 5.70    equals the baseline; the crossover in luminance
             10% -> 5.57    darker than paper: too far

            So 7% is the last share that is genuinely lighter than the paper
            for every preset.

            The largest channel shift available on day is about 14 of 255, so
            the day layer runs at full opacity. Turning it down would erase a
            pattern that is already at the quietest end of visible.

     NIGHT  The paper is nearly black and the text light, so the same trick in
            reverse would mean near-black strokes — invisible. Night uses
            translucent accent, which does lower contrast, and can afford to:
            --muted starts at 8.64:1 there.

            The first version ran night at full opacity with stroke alphas set
            near their contrast ceilings. It measured as safe and still read
            far too strongly — the numbers were fine and the page was not,
            because a dark background shows a light stroke much more readily
            than a light background shows a lighter one. The night layer is
            therefore held at 40%, which is the same instruction in the
            opposite direction: strokes land around 8% effective alpha and the
            worst crossing moves from 4.55:1 to about 7:1.

   DO NOT build a stroke from --line. It is itself only 0.15 alpha, so mixing
   it and then multiplying by a layer opacity compounds three reductions; a
   draft that did this landed at 0.036 effective alpha and every pattern was
   invisible on a real page. Strokes here mix from OPAQUE tokens only.

   ==========================================================================
   WHAT WAS REMOVED, AND WHY IT IS NOT COMING BACK
   ==========================================================================

   Two line patterns were cut for being unreadable behind content:

     Rigging  accented every fourth line, which drew one saturated stripe of
              the current accent across the page every 120px. At Rose that
              stripe was the single loudest thing on screen. The replacement,
              "diagonal", is uniform: no accented line, one weight throughout.
     Horizon  horizontal bands sat parallel to every line of text and to the
              card edges, so it read as interference rather than as texture.
              Horizontal rules and body copy do not coexist.

   No mask anywhere. An early draft faded each pattern out below the top of
   the viewport; on a `position: fixed` layer a mask is measured against the
   viewport, not the document, so that band never scrolls and left the lower
   two thirds of the screen permanently bare. Restraint comes from the stroke
   colours and the layer opacity, which hold at every scroll position.
   ========================================================================== */

/* --- Stroke roles, day ---------------------------------------------------
   All three are lighter than --paper; see the note above. */
:root,
.force-day {
  --pat-accent:  color-mix(in srgb, var(--aqua) 7%, var(--surface));
  --pat-neutral: var(--surface);
  --pat-soft:    color-mix(in srgb, var(--aqua) 4%, var(--surface));
  --pattern-opacity: 1;
}

/* --- Stroke roles, night -------------------------------------------------
   Selector list mirrors tokens.css so the semi-dark sidebar case behaves. */
html[data-theme="night"],
.force-night {
  --pat-accent:  color-mix(in srgb, var(--aqua) 19%, transparent);
  --pat-neutral: color-mix(in srgb, var(--ink)  6%, transparent);
  --pat-soft:    color-mix(in srgb, var(--ink)  4%, transparent);
  --pattern-opacity: .4;
}

/* --- The layer ------------------------------------------------------------
   Fixed and non-interactive. The attribute is on <html>; the custom
   properties inherit down to <body>.

   z-index: -1 IS LOad-BEARING, and it is what keeps this file from having to
   touch the shell's stacking at all.

   <html> sets no background, so body's --paper propagates to the canvas and is
   painted beneath the whole root stacking context. A fixed pseudo-element at
   z-index -1 lands above that canvas and below every in-flow element and every
   positioned one at z-index >= 0. So the pattern sits between the paper and
   the page with nothing else changed.

   The first version used z-index: 0 instead and lifted the content over it
   with `.app, .main { position: relative; z-index: 1 }`. That worked on
   desktop and broke the mobile drawer. .sidebar and .backdrop are SIBLINGS of
   .app, not children — so giving .app a z-index made it a stacking context and
   trapped .topbar (z-50) inside a parent at z-1, which put the whole topbar
   underneath a backdrop at z-45. Opening the drawer then dimmed the topbar
   along with the page.

   Do not reintroduce a z-index on .app or .main. Nothing in the shell needs
   lifting; this layer goes underneath instead. */
html[data-bg-pattern] body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: var(--pattern-bg-image);
  background-size: var(--pattern-bg-size, auto);
  background-position: var(--pattern-bg-position, 0 0);
  opacity: var(--pattern-opacity, 1);
}

/* "None" is a real stored choice, not the absence of one, so it gets a real
   rule rather than relying on the attribute being missing. */
html[data-bg-pattern="none"] body::before {
  display: none;
}

/* ==========================================================================
   THE FOUR
   ========================================================================== */

/* Diagonal — the run of stays and halyards. One direction, one weight, no
   accented line: the version that accented every fourth line drew a single
   saturated stripe across the page and was the reason this pattern was
   rebuilt. The 58deg angle is deliberate — 45deg reads as a hatch swatch, an
   off angle reads as rope. */
html[data-bg-pattern="diagonal"] {
  --pattern-bg-image:
    repeating-linear-gradient(58deg,
      var(--pat-accent) 0 1px, transparent 1px 26px);
  --pattern-bg-size: auto;
}

/* Crosshatch — a fishing net hung to dry. The two directions carry different
   roles and unequal spacing (34 and 30) so the intersections drift rather
   than locking into a lattice; drift also keeps the crossings sparse, which
   matters on night where the two translucent strokes compound where they
   meet. This is the only pattern whose strokes must cross. */
html[data-bg-pattern="crosshatch"] {
  --pattern-bg-image:
    repeating-linear-gradient(45deg,
      var(--pat-accent) 0 1px, transparent 1px 34px),
    repeating-linear-gradient(-45deg,
      var(--pat-neutral) 0 1px, transparent 1px 30px);
  --pattern-bg-size: auto;
}

/* Swell — rows of shallow crests, the way calm water reads at a distance.
   Two rows offset by half a tile in both axes, the second much quieter, so
   they sit at apparent depths instead of forming one flat grid. The arc is
   the outer edge of a radial-gradient ellipse: everything inside is
   transparent and only the last 1.3px of the radius is drawn. */
html[data-bg-pattern="swell"] {
  --pattern-bg-image:
    radial-gradient(ellipse 30px 11px at 50% 100%,
      transparent calc(100% - 1.3px), var(--pat-accent) calc(100% - 1.3px) 100%,
      transparent 100%),
    radial-gradient(ellipse 30px 11px at 50% 100%,
      transparent calc(100% - 1.3px), var(--pat-soft) calc(100% - 1.3px) 100%,
      transparent 100%);
  --pattern-bg-size: 60px 36px, 60px 36px;
  --pattern-bg-position: 0 0, 30px 18px;
}

/* Bubbles — sparse rising air. Six discs of four sizes in ONE 120px tile,
   placed by hand so the group reads as scattered while every disc stays clear
   of every other, in this tile and across the tile seam: the closest pair has
   a 28.9px gap between their edges.

   An earlier version used three separate layers on tiles of 94 / 62 / 41,
   assuming sizes that share no common factor would keep the discs apart. They
   are near-coprime, which does the opposite over a large area — 53 pairs of
   discs actually touched within a 2560x1440 viewport. One shared tile makes
   the relative spacing identical everywhere, so separation is guaranteed by
   construction instead of hoped for. */
html[data-bg-pattern="bubbles"] {
  --pattern-bg-image:
    radial-gradient(circle at 18px 26px,  var(--pat-accent)  0 2.6px, transparent 3.1px),
    radial-gradient(circle at 74px 14px,  var(--pat-neutral) 0 1.3px, transparent 1.8px),
    radial-gradient(circle at 46px 68px,  var(--pat-accent)  0 1.8px, transparent 2.3px),
    radial-gradient(circle at 101px 52px, var(--pat-soft)    0 1.2px, transparent 1.7px),
    radial-gradient(circle at 28px 99px,  var(--pat-accent)  0 1.6px, transparent 2.1px),
    radial-gradient(circle at 88px 106px, var(--pat-neutral) 0 1.1px, transparent 1.6px);
  --pattern-bg-size: 120px 120px;
}

/* ==========================================================================
   Where texture is unhelpful
   ========================================================================== */

/* Narrow screens have less room between cards, so the same pattern shows in
   smaller gaps and reads louder. It comes down rather than off — it is still
   pleasant behind a phone-width page. */
@media (max-width: 720px) {
  html[data-bg-pattern] body::before {
    opacity: calc(var(--pattern-opacity, 1) * .6);
  }
}

/* Someone who asked for reduced data does not want a decorative layer, cheap
   though this one is; and high contrast means they need the foreground to win
   outright. Both turn it off rather than down. The visitor's own "none" is a
   separate thing and is honoured above. */
@media (prefers-reduced-data: reduce) {
  html[data-bg-pattern] body::before { display: none; }
}

@media (prefers-contrast: more) {
  html[data-bg-pattern] body::before { display: none; }
}
