/* The design pass: a nautical chart on parchment. Sepia serif type, brass and
   gold on cream, and one fixed backdrop — the owner's own artwork,
   public/background.webp — behind every screen, so the whole app reads as one
   chart rather than six pages. The layout underneath is unchanged: the
   geometry the suite asserts (arm heights, viewport width, where the error
   banner sits) is load-bearing and the theme is paint on top of it. */

/* The two faces of the chart, bundled in public/fonts (SIL OFL, license text
   beside them) rather than fetched from a CDN: Mapwork works with no network,
   so a font that arrives over one would be a theme that vanishes offline.
   IM Fell English is a revival of 17th-century printing type — the letterforms
   actual sea charts were set in. Cinzel is engraved Roman capitals, the
   lettering on a compass card. */
@font-face {
  font-family: 'IM Fell English';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/im-fell-english.woff2') format('woff2');
}

@font-face {
  font-family: 'IM Fell English';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/im-fell-english-italic.woff2') format('woff2');
}

@font-face {
  font-family: 'Cinzel';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('fonts/cinzel-600.woff2') format('woff2');
}


:root {
  /* Body text is the antique print; identity and actions are the engraved
     capitals. Two roles, not a face per component. */
  --hand: 'IM Fell English', 'Iowan Old Style', Palatino, Georgia, serif;
  --engraved: 'Cinzel', 'Iowan Old Style', Palatino, Georgia, serif;
  /* N, E, S and W. The app's own body face, and no separate file: the owner
     picked IM Fell English SC off a twenty-face specimen sheet, and its
     capitals are the same drawing as the capitals in the IM Fell English
     already bundled here. Measured rather than assumed — see .point-dir. */
  --point-face: 'IM Fell English', 'Iowan Old Style', Palatino, Georgia, serif;

  /* The anchor a Rigging row wears when it is waiting on somebody else. A mask
     rather than a colored drawing, so the faint and inked states are one
     `color` apart and this is drawn once. */
  --anchor-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='4.2' r='2.2'/%3E%3Cpath d='M12 6.4V21'/%3E%3Cpath d='M7.5 9.4h9'/%3E%3Cpath d='M4.2 14.1c0 4 3.4 6.9 7.8 6.9s7.8-2.9 7.8-6.9'/%3E%3Cpath d='M4.2 14.1h2.6M19.8 14.1h-2.6'/%3E%3C/svg%3E");

  /* The Cave, on a Compass row: the way back for something that turned out not
     to be actionable after all.

     A solid rock with the mouth knocked out of it, rather than the outlined
     arch this was first drawn as. Four outlined versions were rendered at the
     size they are actually used and every one of them read as a croquet hoop
     or a tent: at 20px an outline has no mass, and mass is the whole of what
     says rock. The opening being paper-colored rather than inked is what
     makes it a cave and not a hill — it is the one part of the drawing that
     is a hole. `fill-rule='evenodd'` is what cuts it.

     Drawn as a mask over `currentColor` for the same reason the anchor above
     is: resting and hovered are one color apart, and the artwork exists
     once. */
  --cave-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M1.4 20.4h21.2' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='M2.2 20.4C2.6 15 5 11.2 8 9.4c.9-.5 1.5-1.1 2.2-1.9.9-1 2.4-.9 3.2.2.5.7 1.1 1.3 1.9 1.8 3.2 2 5.2 5.6 5.5 10.9' fill='none' stroke='%23000' stroke-width='2' stroke-linejoin='round' stroke-linecap='round'/%3E%3Cpath d='M6.9 20.4c-.2-3.6.9-6 3.2-6.9 2.6-1 5.1.7 5.7 3.4.2 1.1.3 2.2.1 3.5z' fill='%23000'/%3E%3C/svg%3E");

  /* The page is light parchment, and native controls need telling. The select,
     the checkboxes, the text fields and the scrollbars all pick their
     appearance from this, so setting it once is the fix and restyling each
     control would be four copies of it. */
  color-scheme: light;

  /* Sampled from the quiet interior of background.webp, so the color behind
     the artwork is the artwork's own: no seam where `cover` does not reach on
     a wide window, and no flash of a different cream while it loads. */
  --paper: #f5eee4;       /* the parchment itself */
  --paper-deep: #eadcbd;  /* its darkened edges */
  --card: #faf4e6;        /* a raised card face */
  --ink: #2f2b20;         /* sepia-black text */
  --muted: #7c6e50;       /* faded ink */
  --rule: #d8caa4;        /* a hairline on parchment */
  --rule-strong: #a8925f; /* a drawn border */
  --brass: #8a6c33;       /* the accent: active states, focus, links */
  --brass-fill: #ecd5a1;  /* the gold fill an active control wears */
  --brass-fill-deep: #ddbf7f;
  --rust: #b1502a;        /* the compass star's copper */
  --warn: #9d3a24;        /* deep rust-red, legible on cream */

  /* Warm, and the same warmth the admin console is painted in. Across this
     suite, warm on the app's own ground means "this is not the thing itself" —
     the console that administers Mapwork, and the install that is not the real
     one. Distinct from --warn so an error and an environment never read as the
     same kind of statement. */
  --test: #e0a34a;

  /* Empty reads as absence, not as a cold rating. The other three warm.
     Reported from the owner's phone in night mode: medium and high looked
     almost the same. Measuring the old three said the fault was wider than
     that pair. As #4a7fb5 / #c9962f / #c34f28 they sat at greys 123, 159 and
     116 — medium against high was 1.76:1 and fell to 1.61:1 under a warm
     night filter, and low against high was 1.12:1, which is to say the same
     shade. The whole scale was carried by hue, so anything that flattens hue
     — a night filter, a dim screen, sunlight, a red-green deficiency — took
     the scale with it.

     High comes down to a deep near-ink rust, which puts medium against high
     at 3.53:1 and 3.13:1 under the same filter, and the two ends at 2.74:1.
     Medium is still the lightest of the three and is meant to be: brass is
     gold, and darkening it to force a light-to-dark ramp would cost the
     app's own accent color for an ordering the tick marks below now carry
     without needing color at all. */
  --temp-1: #5b8fbe;
  --temp-2: #c9962f;
  --temp-3: #7e2a19;

  /* The decorations, drawn once each and reused. All ink-on-nothing SVG with
     the opacity baked in, because two background layers cannot be faded
     separately in CSS. */
  --star-rose: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-104 -104 208 208'%3E%3Cg fill='%232f2b20'%3E%3Cpath d='M0 -60 L9 -9 L60 0 L9 9 L0 60 L-9 9 L-60 0 L-9 -9 Z' transform='rotate(45)' fill='%238a6c33'/%3E%3Cpath d='M0 -100 L13 -13 L100 0 L13 13 L0 100 L-13 13 L-100 0 L-13 -13 Z'/%3E%3C/g%3E%3Ccircle r='9' fill='%23f5eedd' stroke='%232f2b20' stroke-width='5'/%3E%3C/svg%3E");
  --star-rust: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-104 -104 208 208'%3E%3Cg fill='%23b1502a'%3E%3Cpath d='M0 -58 L9 -9 L58 0 L9 9 L0 58 L-9 9 L-58 0 L-9 -9 Z' transform='rotate(45)'/%3E%3Cpath d='M0 -100 L13 -13 L100 0 L13 13 L0 100 L-13 13 L-100 0 L-13 -13 Z'/%3E%3C/g%3E%3C/svg%3E");
}

* { box-sizing: border-box; }

/* Every checkbox in the app — the Temperature toggle as much as a row's
   completion box. One rule rather than one per class: accent-color on
   `.row-check` alone was how the toggle stayed browser-blue through a passing
   suite. Round, per the design direction: a compact circular control, drawn
   over the native input so it stays a real checkbox to a screen reader and
   the keyboard. The tick is a background image because a replaced element
   cannot carry a pseudo-element. */
input[type="checkbox"],
input[type="radio"] {
  appearance: none;
  width: 1.375rem;
  height: 1.375rem;
  margin: 0;
  border: 1px solid var(--rule-strong);
  background: var(--card);
  box-shadow: inset 0 1px 2px rgb(60 45 15 / 12%);
  cursor: pointer;
}

/**
 * Square for a checkbox, round for a radio.
 *
 * Both were round for eleven builds and the owner asked for the checkboxes to
 * be square — his call, and an aesthetic one. It happens to be the older
 * convention as well: a square is a thing you turn on and off, a circle is one
 * of a set you pick from, and every platform has drawn them that way for long
 * enough that people read it without being told.
 *
 * So the radios keep their circle rather than following. That distinction was
 * already carrying weight in the capitalization card — see the `:checked`
 * rules below, where a tick means "you turned this on" and a dot means "this
 * is the one" — and squaring both would have taken the shape half of it away
 * and left the marker doing the work alone.
 *
 * 0.25rem rather than a hard corner, which is the radius the row buttons and
 * the sort controls already wear. At 22px it still reads square; a 0 would be
 * the only unrounded corner in the app.
 */
input[type="checkbox"] { border-radius: 0.25rem; }
input[type="radio"] { border-radius: 50%; }

input[type="checkbox"]:checked {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M5 10.5 L8.5 14 L15 6.5' fill='none' stroke='%232f2b20' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    center / 0.875rem 0.875rem no-repeat,
    linear-gradient(var(--brass-fill), var(--brass-fill-deep));
}

/* A dot rather than the checkbox's tick, and it is the only difference between
   them. The owner asked for checkboxes over radios once already, for the age
   switches, and was right: those are five independent yes-or-nos. Case is one
   choice with three answers, where checkboxes would draw a control that can be
   put into a state — none ticked, or two — that the setting cannot hold. So it
   stays a radio, and the tick is what marks the two apart at a glance: a tick
   is something you turned on, a dot is the one you picked. */
input[type="radio"]:checked {
  background:
    radial-gradient(circle at center, var(--ink) 0 0.1875rem, transparent 0.1875rem),
    linear-gradient(var(--brass-fill), var(--brass-fill-deep));
}

/* The `hidden` attribute loses to any rule that sets `display`, silently and
   from anywhere in the file. It cost one already: #backup-output sets
   `display: block` for its width, which left an empty textarea sitting on the
   page from first load — through a passing suite, because Playwright reads a
   hidden field's value quite happily. Winning globally means the next element
   given `hidden` cannot repeat it. */
[hidden] { display: none !important; }

body {
  margin: 0;
  /* viewport-fit=cover plus these insets keeps content clear of the notch and
     the home indicator once the app is installed to a home screen. The right
     side carries more than the left because the rope hangs there — the text
     column stops at its rail instead of running under it. */
  padding: max(1.5rem, env(safe-area-inset-top)) 1.875rem
           max(2rem, env(safe-area-inset-bottom)) 1.25rem;
  background: var(--paper);
  color: var(--ink);
  /* The antique print throughout the running text. 17.5px because Fell's
     x-height runs small against a modern serif, and inputs must stay at 16px+
     or iOS zooms on focus. */
  font: 17.5px/1.5 var(--hand);
  -webkit-font-smoothing: antialiased;
  /**
   * No double-tap zoom, anywhere in the app.
   *
   * Reported from the phone: cycling a thermometer means tapping the same
   * 14px control repeatedly, and two taps close together are a zoom gesture
   * to the browser. So the page lurched instead of the level changing — the
   * fastest interaction in the product punished for being fast.
   *
   * `manipulation` is the narrow instrument for that. It removes double-tap
   * zoom and nothing else: panning and PINCH zoom both still work, so the
   * page can still be magnified by anybody who needs to. The blunt instrument
   * — `user-scalable=no` on the viewport — would take that away too, and a
   * person who cannot read 17.5px is not a person to take zoom away from.
   *
   * On body rather than on the controls: the value is intersected down the
   * hit-test ancestry, so one declaration here covers every tappable thing on
   * every screen, including whatever gets added next. The rows already carry
   * their own tap targets, and a control added without this line would have
   * quietly gone back to lurching.
   */
  touch-action: manipulation;
}

/* The chart under everything: public/background.webp, the artwork the owner
   supplied — parchment, the rope down the right edge, a compass rose low on
   the left, an anchor and the chart's construction lines. It replaces the
   layers this rule used to draw by hand (a tiled rope, an SVG rose, a curved
   graticule and a vignette), which were a stand-in for exactly this.

   One fixed layer rather than a background on body, because
   background-attachment: fixed is ignored by iOS Safari and this app lives on
   iPhones. Fixed and full-viewport is also what makes every screen share the
   same backdrop — the rope and the rose sit still while the content scrolls
   over them, like ink under tracing paper.

   `cover` and the artwork's own 853x1844 are a phone's proportions almost
   exactly, so on the device this is built for it lands whole. A wide window
   crops the top and bottom rather than stretching it; the parchment either
   side is --paper, which is sampled from the artwork so the join is invisible
   while it loads and wherever it does not reach. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: url('background.webp') center / cover no-repeat;
  /* Held at 60% against --paper behind it. At full strength the artwork was
     distracting on the busier screens — the owner's read, and the screens
     where it showed are the ones with the most to read: Settings, and a
     Compass with a full list scrolling over the anchor. Fading the layer
     rather than the image keeps one file for every screen.

     It was 70% for eleven builds and the owner asked for another 15% off it,
     which is 0.595 exactly; 0.6 is that rounded, and a hundredth of opacity is
     below anything an eye can separate. Written down because the next person
     to see 0.6 should know it is a measurement rather than a round number
     somebody liked. */
  opacity: 0.6;
}

/* The TEST bar. Sticky rather than in the flow, because standard 6 asks for a
   label that is still obvious while the page is being used — a header banner
   scrolls away on the second screenful and the app is a long scroll.

   The negative margins cancel body's own padding so it reaches all three edges,
   and the safe-area inset is added back as padding so an installed PWA does not
   put the text under the notch. z-index because the compass and the sticky bits
   of the list would otherwise pass over it. */
.environment {
  position: sticky;
  top: 0;
  z-index: 20;
  margin: calc(-1 * max(1.5rem, env(safe-area-inset-top))) -1.875rem 1.5rem -1.25rem;
  padding: calc(0.4rem + env(safe-area-inset-top)) 1.25rem 0.4rem;
  background: var(--test);
  border-bottom: 1px solid var(--rule-strong);
  color: #241706;
  font-family: var(--engraved);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-align: center;
  text-transform: uppercase;
}

main { max-width: 34rem; margin: 0 auto; }

.masthead { margin-bottom: 1.5rem; }

/* Engraved capitals for the name — Cinzel's lowercase are small capitals, so
   "Mapwork" comes out as a compass card's lettering. Sized down from the old
   serif because capitals run wide, and the name has to share 390px with the
   menu button. */
h1 {
  margin: 0;
  font-family: var(--engraved);
  font-size: 1.75rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  line-height: 1.2;
}

.tagline {
  margin: 0.125rem 0 0;
  color: var(--muted);
  font-size: 0.9375rem;
  font-style: italic;
}

.error {
  margin: 0 0 1rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--warn);
  border-radius: 0.5rem;
  background: rgb(157 58 36 / 7%);
  color: var(--warn);
  font-size: 0.9375rem;
}

/* ------------------------------------------------------------------ compass */

/* The rose is the artwork and the space around it is the control. The square
   below the heading carries rose.svg as its backdrop, and the four buttons
   are wedges that tile that square corner to corner — north the top wedge,
   south the bottom, east and west the sides — so a tap anywhere in a quadrant
   of the square selects that direction. The letters and labels sit outside
   the ring, leaving the drawing clear, which is the owner's ask. */
.compass { margin: 0.25rem 0 1.25rem; }

/* The quadrant you are standing in, announced but not drawn. It was a heading
   above the rose until the owner asked for the selected word to stay on its
   own point — "mapping stays on the east even when highlighted" — and once the
   label lives at its point the heading is the same word a second time. So it
   keeps its aria-live job for a screen reader, which otherwise has nothing
   saying the selection moved, and takes no space on the screen. */
.compass-hub {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.compass-rose {
  position: relative;
  /* Wider than tall, and deliberately not a square: the wedges tile any
     rectangle, and a square's height was dead space above and below a rose
     sized by the width the side labels leave it. The rose is sized off the
     height (`auto 56%`) so it stays round whatever the width does. */
  height: 18.75rem;
  /* The square is the tap area and the rose is drawn well inside it, because
     the labels ride at the square's edges and at 94% they sat on the ring —
     the owner's screenshot, three of them circled. The margin the smaller
     percentage buys is what the words stand in.

     The three numbers are one arrangement and move together: the square's
     width, the rose's share of it, and the side labels' column below. What
     has to hold is inked-rose + two label columns + a margin each side <= the
     square, and the square is capped by the content column — 340px on a
     390px phone. So larger words are paid for out of the rose: the owner
     asked for both and said the compass could take the cut. */
  max-width: 21.25rem;
  margin: 0 auto;
  background: url('rose.svg') center / auto 56% no-repeat;
}

/* Each wedge's layout box is half the square — north and south the top and
   bottom halves, east and west the left and right — and the clip-path cuts
   the half into the triangle that meets the center. The triangles tile the
   square with no gaps, and clip-path clips hit-testing as well as paint, so
   they are the real tap areas. The suite measures these boxes: north/south
   full-width half-height, east/west half-width full-height, in symmetric
   pairs. */
.point {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: inherit;
  line-height: 1.3;
  text-align: center;
  cursor: pointer;
}

.point-north {
  inset: 0 0 50% 0;
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  justify-content: flex-start;
  padding-top: 0.125rem;
}

.point-south {
  inset: 50% 0 0 0;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  justify-content: flex-end;
  padding-bottom: 0.125rem;
}

/**
 * East and west lay their two lines out as a grid rather than a flex column,
 * so the letter centers under its own word.
 *
 * Flex aligned each child independently against the wedge's outer edge, which
 * was invisible while the letter and the label were both a fixed 5.5rem —
 * `text-align: center` centered the glyph inside a box the same width as the
 * word above it. Capping the width instead of fixing it (see below, and it is
 * what paid for the larger type) made the two boxes different widths, and the
 * narrow letter went flush to the edge: W under the left of "Coding", E under
 * the right of "Mapping". Reported by the owner as the letters "sitting at the
 * end", which is exactly what they were doing.
 *
 * One implicit column is what fixes it. The column takes the width of its
 * widest item — the word — `justify-items: center` centers the letter inside
 * it, and `justify-content` puts the whole column against the outer edge where
 * flex used to put each item separately. So the word does not move a pixel and
 * the measured clearance is unchanged; only the letter does.
 *
 * `align-content` rather than `justify-content` for the vertical centring: the
 * axes swap meaning between flex-column and grid, and reusing the flex line
 * here would have centered the column horizontally and put the words back on
 * the ring.
 */
.point-west {
  inset: 0 50% 0 0;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
  display: grid;
  align-content: center;
  justify-items: center;
  justify-content: start;
}

.point-east {
  inset: 0 0 0 50%;
  clip-path: polygon(100% 0, 100% 100%, 0 50%);
  display: grid;
  align-content: center;
  justify-items: center;
  justify-content: end;
}

/* The side labels are capped rather than fixed, and that is what paid for the
   larger type.
     
   A fixed 5.5rem column made every name as wide as the longest one it had to
   hold, so "Coding" reserved 88px and stood 11px off the ring exactly as
   "Programming" did. Sizing to the word and capping the result gives that back
   for free: measured at 390px with the owner's four names, west clears the
   ring by 40px and east by 27px, where both used to clear 11px — and the cap
   means the worst case is still bounded at 12px whatever anybody types.

   Two words still wrap at the space. A single word past the cap breaks
   mid-word, which is ugly and deliberate: the alternative is a word that
   overflows in both directions and lands back across the ring, and the
   quadrant names are the user's own. At this size that starts at about nine
   characters — raising the cap to fit an eleven-character word was measured
   and puts the label 4px INSIDE the ring, so the break is the cheaper of the
   two. */
.point-west .point-dir, .point-west .compass-label,
.point-east .point-dir, .point-east .compass-label {
  width: auto;
  max-width: 5.5rem;
  text-align: center;
  overflow-wrap: anywhere;
}

/* The selected wedge wears the brass wash, clipped to its triangle — a lit
   quarter of the chart rather than a box over it. Colour is not the only
   signal: the letter grows and the label turns from faded ink to full. */
.point.is-active { background: none; }

.point-north.is-active { background: linear-gradient(to top, rgb(214 180 112 / 40%), rgb(214 180 112 / 0%)); }
.point-south.is-active { background: linear-gradient(to bottom, rgb(214 180 112 / 40%), rgb(214 180 112 / 0%)); }
.point-west.is-active { background: linear-gradient(to left, rgb(214 180 112 / 40%), rgb(214 180 112 / 0%)); }
.point-east.is-active { background: linear-gradient(to right, rgb(214 180 112 / 40%), rgb(214 180 112 / 0%)); }

/**
 * The four points, in the app's own body face.
 *
 * The owner picked IM Fell English SC off a sheet of twenty, which is the face
 * every other word in the app is already set in, in its small-capitals cut. No
 * separate file was bundled for it, and that is a measurement rather than a
 * shortcut: an SC cut differs from its roman in the LOWERCASE, which becomes
 * small capitals. The capitals themselves are the same drawing, and N, E, S
 * and W are the only characters this ever draws.
 *
 * Checked three ways before dropping the file. The glyph outlines hash almost
 * identically and the advance widths agree to within 3 units in 2048 — S is
 * exact. Rendered at 200px and again at the 30 and 40 these are actually drawn
 * at, the two differ in 2.2% of pixels, which is antialiasing around identical
 * edges rather than a different letter. Overlaid, nothing shows through.
 *
 * So the compass points now share `im-fell-english.woff2` with the body text,
 * and the app ships one font file fewer than it did before the compass had a
 * face of its own. What goes with it is a whole class of bug: there is no
 * four-glyph subset to fall through, nothing that renders blank on a fifth
 * character, and nothing for the service worker's precache list to drift from.
 *
 * Two numbers still move with the face:
 *
 * `font-weight` is 400 and must match the file, which holds that weight and no
 * other. Ask for a weight the file does not have and the browser synthesizes
 * one by smearing the outline sideways, which on a hand this rough is the
 * texture gone. A test pins the CSS weight against the @font-face.
 *
 * `letter-spacing` is 0. Tracking looks like the obvious thing to add to
 * separated Roman capitals — Cinzel's had 0.04em of it here — and it would do
 * nothing: each letter is alone in its own element, so there is no gap between
 * letters for tracking to open. All it adds is a trailing space that pushes
 * the glyph off the center it is supposed to sit on, measured at 0.6px.
 *
 * `font-size` stays at 1.875rem. It was raised from Cinzel's 1.375rem for a
 * script, whose capitals stand well short of their em box — not true here —
 * but the letters have been that size for six builds and shrinking them is a
 * change nobody asked for.
 *
 * Three faces were tried here before this one and the record is worth keeping,
 * because the first two failed the same way. Dancing Script Bold held it for
 * one build, 0.0.42, against a modern-brush-calligraphy reference; the owner
 * called it cartoonish on the phone. Great Vibes followed. Bodoni Moda came
 * from the owner's National Geographic map-type specimen sheet and lasted two
 * builds. What the sheet was really pointing at was a chart's lettering, and
 * the app already had it.
 */
.point-dir {
  color: var(--ink);
  font-family: var(--point-face);
  font-size: 1.875rem;
  font-weight: 400;
  letter-spacing: 0;
  /* Letters and labels sit over the rose’s linework, so both carry the
     paper halo. */
  text-shadow:
    0 0 5px var(--paper), 0 0 5px var(--paper),
    0 0 10px var(--paper), 0 0 10px var(--paper);
}

/* Each label rides above its letter — the owner’s ask: the wording outside
   the compass, above the lettering. DOM order stays letter-then-label for a
   screen reader (“N, Physical Labor”); order swaps only the drawing. */
.point .compass-label {
  order: -1;
  color: var(--muted);
  /* 0.9375rem for two builds, and the owner asked for bigger again. It costs
     the rose nothing this time — the width cap above is what changed, not the
     artwork — and it stays under the 1.375rem letters so N/E/S/W still read as
     the heading and the name as its caption. */
  font-size: 1.1875rem;
  text-shadow:
    0 0 5px var(--paper), 0 0 5px var(--paper),
    0 0 10px var(--paper), 0 0 10px var(--paper);
}

/* The selected point keeps its own word — the owner's ask, and the reason the
   heading above the rose is gone. Mapping stays on the east when east is the
   quadrant you are standing in; it darkens from faded ink to full and takes
   the engraved weight, rather than moving somewhere else on the screen. */
.point.is-active .compass-label {
  color: var(--ink);
  font-weight: 600;
}

/* The selected point's letter grows, and it has to grow from the larger
   resting size the letters have carried since the script did — 1.875 was the
   active size when they were Cinzel and is the resting size now. */
.point.is-active .point-dir { font-size: 2.5rem; }

/* ----------------------------------------------------------------- quadrant */

.add { display: flex; gap: 0.5rem; margin-bottom: 0.875rem; }

.add input {
  flex: 1;
  min-width: 0;
  padding: 0.625rem 2.25rem 0.625rem 0.875rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  /* A small copper star sits in the field's right end, as it does on the
     chart's own capture line. The padding above keeps long text off it. */
  background: var(--star-rust) right 0.7rem center / 0.875rem 0.875rem no-repeat, var(--card);
  box-shadow: inset 0 1px 3px rgb(60 45 15 / 12%);
  color: var(--ink);
  /* 16px minimum, or iOS zooms the page on focus and never zooms back. */
  font: inherit;
}

.add input::placeholder { color: var(--muted); font-style: italic; }

.add input:focus-visible,
.compass button:focus-visible,
button:focus-visible,
select:focus-visible,
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }

.add button[type="submit"] {
  padding: 0.625rem 1.25rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: linear-gradient(var(--brass-fill), var(--brass-fill-deep));
  box-shadow: inset 0 0 0 1px rgb(250 244 230 / 70%), 0 1px 3px rgb(60 45 15 / 25%);
  color: var(--ink);
  font: 600 0.9375rem/1.4 var(--engraved);
  letter-spacing: 0.05em;
  cursor: pointer;
}

.controls {
  display: flex;
  gap: 1.25rem;
  align-items: center;
  margin-bottom: 0.5rem;
  color: var(--muted);
  font-size: 0.875rem;
}

.controls select {
  margin-left: 0.375rem;
  padding: 0.25rem 0.375rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.375rem;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 0.875rem;
}

.toggle { display: flex; align-items: center; gap: 0.375rem; cursor: pointer; }

.direction {
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.375rem;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
}

.direction:disabled { opacity: 0.4; cursor: default; }

/* --------------------------------------------------------------------- list */

/* The rule between controls and list is an ornament — line, star, line — the
   way a chart rules off its title block. Drawn on the list mount so every
   waypoint gets it without an element of its own. */
#list::before {
  content: '';
  display: block;
  height: 1rem;
  margin: 0.375rem 0 0.75rem;
  background:
    linear-gradient(var(--rule-strong), var(--rule-strong)) left center / calc(50% - 1.25rem) 1px no-repeat,
    linear-gradient(var(--rule-strong), var(--rule-strong)) right center / calc(50% - 1.25rem) 1px no-repeat,
    var(--star-rust) center / 1rem 1rem no-repeat;
  opacity: 0.75;
}

.list { margin: 0; padding: 0; list-style: none; }

/**
 * The line every control in a row sits on.
 *
 * `body` is `17.5px/1.5`, so the first line of an item's words occupies a
 * 26.25px line box starting at the row's padding. A row is `align-items:
 * flex-start` — it has to be, or a three-line item puts its controls halfway
 * down — which means each control aligns its own TOP and lands wherever its
 * own height leaves it. Measured on the phone that put the grip 7px below the
 * words, the anchor 1px above them and the three buttons 3px below: four
 * controls, four different lines, none of them the text's.
 *
 * So each control centers on this instead, either by being exactly this tall
 * or by being pulled up half the difference. The difference is worked out in
 * the control's own rule next to the height it already declares — a formula
 * applied locally rather than a number copied around — and the check in
 * test/drive.mjs pins every center to the same pixel, so a height changed
 * without its offset fails rather than drifting.
 */
.row { --row-line: calc(1.5 * 17.5px); }

.row {
  display: flex;
  /* Top-aligned since the text started wrapping. Centered, a three-line item
     put its checkbox and its × halfway down the row with nothing beside them,
     and the controls stopped lining up with each other from row to row —
     which is what a column of tap targets is for. */
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.625rem 0;
  border-bottom: 1px solid var(--rule);
  background: transparent;
}

/* Only while a drag is happening. A permanent transition would animate the
   rebuild after every add and delete too, which reads as lag rather than
   polish. */
.is-reordering .row { transition: transform 120ms ease; }

.is-reordering .row.is-dragging {
  /* The held row follows the finger exactly; easing it would put it behind.
     Opaque while held — the rows it slides over must not show through it. */
  transition: none;
  position: relative;
  z-index: 1;
  border-radius: 0.375rem;
  background: var(--card);
  box-shadow: 0 0.5rem 1.5rem rgb(60 45 15 / 30%);
}

/* ---------------------------------------------------------------------- grip */

.grip {
  display: grid;
  grid-template-columns: repeat(2, 3px);
  grid-auto-rows: 3px;
  gap: 3px;
  flex-shrink: 0;
  /* Size, not padding: the dots are 15px across and this is the target around
     them, which is what a thumb actually hits. It was 0.75rem of vertical
     padding until the controls were lined up — the same 41px, but a number
     that had to be worked out rather than read, and the offset below has to
     read it. */
  height: 2.5625rem;
  align-content: center;
  justify-content: center;
  margin-top: calc((var(--row-line) - 2.5625rem) / 2);
  padding: 0 0.625rem;
  border: 1px solid transparent;
  border-radius: 0.375rem;
  background: none;
  cursor: grab;
  /* Without this the browser claims the gesture as a page scroll and the row
     never moves — the single most common reason a touch drag does nothing. */
  touch-action: none;
}

.grip i { display: block; width: 3px; height: 3px; border-radius: 50%; background: var(--muted); }
.grip:hover i, .grip:focus-visible i { background: var(--ink); }
.grip:active { cursor: grabbing; }

/**
 * The words, whether they are a <span>, the <button> that opens the editor, or
 * the <textarea> that is the editor. One rule for all three, because the whole
 * point of tapping the words is that nothing moves when you do — a button that
 * sat a pixel off, or a field that changed the line breaks, would make every
 * edit start with a flinch.
 */
.row-text {
  flex: 1;
  min-width: 0;
  /* A button and a textarea each bring their own, and neither is the page's. */
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  line-height: inherit;
  text-align: left;
  /* Buttons do not wrap by default in every engine, and this one has to. */
  white-space: pre-wrap;
  /* The words are the affordance, so the pointer says "text" rather than
     "link" — there is no button here to find, which is what was asked for. */
  cursor: text;
  /* Wraps, so a long item can be read.
     
     This truncated to one line with an ellipsis for eight builds, on the
     argument that a reflowing row moves the buttons under a thumb already on
     its way down. The owner reported the cost from the phone — "Recipe
     database with AI captur…" in a quadrant, and the same everywhere from the
     Inbox to the Plank — and it is the larger one: a row whose buttons moved
     is a mis-tap, and a row you cannot read is an item you have to open
     something else to identify.

     `anywhere` rather than plain wrapping, because a pasted URL is one word
     with no break in it and would otherwise push the row wider than the
     screen, taking the × off the edge. */
  overflow-wrap: anywhere;
}

/* Being edited. A ruled underline rather than a box, so the row keeps its
   shape and the change is legible as "these words are live" — a full input
   chrome here would redraw the row around the thing being read. */
.row-edit {
  overflow: hidden;
  border-bottom: 1px solid var(--brass);
  resize: none;
  /* iOS zooms the page on focus for anything under 16px. The rows are 17.5px,
     so this only has to not go backwards. */
  font-size: inherit;
}

.row-edit:focus { outline: none; }

/**
 * The anchor: this item is waiting on somebody else.
 *
 * A mask over `currentColor` rather than two drawings, so the faint and inked
 * states are one `color` apart and the artwork exists once. `-webkit-mask` as
 * well as the standard property, because the phone this is built for is the
 * one that still needs it.
 *
 * Sized and spaced like the thermometer beside it, and centered on the first
 * line for the same reason the rest of the row's controls are — a wrapped item
 * must not leave its controls floating halfway down.
 */
.anchor {
  flex-shrink: 0;
  width: 1.125rem;
  /* The line box itself, so the mask below — which fills this box — centers on
     the first line of the words. It was 1.5rem, which left it a pixel high;
     the owner spotted that on the phone before any of the arithmetic did. */
  height: var(--row-line);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  /* Faint: the resting state is legible as "this control exists" and no more.
     Inked is the statement. */
  color: var(--rule);
}

.anchor::before {
  content: '';
  display: block;
  width: 100%;
  height: 100%;
  background: currentColor;
  -webkit-mask: var(--anchor-mask) center / 1.125rem 1.125rem no-repeat;
  mask: var(--anchor-mask) center / 1.125rem 1.125rem no-repeat;
}

.anchor[aria-pressed="true"] { color: var(--rust); }

/* An item nobody is waiting on reads normally; one that is waiting is not the
   reader's to move, and the words say so quietly rather than by shouting. The
   anchor is the signal and this is the second half of it, so the distinction
   survives being seen out of the corner of an eye. */
.row:has(.anchor[aria-pressed="true"]) .row-text { color: var(--muted); }

/* Centered, because the completion box stands between the two buttons and is
   smaller than they are. Stretch — the flex default — would pull a 22px circle
   to 32px tall and draw it as an oval.

   The box is the line box, and the 2rem buttons inside it overflow by 3px top
   and bottom. That is deliberate: overflow is visible and a button that sticks
   out is still a button, so the tap targets keep their full height while the
   glyphs line up with the words. Giving the container the buttons' height
   instead would have centered them on nothing. */
.row-actions {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  flex-shrink: 0;
  height: var(--row-line);
}

.row-button {
  width: 2rem;
  height: 2rem;
  border: 1px solid transparent;
  border-radius: 0.375rem;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.row-button:hover:not(:disabled) { border-color: var(--rule-strong); color: var(--ink); }
.row-button:disabled { opacity: 0.25; cursor: default; }

/* The Cave button's face. A .row-button in every other respect — same box, same
   border on hover, same muted resting color as the × it stands beside — so the
   two read as two of a kind rather than as a picture next to a character. */
.row-cave::before {
  content: '';
  display: block;
  width: 100%;
  height: 100%;
  background: currentColor;
  -webkit-mask: var(--cave-mask) center / 1.25rem 1.25rem no-repeat;
  mask: var(--cave-mask) center / 1.25rem 1.25rem no-repeat;
}

/* Taller than the row's, because it stands beside a text field rather than a
   line of text, and a 1.5rem control floating against a 2.6rem input reads as
   though it belongs to something else. Stretching rather than fixing a height
   keeps them level if the field's padding is ever changed. */
.add .temp {
  align-self: stretch;
  width: 1.125rem;
  height: auto;
}

.temp {
  flex-shrink: 0;
  width: 0.875rem;
  /* The tube's drawn height, which is design rather than layout — so this one
     is centered by offset rather than by being stretched to the line. */
  height: 1.5rem;
  margin-top: calc((var(--row-line) - 1.5rem) / 2);
  padding: 0;
  border: 1px solid var(--rule-strong);
  border-radius: 0.4375rem;
  background: var(--card);
  cursor: pointer;
  /* The fill grows from the bottom, like a thermometer. */
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  position: relative;
}

.temp-fill { display: block; width: 100%; }

/**
 * Two hairlines at the third and two-third marks, drawn over the fill.
 *
 * This is the half of the night-mode fix that does not depend on color at
 * all. Without them the only way to tell medium from high is to compare one
 * fill's shade against another's, from memory, on a control 14px wide — and
 * comparing shades is exactly what a night filter, a dim screen or a
 * red-green deficiency takes away. With them the level is read against a
 * scale that is still there when every fill looks the same: below both
 * marks, between them, or above both.
 *
 * Over the fill rather than behind it, which is why this is an overlay and
 * not a background on .temp. Behind it, the fill covers each mark as it
 * passes, so medium and high both show no marks and the one comparison that
 * had to survive is the one that would have been lost.
 *
 * --rule-strong because it has to read against both grounds: 2.5:1 on the
 * empty cream tube and 2.4:1 on the deep rust of a full one. A paper tint
 * would vanish on the empty state and an ink one on a full one.
 */
.temp::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    to top,
    transparent 0,
    transparent calc(33.333% - 0.5px),
    var(--rule-strong) calc(33.333% - 0.5px),
    var(--rule-strong) calc(33.333% + 0.5px),
    transparent calc(33.333% + 0.5px),
    transparent calc(66.667% - 0.5px),
    var(--rule-strong) calc(66.667% - 0.5px),
    var(--rule-strong) calc(66.667% + 0.5px),
    transparent calc(66.667% + 0.5px)
  );
}

.temp[data-level="0"] .temp-fill { height: 0; }
.temp[data-level="1"] .temp-fill { height: 33%; background: var(--temp-1); }
.temp[data-level="2"] .temp-fill { height: 66%; background: var(--temp-2); }
.temp[data-level="3"] .temp-fill { height: 100%; background: var(--temp-3); }

/* Words alone. This drew its own anchor above the message until the backdrop
   became the artwork, which has one — and the two landed on top of each other
   on the Compass screen, a drawing over a drawing. The backdrop's anchor is
   fixed and the message moves with the list, so there is no arrangement where
   both belong; the artwork keeps the anchor and the empty state keeps the
   sentence. Centered, because it is a state of the whole list rather than a
   row of it. */
.empty {
  margin: 1.5rem 0;
  color: var(--muted);
  font-style: italic;
  text-align: center;
}

/* ------------------------------------------------------------------- footer */

.footer { margin-top: 2.5rem; }

.link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.8125rem;
  text-decoration: underline;
  cursor: pointer;
}

#backup-output {
  display: block;
  width: 100%;
  margin-top: 0.75rem;
  padding: 0.625rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--card);
  color: var(--muted);
  font-family: ui-monospace, monospace;
  font-size: 0.75rem;
}

/* The engraved face, not the print one: Fell's old-style figures draw a zero
   as a small o, and "o.o.22" is not a build id anyone can read back over the
   phone. Cinzel's figures are lining. */
.build { margin: 1rem 0 0; color: var(--muted); font-family: var(--engraved); font-size: 0.6875rem; }

/* Completion, standing between the Cave and the ×.
   
   It used to sit second in the row, 10px from the thermometer, and that is the
   mis-tap the owner reported: reaching for a temperature and completing the
   item instead. Moving it here fixes that by distance — the thermometer is now
   the width of the words away — but it lands between two other targets, so the
   distance has to be bought back on both sides.
   
   Equal on both sides, at the owner's instruction. `.row-actions` supplies
   0.25rem of its own, so this is 8px of clear space toward the Cave and 8px
   toward the ×.

   It was deliberately unequal for one build — wider toward the × on the
   argument that deleting something you meant to finish is the mistake that
   costs — and the owner rejected it on sight. Worth writing down, because the
   argument was not so much wrong as beside the point: a row of controls is
   read every time and mis-tapped rarely, and a gap that changes width along
   the row is visible on every one of those readings. Even spacing is what
   makes the three read as one set.

   It came in from 10px and 14px in the same pair of builds, at the owner's
   report of too much space. That much was safe because the neighbors changed:
   the box used to sit against the thermometer, a bare 14px target with no
   padding of its own, and it now sits between two 2rem buttons whose glyphs
   are inset about 11px from their edges — a thumb landing short still has that
   inset to fall into.

   The drawing lives on the shared checkbox rule above; this only keeps the row
   from squeezing it. */
.row-actions > .row-check {
  flex-shrink: 0;
  margin-left: 0.25rem;
  margin-right: 0.25rem;
}

/* ------------------------------------------------------------- the shell */

/* Centered, because the row now holds the Waypoints chip between the name and
   the menu, and three things of three heights read as clutter unless they sit
   on one line. The title claims the spare width; the chip and menu keep to
   their own size. */
.masthead-row { display: flex; align-items: center; gap: 0.75rem; }
.masthead-row h1 { margin-right: auto; }

/* The screen headings — Settings, the gate, How to. The compass hub is also an
   h2 and stays in the print face, because it carries the user's words rather
   than the app's. */
#screen-settings h2,
#screen-gate h2,
#screen-howto h2 {
  margin: 0 0 1rem;
  font-family: var(--engraved);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* Three spans rather than the ☰ character, which is a box on a device without
   the glyph and is announced as "trigram for heaven" by some screen readers.
   Padding rather than size: the bars are 18px across, the target around them
   is past the 44px a thumb actually needs. */
.hamburger {
  display: grid;
  gap: 4px;
  flex-shrink: 0;
  padding: 0.75rem;
  margin: -0.5rem -0.5rem 0 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.hamburger i { display: block; width: 18px; height: 2px; background: var(--ink); }

/* Secondary to the four waypoints, and it has to look it. Open above the home
   screen at the same weight, three menu rows and four waypoint rows read as
   one list of seven — which is both wrong about the product, where four is the
   constraint, and wrong about the screen, where only four of them are the
   destinations. Smaller, muted, and ruled off underneath. */
.menu {
  display: grid;
  gap: 0.125rem;
  margin: 0.75rem 0 1.25rem;
  padding-bottom: 0.875rem;
  border-bottom: 1px solid var(--rule);
}

.menu button {
  padding: 0.625rem 0.75rem;
  border: 0;
  border-radius: 0.375rem;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.9375rem;
  text-align: left;
  cursor: pointer;
}

.menu button:hover { background: var(--card); color: var(--ink); }

/* Back to the four waypoints, worn as a small chip in the header row rather
   than a line of its own above every screen — the owner's ask: it was
   spending a row. The rose keeps it "back to where the roses are", and it is
   named rather than a bare arrow, because "‹" on its own is the browser's
   back button in every other app on the phone and this one does not go back —
   it goes up. Sized to share 390px with the title and the menu button. */
.back {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  gap: 0.375rem;
  padding: 0.3125rem 0.625rem 0.3125rem 0.5rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--card);
  color: var(--ink);
  font: 600 0.6875rem/1.4 var(--engraved);
  letter-spacing: 0.04em;
  cursor: pointer;
}

.back::before {
  content: '';
  width: 0.875rem;
  height: 0.875rem;
  background: var(--star-rose) center / contain no-repeat;
}

/* --------------------------------------------------------------- the gate */

/* Mapwork requires an account to start, and this is the whole screen until it
   has one. Given room at the top rather than centered in the viewport: a phone
   keyboard takes half the screen the moment the email field is tapped, and a
   vertically centered form jumps when it opens. */
.gate { padding-top: 1.5rem; max-width: 22rem; }
.gate .note { margin-bottom: 1.25rem; }
/* Block, with room under it: as an inline button it sat hard against the
   paragraph below and the two read as one run of text. */
.gate #forgot { display: block; margin: 0.375rem 0 1.5rem; }

/* ---------------------------------------------------------------- how to */

/* Prose, and the only screen in the app that is. Measure and spacing are what
   this needs rather than components — it is read once or twice and then
   rarely, so it is written to be skimmed by its headings. */
.howto { max-width: 34rem; }
.howto p { margin: 0 0 0.875rem; }
.howto p:last-child { margin-bottom: 0; }
.howto strong { font-weight: 600; color: var(--ink); }

/**
 * What the product is for, before any of the mechanics.
 *
 * The build document's own opening line, and the only sentence on this screen
 * that is an argument rather than an instruction — so it is set as one: larger,
 * inked rather than muted, and held off the left margin by a rule the way a
 * chart's legend is. Everything below it reads as explanation of this.
 *
 * Deliberately not a tinted box. A colored callout would make it look like an
 * alert, and this is the opposite of an alert.
 */
.creed {
  margin: 0 0 1.25rem;
  padding: 0.125rem 0 0.125rem 1rem;
  border-left: 3px solid var(--brass);
  color: var(--ink);
  font-size: 1.125rem;
  line-height: 1.45;
}

/**
 * One question at a time.
 *
 * Shut by default: a dozen answers stacked open is a wall of text on a phone,
 * and somebody arrives here with one question rather than twelve. The marker
 * is the summary's own, not a list bullet, and it turns when the answer opens
 * so a shut question does not read as an unclickable heading.
 */
.faq {
  margin: 0 0 0.5rem;
  border-bottom: 1px solid var(--rule);
}

.faq > summary {
  cursor: pointer;
  padding: 0.5rem 0 0.5rem 1.25rem;
  color: var(--ink);
  font-weight: 600;
  list-style: none;
  position: relative;
}

.faq > summary::-webkit-details-marker { display: none; }

.faq > summary::before {
  content: '';
  position: absolute;
  left: 0.125rem;
  top: 1.05rem;
  width: 0;
  height: 0;
  border-left: 0.375rem solid var(--brass);
  border-top: 0.3125rem solid transparent;
  border-bottom: 0.3125rem solid transparent;
  transition: transform 120ms ease;
  transform-origin: 0.1875rem 50%;
}

.faq[open] > summary::before { transform: rotate(90deg); }
.faq > summary:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.faq > p { margin: 0 0 0.875rem 1.25rem; color: var(--muted); }

@media (prefers-reduced-motion: reduce) {
  .faq > summary::before { transition: none; }
}

.howto h3 {
  margin: 1.75rem 0 0.625rem;
  font-family: var(--engraved);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.05em;
}

/* The rule under each heading is the chart's own, the same line-star-line the
   list mount carries, so the page belongs to the app rather than reading as a
   document pasted into it. */
.howto h3::after {
  content: '';
  display: block;
  height: 0.75rem;
  margin-top: 0.375rem;
  background:
    linear-gradient(var(--rule-strong), var(--rule-strong)) left center / calc(50% - 0.75rem) 1px no-repeat,
    linear-gradient(var(--rule-strong), var(--rule-strong)) right center / calc(50% - 0.75rem) 1px no-repeat,
    var(--star-rust) center / 0.75rem 0.75rem no-repeat;
  opacity: 0.6;
}

/* ------------------------------------------------------------ home screen */

/* Four items, and four is the constraint everywhere. One per row rather than a
   2×2 grid: these are destinations with counts, not a keypad, and a full-width
   target is the easiest thing on the screen to hit. */
.home { display: grid; gap: 0.625rem; }

.home button {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 3.5rem;
  padding: 0.875rem 1rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.625rem;
  background: var(--card);
  box-shadow: inset 0 0 0 1px rgb(250 244 230 / 70%), 0 1px 3px rgb(60 45 15 / 15%);
  color: var(--ink);
  font: inherit;
  font-size: 1.0625rem;
  cursor: pointer;
}

/* Each waypoint wears the rose, the way a chart marks its stations. */
.home button::before {
  content: '';
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
  background: var(--star-rose) center / contain no-repeat;
  opacity: 0.8;
}

.home-name {
  margin-right: auto;
  font-family: var(--engraved);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.05em;
}

.home-count { color: var(--brass); font-size: 0.9375rem; font-weight: 600; font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- chambers */

/* Four chambers mirroring the four quadrants, named by the user's own labels.
   Wrapping rather than a fixed four-across: two-word labels at 390px do not
   fit in a quarter of the width, and truncating the words somebody chose is
   the wrong thing to shorten. */
/**
 * Two by two, always, rather than a flex row that wraps where it happens to
 * run out.
 *
 * Reported from the phone: with the owner's own chamber names the row broke
 * three-and-one, and the orphan on the second line read as a mistake. Wrapping
 * itself is not avoidable — a chamber is named by its quadrant and the names
 * are the user's own, so any width can be overrun — but WHERE it wraps is, and
 * an even split looks chosen where an orphan looks broken.
 *
 * A grid rather than flex with a basis, because four is the constraint the
 * whole product is built on: four quadrants, four chambers, four home-screen
 * items. Two columns of two says that, and it cannot fall out of it the way a
 * flex row can when somebody types a short name.
 *
 * `1fr` and not `auto`, so the four are the same width whatever they are
 * called — "Coding" beside "Physical Labor" at their natural widths is the
 * ragged edge this was reported for, one row down.
 */
.chambers {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.375rem;
  margin-bottom: 1rem;
}

/* Wider than a phone, and four fit across with room to spare. The 34rem is
   where two columns start looking like a waste of the width rather than a
   tidy block. */
@media (min-width: 34rem) {
  .chambers { grid-template-columns: repeat(4, 1fr); }
}

.chamber {
  display: flex;
  align-items: center;
  /* Centered in its own cell now that the cells are equal, and the count sits
     with the name rather than drifting to the far edge of a stretched box. */
  justify-content: center;
  gap: 0.375rem;
  /* A long quadrant name wraps inside its chip instead of widening the column
     past its share and pushing the grid out of square. */
  overflow-wrap: anywhere;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}

.chamber.is-active {
  background: linear-gradient(var(--brass-fill), var(--brass-fill-deep));
  color: var(--ink);
  box-shadow: inset 0 0 0 1px rgb(250 244 230 / 70%), 0 1px 3px rgb(60 45 15 / 25%);
}

.chamber-count { color: var(--muted); font-size: 0.8125rem; font-variant-numeric: tabular-nums; }
.chamber.is-active .chamber-count { color: var(--brass); font-weight: 600; }

/* ------------------------------------------------------------- waypoints */

#waypoint-title {
  margin: 0 0 0.25rem;
  font-family: var(--engraved);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* What a waypoint is, and what is not built in it yet. Muted and small: it is
   orientation on arrival, not a message to be read twice. */
.note { margin: 0 0 1rem; color: var(--muted); font-size: 0.8125rem; line-height: 1.45; }

/* Charting out of the Cave. A word rather than an icon, because there is no
   glyph anybody reads as "this is ready now". */
.row-chart {
  flex-shrink: 0;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.375rem;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
}

.row-chart:hover { border-color: var(--brass); color: var(--brass); }

/* -------------------------------------------------------------- settings */

.labels { display: grid; gap: 0.625rem; }

.label-field { display: flex; align-items: center; gap: 0.75rem; }

.label-dir {
  flex-shrink: 0;
  width: 3.25rem;
  color: var(--muted);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
}

.label-input {
  flex: 1;
  min-width: 0;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--card);
  color: var(--ink);
  /* 16px minimum, or iOS zooms the page on focus and never zooms back. */
  font: inherit;
}

/* --------------------------------------------------- restoring from backup */

/* A settings toggle stands on its own line, unlike the ones packed into the
   controls row above a list, so it gets the row's own spacing. */
.settings-toggle { margin: 0.5rem 0 0; }

/* The Cave's pair belong to the line above them, and the indent is what says
   so — they are two clocks within one waypoint, not two more waypoints. */
.settings-subhead { margin: 1rem 0 0; }
.settings-nested { margin-left: 1.25rem; }

/**
 * A settings card: a titled thing you open, rather than a heading with
 * controls loose underneath it.
 *
 * `.also` already supplies the open/shut behavior — this adds the card, and
 * only the card. The summary takes the engraved capitals every other heading
 * on this screen wears, because it IS the heading when the card is shut, and
 * `.also summary`'s muted 13px would have made it read as a footnote instead.
 */
.card {
  margin: 2rem 0 0.75rem;
  padding: 0.25rem 0.875rem 0.125rem;
  border: 1px solid var(--rule);
  border-radius: 0.5rem;
  background: var(--card);
}

.card > summary {
  cursor: pointer;
  margin: 0 -0.875rem;
  padding: 0.625rem 0.875rem;
  color: var(--ink);
  font-family: var(--engraved);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.05em;
}

/* Only once it is open. A rule under a shut card would draw a line under
   nothing. */
.card[open] > summary {
  margin-bottom: 0.5rem;
  border-bottom: 1px solid var(--rule);
}

.card-note { margin: 0.75rem 0 0.25rem; }
.card .settings-toggle:last-of-type { margin-bottom: 0.75rem; }
/* ...except when the sample belongs to it. That rule exists to hold the last
   toggle off the bottom of its card; on the case card the last option is
   followed by its own example, and the gap pushed the two apart so the third
   sample read as though it belonged to the note underneath. */
.card .settings-toggle:has(+ .case-sample) { margin-bottom: 0; }

/* What each case does to the same line, shown rather than described. The
   sample sits under its own option and takes the case it names, so choosing
   is reading three lines instead of parsing three definitions — which is the
   only honest way to show that Proper Case lowercases an acronym. */
.case-sample {
  margin: 0.125rem 0 0 1.75rem;
  color: var(--muted);
  font-size: 0.8125rem;
  font-style: italic;
}

.settings-heading {
  margin: 2rem 0 0.25rem;
  font-family: var(--engraved);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.05em;
}

#restore-input {
  display: block;
  width: 100%;
  margin-bottom: 0.625rem;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--card);
  color: var(--muted);
  font-family: ui-monospace, monospace;
  /* 16px minimum, or iOS zooms the page on focus and never zooms back. */
  font-size: 1rem;
}

/* Outlined rather than filled while it is only an offer. Filling it in the
   warning color would make the loudest thing on the Settings screen the one
   control nobody should press by accident. */
.danger {
  width: 100%;
  padding: 0.75rem 1rem;
  border: 1px solid var(--warn);
  border-radius: 0.5rem;
  background: none;
  color: var(--warn);
  font: inherit;
  cursor: pointer;
}

/* Armed. It has said what it will do and is waiting for the second tap, so now
   it is worth being the loudest thing on the screen. */
.danger.is-armed { background: var(--warn); color: var(--paper); font-weight: 600; }

/* How long a deleted item has left. Muted and after the text, because it is
   the row saying when it goes rather than what it is. */
.row-meta {
  flex-shrink: 0;
  color: var(--muted);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  /* "7d old" is three times the width of "7d", and it shares a 390px row with
     a checkbox, a thermometer, wrapped text and the ×. Aligned to the first
     line so it reads beside the words rather than beside the middle of a
     three-line item, and it never wraps — a label that broke across lines
     would take more from the text than it is worth. */
  align-self: flex-start;
  padding-top: 0.125rem;
  white-space: nowrap;
  /* Stacked when there is more than one, right-aligned so the numbers line up
     with each other rather than with the ragged left edge two words make. */
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.4;
}

/* ------------------------------------------------------------- triage */

/* Ruled off rather than floated over the list: the item being charted is still
   on screen above it, and a panel that covered the list would hide the thing
   the decision is about. */
.chart-panel {
  margin-bottom: 1rem;
  padding: 0.875rem;
  border: 1px solid var(--brass);
  border-radius: 0.625rem;
  background: var(--card);
  box-shadow: 0 1px 4px rgb(60 45 15 / 15%);
}

.chart-what { margin: 0 0 0.75rem; font-size: 0.9375rem; }
.chart-what strong { font-weight: 600; }

.chart-modes { display: flex; gap: 0.375rem; margin-bottom: 0.75rem; }

.chart-mode {
  flex: 1;
  min-width: 0;
  padding: 0.5rem 0.375rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
}

.chart-mode.is-active {
  border-color: var(--rule-strong);
  background: linear-gradient(var(--brass-fill), var(--brass-fill-deep));
  color: var(--ink);
  box-shadow: inset 0 0 0 1px rgb(250 244 230 / 70%);
}

/* One per row, full width. These carry the user's own words, which are two
   words often enough that a 2×2 grid truncates them — and truncating the names
   somebody chose is the wrong thing to shorten. */
.chart-directions { display: grid; gap: 0.375rem; margin-bottom: 0.625rem; }

.chart-direction {
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.chart-direction:hover { border-color: var(--brass); color: var(--brass); }

/* ------------------------------------------------------------- snapshots */

.snapshots { display: grid; gap: 0.25rem; margin-bottom: 0.75rem; }

.snapshot {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--rule);
}

/* Reason and age together, because "on opening, 2 hours ago" is one fact about
   a snapshot and reading it as two would need the eye to travel twice. */
.snapshot-what { flex: 1; min-width: 0; font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.snapshot-size { flex-shrink: 0; color: var(--muted); font-size: 0.75rem; font-variant-numeric: tabular-nums; }

.snapshot-restore,
.snapshot-save {
  flex-shrink: 0;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.375rem;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
}

.snapshot-restore:hover,
.snapshot-save:hover { border-color: var(--brass); color: var(--brass); }

/* Not `.danger`: restoring a snapshot takes one of what it replaces first, so
   the mistake is already undoable and dressing it as destructive would be the
   warning that cries wolf. */
.wide {
  width: 100%;
  margin-bottom: 0.5rem;
  padding: 0.625rem 1rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

/* --------------------------------------------------------------- files */

/* The native file input is unstyleable and reads as "No file chosen" next to a
   gray button on every platform. Hidden behind its own label, which is a real
   control for a screen reader and opens the picker on a tap. */
.file-pick { display: block; margin-bottom: 0.75rem; }
.file-pick input { position: absolute; width: 1px; height: 1px; opacity: 0; }

.file-pick span {
  display: block;
  padding: 0.625rem 1rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--card);
  color: var(--ink);
  text-align: center;
  cursor: pointer;
}

.file-pick input:focus-visible + span { outline: 2px solid var(--brass); outline-offset: 2px; }

/* The text route still exists for anyone who wants it, folded away because a
   file is the better answer on the device this is built for. */
.also { margin-bottom: 0.75rem; }
.also summary { color: var(--muted); font-size: 0.8125rem; cursor: pointer; padding: 0.375rem 0; }

/* ----------------------------------------------------------------- account */

/* Stacked rather than a row like `.add`, because two fields and a button side
   by side on a 390px screen leaves each field too narrow to read what is in
   it — and an email address is long. */
.sign-in { display: grid; gap: 0.5rem; margin-bottom: 0.75rem; }

.sign-in input {
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: var(--card);
  color: var(--ink);
  /* 16px minimum, or iOS zooms the page on focus and never zooms back. */
  font: inherit;
}

.sign-in button[type="submit"] {
  padding: 0.625rem 1rem;
  border: 1px solid var(--rule-strong);
  border-radius: 0.5rem;
  background: linear-gradient(var(--brass-fill), var(--brass-fill-deep));
  box-shadow: inset 0 0 0 1px rgb(250 244 230 / 70%);
  color: var(--ink);
  font: 600 0.9375rem/1.4 var(--engraved);
  letter-spacing: 0.05em;
  cursor: pointer;
}

/* The signed-in line is a fact rather than a warning, so it reads as body text
   with the address picked out — not as a banner. */
#account-state strong { color: var(--ink); font-weight: 600; }
